博客 / 詳情

返回

打通可觀測性的“任督二脈”:實體與關係的終極融合

作者:隰宗正(霜鍵)

點擊此處,查看視頻演示!

當可觀測數據遇上“關係圖譜”

1.1 從“孤立的實體”到“連接的網絡”

在現代雲原生架構的宏大敍事中,我們習慣於將系統中的每個組件——服務、容器、中間件、基礎設施,視為獨立的“實體”進行監控和管理。我們為它們配置儀表盤,設置告警,追蹤它們的性能指標。然而,這種“個體視角”存在一個根本性的盲點:它忽略了系統最本質的特徵——連接(Connection)。任何一個實體都不是孤立存在的,它們通過調用、依賴、包含等關係,構成了一張複雜巨大、動態變化的“關係圖譜”。

傳統的監控和查詢工具,無論是基於 SQL 還是 SPL,其核心都是處理二維的、表格化的數據。它們擅長回答關於“個體”的問題(“這個 Pod 的 CPU 使用率是多少?”),但在回答關於“關係”的問題時卻顯得力不從心。當面對“這個服務的故障會影響哪些下游業務?”或“要訪問到核心數據庫,需要經過哪些中間服務?”這類問題時,傳統工具往往需要複雜的 JOIN 操作、多步查詢,甚至需要工程師結合線下架構圖進行“人腦拼湊”。這種方式不僅效率低下,而且在關係複雜、層級深的情況下幾乎無法完成。我們擁有了所有“點”的數據,卻失去了一張看清“線”的地圖。

image

1.2 我們的思路:融合圖查詢

面對這一挑戰,我們的解決思路是:將“圖”(Graph)作為可觀測數據模型的重要組成。我們認為,系統的真實形態本就是一張圖,那麼對它的查詢和分析,也應該使用最符合其本質的方式——圖查詢。

為了實現這一點,我們在 UModel 體系的核心構建了 EntityStore。它採用了創新的雙存儲架構,同時維護了 __entity__ 日誌庫(存儲實體的詳細屬性)和 __topo__ 日誌庫(存儲實體間的拓撲關係)。這相當於我們為整個可觀測系統建立了一個實時更新的、可查詢的數字孿生圖譜。

基於這個圖譜,我們提供了從易到難、層層遞進的三種圖查詢能力,以滿足不同用户的需求:

  • graph-match:為最常見的路徑查詢場景設計,語法直觀,讓用户能像描述一句話一樣(“A 經過 B 調用了 C”)來快速查找特定鏈路。
  • graph-call:封裝了最高頻的圖算法(如鄰居查找、直接關係查詢),通過函數式接口提供,用户只需關心意圖(“找 A 的 3 跳鄰居”)而無需關心實現細節。
  • Cypher:引入業界標準的圖查詢語言,提供最完整、最強大的圖查詢能力,支持任意複雜的模式匹配、多級跳躍、聚合分析,是處理複雜圖問題的終極武器。

這一整套解決方案,旨在將強大的圖分析能力,以一種低門檻、工程化的方式,提供給每一位運維和開發工程師。

1.3 核心價值:解鎖系統洞察的新維度

引入圖查詢能力,不僅僅是增加了一種新的查詢語法,更是為系統洞察解鎖了一個全新的維度。

  • 全局化的故障影響分析(爆炸半徑分析): 當故障發生時,可以通過一次查詢,快速確定該故障點向下遊輻射的所有可能路徑和受影響的業務範圍,為故障處理的優先級排序和決策提供實時數據支持。
  • 端到端的根因溯源: 與影響分析相反,當某個底層服務出現問題時,可以向上游回溯,快速定位是哪個業務或變更觸發了異常,實現精準的根因定位。
  • 架構健康度與合規性審計: 可以通過圖查詢來驗證線上系統的實際架構是否與設計相符。例如,查詢是否存在“跨網絡域的非法調用”,或者“某個核心數據服務是否被非授權的應用依賴”,從而實現架構的持續治理。
  • 安全與權限鏈路分析: 在安全審計中,可以追蹤從用户到具體資源的完整訪問路徑,確保每一層權限授予都符合安全規範,防止潛在的數據泄露風險。

總而言之,圖查詢能力將我們對系統的認知從“點的集合”提升到了“結構化的網絡”,使得我們能夠基於系統組件之間的真實關係進行提問和分析,從而獲得前所未有的深度洞察力。它是一把鑰匙,開啓了在複雜系統中進行高效故障排查、架構治理和安全審計的大門。

圖查詢相關概念

2.1 相關概念

image

協作關係:

UModel (知識圖譜)
├── EntitySet: apm.service (類型定義)
│   ├── Entity: user-service (實例1)
│   ├── Entity: payment-service (實例2)
│   └── Entity: order-service (實例3)
├── EntitySet: k8s.pod (類型定義)
│   ├── Entity: web-pod-123 (實例1)
│   └── Entity: api-pod-456 (實例2)
└── EntitySetLink: service_runs_on_pod (關係定義)
    ├── Relation: user-service -> web-pod-123
    └── Relation: payment-service -> api-pod-456

