這裏的數據流指數據從前端流向後端的過程當中途徑的全部組件或者說服務,好比用戶的http請求先到Nginx, 再到後端服務1, 再到發現服務,再到緩存服務,再到後端服務2, 而後是數據庫,以及其餘調用,總的來講就是一個請求進入的路徑,若是將這樣的一個過程可視化出來,我想是不錯的,而這樣的一個可視化也就能夠作成一個監控的可視化,監控測試的鏈接是否能夠完成的完成相應以及發現那個組件出現了問題。css
而這裏的前端後端並不侷限於開發中的前端後端,只有有數據流動的地方就是數據流,好比不一樣物理機與交換機,路由器之間的網絡流動,或者容器之間的數據流動,總的來講全部的數據總會有一個流動的方法,若是能夠經過必定的技術獲取每一個節點的標誌,那麼就能夠將這條路徑動態的可視化出來。前端
這裏選擇JavaScript和SVG, 之因此選擇SVG是由於D3以及有對應的layout了,因此能夠很方便的將數據進行樹狀的可視化。node
這裏假設咱們要監控的數據流架構以下nginx
|---> backend11 |---> nginx1 ---> backend1 ---> | | |---> backend12 client --> | | |---> backend21 |---> nginx2 ---> backend2 ---> | |---> backend22
那麼咱們能夠用下面兩種方式來表示git
# 將每一行做爲一個節點,而後經過parent指向對應的parent節點 rawData1 = [ {name: "client", parent: "", status: 1}, {name: "nginx1", parent: "client", status: 1}, {name: "nginx2", parent: "client", status: 1}, {name: "backend1", parent: "nginx1", status: 1}, {name: "backend2", parent: "nginx2", status: 1}, {name: "backend11", parent: "backend1", status: 1}, {name: "backend12", parent: "backend1", status: 1}, {name: "backend21", parent: "backend2", status: 1}, {name: "backend22", parent: "backend2", status: 1}, ] # 經過嵌套的json數據結構來表示 rawData2 = {data:{id: "client"}, children: [ {data: {id: "nginx1"}, children: [ {data: {id: "backend1"}, children: [ {data: {id: "backend11"}}, {data: {id: "backend12"}} ] } ] }, {data: {id: "nginx2"}, children: [ {data: {id: "backend2"}, children: [ {data: {id: "backend21"}}, {data: {id: "backend22"}} ] } ] } ]};
經過D3.js繪製樹狀圖主要分爲2個部分github
樹狀圖的各個部分能夠看本身須要繪製相應的部分,好比不繪製節點的標識或者文字。數據庫
經過d3經過的stratify方法將第一種形式的數據處理成能夠直接傳給d3.tree對象的數據形式json
var rawData = [ {name: "client", parent: "", status: 1}, {name: "nginx1", parent: "client", status: 1}, {name: "nginx2", parent: "client", status: 1}, {name: "backend1", parent: "nginx1", status: 1}, {name: "backend2", parent: "nginx2", status: 1}, {name: "backend11", parent: "backend1", status: 1}, {name: "backend12", parent: "backend1", status: 1}, {name: "backend21", parent: "backend2", status: 1}, {name: "backend22", parent: "backend2", status: 1}, ] // stratify處理完成的數據,至少須要兩個字段: id, children // 經過鏈式的調用id方法, 能夠傳入一個回調函數處理數據中的每一行,函數應該返回這一行數據做爲id的數值 // 經過鏈式的調用parentId, 能夠傳入一個回調函數處理數據中的每一行,函數應該返回這一行數據它指向的parent id const data = d3.stratify() .id(d => d.name) .parentId(d => d.parent) (rawData);
經過d3經過的hierarchy方法將第二種形式的數據處理成能夠直接傳給d3.tree對象的數據形式後端
var rawData = {data:{id: "client"}, children: [ {data: {id: "nginx1"}, children: [ {data: {id: "backend1"}, children: [ {data: {id: "backend11"}}, {data: {id: "backend12"}} ] } ] }, {data: {id: "nginx2"}, children: [ {data: {id: "backend2"}, children: [ {data: {id: "backend21"}}, {data: {id: "backend22"}} ] } ] } ]}; // 因爲上面的數據已經有對應的id, children字段,因此不須要額外的處理,能夠直接傳給hierarchy. const data = d3.hierarchy(rawData)
不管是hierarchy仍是stratify都是將數據轉換成一個嵌套的對象,對象的結構是一棵樹。緩存
效果以下
初始化樹對象,將上面處理完成的數據能夠直接傳給初始化後的d3.tree對象。將data傳遞調用後會獲得一個tree的對象(代碼裏面的root),這個對象有兩個比較重要的屬性, links, descendants.
它們分別對應了樹狀結構的各個邊的路徑以及節點的位置。
const myTree = d3.tree().size([innerWidth - 400, innerHeight]); const root = myTree(data); const links = root.links(); const nodes = root.descendants();
之因此將innerWidth減去400,是由於這個內置的tree的位置計算方法會根據數據結構來計算圖的比例,而不是畫布的大小來計算比例,總的來講,若是須要將樹的可視化圖放到一個合適的位置,這個寬高須要根據數據結構調整一下,你們多試一下就瞭解了。
繪製樹的每條邊
// 繪製links svg.selectAll("path").data(links) .enter().append("path") .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x) )
繪製樹的每一個節點以及文字
// 繪製節點 const circles = g.selectAll("circle").data(nodes, d=>d.data.id) const circleFill = d => d.data.status ? successColor:failedColor circles .enter().append("circle") .attr("r", 0) // 注意!x 對於 y, 這很奇葩 .attr("cx", d => d.y) .attr("cy", d => d.x) .attr("fill", circleFill) // 繪製文字 g.selectAll("text").data(nodes) .enter().append("text") .attr("x", d => d.y) .attr("y", d => d.x) // 若是該節點有children字段,則說明有子節點,那麼x方向的位置不變 // 反之,位置向右移動10個像素 .attr("dx", d => d.children? 0: 10) // 若是該節點有children字段,則說明有子節點,那麼y方向的位置向上移動10個像素 // 反之,位置向下移動5個像素 .attr("dy", d => d.children? -10: 5) // 若是該節點有沒有parent,則說明是root節點,文字的對其方式爲end // 若是該節點有children字段,則說明有子節點,文字的對其方式爲middle // 反之,文字的對其方式爲middle .attr("text-anchor", d => { if (!d.parent) { return "end" } else if (d.children) { return "middle" } else { return "start" } }) .attr("font-size", "1em") .text(d=> d.data.name)
動畫的效果分爲兩個部分實現,一是在原有路徑上加一個Path,二是讓這個path經過css動起來。
繪製流動的線條
// 繪製流動的線條 g.selectAll("path.flow").data(links) .enter().append("path") // 注意!x 對於 y, 這很奇葩 // 經過linkHorizontal將links裏面的數據轉換成圖中對應的path的繪製路徑 .attr("d", d3.linkHorizontal() .x(d => d.y) .y(d => d.x) ) .attr("class", "flow")
配置css樣式
path.flow { fill:transparent; stroke:#6D9459; stroke-dasharray: 20 4; animation: flowpath 4s linear infinite; } @keyframes flowpath { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
最後的效果將文章開始的動畫
經過鼠標中鍵的滑動縮放圖的大小,經過鼠標也能夠移動svg對象。
const zoomG = svg.append("g"); const g = zoomG.append("g").attr("transform", `translate(${margin.left} ${margin.top})`); // 增長縮放拖動的操做 svg.call(d3.zoom().on("zoom", () => { zoomG.attr("transform", d3.event.transform) }));
因爲文中的代碼部分根據須要切除或者替換了相關的變量,因此只是複製文中的代碼運行是有問題的,完整的代碼請參考完整的源代碼。
https://github.com/youerning/blog/tree/master/dataflow-vis
若是期待後續文章能夠關注個人微信公衆號(又耳筆記),頭條號(又耳筆記),github。
這裏提供兩個數據流的監控做爲參考:
後端能夠爲特定的請求配置一個惟一的ID, 這個ID會在各個組件傳遞,因此每一個組件在監控到這個特定的ID就能夠將數據發送到本身的監控中心,監控中心爲前端提供處理好的數據。
每個節點都須要設置的代理或者說Agent, 監控中心在按照特定的時間間隔往本身直連的Agent發送ping或者特定的請求以檢查網絡連通性。
以爲有用的就會有用吧,以爲沒用的就沒有咯。
關於非樹狀結構就放在後面的文章在說吧,