如何使用GES進行社交關系考據?---GES查詢能力介紹


開發者李雷小朋友維護了一個自己的關系鏈圖數據庫,他怎么能從圖數據庫中查詢出與他互相關注且年齡大於30的朋友呢?

這里先介紹幾種圖原生查詢語言寫法:

1.gremlin

g.V("李雷").outE('friend').has('age',gt(30)).otherV().where(out('friend').
(hasId('李雷'))).limit(100)

2.cypher

match (a)-[r1:friend]->(b)-[r2:friend]->(c) where a.mid='李雷' and r1.age>30 and a=c return id(b) limit 100

以上兩種寫法等價,只是使用的圖查詢語言有區別。前者使用gremlin(Apache軟件基金會下TinkerPop開發的graph traversal language)編寫, 后者為Neo4j於2015年發布的圖查詢語言開源版本openCypher。

查詢方式一覽

GES支持多種查詢方式,本文主要討論復雜查詢,如多跳過濾,簡單環路查詢,模式匹配等復雜查詢類。當前GES主要通過gremlin,cypher和一些原生API來支持各種場景的查詢需求。

||優點|缺點|說明
:--😐:--😐:--|:--|:--|:--
Gremlin|functional language|1.表達能力(圖靈完備) 2.支持groovy腳本|1.性能差(exponential runtime tree) 2.復雜的查詢書寫困難|適用於體驗,demo,或不追求性能的場景
Cypher|Pattern match style, declarative|1.類SQL寫法 2.深度集成於GES,性能比gremlin好一些|1.表達能力對圖來說差一些(僅為SQL complete) 2.流式支持有限|適用於一般性場景
原生API|json parameter|1.性能非常好 2.傻瓜參數形式|1.靈活性差 2.使用場景有限 3.表達能力差|提供常用查詢API,適用於最求性能,高並發,低延遲的場景

性能

本節以上文所述李雷的好友場景展示一個查詢k=2環路的性能測試,用以幫助大家更直觀地了解各個查詢間的性能差距:

其中,

  • filterV2 - GES原生API,該API性能最佳,TPS與延遲表現優異。
  • Cypher - GES的cypher查詢。
  • Neo4j - Neo4j community 4.2.3版本cypher。
  • gremlin - GES的gremlin,未在圖中體現,實際性能最差,故未做對比。

gremlin使用手冊

gremlin包括OLTP和OLAP兩個部分的語法。其OLTP的語法靈活多變,符合圖原生的表達方式,被廣泛集成於各個圖數據庫廠商。

GES查詢能力優異,但我們僅建議在demo/簡單查詢中使用gremlin。其性能遠遠低於內核原生API與cypher, 這是由gremlin的集成方式決定的,雖然GES在常用語法上做了適當的優化,但是並不能完全覆蓋所有的gremlin查詢。我們向您推薦使用集成度更加高的cypher查看后續cypher章節進行常態化查詢,如果有更需要性能的場景,請使用原生API。

查看以下參考資料獲取更多信息:

  1. tinkerpop documentation tinkerpop官方文檔。內容詳實,各個step都有案例介紹。
  2. gremlin 實踐案例 gremlin實踐案例

常用語法

g.V() //獲取圖中所有點。注意該行為在大圖上是高危操作,小心使用。
g.V().limit(10) //取圖中10個點。非隨機。
g.V('小霞','小智').values('age')  //獲取圖中id為小智和小霞的屬性值age的值。
> [20, 22]
g.V('小智').out('朋友').out('朋友')  //獲取小智的朋友的朋友
> [小明]
g.V('小智').outE('朋友').inV().outE('朋友').inV()  //該條語句含義和上條完全一致。
g.V('小智').out('朋友').has('age',gt(30))  //獲取小智年齡大於30的朋友
g.V('小智').out('朋友').values('age').sum()  //統計小智所有朋友的年齡總和

GES gremlin特殊語法/優化

GES集成了gremlin中的OLTP功能,並在一定程度上做了部分功能增強與strategy優化。

增強版Text Predicate