EntityStore 藉助於 SLS LogStore 資源實現數據寫入、消費等功能,在創建 EntityStore 時,會同步創建以下 LogStore 資產:

  • ${workspace}__entity:用於寫入實體數據
  • ${workspace}__topo:用於寫入關係數據

本文介紹的圖查詢用法,是針對於寫入 ${workspace}__topo 的關係數據的查詢。支持多跳關係路徑分析、實體鄰接關係分析、自定義拓撲模式識別等能力。

注意: 本文介紹的圖查詢用法,系可觀測 2.0 高階 PaaS API 的底層查詢,適合高度定製化自由查詢模式的資深用户。若僅需簡單的關聯查找、查詢信息等能力,推薦使用高階 PaaS API,接口更友好。

2.2 總覽

image

圖查詢基礎概念

在深入使用圖查詢之前,理解其基礎概念至關重要。圖查詢的核心思想是將數據抽象為圖(Graph)結構:實體是節點(Node),關係是邊(Edge)。每個節點都有標籤(Label)和屬性(Properties),標籤用於標識節點的類型,屬性用於存儲節點的詳細信息。同樣,每條邊也有類型(Type)和屬性,類型表示關係的類別,屬性可以存儲關係的額外信息。

3.1 節點和邊的描述語法

在圖查詢中,使用特定的語法來描述節點和邊:

  • 節點: 使用小括號 () 表示
  • 邊: 使用中括號 [] 表示
  • 描述格式: <變量名>:<標籤> {屬性鍵值對}

下面是一些基礎語法示例:

// 任意節點
()
// 具有特定標籤的節點  
(:"apm@apm.service")           // graph-match寫法
(:`apm@apm.service`)           // cypher寫法
// 具有標籤和屬性的節點
(:"apm@apm.service" { __entity_type__: 'apm.service' })
// 命名變量的節點
(s:"apm@apm.service" { __entity_id__: '123456' })
// 任意邊
[]
// 命名邊
[edge]
// 具有類型的邊
[e:calls { __type__: "calls" }]

語法差異説明:

  • graph-match:在 SPL 上下文中,特殊字符需要雙引號包裹
  • Cypher:作為獨立語法,標籤使用反引號包裹
// graph-match語法
.topo | graph-match (s:"apm@apm.service" {__entity_id__: '123'})-[e]-(d)
        project s, e, d
// Cypher語法(``apm@apm.service`` 反引號字符串格式,使用兩個反引號包裹)
.topo | graph-call cypher(`
    MATCH (s:``apm@apm.service`` {__entity_id__: '35af918180394ff853be6c9b458704ea'})-[e]-(d)
    RETURN s, e, d
`)

3.2 路徑語法與方向

圖查詢路徑使用 ASCII 字符描述關係方向:

image

3.3 返回值結構

在 EntityStore 的體系中,節點的標籤格式為 domain@entity_type,例如 apm@apm.service 表示域為 apm、實體類型為 apm.service 的節點。這種標籤設計不僅清晰地表示了節點的歸屬和類型,還支持基於域的快速過濾和查詢。節點的屬性包括了系統內置的屬性(如 __entity_id____domain____entity_type__)以及用户在寫入時自定義的屬性(如 servicename、instanceid 等)。邊的類型同樣可以用字符串表示,比如 callsruns_oncontains 等,每條邊也會攜帶相應的屬性信息。

3.3.1 節點 JSON 格式

{
  "id": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
  "label": "apm@apm.service", 
  "properties": {
    "__domain__": "apm",
    "__entity_type__": "apm.service",
    "__entity_id__": "347150ad7eaee43d2bd25d113f567569",
    "__label__": "apm@apm.service"
  }
}

3.3.2 邊 JSON 格式

{
  "startNodeId": "apm@apm.service:347150ad7eaee43d2bd25d113f567569",
  "endNodeId": "apm@apm.service.host:34f627359470c9d36da593708e9f2db7",
  "type": "contains",
  "properties": {
    "__type__": "contains"
  }
}

圖查詢的本質是模式匹配:用户描述一個圖模式(Pattern),系統在圖中查找所有符合該模式的子圖。圖模式可以用路徑表達式來表示,最基礎的路徑表達式就是 (節點)-[邊]->(節點),這表示從源節點通過一條邊到達目標節點。路徑表達式可以擴展為更復雜的模式,比如 (A)-[e1]->(B)-[e2]->(C) 表示從 A 經過 B 到達 C 的兩跳路徑,或者 (A)-[*1..3]->(B) 表示從 A 到 B 的可能的多跳路徑。這種表達方式既直觀又強大,能夠描述從簡單的一對一關係到複雜的多層級網絡路徑。

graph-match:直觀的路徑查詢

