微服务场景下基于监控指标数据按照场景入口自动生成链路数据图实时展示

1 背景

  日常运维工作中,面对日益复杂的交易链路,需要时刻关注生产环境中各个服务组件运行情况,当时随着微服务化后,各个系统的调用关系日趋复杂,难以按照传统方式靠人工进行绘图,获取指标进行实时链路观察,也即是云原生可观测性的实现。
  用到的技术主要是Django、VUE、Element、Antv G6等。

2 效果展示

在这里插入图片描述

3 后端实现逻辑

3.1 前提

  已知后端服务间两两调用关系逻辑,及(A,B)(B,C) (B,D) (C, D) (D,B)等这样的有向图,如何从大量的有向访问关系数据中自动生成按照入口起始服务形成交易链路,并实时展示相关指标。

3.2 生成场景

def queryServerByClient(dbcon, cloudtype, tenant, namespace, client, tplTupleList):
    # tplTupleList存放根据1个client查询所有相关的调用关系
    sql = "select distinct(server) from tb_trace_info " + \
          " where cloudType = '" + cloudtype + \
          "' and env = 'prod'" + \
          " and tenant = '" + tenant + \
          "' and nameSpace = '" + namespace + \
          "' and client = '" + client + "';"
    if int(dbcon.select(sql)['code']) != 1000:
        return {"code": 500,
                "context": "查询数据库失败"
                }
    else:
        if len(dbcon.select(sql)['context']) == 0:
            return {"code": 600,
                    "context": "查询数据为空"
                    }
    server_res = dbcon.select(sql)['context']
    if len(server_res) == 0:
        return tplTupleList
    else:
        for svc in server_res:
            server = svc[0]
            tpl = (client, server)
            if tpl in tplTupleList:
                break
            else:
                tplTupleList.append(tpl)
                queryServerByClient(dbcon, cloudtype, tenant, namespace, server, tplTupleList)

4 前端实现逻辑

4.1 创建画布高宽并对中英文展示长度处理

      const width = document.getElementById('mountNode').scrollWidth;
      const height = (document.getElementById('mountNode').scrollHeight * 0.8) || 600;
      const fittingString = (str, maxWidth, fontSize) => {
        let currentWidth = 0;
        let res = str;
        const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
        str.split('').forEach((letter, i) => {
          if (currentWidth > maxWidth) return;
          if (pattern.test(letter)) {
            // Chinese charactors
            currentWidth += fontSize;
          } else {
            // get the width of single letter according to the fontSize
            currentWidth += G6.Util.getLetterWidth(letter, fontSize);
          }
          if (currentWidth > maxWidth) {
            res = `${str.substr(0, i)}\n${str.substr(i)}`;
          }
        });
        return res;
      };
      const ICON_MAP = {
        pod: 'src/icons/pod.png',
        service: 'src/icons/service.png',
        trans: 'src/icons/trans.png',
        alarm: 'src/icons/alarm.png',
        useTime: 'src/icons/time.png',
        successRate: 'src/icons/success.png',
      };

4.2 自定义节点样式

