微服务架构下的服务关联图
程序员文章站
2022-06-07 08:10:10
...
在微服务架构下,服务之间的关系是非常复杂的,是一个典型的有向有环图,在一个中等规模的项目中,一般会有100多个服务,而大型项目中,则会有数百个服务。
假设我们有如下6个服务:
每个服务都指定了自己依赖的服务:
AaaSvc:
BbbSvc:
CccSvc:
DddSvc:
EeeSvc:
FffSvc:
我们如何把如上6个服务中跟服务AaaSvc相关的服务可视化呢?如下图所示:
要完成这样的服务关联图,需要如下几个步骤:
1、遍历指定项目下的所有服务,构造两个map,serviceToServiceMap 和 reverseServiceToServiceMap,存储所有服务的直接依赖和反向直接依赖。
public static void runService(String projectId, Map<String, Set<String>> serviceToServiceMap, Map<String, Set<String>> reverseServiceToServiceMap){ if(! (serviceToServiceMap instanceof ConcurrentHashMap) ){ throw new RuntimeException("参数serviceToServiceMap必须是ConcurrentHashMap的实例"); } if(! (reverseServiceToServiceMap instanceof ConcurrentHashMap) ){ throw new RuntimeException("参数reverseServiceToServiceMap必须是ConcurrentHashMap的实例"); } MetaServiceRepository metaServiceRepository = SpringContextUtils.getBean("metaServiceRepository"); List<MetaService> services = metaServiceRepository.findByProjectId(projectId); services.parallelStream().filter(item->!item.getName().contains("Deprecate")).forEach(item->{ List<DependencyService> dependencyServices = item.constructDependencyServices(); String key = item.getName()+"("+item.getDescription()+")"; if(dependencyServices != null){ dependencyServices.parallelStream().filter(dep->!dep.getName().contains("Deprecate")).forEach(dependencyService->{ String value = dependencyService.getName()+"("+dependencyService.getDescription()+")"; serviceToServiceMap.putIfAbsent(key, Collections.newSetFromMap(new ConcurrentHashMap<>())); serviceToServiceMap.get(key).add(value); reverseServiceToServiceMap.putIfAbsent(value, Collections.newSetFromMap(new ConcurrentHashMap<>())); reverseServiceToServiceMap.get(value).add(key); }); } }); }
2、以服务AaaSvc为入口,利用直接依赖和反向直接依赖,构造服务依赖图和反向服务依赖图。
String name = metaService.getName()+"("+metaService.getDescription()+")"; Set<String> set = serviceToServiceMap.get(name); ServiceDependencyGraph serviceDependencyGraph = new ServiceDependencyGraph(new HashMap<>(), name, set, serviceToServiceMap); set = reverseServiceToServiceMap.get(name); ServiceDependencyGraph reverseServiceDependencyGraph = new ServiceDependencyGraph(new HashMap<>(), name, set, reverseServiceToServiceMap);
import org.apache.commons.collections4.CollectionUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * 服务依赖图 * @date 2017-09-2 * @author 杨尚川 */ public class ServiceDependencyGraph { private String name; private List<ServiceDependencyGraph> children = new ArrayList<>(); public ServiceDependencyGraph(Map<String, AtomicInteger> stopServiceNames, String name, Set<String> set, Map<String, Set<String>> serviceToServiceMap){ this.name = name; if(CollectionUtils.isNotEmpty(set)) { for (String item : set) { String key = name+"_"+item; stopServiceNames.putIfAbsent(key, new AtomicInteger()); stopServiceNames.get(key).incrementAndGet(); if(stopServiceNames.get(key).get()<10) { Set<String> sub = serviceToServiceMap.get(item); ServiceDependencyGraph serviceDependencyGraph = new ServiceDependencyGraph(stopServiceNames, item, sub, serviceToServiceMap); children.add(serviceDependencyGraph); } } } } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<ServiceDependencyGraph> getChildren() { return children; } }
3、利用服务依赖图和反向服务依赖图,构造带有点和边描述信息的JSON结构:
List<Map<String, String>> nodes = new ArrayList<>(); addNode(serviceDependencyGraph, nodes); addNode(reverseServiceDependencyGraph, nodes); List<Map<String, Object>> edges = new ArrayList<>(); addEdge(reverseServiceToServiceMap, serviceDependencyGraph, edges, false); addEdge(reverseServiceToServiceMap, reverseServiceDependencyGraph, edges, true); Map<String, Object> graph = new HashMap<>(); graph.put("edges", edges); graph.put("nodes", nodes);
private void addNode(String node, List<Map<String, String>> nodes){ for(Map<String, String> item : nodes){ if(node.equals(item.get("id"))){ return; } } Map<String, String> nodeMap = new HashMap<>(); nodeMap.put("id", node); nodeMap.put("name", node); nodes.add(nodeMap); } private void addNode(ServiceDependencyGraph serviceDependencyGraph, List<Map<String, String>> nodes){ if(serviceDependencyGraph == null){ return; } String node = serviceDependencyGraph.getName(); addNode(node, nodes); if(serviceDependencyGraph.getChildren() != null){ serviceDependencyGraph.getChildren().forEach(item->addNode(item, nodes)); } } private void addEdge(Map<String, Set<String>> reverseServiceToServiceMap, ServiceDependencyGraph serviceDependencyGraph, List<Map<String, Object>> edges, boolean reverse){ if(serviceDependencyGraph == null){ return; } String source = serviceDependencyGraph.getName(); serviceDependencyGraph.getChildren().forEach(target -> { boolean duplicate = false; Map<String, Object> map = new HashMap<>(); if(reverse){ String id = target.getName()+"-->"+source; for(Map<String, Object> item : edges){ if(id.equals(item.get("id"))){ duplicate = true; } } map.put("id", id); map.put("target", source); map.put("source", target.getName()); map.put("directed", true); map.put("source_score", reverseServiceToServiceMap.get(target.getName()) == null ? 0 : reverseServiceToServiceMap.get(target.getName()).size()); map.put("target_score", reverseServiceToServiceMap.get(source) == null ? 0 : reverseServiceToServiceMap.get(source).size()); }else { String id = source+"-->"+target.getName(); for(Map<String, Object> item : edges){ if(id.equals(item.get("id"))){ duplicate = true; } } map.put("id", id); map.put("source", source); map.put("target", target.getName()); map.put("directed", true); map.put("source_score", reverseServiceToServiceMap.get(source) == null ? 0 : reverseServiceToServiceMap.get(source).size()); map.put("target_score", reverseServiceToServiceMap.get(target.getName()) == null ? 0 : reverseServiceToServiceMap.get(target.getName()).size()); } if(!duplicate) { edges.add(map); } addEdge(reverseServiceToServiceMap, target, edges, reverse); }); }
生成的JSON结构如下所示:
{ "nodes":[ { "globalWeight":4, "name":"AaaSvc(服务Aaa)" }, { "globalWeight":4, "name":"CccSvc(服务Ccc)" }, { "globalWeight":5, "name":"DddSvc(服务Ddd)" }, { "globalWeight":4, "name":"EeeSvc(服务Eee)" }, { "globalWeight":4, "name":"FffSvc(服务Fff)" }, { "globalWeight":3, "name":"BbbSvc(服务Bbb)" } ], "edges":[ { "distance":8, "source":0, "target":1 }, { "distance":8, "source":1, "target":0 }, { "distance":9, "source":0, "target":2 }, { "distance":9, "source":2, "target":3 }, { "distance":9, "source":3, "target":2 }, { "distance":9, "source":2, "target":4 }, { "distance":8, "source":4, "target":3 }, { "distance":8, "source":3, "target":4 }, { "distance":9, "source":4, "target":2 }, { "distance":7, "source":0, "target":5 }, { "distance":7, "source":5, "target":1 }, { "distance":7, "source":1, "target":5 } ] }
4、使用d3-force对如上的JSON进行展示:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>测试项目_testDemo -- 服务Aaa_AaaSvc -- 服务关联图</title> <style> .node { stroke: #fff; stroke-width: 1.5px; } .node-active{ stroke: #555; stroke-width: 1.5px; } .link { stroke: #555; stroke-opacity: .3; } .link-active { stroke-opacity: 1; stroke-width: 1.5px; } .overlay { fill: none; pointer-events: all; } #map{ height:100%; } #ex1Slider .slider-selection { background: #BABABA; } #ex2Slider .slider-selection { background: #BABABA; } #ex3Slider .slider-selection { background: #BABABA; } #ex4Slider .slider-selection { background: #BABABA; } #ex5Slider .slider-selection { background: #BABABA; } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/10.2.0/css/bootstrap-slider.min.css"/> <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script> <script src="https://code.jquery.com/jquery-2.2.4.min.js" charset="utf-8"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/10.2.0/bootstrap-slider.min.js" charset="utf-8"></script> <script type="text/javascript"> var gravity = 0.03; var linkDistanceMultiFactor = 10; var linkDistance = 100; var circleRadiusMultiFactor = 2; var circleRadius = 4; var arrowhead = 10; var graph; function load(graph){ console.log("loading ...... "); console.log("gravity: "+gravity); console.log("linkDistanceMultiFactor: "+linkDistanceMultiFactor); console.log("linkDistance: "+linkDistance); console.log("circleRadiusMultiFactor: "+circleRadiusMultiFactor); console.log("circleRadius: "+circleRadius); var margin = {top: -5, right: -5, bottom: -5, left: -5}; var width = $(window).width() - margin.left - margin.right, height = $(window).height() - margin.top - margin.bottom; var color = d3.scale.category10(); var force = d3.layout.force() .charge(-200) .linkDistance(function(d) {return (d.source.weight+d.target.weight)*linkDistanceMultiFactor+linkDistance;}) .size([width + margin.left + margin.right, height + margin.top + margin.bottom]) .charge([-500]) .theta(0.1) .gravity(gravity); var zoom = d3.behavior.zoom() .scaleExtent([0.3, 10]) .on("zoom", zoomed); var drag = d3.behavior.drag() .origin(function(d) { return d; }) .on("dragstart", dragstarted) .on("drag", dragged) .on("dragend", dragended); var svg = d3.select("#map").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.right + ")") .call(zoom); var rect = svg.append("rect") .attr("width", width) .attr("height", height) .style("fill", "none") .style("pointer-events", "all"); var container = svg.append("g"); force .nodes(graph.nodes) .links(graph.edges) .start(); var link = container.append("g") .attr("class", "links") .selectAll(".link") .data(force.links()) .enter() .append("line") .attr("class", "link") .attr('marker-end', function(d,i) { return d.rpm === 0 ? '' : 'url(#arrowhead)'}) .style("stroke-width", function(d) { return Math.sqrt(d.value); }); var node = container.append("g") .attr("class", "nodes") .selectAll(".node") .data(force.nodes()) .enter().append("g") .attr("class", "node") .attr("cx", function(d) { return d.x; }) .attr("cy", function(d) { return d.y; }) .call(drag); var nodeLabel = container.selectAll(".nodelabel") .data(force.nodes()) .enter() .append("text") .style("pointer-events", "none") .attr({"x":function(d){return d.x;}, "y":function(d){return d.y;}, "class":"nodelabel", "stroke":"#666"}) .text(function(d){return d.name;}); var linkPath = container.selectAll(".linkpath") .data(force.links()) .enter() .append('path') .attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y}, 'class':'linkpath', 'fill-opacity':0, 'stroke-opacity':0, 'fill':'blue', 'stroke':'red', 'id':function(d,i) {return 'linkpath'+i}}) .style("pointer-events", "none"); var linkLabel = container.selectAll(".linklabel") .data(force.links()) .enter() .append('text') .style("pointer-events", "none") .attr({'class':'linklabel', 'id':function(d,i){return 'linklabel'+i}, 'dx':90, 'dy':-5, 'font-size':12, 'fill':'#666'}); linkLabel.append('textPath') .attr('xlink:href',function(d,i) {return '#linkpath'+i}) .style("pointer-events", "none") .text(function(d,i){ return d.rpm > 0 ? d.rpm + ' req/min' : ""; }); container.append('defs').append('marker') .attr({'id':'arrowhead', 'viewBox':'-0 -5 10 10', 'refX':25, 'refY':0, 'orient':'auto', 'markerWidth':arrowhead, 'markerHeight':arrowhead, 'xoverflow':'visible'}) .append('svg:path') .attr('d', 'M 0,-5 L 10 ,0 L 0,5') .attr('fill', '#000') .attr('stroke','#000'); node.append("circle") .attr("r", function(d) { return d.weight*circleRadiusMultiFactor + circleRadius; }) .style("fill", function(d,i) { return "#e7ba52"; }); force.on("tick", function() { link.attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); nodeLabel.attr("x", function(d) { return d.x; }) .attr("y", function(d) { return d.y; }); linkPath.attr('d', function(d) { var path='M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y; return path; }); linkLabel.attr('transform',function(d){ if (d.target.x < d.source.x) { bbox = this.getBBox(); rx = bbox.x+bbox.width/2; ry = bbox.y+bbox.height/2; return 'rotate(180 ' + rx + ' ' + ry + ')'; } else { return 'rotate(0)'; } }); }); var linkedByIndex = {}; force.links().forEach(function(d) { linkedByIndex[d.source.index + "," + d.target.index] = 1; }); function isConnected(a, b) { return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index]; } node.on("mouseover", function(d) { node.classed("node-active", function(o) { thisOpacity = isConnected(d, o) ? true : false; this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; }); link.classed("link-active", function(o) { return o.source === d || o.target === d ? true : false; }); d3.select(this).classed("node-active", true); d3.select(this).select("circle").transition() .duration(750) .attr("r", function(d) { return (d.weight*circleRadiusMultiFactor + circleRadius)*1.5; }); }) .on("mouseout", function(d){ node.classed("node-active", false); link.classed("link-active", false); d3.select(this).select("circle").transition() .duration(750) .attr("r", function(d) { return d.weight*circleRadiusMultiFactor + circleRadius; }); }); function dottype(d) { d.x = +d.x; d.y = +d.y; return d; } function zoomed() { container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); } function dragstarted(d) { d3.event.sourceEvent.stopPropagation(); d3.select(this).classed("dragging", true); force.start(); } function dragged(d) { d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y); } function dragended(d) { d3.select(this).classed("dragging", false); } } $(function() { $('#ex1').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 gravity = e.value/100; load(graph); }); $('#ex2').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 linkDistance = e.value; load(graph); }); $('#ex3').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 linkDistanceMultiFactor = e.value; load(graph); }); $('#ex4').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 circleRadius = e.value; load(graph); }); $('#ex5').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 circleRadiusMultiFactor = e.value; load(graph); }); $('#ex6').slider({ tooltip: 'hide' }).on('slideStop', function (e) { //获取新值 arrowhead = e.value; load(graph); }); graph = JSON.parse('{"nodes":[{"globalWeight":4,"name":"AaaSvc(服务Aaa)"},{"globalWeight":4,"name":"CccSvc(服务Ccc)"},{"globalWeight":5,"name":"DddSvc(服务Ddd)"},{"globalWeight":4,"name":"EeeSvc(服务Eee)"},{"globalWeight":4,"name":"FffSvc(服务Fff)"},{"globalWeight":3,"name":"BbbSvc(服务Bbb)"}],"edges":[{"distance":8,"source":0,"target":1},{"distance":8,"source":1,"target":0},{"distance":9,"source":0,"target":2},{"distance":9,"source":2,"target":3},{"distance":9,"source":3,"target":2},{"distance":9,"source":2,"target":4},{"distance":8,"source":4,"target":3},{"distance":8,"source":3,"target":4},{"distance":9,"source":4,"target":2},{"distance":7,"source":0,"target":5},{"distance":7,"source":5,"target":1},{"distance":7,"source":1,"target":5}]}'); load(graph); }); </script> </head> <body> <h3>测试项目_testDemo -- 服务Aaa_AaaSvc -- 服务关联图</h3> <table> <tr> <td width="50"></td> <td> <span class="item"> 重力调节 <input id="ex1" type="text" data-slider-id="ex1Slider" class="span2" data-slider-min="0" data-slider-max="100" data-slider-step="1" data-slider-value="0.03"/> </span> </td> <td width="50"></td> <td> <span class="item"> 最短边长 <input id="ex2" type="text" data-slider-id="ex2Slider" class="span2" data-slider-min="0" data-slider-max="250" data-slider-step="1" data-slider-value="100"/> </span> </td> <td width="50"></td> <td> <span class="item"> 边长放大倍数 <input id="ex3" type="text" data-slider-id="ex3Slider" class="span2" data-slider-min="0" data-slider-max="50" data-slider-step="1" data-slider-value="10"/> </span> </td> <td width="50"></td> <td> <span class="item"> 最短半径 <input id="ex4" type="text" data-slider-id="ex4Slider" class="span2" data-slider-min="0" data-slider-max="10" data-slider-step="1" data-slider-value="4"/> </span> </td> <td width="50"></td> <td> <span class="item"> 半径放大倍数 <input id="ex5" type="text" data-slider-id="ex5Slider" class="span2" data-slider-min="0" data-slider-max="5" data-slider-step="1" data-slider-value="2"/> </span> </td> <td width="50"></td> <td> <span class="item"> 箭头大小 <input id="ex6" type="text" data-slider-id="ex5Slider" class="span2" data-slider-min="10" data-slider-max="30" data-slider-step="1" data-slider-value="10"/> </span> </td> </tr> </table> <div id="map"></div> </body> </html>
最终运行的效果如下:
推荐阅读
-
微信小程序上传多图到服务器并获取返回的路径
-
SQL Server Alwayson架构下 服务器 各虚拟IP漂移监控告警的功能实现 -1(服务器视角)
-
组织架构适配下的敏捷开发 Node.jsNode.js微服务架构组织架构
-
组织架构适配下的敏捷开发 Node.jsNode.js微服务架构组织架构
-
【星云测试】Devops微服务架构下具有代码级穿透能力的精准测试
-
Java生鲜电商平台-深入理解微服务SpringCloud各个组件的关联与架构
-
微信小程序上传多图到服务器并获取返回的路径
-
优雅的微服务架构下的鉴权
-
开源、易扩展、方便集成的在线绘图(微服务架构图、网络拓扑图、流程图)工具
-
微服务架构下的服务关联图