graph-match 是圖查詢中最直觀、最容易上手的功能。它的設計哲學是讓用户能夠用接近自然語言的方式描述查詢意圖,然後系統自動執行查詢並返回結果。graph-match 的語法結構相對簡單,主要由路徑描述和結果投影兩部分組成。

graph-match 的核心特點是必須從已知的起始點開始查詢。起始點需要同時指定標籤和 __entity_id__ 屬性,這確保了查詢能夠快速定位到具體的實體。從技術實現的角度看,這種設計是有意為之:圖的遍歷通常是一個指數級複雜度的操作,如果允許從任意模式開始查詢,可能會導致全圖掃描,性能無法保證。而強制指定起始點後,系統可以基於該點進行有向遍歷,將搜索空間限制在可控範圍內。

路徑描述的語法遵循直觀的方向性表達。(A)-[e]->(B) 表示從 A 到 B 的有向邊,(A)<-[e]-(B) 表示從 B 到 A 的有向邊,(A)-[e]-(B) 表示雙向邊(不限制方向)。用户可以為路徑中的每個節點和邊命名變量,這些變量可以在後續的 project 語句中使用。路徑可以連接多個節點和邊,形成多跳路徑,比如 (start)-[e1]->(mid)-[e2]->(end)

project 語句用於指定返回的內容。用户可以直接返回節點或邊的 JSON 對象,也可以使用點號語法提取特定的屬性,如 "node.__entity_type__"、"edge.__type__ attribution。project 還支持重命名操作,讓返回的字段具有更友好的名稱。這種靈活的輸出方式讓 graph-match 既能滿足快速探索的需求(返回完整對象),也能滿足數據分析的需求(提取特定字段)。

4.1 實際應用案例

4.1.1 全鏈路路徑查詢

查找從特定操作開始的完整調用鏈路:

.topo |
  graph-match (s:"apm@apm.operation" {__entity_id__: '925f76b2a7943e910187fd5961125288'})
              <-[e1]-(v1)-[e2:calls]->(v2)-[e3]->(v3)
  project s, 
          "e1.__type__", 
          "v1.__label__", 
          "e2.__type__", 
          "v2.__label__", 
          "e3.__type__", 
          "v3.__label__", 
          v3

返回結果:

  • s:起始操作節點
  • e1.type:第一段關係類型
  • v1.label:中間節點標籤
  • v2, v3:後續節點信息

4.1.2 鄰居節點統計

統計特定服務的鄰居分佈情況:

.topo |
  graph-match (s:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})
              -[e]-(d)   
  project eType="e.__type__", dLabel="d.__label__"
| stats cnt=count(1) by dLabel, eType
| sort cnt desc
| limit 20

4.1.3 條件路徑查詢

查找滿足特定條件的路徑終點:

.topo |
  graph-match (s:"apm@apm.service.operation" {__entity_id__: '6f0bb4c892effff81538df574a5cfcd9'})
              <-[e1]-(v1)-[e2:runs_on]->(v2)-[e3]->(v3)
  project s, 
          "e1.__type__", 
          "v1.__label__", 
          "e2.__type__", 
          "v2.__label__", 
          "e3.__type__", 
          destId="v3.__entity_id__", 
          v3 
| where destId='9a3ad23aa0826d643c7b2ab7c6897591'
| project s, v3

4.1.4 Pod 到 Node 的關係鏈

追蹤 Pod 的完整部署鏈:

.topo |
  graph-match (pod:"k8s@k8s.pod" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'})
              <-[r1:contains]-(node:"k8s@k8s.node")
              <-[r2:contains]-(cluster:"k8s@k8s.cluster")
  project 
    pod,
    node, 
    cluster,
    "r1.__type__",
    "r2.__type__"

4.1.5 graph-match 限制

儘管 graph-match 非常直觀易用,但它也有一些限制:

image

graph-call:函數式圖操作

graph-call 提供了一套函數式的圖查詢接口,這些函數封裝了常見的圖操作模式,讓用户能夠更高效地執行特定類型的查詢。graph-call 的設計理念是提供聲明式的函數接口,用户只需指定意圖和參數,具體的遍歷算法由系統優化執行。

getNeighborNodes 是最常用的 graph-call 函數,它用於獲取指定節點的鄰居節點。函數的簽名是 getNeighborNodes(type, depth, nodeList),其中 type 參數控制遍歷的類型,depth 參數控制遍歷的深度,nodeList 參數指定起始節點列表。type 參數的取值包括:sequence(有向序列遍歷,保持邊的方向性)、sequence_in(只返回指向起始節點的路徑)、sequence_out(只返回從起始節點出發的路徑)、full(全方向遍歷,不考慮邊的方向)。這種類型劃分讓用户能夠根據實際需求選擇合適的遍歷策略。

depth 參數控制遍歷的深度,實際使用中建議不要設置過大,一般 3 到 5 層已經足夠覆蓋大多數場景。過深的遍歷不僅會帶來性能問題,返回的結果也可能因為關聯關係過多而失去實際意義。nodeList 參數接受一個節點描述數組,每個節點描述遵循與 graph-match 相同的語法,需要指定標籤和 __entity_id__getNeighborNodes 會為每個起始節點分別執行遍歷,然後合併結果返回。

image

getNeighborNodes 的返回結果包含四個字段:srcNode(源節點 JSON)、destNode(目標節點 JSON)、relationType(關係類型)、srcPosition(源節點在路徑中的位置,-1 表示直接鄰居)。srcPosition 字段特別有用,它讓用户能夠區分直接關係和間接關係,在做統計分析時可以按位置分組,瞭解不同層級的關係分佈。

getDirectRelations 函數用於批量查詢節點之間的直接關係。與 getNeighborNodes 不同,getDirectRelations 只返回直接相連的關係,不進行多跳遍歷。這個函數特別適合批量檢查多個已知節點之間的關係,比如檢查一組服務之間是否存在調用關係,或者檢查一組資源之間的依賴關係。函數的參數是一個節點列表,返回結果是關係數組,每個關係包含完整的節點和邊信息。

5.1 實際應用案例

5.1.1 獲取服務的完整鄰居關係

-- 獲取服務的所有鄰居(2跳內)
.topo | graph-call getNeighborNodes(
  'full', 2,
  [(:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})]
)
| stats cnt=count(1) by relationType
| sort cnt desc