// 自定义节点
      G6.registerNode(
          "service", //第一个参数自定义节点的名字
          // 第二个参数是这个节点的图形分组
          {
            draw: function (cfg, group){
              //根据数据动态改变颜色或者图片
              let duration_avg = cfg.statistics.duration_avg>1 ?"#F3FAFF" :"#F3FAFF";
              // let img2=cfg.statistics.duration_avg>1 ?require('../../../../../assets/shouye备份1.png') :require('../../../../../assets/shouye备份.png');
              let alert_num = cfg.statistics.alert_num>20 ?"#F3FAFF" :"#F3FAFF";
              // let img3=cfg.statistics.alert_num>20 ?require('../../../../../assets/a-fuwuqilan2.png') :require('../../../../../assets/fuwuqilan.png');
              let svc_fontsize = 10
              let metric_fontsize = 8
              // 增加一个图像 最外边的虚线框
              let keyShape=group.addShape('rect',{
                // 代表矩形的一些属性
                // 设置节点部署 相对定位,整个节点布局
                attrs:{
                  x: 0,
                  y: 0,
                  width: 130,
                  height: 80,
                  stroke: "#1a66b2",   //描边色
                  fill: "#d7dae0",     //填充颜色
                  radius: 3,
                  lineWidth: 2,
                  lineDash: [2,2]      //设置虚线的样式
                },
                name:"card-node-keyShape" //起个唯一名字便于识别
              });
              // 设置节点上方服务名显示布局
              group.addShape("rect",{
                attrs:{
                  x:5,
                  y:1,
                  width: 120,
                  height:25,
                  //  stroke:"pink",   //描边色
                  fill:"#3d8ad7",
                  radius:3,
                },
                name:"card-node-titleShape"
              });
              // 左上布局
              group.addShape("rect",{    // 指标部分展示
                attrs:{
                  x: 5,
                  y: 29,
                  width: 59,
                  height: 24,
                  //  stroke:"pink",   //描边色
                  fill: "#F3FAFF",
                  radius: 3,
                },
                name:"card-node-countShape"
              });
              // 右上布局
              group.addShape("rect",{    // 指标部分展示
                attrs:{
                  x: 66,
                  y: 29,
                  width: 59,
                  height: 24,
                  fill: duration_avg,
                  radius: 3,
                },
                name:"card-node-timeShape"
              });
              // 左下布局
              group.addShape("rect",{    // 指标部分展示
                attrs:{
                  x: 5,
                  y: 55,
                  width: 59,
                  height: 24,
                  fill: alert_num,
                  radius: 3,
                },
                name:"card-node-alarmShape"
              });
              // 右下布局
              group.addShape("rect",{    // 指标部分展示
                attrs:{
                  x: 66,
                  y: 55,
                  width: 59,
                  height: 24,
                  fill: "#F3FAFF",
                  radius: 3,
                },
                name:"card-node-successShape"
              });
              // 设置服务图标
              group.addShape('image', {
                attrs: {
                  x: 4,
                  y: 2,
                  height: 16,
                  width: 16,
                  cursor: 'pointer',
                  img: ICON_MAP[cfg.nodeType],
                },
                // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                name: 'node-icon',
              });
              // 设置服务名称位置
              group.addShape('text',{
                attrs:{
                  text: fittingString(cfg.name,80, 8), //fittingString('Break the line if it is too long', 80, globalFontSize),
                  x: 20,
                  y: 2,
                  fontSize: svc_fontsize,
                  fill: "block",
                  textBaseline: "top"
                },
                name: "card-node-title"
              });
              // 交易量布局
              group.addShape("image",{
                attrs:{
                  x: 5,
                  y: 33,
                  width: 16,
                  height: 16,
                  img: ICON_MAP['trans'],
                },
                name:"card-node-countIco"
              });
              group.addShape("text",{
                attrs:{
                  text:cfg.statistics.trace_num,
                  x: 22,
                  y: 46,
                  fill: "#226DFF",
                  fontSize: metric_fontsize,
                },
                name:"card-node-count",
              });
              // 平均响应时长布局
              group.addShape("image",{
                attrs:{
                  x: 65,
                  y: 33,
                  width: 16,
                  height: 16,
                  img: ICON_MAP['useTime'],
                },
                name:"card-node-timeIco"
              });
              group.addShape("text",{
                attrs:{
                  text:cfg.statistics.duration_avg*1000+"ms",
                  x: 82,
                  y: 46,
                  fill: "#226DFF",
                  fontSize: metric_fontsize,
                },
                name:"card-node-time",
              });
              // 告警数
              group.addShape("image",{
                attrs:{
                  x: 5,
                  y: 57,
                  width: 16,
                  height: 16,
                  img: ICON_MAP['alarm'],
                },
                name:"card-node-alarmIco"
              });
              group.addShape("text",{
                attrs:{
                  text:cfg.statistics.alert_num,
                  x: 22,
                  y: 72,
                  fill: "#226DFF",
                  fontSize: metric_fontsize,
                },
                name:"card-node-alarm",
              });
              // 成功率模块
              group.addShape("image",{
                attrs:{
                  x: 65,
                  y: 57,
                  width: 16,
                  height: 16,
                  img: ICON_MAP['successRate'],
                },
                name:"card-node-successIco"
              });
              group.addShape("text",{
                attrs:{
                  text: cfg.statistics.situation_num + "%",
                  x: 82,
                  y: 72,
                  fill: "#226DFF",
                  fontSize: metric_fontsize,
                },
                name:"card-node-success",
              });
              return keyShape;
            },
          },'rect'); //第三个参数,是如果没有设置样式,会默认继承rect的样式

4.3 对节点或画布双击、单击事件

function clearAllStats() {
          graph.setAutoPaint(false);
          graph.getNodes().forEach(function (node) {
            graph.clearItemStates(node);
          });
          graph.getEdges().forEach(function (edge) {
            graph.clearItemStates(edge);
          });
          graph.paint();
          graph.setAutoPaint(true);
        };
        // graph.on('node:mouseleave', clearAllStats);
        // 单击画布
        graph.on('canvas:click', clearAllStats);
        // 双击节点
        graph.on('node:dblclick', function (e) {
          const item = e.item;
          graph.setAutoPaint(false);
          graph.getNodes().forEach(function (node) {
            graph.clearItemStates(node);
            graph.setItemState(node, 'dark', true);
          });
          graph.setItemState(item, 'dark', false);
          graph.setItemState(item, 'highlight', true);
          graph.getEdges().forEach(function (edge) {
            if (edge.getSource() === item) {
              graph.setItemState(edge.getTarget(), 'dark', false);
              graph.setItemState(edge.getTarget(), 'highlight', true);
              graph.setItemState(edge, 'highlight', true);
              edge.toFront();
            } else if (edge.getTarget() === item) {
              graph.setItemState(edge.getSource(), 'dark', false);
              graph.setItemState(edge.getSource(), 'highlight', true);
              graph.setItemState(edge, 'highlight', true);
              edge.toFront();
            } else {
              graph.setItemState(edge, 'highlight', false);
            }
          });
          graph.paint();
          graph.setAutoPaint(true);
        });
        // 鼠标进入节点 click dblclick mouseenter
        graph.on('node:click', (e) => {
          const nodeItem = e.item // 获取鼠标进入的节点元素对象
          console.log('单击节点: ', nodeItem._cfg)
        });
        graph.on('edge:click', (e) => {
          // console.log('单击边:', e)
          const nodeItem = e.item // 获取鼠标进入的节点元素对象
          console.log('单击边: ', nodeItem._cfg)
        });
        graph.on('canvas:dblclick', (e) => {
          // console.log('双击画布: ', e)
          console.log('双击画布: ', e.currentTarget.cfg.nodes)
          for( var k=0; k < e.currentTarget.cfg.nodes.length; k++) {
            var x = e.currentTarget.cfg.nodes[k]._cfg.bboxCache.centerX
            var y = e.currentTarget.cfg.nodes[k]._cfg.bboxCache.centerY
            console.log(e.currentTarget.cfg.nodes[k]._cfg.id, {"x": x,"y": y})
          }
        });

参考文件:
[1]: https://g6.antv.antgroup.com/
[2]: https://www.iconfont.cn/search/index?searchType=icon&q=time&page=1&fromCollection=-1

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值