g.V().has('name', Text.textSubString('xx'))...
Predicate 描述
textSubString 子字符串
textCISubString 忽略大小寫的子字符串
textFuzzy 模糊匹配
textPrefix 前綴查詢
textRegex 正則匹配

注意事項

在指定schema時,最好不要給屬性取名為id, label, property, properties。

在進行gremlin操作時,有很多step會把結果轉化為map結果。眾所周知,在map結構中,是不允許出現兩個相同key的,一般來說當我們向一個map中重復insert多個相同的key,其value會被覆蓋 or 該操作被取消。

故如果我們把屬性名取為id, label, property, properties,在很多操作中,如果id與屬性中的id一起返回,結果將是不完整的。

cypher使用手冊

cypher在語法上更接近於SQL,表達能力稍微欠缺一些(sql完備),但已能應對絕大多數場景。

GES對於cypher的集成更加接近內核端,充分利用了內核的性能優勢。

查看以下參考資料獲取更多信息:

  1. cypher refcard cypher語法參考卡。
  2. GES cypher GES cypher公有雲API文檔,包含兼容性說明,數據類型支持,表達式,函數等說明。

常用語法

match (n) return n //獲取圖中所有點。注意該行為在大圖上需小心使用。
match (n) return n limit 10 //取圖中10個點。非隨機。
match (n) where n.movieid IN ['小霞','小智']  return n.age  //獲取圖中id為小智和小霞的屬性值age的值。
match (n)-->(m1:朋友)-->(s1)-->(m2:朋友)-->(s2) where id(n)='小智' return s2 //獲取小智的朋友的朋友
match (n)-->(m1:朋友)-->(s) where id(n)='小智' and s.age>30 return s //獲取小智年齡大於30的朋友

兼容性說明

GES對cypher會進行持續性的優化與加強。當前因為特性/開發進度的原因暫不支持包括:

  1. 目前暫不支持union、merge、foreach、optional等操作,暫不支持使用Cypher語句增刪索引,后續會逐漸開放支持。
  2. 由於GES的元數據不是Schema Free的,點邊label屬性等有嚴格的限制,因此不支持Remove操作。
  3. Order by子句不支持List類型的排序,當屬性值的Cardinality不為single時,排序結果未知。

以上說明可在cypher文檔中獲取最新的支持情況。

GES cypher 特殊化處理

預置條件

為了加速查詢以及優化查詢計划,GES的Cypher查詢編譯過程中使用了基於label的點邊索引。可通過界面/API添加相關的索引新建索引API。

POST http://{SERVER_URL}/ges/v1.0/{project_id}/graphs/{graph_name}/indices
{
        "indexName": "cypher_vertex_index",
        "indexType": "GlobalCompositeVertexIndex",
        "hasLabel": "true",
        "indexProperty": []
}

原生API使用手冊

內核原生API是專門為常用場景提供的高性能版本。其輸入輸出均為json格式。

GES提供了豐富多樣的原生API接口,包括管理面API(主要是圖操作,導入導出備份等),業務面API。業務面API包括基礎的圖數據庫功能,如點邊的CRUD,索引的管理,高級查詢path query,高級算法庫。

查看以下參考資料獲取更多信息:

  1. 管理面API概覽
  2. 業務面API概覽,業務面API的調用方法可以參考此篇博文。
  3. 執行算法說明

常用API說明

一.Path Query

該接口支持對多跳過濾,循環執行遍歷查詢進行加速。

例如以下gremlin語句:

g.V('node1').repeat(out('label_1')).times(3).emit()

以上gremlin是一個三跳查詢,從點node1出發,查詢其出邊關系為label1的鄰居的鄰居的鄰居。並返回過程中所有涉及到的點。該腳本通過repeat來組織查詢,並通過times來控制loop的次數,甚至后續還可以通過關鍵字until來終止traversal。

以上gremlin使用path query調用可使用:

POST /ges/v1.0/{projectId}/graphs/{graphName}/action?action_id=path-query
{
  "repeat": [
    {
      "operator": "outV",
      "edge_filter": {
        "property_filter": {
          "leftvalue": {
            "label_name": "labelName"
          },
          "predicate": "=",
          "rightvalue": {
            "value": "label_1"
          }
        }
      }
    }
  ],
  "emit": true,
  "times": 3,
  "vertices": [
    "node1"
  ]
}

