用 Lucene 构建文档数据库
说到“档案”系统,选文档数据库再合适不过了。谈到文档数据库一般想到的是 MongoDB、CouchDB 之类的,可这里要说的不是这些,而是另一个 NoSQL “文档数据库” —— Lucene。之所以要打引号,是因为暂时还没听到别人这样说。
需求
最近公司要弄一个内部搜索,对比各种方案后,决定用 Lucene。当做出第一个原型后,考虑到公司另外几个项目将来也许用的上,而再写一遍代码可不是我的风格;又试用了开箱即用的 Solr,觉得那也不是我的菜。因为我项目内已经有类似 Solr 的 Schame 的配置在用了,我打算复用这个模块;接口规范我也打算复用我现有的规范。
基础的增删改查比较简单,很快就做出了原型。此时我想到公司另一个大模块:档案(或叫简历)。这部分我已计划与另一个项目的类似模块做整合,考虑用 MongoDB 重构。既然 Lucene 可以存取较复杂的数据结构,何不借此机会研究一下用 Lucene 作为档案系统的底层支撑呢。
那这里说的档案是什么样子呢?举一个简单例子,一份个人简历:
姓名:XXX
性别:男
照片:xxx/xxx.jpg
兴趣爱好兴趣:跑步、游泳、XX自定义简介:是浪费时间的服务吉林省地方就,受到法律书籍地方
教育经历经历1日期区间: 2014/1/1~2015/1/1学校: Jiali.Dun专业: 挖掘机学位:没士经历2……
大概的文档结构就是就是这样,字段、层级是不确定的,需要保持此结构,能存、能取,大部分字段可查询、排序。
结构化数据
总结以上档案结构,组成上可分为:
a. 基础板块(名字,性别,照片)
b. 其他板块(同上,但被区分开)
c. 列表板块(教育经历)
上面特意将基础信息称为基础“板块”,也就是说,一般情况下一份档案是由多个板块组成的。也许您的档案还会更复杂,比如兴趣爱好下再分为运动、娱乐,这种划分方式从存储上来说与两层设计没什么区别,多了一个父级板块的指向而已,但这增加了展现的复杂度。现在大家都在谈“扁平化”,我所理解的扁平不仅仅是把图标拍扁了,更是信息获取的渠道扁平了,能一下给我看的,不要让我点一层菜单进去又点一层;能用标签、搜索筛选的,不要让我点目录树查找。
一个板块就是一组键值对,此处我们将这一组规则称为表单。那么,列表板块就是由多个可重复表单组成的板块。
字段上可以有:
a. 文本
b. 数字
c. 文件
d. 日期、时间(区间)
e. 单选、多选
f. 多条数据(文本、数字、日期等)
从 a~e 都是很常见的类型,文件可以转储到文件服务器上,这里只存 URL;日期、时间可以转换成时间戳。而 f 是指这个字段的值可以输入多个,通常用来记录一些需要多条记录东西,存储上与多选一样。
Lucene 原本就是一个字段可以存多个值,这太妙了。
表单及验证
前面谈到我自己有一个数据校验模块,对数据结构的描述如下:
表单1字段1:类型,是否必填,是否重复,其他校验参数字段2……
枚举1取值1:名称取值2……
举一个栗子:
简历表单姓名:文本,必填,不重复,最大长度100性别:选项,必填,不重复,性别枚举照片:图片,选填,可重复,类型(jpg,png)兴趣爱好:表单,选填,不重复,兴趣爱好表单教育经历:表单,选填,可重复,教育经历表单
性别枚举0:女1:男2:中性
兴趣爱好表单兴趣:文本,必填,可重复,最大长度50简介:文本,选填,不重复,多行文本
教育经历表单日期区间:日期区间,必填,不重复学校:文本,必填,不重复专业:文本,必填,不重复
此表单描述上也是为了方便编辑和解析,设计成了 表单->字段 两层结构,未使用代码嵌套而是使用链接嵌套的方式。校验器在校验的时候,发现字段类型为表单,取出对应表单递归下去就行了。那这么多表单都堆积在一起,怎么解决命名空间的问题呢?我设计为每个模块(同一应用主题)一个这样的配置,校验器在处理表单时如果没给出模块名(配置名),则取当前模块的指定名字的表单,有则取指定模块下的表单。
数据在校验成功后,会将数据清理为类似以下 JSON 的结构:
{"name": "XXX","gender": 1,"photo": "upload/photo/xxxxxx.jpg","hobby": {"interest": ["ljsdfsdfsd","sldfj2ef"],"comment": "sjldfjsldfsdlfjsldfsdfsdfsdfsdfsdf"},"education": [{"date": {"begin": Date(2014/1/1), "end": Date(2015/1/1)},"university": "lwnfdsfwe","professional": "slwef"}]
}
输入的数据结构与此一致,对于使用 application/x-www-form-urlencoded 格式提交的数据,可以根据"."、"["和"]"解析成上面的数据结构,就像 PHP 的请求参数解析方式。
存储方式
OK,上面已经扯了很多了,这开始进入正题了。数据都清理好了,可是这样一个结构的数据怎么存到 Lucene 检索库里呢?Lucene 可不是 MongoDB 能存储 BSON 那样的复杂结构呀。难道像设计关系数据库的 ERM 一样,建几个索引目录当表使,然后用外键做关联,然后自己实现关联查询。或者,把整个数据序列化扔到一个字段里,自己写 Filter 、Query 来实现对复杂结构的查询?
我可不想这么费劲。
为解决这些问题,先梳理一下,Lucene 的基本字段类型有:
StringField: 基础文本字段,可指定是否索引
StoredField: 仅存储不索引(也就是不能搜索、查询只能跟着文档取出来看)
TextField : 会在这上面应用分词器,用来做全文检索的
还有其他的 IntField,FloatField…… 可以存数字的(关键的是可以按数字值大小来排序),ByteField 存二进制数据等。还有,Lucene 支持一个字段存储多个值,当只需要一个值得时候拿一个就是了,需要多个就取多个值。
现在,我可以假定默认的情况下基础数据要能独立索引以方便查询的,他们用单独的字段存放。其他数据可以在字段名上用一个分隔符连接板块名和字段名。如果这些字段的字段名是不重复的(比如随机生成的),直接用字段名即可。这样做的好处是展现和存储分离,当一个字段的数据从A板块迁移到B板块时,不用去修改过去已经存储的数据,因为这个迁移仅仅是视觉上的迁移而已。目前我用 RDMS 实现的一套档案系统就是这么干的。
比较麻烦的是列表板块。
如果不需要对这部分的数据做查询,那就直接序列化存起来。
如果需要对里面独立的字段做搜索和排序,那就再序列化的基础上,多加一个字段独立存储要索引的字段。比如添加字段 教育经历-学校,就可以对曾就读过某个学校的档案做搜索了。
如果还想完成需求:查询某个日期范围内就读某某学校的档案,还是另行存储吧。查询时可以用外键关联,查出一个再 IN 去查另一个(注:Lucene没有IN的操作,需要联合使用MUST和SHOULD)。可以另外作为一个档案存在当前索引目录内,更好的方式是独立开个附属目录存储,这样做可以确保主数据更干净。
完整的存储结构为:
主要数据存储记录ID字段1:值1,值2……字段2……
列表数据存储主记录ID行记录ID序号字段1:值1,值2……字段2……
查询规则
我有一套已经应用在 RDBMS 模型上的查询规则,需要做的是将规则解析成 Lucene 的 Query。查询规则如下:
{"id": "xxx", // 等于"star": [1, 2], // IN, Lucene 的 Must + Should"f1": {"-gt": 18, // 大于"-le": 35 // 小于或等于},"f2": {"-ne": "zzz" // 不等于},"f3": {"-or": "zzz" // OR, 对应 Lucene 的 Should},"f4": {"-ni": [3, 4] // NOT IN, 对应 Lucene 的 Must_Not},"f5": {"-ai": [1, 2] // ALL IN, 对应 Lucene 的 Must},"f6": {"-oi": [5, 6] // OR IN, 对应 Lucene 的 Should}
}
用 application/x-form-urlencode 可表示为:
id=xxx&star[]=1&star[]=2&f1[-gt]=18&f1[-le]=35&f6[-oi][]=5&f6[-oi][]=6
系统会以类似 PHP 的请求参数解析方式解析类似上面 JSON 的数据结构。为了方便看和写,也可支持将[]换成.,如:f6.-oi.=6 与 f6[-oi][]=6 是相同的。
熟悉 MongoDB 的人看这个会很眼熟,没错,这就是从 MongoDB 借鉴过来,并用在我的关系数据库查询上。这里的 -or 和 -oi 是 Lucene 特有的,可以影响到排序,这对搜索那些可有可无的字段很有帮助。-ai 类似于 Mongo 的 containsAll。
注:[2015/12/01] 以上"-"已换成"!"符号。
接口规范
接口的主要目是为了传递数据,数据结构已经在上面给出。接口以 REST 风格给出,请求数据支持 application/x-form-urlencode,json,返回数据为 json。
如果你熟悉 Protobuf,也许意识到了上面的表单跟 proto 的描述很像,没错,这也是借鉴的。只是 Protobuf 没法加更多的描述,所以我没去用。这里的表单配置可以转换为 proto 描述。为便于不同系统、不同终端的数据交换,protobuf 也将(应当)在接口支持之内。
后注
如果不去考虑 Lucene 写锁的“问题”,我真心觉得这是个相当不错的嵌入式文档数据库;虽然用 Lucene 存储复杂结构数据的可行性还有待商榷,但折腾一下对了解 Lucene 还是有价值的。不必强求必须用什么语言、框架或工具才能完成某件事,其实能办成一件事的途径有很多,多尝试一下思路就更清晰一点。
我在 github 上有个项目,不过还没有搭建演示,日后有了再将链接添加到这里。
部分代码:
Lucene CRUD 封装:https://github.com/ihongs/Hon...
表单校验程序:https://github.com/ihongs/Hon...
表单配置规范:https://github.com/ihongs/Hon...
参考资料:
MongoDB 查询:http://docs.mongodb.org/manua...
Lucene 查询:https://lucene.apache.org/cor...
REST 简介:http://baike.baidu.com/view/5...
PHP 请求参数解析(见第一条 Note):http://php.net/manual/zh/rese...
用 Lucene 构建文档数据库相关推荐
- Lucene构建索引的原理及源代码分析
文章目录 1. Lucene是什么 2. 全文检索是什么 3. 术语 4. 创建索引过程 4.1 Lucene创建索引示例代码 4.2 分词的过程 4.2.1 原理 4.2.2 源代码 4.3 建索引 ...
- lucene构建同义词分词器
lucene4.0版本号以后 已经用TokenStreamComponents 代替了TokenStream流.里面包含了filter和tokenizer 在较复杂的lucene搜索业务场景下,直接网 ...
- datetime 索引_【免费毕设】ASP.NET基于Ajax+Lucene构建搜索引擎的设计和实现(源代码+论文)...
点击上方"蓝字"关注我们目录 系统设计 4.1 搜索引擎模型 模型包括爬虫.索引生成.查询以及系统配置部分.爬虫包括:网页抓取模块.网页减肥模块.爬虫维持模块.索引生成包括:基于文 ...
- 【免费毕设】基于Ajax+Lucene构建搜索引擎的设计和实现(源代码+lunwen)
4.2 数据库的设计 本课题包含一张用于存放抓取回来的网页信息如表1. 4.3 模块设计 该模型按照功能划分为三个部分,一是爬虫抓取网页部分,二是从数据库建立索引部分,三是从前台页面查询部分.系统的功 ...
- 使用 Apache Lucene 搜索文本——轻松为应用程序构建搜索和索引功能
简介: 本文将探讨 Apache Lucene -- 性能卓越.功能全面的文本搜索引擎库.我们将学习 Lucene 架构及其核心 API.学习如何使用 Lucene 进行跨平台全文本搜索.建立索引.显 ...
- Lucene倒排索引简述 细说倒排索引构建
文章目录 一.数据结构 1. ByteBlockPool 1.1 Buffer结构 1.2 Slice链表 2. BytesRefHash 3. PostingsArrays 二.构建索引过程 在 & ...
- lucene introduction
2019独角兽企业重金招聘Python工程师标准>>> Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序 ...
- 使用Lucene2.3构建搜索引擎
Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能. Lucene的作者:Lucene的贡献者Doug C ...
- Lucene 简单手记
什么是全文检索与全文检索系统? 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查 ...
- 实战 Lucene,第 1 部分: 初识 Lucene
Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能.Lucene 目前是 Apache Jakarta 家 ...
最新文章
- java sql编码_java+sql 编码 UTF-8、ISO-8859-1、GBK
- Chkconfig命令
- go语言培训班多少钱
- Spring - Java/J2EE Application Framework 应用框架 第 18 章 使用Quartz或Timer完成时序调度工作
- synchronized,ReetrantLock与volatile(二)
- 电脑下载的M4A格式文件怎么转换为MP3格式
- python tk mainloop原理_理解Tkinter mainloop()的逻辑以及为什么变量没有重新分配它们的原始值?...
- c++ 测试串口速率_山西充放电测试设备实现多台仪器准确通
- 阵列信号处理仿真一——延时求和滤波器
- 腾讯云数据库 TDSQL—— 私有云安装部署手册
- C#资源文件的使用实例
- 【1】EFR32配置433Mhz(可通信CC1101)——EFR32 项目例程打开和烧录
- u盘插在电脑上灯亮没有反应_U盘插入电脑指示灯一直闪烁却不显示的解决方法...
- Microsoft Visual Studio 2005中使用水晶报表详细说明
- (每日一练C++)16. 最接近的三数之和
- leetcode【121】Best Time to Buy and Sell Stock【c++,O(n)复杂度,时间97%,空间100%】
- 面试官:知道你的接口QPS是多少么?
- LR字符串截取lr_save_var
- 三极管集电极电阻的作用
- 京东网页版静态页面的几个问题