5.1.2 故障上游影響分析

查找可能影響目標服務的上游服務:

.topo | graph-call getNeighborNodes(
  'sequence_in', 3,
  [(:"apm@apm.service" {__entity_id__: '0e73700c768a8e662165a8d4d46cd286'})]
)
| where relationType in ('calls', 'depends_on')
| extend impact_level = CASE
    WHEN srcPosition = '-1' THEN 'direct'
    WHEN srcPosition = '-2' THEN 'secondary'
    ELSE 'indirect' END
| extend parsed_service_id = json_extract_scalar(srcNode, '$.id')
| project 
    upstream_service = parsed_service_id,
    impact_level,
    relation_type = relationType
| stats cnt=count(1) by impact_level, relation_type

5.1.3 故障下游影響分析

查找受目標服務故障影響的下游服務:

.topo | graph-call getNeighborNodes(
  'sequence_out', 3,
  [(:"apm@apm.service" {__entity_id__: 'failing-service-id'})]
)
| where relationType in ('calls', 'depends_on')
| extend affected_service = json_extract_scalar(destNode, '$.id')
| stats impact_count=count(1) by affected_service
| sort impact_count desc
| limit 20

5.1.4 雲資源依賴分析

分析 ECS 實例的網絡依賴:

.topo | graph-call getNeighborNodes(
  'sequence_out', 2,
  [(:"acs@acs.ecs.instance" {__entity_id__: 'i-bp1234567890'})]
)
| extend relation_category = CASE
    WHEN relationType in ('belongs_to', 'runs_in') THEN 'infrastructure'
    WHEN relationType in ('depends_on', 'uses') THEN 'dependency'
    WHEN relationType in ('connects_to', 'accesses') THEN 'network'
    ELSE 'other' END
| stats cnt=count(1) by relation_category
| sort cnt desc
| limit 0, 100

5.1.5 批量查詢節點間的直接關係

.topo | graph-call getDirectRelations(
  [
    (:"app@app.service" {__entity_id__: '347150ad7eaee43d2bd25d113f567569'}),
    (:"app@app.operation" {__entity_id__: '73ef19770998ff5d4c1bfd042bc00a0f'})
  ]
)

返回的關係示例:

{
  "startNodeId": "app@app.service:347150ad7eaee43d2bd25d113f567569",
  "endNodeId": "app@app.operation:73ef19770998ff5d4c1bfd042bc00a0f", 
  "type": "contains",
  "properties": {"__type__": "contains"}
}

graph-call 的函數式設計帶來的優勢是查詢意圖清晰,系統能夠針對特定模式進行優化。但這也意味着它只適合預定義的查詢模式,對於需要自定義複雜路徑模式的場景,還是需要使用 Cypher。在實際使用中,建議優先考慮 graph-call 的預定義函數,只有當預定義函數無法滿足需求時,再考慮使用更靈活 Cypher。

Cypher:強大的聲明式查詢語言

Cypher 是圖數據庫領域的標準查詢語言,借鑑了 SQL 的易用性和聲明式風格,同時針對圖結構進行了專門優化。在 EntityStore 中,Cypher 提供了最強大和靈活的圖查詢能力,能夠處理從簡單的單節點查詢到複雜的多級跳路徑網絡的各種場景。

Cypher 的語法遵循三段式結構:MATCH、WHERE、RETURN,這與 SQL 的 SELECT、WHERE、FROM 結構類似,但邏輯更符合圖查詢的思維模式。MATCH 子句用於描述圖模式,WHERE 子句用於添加篩選條件,RETURN 子句用於指定返回的內容。這種結構化的語法讓複雜的圖查詢也變得易於閲讀和維護。