1. by mode

例如針對二跳鄰居,我們可以通過參數by對id,label,property進行過濾:

g.V("a").repeat(out()).times(2).by(id()) //輸出a的所有一跳二跳鄰居的id。
g.V("a").repeat(out()).times(2).by(label()) //輸出a的所有一跳二跳鄰居的label。

而使用path query我們可以很輕易地把上述gremlin轉化為原生API以獲得更加優異的性能,關鍵字含義幾乎一致。在by中可以指定輸出的形式,如輸出id:

{
  "repeat": [
    {
      "operator": "outV"
    }
  ],
  "times": 2,
  "vertices": [
    "a"
  ],
  "by": [{"id": true}]
}

2. select+by mode

該模式可任意選擇traverse路徑上經過的N層。

如上圖,我們希望顯示李雷與其第二跳好友的關聯關系:

gremlin寫法:

g.V('李雷').as('v0').repeat(out()).times(2).as('v2').select('v0','v2').by(id()).by(id()).dedup()
{//返回樣例
  "results": [
    {
      "v0": "李雷",
      "v2": "小智"
    },
    {
      "v0": "李雷",
      "v2": "小霞"
    }
  ]
}

cypher寫法:

match (v0)<--(v1)<--(v2) where id(v0)='1' return distinct id(v0),id(v2)

//返回json如下,形式稍顯冗余:
{
  "data": [
    {
      "row": [
        "李雷",
        "小智"
      ],
      "meta": [
        null,
        null
      ]
    },
    {
      "row": [
        "李雷",
        "小霞"
      ],
      "meta": [
        null,
        null
      ]
    }
  ]
}

使用path query:

{
  "repeat": [
    {
      "operator": "outV"
    }
  ],
  "times": 2,
  "vertices": [
    "李雷"
  ],
  "by": [{"id": true},{"id": true}],
  "select": ["v0", "v2"]
}

//返回json如下,直接返回select中的參數:
{
  "select": [
       ["李雷", "小智"],
       ["李雷", "小霞"]
  ]
}

二.算法API

算法API提供豐富的原生算法庫,可以通過界面或者API使用。

在界面上直接運行算法能夠可視化地查看算法結果,關於每個算法的詳情,也可以查詢官方算法文檔。

參數 是否必選 說明 類型 取值范圍 默認值
alpha 權重系數(又稱阻尼系數) Double 0~1,不包括0和1 0.85
convergence 收斂精度 Double 0~1,不包括0和1 0.00001
max_iterations 最大迭代次數 Integer 1~2000 1000
directed 是否考慮邊的方向 Boolean true或false true

如上pagerank算法參數列表,列出了參數說明,取值范圍以及默認值。

此外,我們也可以直接使用RESTful API的形式調用算法,大部分算法以異步的形式交互:調用算法后我們可以獲得一個jobId。使用jobId我們可以查詢算法運行的狀態及運行結果。同時也可以對結果進行分頁及數量截取。

GET http://{SERVER_URL}/ges/v1.0/{project_id}/graphs/{graph_name}/jobs/{job_id}/status?offset=0&limit=2

最短路徑 shortest_path

最短路徑屬於一個傳統的圖論問題,其包含各種不同的形式。如單源最短路,多源最短路。常用的算法如Dijkstra, Bellman-Ford, Floyd-Warshall, Johnson等算法。

GES提供多種場景的最短路徑,如shortest_path, all_shortest_paths, shortest_path_of_vertex_sets等。均以原生API或圖形界面的方式提供

該API屬於算法API,其調用方式查看文檔。

POST /ges/v1.0/{project_id}/graphs/{graph_name}/action?action_id=execute-algorithm
{
    "executionMode": "sync",
    "algorithmName": "shortest_path",
    "parameters": {
        "source": "Alexander Payne",
        "target": "Harrison Ford",
        "weight":"score"
    }
}

不同查詢方式場景詳解

案例1-好友推薦

