說到「檔案」系統,選文檔數據庫再合適不過了。談到文檔數據庫通常想到的是 MongoDB、CouchDB 之類的,可這裏要說的不是這些,而是另外一個 NoSQL 「文檔數據庫」 —— Lucene。之因此要打引號,是由於暫時還沒聽到別人這樣說。php
最近公司要弄一個內部搜索,對比各類方案後,決定用 Lucene。當作出第一個原型後,考慮到公司另外幾個項目未來也許用的上,而再寫一遍代碼可不是個人風格;又試用了開箱即用的 Solr,以爲那也不是個人菜。由於我項目內已經有相似 Solr 的 Schame 的配置在用了,我打算複用這個模塊;接口規範我也打算複用我現有的規範。html
基礎的增刪改查比較簡單,很快就作出了原型。此時我想到公司另外一個大模塊:檔案(或叫簡歷)。這部分我已計劃與另外一個項目的相似模塊作整合,考慮用 MongoDB 重構。既然 Lucene 能夠存取較複雜的數據結構,何不借此機會研究一下用 Lucene 做爲檔案系統的底層支撐呢。java
那這裏說的檔案是什麼樣子呢?舉一個簡單例子,一份我的簡歷:git
姓名:XXX 性別:男 照片:xxx/xxx.jpg 興趣愛好 興趣:跑步、游泳、XX自定義 簡介:是浪費時間的服務吉林省地方就,受到法律書籍地方 教育經歷 經歷1 日期區間: 2014/1/1~2015/1/1 學校: Jiali.Dun 專業: 挖掘機 學位:沒士 經歷2……
大概的文檔結構就是就是這樣,字段、層級是不肯定的,須要保持此結構,能存、能取,大部分字段可查詢、排序。github
總結以上檔案結構,組成上可分爲:mongodb
a. 基礎板塊(名字,性別,照片) b. 其餘板塊(同上,但被區分開) c. 列表板塊(教育經歷)
上面特地將基礎信息稱爲基礎「板塊」,也就是說,通常狀況下一份檔案是由多個板塊組成的。也許您的檔案還會更復雜,好比興趣愛好下再分爲運動、娛樂,這種劃分方式從存儲上來講與兩層設計沒什麼區別,多了一個父級板塊的指向而已,但這增長了展示的複雜度。如今你們都在談「扁平化」,我所理解的扁平不只僅是把圖標拍扁了,更是信息獲取的渠道扁平了,能一下給我看的,不要讓我點一層菜單進去又點一層;能用標籤、搜索篩選的,不要讓我點目錄樹查找。數據庫
一個板塊就是一組鍵值對,此處咱們將這一組規則稱爲表單。那麼,列表板塊就是由多個可重複表單組成的板塊。apache
字段上能夠有:json
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...