MATCH 子句的強大之處在於它支持的圖模式描述。用户可以在 MATCH 中指定任意複雜的路徑模式,包括多級跳、可選路徑、路徑變量等。多級跳的語法是 [*min..max],其中範圍是左閉右開的,比如 [*2..3] 表示只查詢 2 跳路徑。這種語法設計讓用户能夠靈活地控制遍歷深度,在精度和性能之間取得平衡。MATCH 還支持多個路徑模式的組合,用户可以同時描述多個路徑模式,系統會找到所有滿足任一模式的子圖。

WHERE 子句支持豐富的篩選條件。用户可以對節點的屬性、邊的屬性進行各種條件判斷,包括相等、包含、以某字符串開頭或結尾、範圍判斷等。WHERE 子句還支持邏輯組合(AND、OR、NOT)和複雜的表達式。相比 graph-match,Cypher 的 WHERE 子句更加靈活,不僅可以在查詢時進行篩選,還可以對中間節點進行條件限制,這對於複雜路徑模式的查詢特別有用。

RETURN 子句提供了靈活的輸出控制。用户可以返回節點對象、邊對象、路徑對象,也可以提取特定的屬性字段。RETURN 還支持聚合函數(如 count、sum、avg 等)和分組操作,這讓 Cypher 不僅能夠進行圖遍歷,還能夠進行圖分析。結合 SPL 的強大處理能力,Cypher + SPL 的組合能夠完成從數據查詢到分析計算的全流程。

6.1 基礎查詢示例

6.1.1 單節點查詢

-- 查詢特定類型的所有節點
.topo | graph-call cypher(`
    MATCH (n {__entity_type__:"apm.service"})
    WHERE n.__domain__ STARTS WITH 'a' AND n.__entity_type__ = "apm.service"
    RETURN n
`)

相比 graph-match 的優勢:

  • 支持 WHERE 子句進行復雜篩選
  • MATCH 可以只包含節點,無需指定關係
  • 支持更多的屬性查詢(__entity_type____domain__ 等)

6.1.2 關係查詢

-- 查詢服務間調用關係
.topo | graph-call cypher(`
    MATCH (src:``apm@apm.service``)-[e:calls]->(dest:``apm@apm.service``)
    WHERE src.cluster = 'production' AND dest.cluster = 'production'
    RETURN src.service, dest.service, e.__type__
`)

6.2 多級跳查詢

6.2.1 基礎多級跳語法

-- 查找2-3跳的調用鏈路
.topo | graph-call cypher(`
    MATCH (src {__entity_type__:"acs.service"})-[e:calls*2..4]->(dest)
    WHERE dest.__domain__ = 'acs'
    RETURN src, dest, dest.__entity_type__
`)

重要説明:

  • 多級跳規則是左閉右開:*2..4 表示查詢 2 跳和 3 跳
  • *1..3 表示 1 跳或 2 跳,不包括 3 跳

6.2.2 連通性分析

-- 查找服務間的可達路徑
.topo | graph-call cypher(`
    MATCH (startNode:``apm@apm.service`` {service: 'gateway'})
          -[path:calls*1..4]->
          (endNode:``apm@apm.service`` {service: 'database'})
    RETURN startNode.service, length(path) as hop_count, endNode.service
`)

6.2.3 影響鏈分析

-- 分析故障傳播路徑
.topo | graph-call cypher(`
    MATCH (failed:``apm@apm.test_service`` {status: 'error'})
          -[impact:depends_on*1..3]->
          (affected)
    WHERE affected.__entity_type__ = 'apm.service'
    RETURN failed.service, 
           length(impact) as impact_distance,
           affected.service
    ORDER BY impact_distance ASC
`)

6.2.4 節點聚合統計

-- 統計不同域的服務數量
.topo | graph-call cypher(`
    MATCH (src {__entity_type__:"apm.service"})-[e:calls*2..3]->(dest)
    WHERE dest.__domain__ = 'apm'
    RETURN src, count(src) as connection_count
`)

適用場景:

  • 連通分量分析:識別圖中的連通子圖
  • 中心度計算:找出網絡中的關鍵節點
  • 集羣檢測:發現緊密連接的節點羣組

6.2.5 路徑模式查找

-- 查找特定的拓撲模式
.topo | graph-call cypher(`
    MATCH (src:``acs@acs.vpc.vswitch``)-[e1]->(n1)<-[e2]-(n2)-[e3]->(n3)
    WHERE NOT (src = n2 AND e1.__type__ = e2.__type__) 
        AND n1.__entity_type__ <> n3.__entity_type__ 
        AND NOT (src)<-[e1:``calls``]-(n1)
    RETURN src, e1.__type__, n1, e2.__type__, n2, e3.__type__, n3
`)

適用場景:

  • 安全審計:發現異常的網絡連接模式
  • 合規檢查:驗證網絡架構的合規性
  • 模式檢測:識別特定的系統拓撲結構

