说到“档案”系统,选文档数据库再合适不过了。谈到文档数据库一般想到的是 MongoDB、CouchDB 之类的,可这里要说的不是这些,而是另一个 NoSQL “文档数据库” —— Lucene。之所以要打引号,是因为暂时还没听到别人这样说。

  1. 需求

最近公司要弄一个内部搜索,对比各种方案后,决定用 Lucene。当做出第一个原型后,考虑到公司另外几个项目将来也许用的上,而再写一遍代码可不是我的风格;又试用了开箱即用的 Solr,觉得那也不是我的菜。因为我项目内已经有类似 Solr 的 Schame 的配置在用了,我打算复用这个模块;接口规范我也打算复用我现有的规范。

基础的增删改查比较简单,很快就做出了原型。此时我想到公司另一个大模块:档案(或叫简历)。这部分我已计划与另一个项目的类似模块做整合,考虑用 MongoDB 重构。既然 Lucene 可以存取较复杂的数据结构,何不借此机会研究一下用 Lucene 作为档案系统的底层支撑呢。

那这里说的档案是什么样子呢?举一个简单例子,一份个人简历:

姓名:XXX
性别:男
照片:xxx/xxx.jpg
兴趣爱好兴趣:跑步、游泳、XX自定义简介:是浪费时间的服务吉林省地方就,受到法律书籍地方
教育经历经历1日期区间: 2014/1/1~2015/1/1学校: Jiali.Dun专业: 挖掘机学位:没士经历2……

大概的文档结构就是就是这样,字段、层级是不确定的,需要保持此结构,能存、能取,大部分字段可查询、排序。

  1. 结构化数据

总结以上档案结构,组成上可分为:

a. 基础板块(名字,性别,照片)
b. 其他板块(同上,但被区分开)
c. 列表板块(教育经历)

上面特意将基础信息称为基础“板块”,也就是说,一般情况下一份档案是由多个板块组成的。也许您的档案还会更复杂,比如兴趣爱好下再分为运动、娱乐,这种划分方式从存储上来说与两层设计没什么区别,多了一个父级板块的指向而已,但这增加了展现的复杂度。现在大家都在谈“扁平化”,我所理解的扁平不仅仅是把图标拍扁了,更是信息获取的渠道扁平了,能一下给我看的,不要让我点一层菜单进去又点一层;能用标签、搜索筛选的,不要让我点目录树查找。

一个板块就是一组键值对,此处我们将这一组规则称为表单。那么,列表板块就是由多个可重复表单组成的板块。

字段上可以有:

a. 文本
b. 数字
c. 文件
d. 日期、时间(区间)
e. 单选、多选
f. 多条数据(文本、数字、日期等)

从 a~e 都是很常见的类型,文件可以转储到文件服务器上,这里只存 URL;日期、时间可以转换成时间戳。而 f 是指这个字段的值可以输入多个,通常用来记录一些需要多条记录东西,存储上与多选一样。

Lucene 原本就是一个字段可以存多个值,这太妙了。

  1. 表单及验证

前面谈到我自己有一个数据校验模块,对数据结构的描述如下:

表单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 的请求参数解析方式。

  1. 存储方式

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……
  1. 查询规则

我有一套已经应用在 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] 以上"-"已换成"!"符号。

  1. 接口规范

接口的主要目是为了传递数据,数据结构已经在上面给出。接口以 REST 风格给出,请求数据支持 application/x-form-urlencode,json,返回数据为 json。

如果你熟悉 Protobuf,也许意识到了上面的表单跟 proto 的描述很像,没错,这也是借鉴的。只是 Protobuf 没法加更多的描述,所以我没去用。这里的表单配置可以转换为 proto 描述。为便于不同系统、不同终端的数据交换,protobuf 也将(应当)在接口支持之内。

  1. 后注

