在微服务架构下,服务之间的关系是非常复杂的,是一个典型的有向有环图,在一个中等规模的项目中,一般会有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>
最终运行的效果如下:
原网址: 访问
创建于: 2018-10-13 16:09:35
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论