Cypher 的一個重要特性是支持基於實體自定義屬性的查詢。在 graph-match 中,中間節點只能通過標籤進行過濾,但在 Cypher 中,用户可以基於實體的任意自定義屬性進行查詢和篩選。這個特性讓 Cypher 能夠處理更加細粒度的查詢需求,比如查找所有 CPU 使用率大於 80% 的實例,或者查找所有屬於某個特定用户的資源。

6.3 自定義屬性查詢示例

基於實體自定義屬性的查詢是完整版 Cypher 的核心亮點。在標準查詢中,雖然可以通過 Usearch 獲取實體的詳細信息,但在圖遍歷過程中使用實體屬性進行篩選還是有限制的。完整版 Cypher 實現了真正的屬性級查詢,用户可以在 MATCH 或 WHERE 子句中直接使用實體的自定義屬性,系統會自動從 EntityStore 中獲取實體的詳細信息,並基於這些信息進行過濾。這種設計讓圖查詢不再只是基於拓撲結構的遍歷,還能夠基於實體的實際屬性進行智能篩選,大大提升了查詢的精確度。

多級路徑輸出是另一個重要特性。在傳統的圖查詢中,多級跳查詢通常只返回起點和終點,中間的路徑信息可能會丟失。但在故障排查和影響分析場景中,瞭解完整的路徑往往比只知道起點和終點更有價值。完整版 Cypher 支持返回路徑對象,路徑對象包含了路徑中所有節點和邊的信息,用户可以通過路徑對象瞭解數據流轉的完整鏈路。這個特性特別適用於分析故障傳播路徑、追蹤數據流、理解系統架構等場景。

6.3.1 基於實體自定義屬性查詢

-- 使用實體的自定義屬性進行查詢 (僅為示例,實際屬性kv以真實場景為準)
.topo | graph-call cypher(`
    MATCH (n:``acs@acs.alb.listener`` {listener_id: 'lsn-rxp57*****'})-[e]->(d)
    WHERE d.vSwitchId CONTAINS 'vsw-bp1gvyids******' 
        AND d.user_id IN ['1654*******', '2'] 
        AND d.dns_name ENDS WITH '.com'
    RETURN n, e, d
`)

6.3.2 複雜的屬性條件查詢

-- 複雜的屬性條件查詢 (僅為示例,實際屬性kv以真實場景為準)
.topo | graph-call cypher(`
    MATCH (instance:``acs@acs.ecs.instance``)
    WHERE instance.instance_type STARTS WITH 'ecs.c6'
        AND instance.cpu_cores >= 4
        AND instance.memory_gb >= 8
        AND instance.status = 'Running'
    RETURN 
        instance.instance_id,
        instance.instance_type,
        instance.cpu_cores,
        instance.memory_gb,
        instance.availability_zone
    ORDER BY instance.cpu_cores DESC, instance.memory_gb DESC
`)

6.4 多級路徑輸出

6.4.1 返回完整路徑信息

-- 返回多級跳的完整路徑信息
.topo | graph-call cypher(`
    MATCH (n:``acs@acs.alb.listener``)-[e:``calls``*2..3]-()
    RETURN e
`)

路徑結果格式:

  • 返回路徑中所有邊的數組
  • 每個邊包含完整的起止節點和屬性信息
  • 支持路徑長度和路徑權重計算

6.5 細粒度鏈路控制的連通性查找

6.5.1 跨網絡層級的連接分析

-- 查找ECS實例到負載均衡器的連接路徑
.topo | graph-call cypher(`
    MATCH (start_node:``acs@acs.ecs.instance``)
          -[e*2..3]-
          (mid_node {listener_name: 'entity-test-listener-zuozhi'})
          -[e2*1..2]-
          (end_node:``acs@acs.alb.loadbalancer``)
    WHERE start_node.__entity_id__ <> mid_node.__entity_id__ 
        AND start_node.__entity_type__ <> mid_node.__entity_type__
    RETURN 
        start_node.instance_name, 
        e, 
        mid_node.__entity_type__, 
        e2, 
        end_node.instance_name
`)

6.5.2 服務網格連接分析

-- 分析微服務網格中的流量路徑
.topo | graph-call cypher(`
    MATCH (client:``apm@apm.service``)
          -[request:calls]->
          (gateway:``apm@apm.gateway``)
          -[route:routes_to]->
          (service:``apm@apm.service``)
          -[backend:calls]->
          (database:``middleware@database``)
    WHERE client.environment = 'production'
        AND request.protocol = 'HTTP'
        AND route.load_balancer_type = 'round_robin'
    RETURN 
        client.service,
        gateway.gateway_name,
        service.service,
        database.database_name,
        request.request_count,
        backend.connection_pool_size
`)

6.5.3 級聯故障分析

