1. Things Happen for A Reason
我最喜歡的一部電影《危情諜戰》中有這樣一句臺詞,“Things Happen for A Reason”,不知道為什麼,直到今天我對這句臺詞的印象都很深刻,也許是當時看到這句話的時候腦海裡靈光一現,或是引起共鳴才會烙下這麼深刻的印象吧。幾乎從那以後,不論是做事還是進行思考,我都會自然而然的養成從底層邏輯逐步的來挖掘和分析事物的核心原理。
比如說最近在做產品的技術架構設計,其中幾乎有一大半時間都是在梳理業務流程和需求,將業務進行分類處理,再做相關的技術調研。其中 Neo4j 算是技術方案中的核心角色之一,這篇 post 也算是做這個架構設計中關於 Neo4j 這個 NoSQL 資料庫的一些感想。
1.1 Neo4j
如何快速的瞭解 Neo4j 呢,網上有很多很長的文字,不過我覺得都沒有簡單明確的講清楚。我想了想,如何讓大家最快最高效的理解 Neo4j,最好的辦法就是下面總結的幾句話:
- 開源 NoSQL 資料庫,原生的圖資料庫,2003 年開始開發,使用 scala和java 語言,2007年開始釋出;
- 世界上最先進的圖資料庫之一,提供原生的圖資料儲存,檢索和處理;
- 採用屬性圖模型(Property graph model),極大的完善和豐富圖資料模型;
- 專屬查詢語言 Cypher,直觀,高效;
2. Neo4j Kernel
圖資料庫有很多,在這個 wiki 上有一個比較完整的列表:List of graph databases,其中有的似乎已經沒有維護了,有的呢是傳統的 NoSQL 資料庫,但是提供圖資料結構儲存,有的也像 Neo4j 一樣是專用的圖資料庫,但是即使是專門的圖資料庫,其底層實現,應用場景和生態也是有天差地別的。
今天我們著重看看 Neo4j,我們們主要從下面幾個方面來學習和深入理解 Neo4j。
2.1 Native Graph Storage and Processing
前面我們講過,現在其實已經有很多圖資料庫了,其中有一些 document 類 NoSQL 資料庫(假設叫 A DB),也支援圖資料結構的儲存和檢索,那這類資料庫和 Neo4j 最大的區別,就是接下來我們要說的 Native 和 non-Native 圖資料庫了。簡單來說,Neo4j 是 Native 的圖資料庫,A DB 屬於 non-Native 圖資料庫了。
所謂 Native 圖資料庫,中文一般翻譯為 “原生圖資料庫”,指從一開始便是為了解決圖類資料結構而設計的資料庫。對了解資料結構的同學來看,這裡的 “原生” 的體現,其實主要就在兩個方面:
- 圖資料結構的儲存
- 圖資料的處理和查詢
我們也從這兩個點來聊聊 Neo4j。
圖資料結構的儲存
不管是什麼資料庫,mysql,sql server,mongo,neo4j 等等,其儲存的資料,最終都要落地到檔案系統上的,那 neo4j 落地的檔案大概都是什麼樣的呢?這裡我以本地的一個應用的資料庫來介紹一下。
上面是我的本地一個圖資料庫的資料庫檔案,可以看出 Neo4j 把其資料庫檔案分為四大類來分類儲存:
- 標籤
- 節點
- 屬性
- 關係
其實按照我們圖論中的一般說法,其實對於一個圖資料結構來說,只需要儲存節點和關係就可以了,如下面這個圖資料一樣:
但是現實生活中,我們的圖結構是非常複雜的,而且資料量很大,經常需要做各種各樣的分析,甚至還需要做聚類分析(這裡的聚類分析不是指機器學習裡面的聚類模型,而是指把同一類資料提取出來單獨做分析,類似於 groupby 的操作)。所以 Neo4j 裡面在檔案系統上單獨儲存了標籤和屬性,就是為了在做檢索和分析的時候保證效能。下面我們們以一個形象的例子來介紹什麼是標籤,什麼是屬性。
這裡是我們應用中一個實際案例,大家看看我們對標籤和屬性的劃分:
-- 這裡我們們先看看這個語句表達的什麼意思,這是 Neo4j 專用的查詢語言 Cypher
-- 大家不要聽到又要學一門新語言就打退堂鼓哈,哈哈哈,後面我們會簡單講講這個語言的,兩個字:簡單
-- 下面這個查詢語句含義如下:
-- step 1: 首先查詢一個具有標籤為 “滬深股市”,且有一個屬性對 “{name: "sha_600610"}” 的節點,
-- step 2: 然後查詢這個節點的所有雙向關係
-- step 3: 在這些關係的中,要求另外一個節點具有 “人物_公司股東” 這個標籤
-- step 4: 返回這個節點,以及滿足要求的關係和相關節點
MATCH (node1:滬深股市 {name: "sha_600610"})-[relation]-(node2:人物_公司股東)
RETURN node1, node2, relation
複製程式碼
有人可能會問,其實也可以不用單獨儲存標籤和屬性的呀,我們完全可以把標籤和屬性作為節點的元資訊儲存在節點裡面。這個說法首先在理論和工程實現上來說完全沒有問題。但是試想一下,一個節點和關係非常非常大的圖,要做上面這個檢索的時候,會不會面臨一些問題?而 Neo4j 的這種儲存設計,是如何來解決這些問題的呢?這兩個問題留給大家思考,可以在評論裡交流大家的想法。
圖資料的處理和查詢
圖資料的處理,同傳統資料庫一樣的四字法則:CURD。而因為圖資料的特殊性,在大多數情況下,資料庫中每一個節點都有與之相連的關係,每一條關係,都必須有這個關係對接的兩個節點。這就要求圖資料庫的建立,更新,讀取,刪除都必須滿足一致性(或者事務完整)的原則。Neo4j 如何實現 ACID 的底層演算法我還沒有看,感興趣的可以先看看這個 talk: Evolution of Neo4j with ACID transactions, HA cluster, and CRUD transactions。
資料庫的查詢,這個不管是什麼資料庫,都是最基礎的功能,對於 sql 和 nosql 來說,簡單的查詢其實並沒有多大差別,只是一些複雜查詢或者針對特定場景的查詢條件下才會特地的選擇某一種資料庫。比如最常見的就是現在的大多數 WEB 應用,在儲存上基本上都會涉及下面幾類資料庫:
- HDFS: 儲存原始資料檔案(這裡把 HDFS 叫做資料庫有些許不準確,只是想表達常見應用中的資料儲存結構,請大家莫深究)
- MySQL: 儲存應用核心資料
- Mongo: 儲存一些不合適用關係型資料庫儲存的資料,比如說圖片,文件等
- Redis: 快取熱點資料,比如說使用者 cookies,應用中的計數器等
但是在實際應用中,會經常性出現一些很複雜的查詢語句,比如下面幾種例子:
-
在 APP 應用中,我們想要知道昨天新增的使用者中,有多少今天是繼續登陸 APP 的,這種查詢擴充套件開來,就是做 APP 使用者增長中耳熟能詳的 cohort analysis,下面有截圖展示這種分析的圖表
-
人際關係網路中,給定兩個節點,查詢這兩個節點的最短路徑,並且這些路徑滿足一定的條件?這樣說是不是有點書面化,那我們換一種說法,在社交網路中,如果你想認識川普的女兒伊萬卡,查詢一條最短路徑讓你結識到伊萬卡,並且這個路徑中每個人都是單身的。相信我,這是完完全全可能的,而且不管你是誰,很可能通過6~7個人就能認識伊萬卡了,amazing。不信的同學可以去了解了解六度分隔理論這個東西。
在上面這些複雜查詢中,特別是第二種情況,用傳統的關係型資料庫實現起來實在是不切實際。不光是效能上的原因,還有開發人員可用性的考慮,試想如果這樣子一個查詢,用 sql 來寫的話,這個 sql 語句應該怎樣寫?下面是一個案例,展示一個查詢用 neo4j 和 sql 下的對比。
2.2 Property Graph Model
上面我們講了 Neo4j 的資料儲存大概分為:節點,關係,屬性,標籤這四個類別,這是 Neo4j 儲存模組的工程實現,而現在我們要講的 Property Graph Model 就是這個工程實現的理論來源。正如有的朋友會說,其實簡單的儲存節點和關係,然後其他標籤,屬性什麼的都可以算做節點和關係的元資訊來儲存就行了。工程上實現的確也可以,但是理論上去研究,會發現這樣的效率會很低。
Property Graph Model,簡單的說,其實就是 Neo4j 的底層資料模型,這個資料模型推動了工程上按照四個方面來實現儲存模型。那什麼叫 Property Graph Model 呢,其實從核心上來講,Property Graph Model 有兩個核心要素:
- 節點:
- 每一個節點都是圖中的一個實體
- 每一個節點可以容納足夠多的屬性,以鍵值對的形式
- 每一個節點可以被打一個標籤,這些標籤相當於是特定知識領域的一種分類標準
- 關係:
- 每一條關係可以是有向的,無向的,命名的
- 每一條關係必須有兩個對應的節點
下面是一個 Property Graph Model 的展示:
2.3 Cypher Language
每種資料庫都有自己的一套查詢語言或者標準,就算是 SQL 中的王者 Sql Server 和 MySQL,其在一些語法細節上也有差異,更別說 Mongo, Redis 類似的非關係型資料庫了。Neo4j 也一樣,有自己專屬的查詢語言 Cypher。有的朋友聽到如果用 Neo4j 又要學習一門新的程式語言就很頭疼,其實這裡可以分享下我自己的經歷,以前我剛接觸 Neo4j 的時候,確實覺得又要學習一門新語言有些蛋疼,可是後來接觸下來,我覺得可能普通人估計只用花一兩天就能掌握 Cypher 了,因為 Cypher 的語意確實簡潔,直觀。
比如下面我們直接通過程式碼來對比下 MySQL 和 Neo4j 裡面的查詢,我相信即使不寫任何註釋,即使第一次接觸 Neo4j 的人也能輕輕鬆鬆的看懂這些查詢語句。
<!-- 1. 全表掃描 -->
<!-- mysql -->
SELECT p.*
FROM products as p;
<!-- neo4j -->
MATCH (p:Product)
RETURN p;
<!-- 2. 查詢價格最貴的10個商品,只返回商品名字和單價 -->
<!-- mysql -->
SELECT p.ProductName, p.UnitPrice
FROM products as p
ORDER BY p.UnitPrice DESC
LIMIT 10;
<!-- neo4j -->
MATCH (p:Product)
RETURN p.productName, p.unitPrice
ORDER BY p.unitPrice DESC
LIMIT 10;
<!-- 3. 按照商品名字篩選 -->
<!-- mysql -->
SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName = 'Chocolade';
<!-- neo4j -->
MATCH (p:Product)
WHERE p.productName = "Chocolade"
RETURN p.productName, p.unitPrice;
<!-- 其他的寫法 -->
MATCH (p:Product {productName:"Chocolade"})
RETURN p.productName, p.unitPrice;
<!-- 4. 按照商品名字篩選2 -->
<!-- mysql -->
SELECT p.ProductName, p.UnitPrice
FROM products as p
WHERE p.ProductName IN ('Chocolade','Chai');
<!-- neo4j -->
MATCH (p:Product)
WHERE p.productName IN ['Chocolade','Chai']
RETURN p.productName, p.unitPrice;
<!-- 5. 模糊查詢和數值過濾 -->
<!-- mysql -->
SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName LIKE 'C%' AND p.UnitPrice > 100;
<!-- neo4j -->
MATCH (p:Product)
WHERE p.productName STARTS WITH "C" AND p.unitPrice > 100
RETURN p.productName, p.unitPrice;
<!-- 6. 多表聯合查詢-->
<!-- mysql -->
SELECT DISTINCT c.CompanyName
FROM customers AS c
JOIN orders AS o ON (c.CustomerID = o.CustomerID)
JOIN order_details AS od ON (o.OrderID = od.OrderID)
JOIN products AS p ON (od.ProductID = p.ProductID)
WHERE p.ProductName = 'Chocolade';
<!-- neo4j -->
MATCH (p:Product {productName:"Chocolade"})<-[:PRODUCT]-(:Order)<-[:PURCHASED]-(c:Customer)
RETURN distinct c.companyName;
<!-- 7. -->
<!-- mysql -->
SELECT e.EmployeeID, count(*) AS Count
FROM Employee AS e
JOIN Order AS o ON (o.EmployeeID = e.EmployeeID)
GROUP BY e.EmployeeID
ORDER BY Count DESC LIMIT 10;
<!-- neo4j -->
MATCH (:Order)<-[:SOLD]-(e:Employee)
RETURN e.name, count(*) AS cnt
ORDER BY cnt DESC LIMIT 10
複製程式碼
2.4 Ecosystem
隨著應用的複雜化,技術的專業化,現在越來越難全面準確的評論一個框架,平臺甚至產品的好壞了。不過在我自身的經驗裡,我覺得一個好的框架,肯定是有一個完善的周邊生態的。這也是我經常說,在比較常見的開源框架裡面,neo4j 和 spark 是我覺得做得比較出色的,同時也是我最看好的兩個開源團隊。其一是他們做的很多事情都是從使用者角度來考慮的,而不僅僅是單純從工程角度來思考,特別是 spark 團隊,還成立了他們自己的平臺 databricks 提供給大眾使用;其二是這兩個開源產品的周邊生態都很豐富,體現在兩個方面,和流行框架產品的融合度,以及自己周邊開發工具的豐富程度。
比如說 Neo4j,官網有專門介紹其生態系統的地方,感興趣的盆友們請移步這裡:Neo4j Ecosystem。在這麼多開發工具裡面,最讓我驚歎的是 Neo4j browser 和今年上半年釋出的 neo4j bloom。
neo4j browser
neo4j browser 有點類似於 mysql 裡面的 workbench 或者 mongo 裡面的 RoboMongo,說白了就是一個資料庫的客戶端,但是 neo4j browser 做得非常友好,甚至看上去,用起來就像是一個成熟的產品一樣。記得很久很久以前我在跟團隊分享 neo4j 的時候,團隊裡面對 neo4j 不熟悉的人還問我這個哪個產品,互動做得很不錯。amazing!
neo4j bloom
說起 neo4j bloom,到現在我還忍不住想稱讚 neo4j 團隊,雖然 bloom 不是實現了什麼特別 NB 的演算法,也沒有什麼偉大的創新,但是這個產品卻是實打實的站在使用者層面去考慮,一下子就把 neo4j 的可用性,宜用性,實用性提高了好幾個檔次了。
談起 bloom,還得說我們準備做的知識圖譜產品開展開了,我們做的每一個圖譜,都有兩個亮點的地方:
- 我們做的圖譜背後有很多業內專家支援,我們需要把產品設計成支援這些專家修改更新圖譜內容的模式
- 圖譜的內容,都是會隨著時間變更的,我們也要支援時間序列上的圖譜,比如說可以查詢某個節點特定時間區間的圖譜
其中第一個亮點,我們之前做了很多調研,發現很多傳統的圖譜產品在內容更新和修改上都做得很複雜很不好用,比如說有的圖譜更新是後臺提供一個 excel 表格給相關專業人員去填寫這個表格;有的產品呢是有專門做的一個內容管理後臺,學會用那個管理後臺還得話不少時間。最重要的是,如果以這種模式提供給專家去修改內容,首先我們還得讓專家理解從 excel模版或者內容管理後臺 到圖譜內容結構這樣一個過程的思維轉變,要不然極有可能出現內容修改錯誤的情況。
所以,我們一直想做一個允許特定賬號直接在圖譜上進行修改的產品,比如說給專家的賬號開通修改許可權,專家可以和普通使用者開啟同樣的產品頁面,看到同樣的產品內容,只是專家賬號在點選圖譜節點時,可以有一個修改節點及其關係的許可權,做到實時修改,這樣子極大的提高了專家操作的效率,而且還節省了不少前臺,後臺開發的工程量。
正當我們定下這個方案不久,我就看到了 neo4j 釋出的關於 bloom 的文章和視訊,真的大為驚歎,當時我就直接把文章和視訊轉發到團隊的微信群裡,neo4j 開發的 bloom 和我們設想的產品模式幾乎一致,它直接想把我們做的事完成了,而且還做得漂亮,光說 bloom 的那個互動,就已經把國內不少做圖譜的產品甩開幾條街了。
感興趣的盆友可以先看看這個視訊體驗體驗:Neo4j Bloom: Investigating Patterns in Financial Transactions
3. Neo4j and Knowledge Graph
伴隨 2012 年 google 正式釋出知識圖譜搜尋引擎和 2013 年 facebook 開放知識圖譜搜尋入口以來,知識圖譜迎來了一波發展浪潮,neo4j 作為原生的圖資料庫,也迎來了它的春天。但是就業內朋友交流來看,知識圖譜類創業公司和產品數量上並不多,質量上也缺乏重磅產品,除了傳統的社交網路方面(這方面有 facebook 的關係搜尋和 linkedin 的人脈搜尋)。究其原因,其實有兩個比較大的原因,一個是熟悉圖資料庫,瞭解知識圖譜的人才還比較少;另外一個原因是業務的抽象化本身門檻就比較高。
葛優在《天下無賊》中有一句話特別出名 “21世紀什麼最貴?人才“。我們在業內交流下來,其實也發現一個比較值得思考的問題,就是雖然知識圖譜發展也有六七年的樣子,但其實真正在圖資料庫方面去研究的人才很少。同樣是 NoSQL 資料庫,為什麼經常可以聽到關於 mongo,redis 等資料庫技術的討論,卻很少見到 neo4j 的技術問答呢?我們覺得,其中的原因可能是因為 mongo,redis 這類資料庫相對於 neo4j 來說,是比較 general 的技術,就是說 mongo,redis 在各種應用中的大多數場景中都可以用到,它們並不依賴某一種特定型別的應用。而 neo4j 則有所不同,neo4j 比較專注在圖相關的資料結構的儲存和查詢方面,而且 neo4j 本身還需要使用特定的查詢語言 (cypher,我們前面也有講過),一些企業和工程師就缺乏動力去使用這門新的技術。所以從這兩個點來說,neo4j 的辨識度,流行度遠遠不及 mongo,redis 類的 nosql。下面是我查詢了 google trend 自 2008 年來這三個關鍵詞的表現,可見一斑。
不過如果只看 neo4j 的 google trend 資料的話,它還是慢慢在走向人們的視野。
而說到知識圖譜,其實國內外在這方面的成熟產品可以說鳳毛麟角,出去上面我們說的技術方面的人才儲備問題外,其實最大的還是本身業務的抽象化門檻太高。比如說,在金融領域有不少創業公司嘗試過用知識圖譜來描述和分析一個公司,我們們來看看下面這個案例截圖:
上面這個金融知識圖譜,眨眼一看,似乎資訊還挺齊全,可是如果站在一個金融專業投資者角度來看,問題就暴露很多了:
- 圖譜本身缺乏時間資訊,如果說圖譜中的資料發生更新,如何在圖譜中體現這個更新
- 圖譜設計缺陷,看完這個圖譜上下兩個部分的文字內容,估計你多年的頸椎病都可以治好了,哈哈哈
- 圖譜設計缺陷,有的公司光產品,子公司就有上百家,在圖譜中如果一次性全展現開來,那個視覺效果可能沒法讓人接受
- 缺乏分析基礎,類似股東股權關係的時候,在圖譜中都沒展示比例資料,沒法做更細一步的分析
還有其他很多的點就不一一列出了,列這麼多其實只是想說,在知識圖譜這個領域,不管做什麼產品都要於業務本身緊密相連,現在是一個論專業化,比專業性的時代,不能簡簡單單的把一個技術套到一個產品上就叫創新了,而是要通過技術把之前難以實現的具有價值的事情做成做好。
4. Summary
如果要用幾句話來總結這篇 post 裡我想表達的意思,我想應該是下面幾句:
- 凡事均有因果,不論是做事還是思考,在沒有找到核心底層邏輯之前,都是存在很大的挖掘和探索空間的;
- 做技術架構設計,如果你能回答下面幾個問題,那你的架構就沒有問題:
- 【1】你的同類產品的架構方案大概是什麼樣子的,他們都有哪些優缺點
- 【2】你的產品主要解決的核心問題和麵臨的核心挑戰是哪些
- 【3】你的這個技術架構設計相對同類產品來說,相對同類產品有哪些優勢,是如何解決上面的第二點中的問題的
- 圖資料庫,本質上是圖資料結構的固化,查詢解決方案,往底層追問,其實是對真實世界中非結構化的關聯關係的計算機抽象表達
- 不管做架構還是做開發,永遠要相信這個世界上沒有最好的技術,只有最適合當前應用場景的技術
5. Reference
- History of Databases and Graph Database
- What is the graph database?
- 柯尼斯堡七橋問題
- keylines
- linkurio
- Graph_database
- 8 Solid Tips for Succeeding with Neo4j
- Graph Databases for Beginners: Native vs. Non-Native Graph Technology
- Neo4j Graph Storage
- An overview of Neo4j Internals
- Evolution of Neo4j with ACID transactions, HA cluster, and CRUD transactions
- How to Run a Cohort Analysis in Google Analytics
- 六度分隔理論
- Property Graphs Explained
- From SQL to Cypher – A hands-on Guide
- Neo4j Ecosystem
- Neo4j Browser User Interface Guide
- Introducing Neo4j Bloom: Graph Data Visualization for Everyone
- Neo4j Bloom for Visualization - Pre-release Demo of Beer Graph
- Neo4j Bloom: Investigating Patterns in Financial Transactions
- Google Knowledge Graph
- Introducing Graph Search Beta