摘要:说到档案系统,选文档数据库再合适不过了。熟悉的人看这个会很眼熟,没错,这就是从借鉴过来,并用在我的关系数据库查询上。接口规范接口的主要目是为了传递数据,数据结构已经在上面给出。为便于不同系统不同终端的数据交换,也将应当在接口支持之内。
说到“档案”系统,选文档数据库再合适不过了。谈到文档数据库一般想到的是 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...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/20914.html
摘要:说到档案系统,选文档数据库再合适不过了。熟悉的人看这个会很眼熟,没错,这就是从借鉴过来,并用在我的关系数据库查询上。接口规范接口的主要目是为了传递数据,数据结构已经在上面给出。为便于不同系统不同终端的数据交换,也将应当在接口支持之内。 说到档案系统,选文档数据库再合适不过了。谈到文档数据库一般想到的是 MongoDB、CouchDB 之类的,可这里要说的不是这些,而是另一个 NoSQL...
摘要:倒排索引是基于词的搜索。关于倒排索引要学习搜索引擎,就需要了解倒排索引,要更加深刻地理解倒排索引,就要先了解什么是正排索引表。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引。 Lucene是什么? Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引...
摘要:基本概念在深入解读之前,先了解下的几个基本概念,以及这几个概念背后隐藏的一些东西。如图是一个内的基本组成,内数据只是一个抽象表示,不代表其内部真实数据结构。即词典,是根据条件查找的基本索引。 前言 Apache Lucene是一个开源的高性能、可扩展的信息检索引擎,提供了强大的数据检索能力。Lucene已经发展了很多年,其功能越来越强大,架构也越来越精细。它目前不仅仅能支持全文索引,也...
摘要:基本概念在深入解读之前,先了解下的几个基本概念,以及这几个概念背后隐藏的一些东西。如图是一个内的基本组成,内数据只是一个抽象表示,不代表其内部真实数据结构。即词典,是根据条件查找的基本索引。 前言 Apache Lucene是一个开源的高性能、可扩展的信息检索引擎,提供了强大的数据检索能力。Lucene已经发展了很多年,其功能越来越强大,架构也越来越精细。它目前不仅仅能支持全文索引,也...
阅读 2170·2021-09-02 15:11
阅读 1491·2019-08-30 15:43
阅读 2053·2019-08-29 13:48
阅读 2770·2019-08-26 13:55
阅读 2090·2019-08-23 15:09
阅读 2875·2019-08-23 14:40
阅读 3404·2019-08-23 14:23
阅读 2587·2019-08-23 14:20