-- 分析服務故障的級聯影響
.topo | graph-call cypher(`
    MATCH (failed_service:``apm@apm.service`` {service: 'load-generator'})
    MATCH (failed_service)-[cascade_path*1..4]->(affected_service:``apm@apm.service``)
    RETURN 
        failed_service.service as root_cause,
        length(cascade_path) as impact_depth,
        affected_service.service as affected_service,
        cascade_path as dependency_chain
    ORDER BY impact_depth ASC
`)

典型應用場景

圖查詢在實際運維和分析場景中的應用非常廣泛,以下列舉幾個典型的應用模式,幫助用户更好地理解如何將圖查詢能力應用到實際工作中。

7.1 分析服務調用鏈

-- 分析特定服務的調用模式
.topo |
  graph-match (s:"apm@apm.service" {__entity_id__: 'abcdefg123123'})
              -[e:calls]-(d:"apm@apm.service")
  project 
    source_service="s.service",
    target_service="d.service", 
    call_type="e.__type__"
| stats call_count=count(1) by source_service, target_service
| sort call_count desc

7.2 權限鏈追蹤

在複雜的系統中,理解用户的權限是如何傳遞到資源的,對於安全審計和合規檢查至關重要:

-- 追蹤用户到資源的訪問路徑
.topo |
  graph-match (user:"identity@user" {__entity_id__: 'user-123'})
              -[auth:authenticated_to]->(app:"apm@apm.service")
              -[access:accesses]->(resource:"acs@acs.rds.instance")
  project 
    user_id="user.user_id",
    app_name="app.service",
    resource_id="resource.instance_id",
    auth_method="auth.auth_method",
    access_level="access.permission_level"

7.3 數據完整性檢查

7.3.1 檢查數據完整性

.topo | graph-call cypher(`
    MATCH (n)-[e]->(m)
    RETURN 
        count(DISTINCT n) as unique_nodes,
        count(DISTINCT e) as unique_edges,
        count(DISTINCT e.__type__) as edge_types
`)

7.3.2 識別懸掛關係

-- 查找指向不存在實體的關係
.let topoData = .topo | graph-call cypher(`
        MATCH ()-[e]->()
        RETURN e
    `)
    | extend startNodeId = json_extract_scalar(e, '$.startNodeId'), endNodeId = json_extract_scalar(e, '$.endNodeId'), relationType = json_extract_scalar(e, '$.type')
    | project startNodeId, endNodeId, relationType;
--$topoData
.let entityData = .entity with(domain='*', type='*') 
| project __entity_id__, __entity_type__, __domain__
| extend matchedId = concat(__domain__, '@', __entity_type__, ':', __entity_id__)
| join -kind='left' $topoData on matchedId = $topoData.endNodeId
| project matchedId, startNodeId, endNodeId, relationType
| extend status = COALESCE(startNodeId, '懸掛')
| where status = '懸掛';
$entityData

數據完整性與查詢模式選擇

在使用圖查詢時,數據完整性是一個需要特別關注的問題。EntityStore 的圖查詢能力依賴於三方面的數據:UModel(數據模型定義)、Entity(實體數據)、Topo(拓撲關係數據)。這三方面的數據完整性直接影響了查詢的能力和結果。

8.1 數據缺失場景分析

image

8.2 pure-topo 模式

需要注意的是,完整版 Cypher 依賴於 UModel、Entity 和 Topo 三方面的數據都要完備。如果 Entity 數據不完整,雖然仍然可以進行拓撲查詢,但無法使用自定義屬性進行篩選。為了解決這個問題,系統提供了 pure-topo 模式:

-- 標準模式(需要完整數據)
.topo | graph-call cypher(`
    MATCH (n:``acs@acs.alb.listener`` {ListenerId: 'lsn-123'})-[e]->(d)
    WHERE d.vSwitchId CONTAINS 'vsw-456'
    RETURN n, e, d
`)
-- pure-topo模式(僅依賴關係數據)
.topo | graph-call cypher(`
    MATCH (n:``acs@acs.alb.listener``)-[e]->(d)
    RETURN n, e, d
`, 'pure-topo')

pure-topo 模式特點:

  • 優勢:不依賴 Entity 數據,查詢速度更快
  • 限制:無法使用實體的自定義屬性進行篩選
  • 適用:拓撲結構分析、關係驗證等場景

8.3 查詢模式選擇策略

當三方面數據都完整時,用户可以使用完整版 Cypher 的所有功能,包括基於自定義屬性的查詢、多級路徑輸出等。當 Entity 數據不完整但 Topo 數據完整時,可以使用 pure-topo 模式進行查詢,這種模式下查詢速度會更快,但只能基於拓撲結構進行查詢,無法使用實體屬性進行篩選。當 Topo 數據不完整時,雖然 Entity 數據完整,也無法進行圖查詢,因為圖查詢的核心是關係,沒有關係數據就無法構成圖。