如果不去考虑 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 构建文档数据库相关推荐

  1. Lucene构建索引的原理及源代码分析

    文章目录 1. Lucene是什么 2. 全文检索是什么 3. 术语 4. 创建索引过程 4.1 Lucene创建索引示例代码 4.2 分词的过程 4.2.1 原理 4.2.2 源代码 4.3 建索引 ...

  2. lucene构建同义词分词器

    lucene4.0版本号以后 已经用TokenStreamComponents 代替了TokenStream流.里面包含了filter和tokenizer 在较复杂的lucene搜索业务场景下,直接网 ...

  3. datetime 索引_【免费毕设】ASP.NET基于Ajax+Lucene构建搜索引擎的设计和实现(源代码+论文)...

    点击上方"蓝字"关注我们目录 系统设计 4.1 搜索引擎模型 模型包括爬虫.索引生成.查询以及系统配置部分.爬虫包括:网页抓取模块.网页减肥模块.爬虫维持模块.索引生成包括:基于文 ...

  4. 【免费毕设】基于Ajax+Lucene构建搜索引擎的设计和实现(源代码+lunwen)

    4.2 数据库的设计 本课题包含一张用于存放抓取回来的网页信息如表1. 4.3 模块设计 该模型按照功能划分为三个部分,一是爬虫抓取网页部分,二是从数据库建立索引部分,三是从前台页面查询部分.系统的功 ...

  5. 使用 Apache Lucene 搜索文本——轻松为应用程序构建搜索和索引功能

    简介: 本文将探讨 Apache Lucene -- 性能卓越.功能全面的文本搜索引擎库.我们将学习 Lucene 架构及其核心 API.学习如何使用 Lucene 进行跨平台全文本搜索.建立索引.显 ...

  6. Lucene倒排索引简述 细说倒排索引构建

    文章目录 一.数据结构 1. ByteBlockPool 1.1 Buffer结构 1.2 Slice链表 2. BytesRefHash 3. PostingsArrays 二.构建索引过程 在 & ...

  7. lucene introduction

    2019独角兽企业重金招聘Python工程师标准>>> Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序 ...

  8. 使用Lucene2.3构建搜索引擎

    Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能. Lucene的作者:Lucene的贡献者Doug C ...

  9. Lucene 简单手记

    什么是全文检索与全文检索系统? 全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查 ...

  10. 实战 Lucene,第 1 部分: 初识 Lucene

    Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能.Lucene 目前是 Apache Jakarta 家 ...

最新文章

  1. java sql编码_java+sql 编码 UTF-8、ISO-8859-1、GBK
  2. Chkconfig命令
  3. go语言培训班多少钱
  4. Spring - Java/J2EE Application Framework 应用框架 第 18 章 使用Quartz或Timer完成时序调度工作
  5. synchronized,ReetrantLock与volatile(二)
  6. 电脑下载的M4A格式文件怎么转换为MP3格式
  7. python tk mainloop原理_理解Tkinter mainloop()的逻辑以及为什么变量没有重新分配它们的原始值?...
  8. c++ 测试串口速率_山西充放电测试设备实现多台仪器准确通
  9. 阵列信号处理仿真一——延时求和滤波器
  10. 腾讯云数据库 TDSQL—— 私有云安装部署手册
  11. C#资源文件的使用实例
  12. 【1】EFR32配置433Mhz(可通信CC1101)——EFR32 项目例程打开和烧录
  13. u盘插在电脑上灯亮没有反应_U盘插入电脑指示灯一直闪烁却不显示的解决方法...
  14. Microsoft Visual Studio 2005中使用水晶报表详细说明
  15. (每日一练C++)16. 最接近的三数之和
  16. leetcode【121】Best Time to Buy and Sell Stock【c++,O(n)复杂度,时间97%,空间100%】
  17. 面试官:知道你的接口QPS是多少么?
  18. LR字符串截取lr_save_var
  19. 三极管集电极电阻的作用
  20. 京东网页版静态页面的几个问题

热门文章

  1. 李阳疯狂英语900句 331-545
  2. 中电信推大学生3G上网套餐
  3. 内存管理学习之内存寻址
  4. 网路收报流程-网桥的处理流程(br网桥)(四)
  5. Zebra 命令模式分析2
  6. Makefile.am详解
  7. qt写的一个计算器程序
  8. sort()函数关于结构内容要怎么写
  9. Stamps 邮票问题 详解(C++)
  10. 一整个网站的全部数据,我只能给你这么多了。