欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

微服务架构下的服务关联图

程序员文章站 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>

 

最终运行的效果如下:

 

微服务架构下的服务关联图
            
    
    博客分类: 微服务 微服务服务关联图服务依赖图服务可视化