在實際使用中,用户應該根據數據的完整性情況選擇合適的查詢方式。如果數據完整性足夠,優先使用完整版 Cypher,享受屬性級查詢的便利。如果性能是首要考慮,且只需要拓撲結構信息,可以使用 pure-topo 模式。如果需要進行數據完整性檢查,可以先使用簡單的查詢測試數據的完整性,然後再執行復雜的查詢。

性能優化與最佳實踐

圖查詢雖然強大,但在大數據量的情況下,性能也可能成為瓶頸。合理的使用方法和優化策略能夠顯著提升查詢性能,確保系統在高負載下也能穩定響應。

9.1 查詢結構優化

9.1.1 合理使用索引

-- ❌ 優化前:全表掃描
.topo | graph-call cypher(`
    MATCH (n) WHERE n.service = 'web-app'
    RETURN n
`)
-- ✅ 優化後:使用標籤索引
.topo | graph-call cypher(`
    MATCH (n:``apm@apm.service`` {service: 'web-app'})
    RETURN n
`)

9.1.2 早期條件過濾

-- ❌ 優化前:後期過濾
.topo | graph-call cypher(`
    MATCH (start)-[*1..5]->(endNode)
    WHERE start.environment = 'production' AND endNode.status = 'active'
    RETURN start, endNode
`)
-- ✅ 優化後:早期過濾
.topo | graph-call cypher(`
    MATCH (start {environment: 'production'})-[*1..5]->(endNode {status: 'active'})
    RETURN start, endNode
`)

9.2 查詢範圍控制

查詢範圍的精確控制是最重要的優化策略:

  • 時間範圍優化:合理利用時間字段進行範圍限制
  • 限制遍歷深度:深度超過 5 層會顯著影響性能
  • 精確起始點:使用具體的 entity_id 而非模糊匹配
  • 合理選擇遍歷類型:根據實際需求選擇 sequence 或 full

9.3 結果集控制

9.3.1 分頁和限制

-- 使用LIMIT控制結果數量
.topo | graph-call cypher(`
    MATCH (service:``apm@apm.service``)-[calls:calls]->(target)
    WHERE calls.request_count > 1000
    RETURN service.service, target.service, calls.request_count
    ORDER BY calls.request_count DESC
    LIMIT 50
`)

9.3.2 結果採樣

-- 對大結果集進行採樣
.topo | graph-call cypher(`
    MATCH (n:``apm@apm.service``)
    RETURN n.service
    LIMIT 100
`)
| extend seed = random()
| where seed < 0.1

9.4 多級跳優化

9.4.1 控制跳躍深度

-- 避免過深的遍歷
.topo | graph-call cypher(`
    MATCH (start)-[path*1..3]->(endNode)
    WHERE length(path) <= 2
    RETURN path
`)

9.4.2 使用方向性優化

-- 利用關係方向減少搜索空間
.topo | graph-call cypher(`
    MATCH (start)-[calls:calls*1..3]->(endNode)  -- 明確方向
    WHERE start.__entity_type__ = 'apm.service'
    RETURN start, endNode
`)

9.5 最佳實踐建議

  • 使用 SPL 過濾:在圖查詢後及時過濾不需要的結果
  • 分批處理:對於大型圖查詢,考慮分批處理
  • 結果緩存:對於頻繁查詢的路徑,考慮結果緩存
  • 查詢拆分:將複雜查詢拆分為多個簡單查詢,然後使用 SPL 合併

常見問題

10.1 邊類型恰好與 Cypher 關鍵字重合

.topo | graph-call cypher(`
    MATCH (s)-[e:``contains``]->(d)
    WHERE s.__domain__ CONTAINS "apm"
    RETURN e
`)

contains 是 cypher 關鍵字,同時也是邊類型,此時作為 Cypher 語法需要在邊類型上面加入 back-tick 標識進行包裹,又因為在 SPL 上下文中,所以作為 SPL 語法需要變為雙 back-tick 標識進行包裹。

10.2 多級跳語法説明

-- 查找2-3跳的調用鏈路
.topo | graph-call cypher(`
    MATCH (src {__entity_type__:"acs.service"})-[e:calls*2..4]->(dest)
    WHERE dest.__domain__ = 'acs'
    RETURN src, dest, dest.__entity_type__
`)

重要説明:

  • 多級跳規則是左閉右開:*2..4 表示查詢 2 跳和 3 跳
  • *1..3 表示 1 跳或 2 跳,不包括 3 跳

驗證該結論:

.topo | graph-call cypher(`
    MATCH (s)-[e*1..3]->(d)
    RETURN length(e) as len
`, 'pure-topo')
| stats cnt=count(1) by len
| project len, cnt

10.3 不支持簡寫 Cypher 關係

✅ 支持的寫法:

.topo | graph-call cypher(`
    MATCH (s)-[]->(d)
    RETURN s
`, 'pure-topo')

❌ 不支持的寫法:

.topo | graph-call cypher(`
    MATCH (s)-->(d)
    RETURN s
`, 'pure-topo')

點擊此處查看視頻演示。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.