我們向李雷推薦好友,思路是:向他推薦其好友的好友,但是推薦的好友中不應包含李雷本身的好友,比如圖中韓梅梅同時是李雷的一跳好友和二跳好友。這時我們不應向李雷推薦韓梅梅,因為她已經是李雷的好友了。

下面將分別展示使用gremlin,cypher和path query查詢,返回均為推薦路徑:李雷->李雷好友->推薦好友。

gremlin

gremlin>
g.V("李雷").repeat(out("friend").simplePath().where(without('1hop')).store('1hop')).
times(2).path().by("name").limit(100)

gremlin>
[李雷,小明,小智]
[李雷,韓梅梅,小智]
[李雷,韓梅梅,小霞]

cypher

match (a)-[:friend]->(d) where id(a)='李雷' with a, collect(d) as neighbor
match (a)-[:friend]-(b)-[:friend]-(c)
where not (c in neighbor)
return a.name, b.name, c.name

//返回樣例
[
  {
    "row": ["李雷", "小明","小智"],
    "meta": [null, null, null]
  },
  {
    "row": ["李雷","韓梅梅", "小智"],
    "meta": [null, null, null]
  },
  {
    "row": ["李雷", "韓梅梅", "小霞"],
    "meta": [null, null, null]
  }
]

path query

{
  "repeat": [
    {
      "operator": "outV",
      "edge_filter": {
        "property_filter": {
          "leftvalue": {
            "label_name": "labelName"
          },
          "predicate": "=",
          "rightvalue": {
            "value": "friend"
          }
        }
      }
    }
  ],
  "times": 2,
  "vertices": [
    "李雷"
  ],
  "by": [{"id": true},{"id": true},{"id": true}],
  "select": ["v0", "v1", "v2"]
}
{
  "select": [
	["李雷", "小明","小智"],
	["李雷","韓梅梅", "小智"],
	["李雷", "韓梅梅", "小霞"]
  ]
}

案例2-(k=2環路)

開發者李雷小朋友維護了一個自己的關系鏈圖數據庫,他怎么能從圖數據庫中查詢出與他互相關注且年齡大於30的朋友呢?

回到文章開頭,我們如何幫助李雷完成查詢呢?這其實可以歸結為一個環路問題。

我們需要獲取李雷的雙向好友。即從李雷出發的k=2且邊上過濾條件為age>30,friend的環路。

下面將分別展示使用gremlin,cypher,path query和環路算法進行查詢,該四種方式均可以幫助李雷完成目標。

gremlin

gremlin>
g.V("李雷").outE().has('age',gt(30)).otherV().where(out('friend').
(hasId('李雷'))).limit(100)

cypher

match (a)-[r1]->(b)-[r2:friend]->(c) where a.mid='李雷' and r1.age>30 and a=c return id(b) limit 100
path query
POST /ges/v1.0/{projectId}/graphs/{graphName}/action?action_id=path-query
{
    "repeat": [
        {
            "operator": "outV",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "property_name": "age"
                    },
                    "predicate": ">",
                    "rightvalue": {
                        "value": "30"
                    }
                }
            }
        },
        {
            "operator": "outV",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "label_name": "label_name"
                    },
                    "predicate": "=",
                    "rightvalue": {
                        "value": "friend"
                    }
                }
            }
        }
    ],
    "limit": 100,
    "times": 2,
    "vertices": ["李雷"],
    "by": [{ "id": true}],
    "select": ["v1"]
}

環路算法

{
    "algorithmName": "filtered_circle_detection",
    "parameters": {
        "sources": "李雷",
        "n": 100
    },
    "filters": [
        {},
        {
            "operator": "out",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "property_name": "age"
                    },
                    "predicate": ">",
                    "rightvalue": {
                        "value": "30"
                    }
                }
            }
        },
        {
            "operator": "out",
            "edge_filter": {
                "property_filter": {
                    "leftvalue": {
                        "label_name": "label_name"
                    },
                    "predicate": "=",
                    "rightvalue": {
                        "value": "friends"
                    }
                }
            }
        }
    ]
}

本文由華為雲發布。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM