通用技术 简易接口定位(二)

大林 · 2022年12月02日 · 3709 次阅读

前面说了使用 skywalking 进行全链路跟踪,一个 api 请求,我们可以根据trace_id获取到请求的所有记录,测试在正常测试的过程中,会产生跟踪日志,我们可以将关键日志作为原始对比数据。

查看 skywalking 的 mysql 数据库,研究源码,可以看到,在 segment 表中,data_binary 字段就是前端显示的对应的链路数据,但是这个字段是加密的。

起初,我原本考虑加个字段,然后修改一下源码。在生成日志的时候直接写入明文,但在调试过后发现不行,新增字段后写入就会报错,请教吴大后回复说这是我特殊的需求😅

于是使用使用如下方案:

1、新增一个跟踪表data_sync,每天定时对 segment 进行同步

具体步骤:

mysql 定时同步至 data_sync 表用的事件,写法:

INSERT IGNORE INTO data_sync(id,trace_id,service_id,data_binary,segment_id,start_time,time_bucket) SELECT id,trace_id,service_id,data_binary,segment_id,start_time,time_bucket FROM segment

忽略重复项进行同步

2、编写模块程序每天对 data_binary 字段进行解码为明文后,存入 data_binary_str 字段,并完善接口名称。

具体步骤:

a.查看源码可知,data_binary 字段存入的是 byte 格式,并且做了 base64 加密。模块利用 skywalking 源码中的apm-network-8.9.1.jar包,该包提供的SegmentObject.parseFrom方法,每天去解析 data_binary 数据成字符串,保存至 data_binary_str 字段。

解析出来如下明文:

traceId: "2b2bf37fa4104503b4cb9e170478429b.126.16698890354320007"
traceSegmentId: "2b2bf37fa4104503b4cb9e170478429b.126.16698890354320006"
spans {
  spanId: 1
  startTime: 1669889035433
  endTime: 1669889035433
  operationName: "Jedis/get"
  peer: "172.24.11.48:6379"
  spanType: Exit
  spanLayer: Cache
  componentId: 30
  tags {
    key: "db.type"
    value: "Redis"
  }
  tags {
    key: "db.statement"
    value: "get"
  }
}
spans {
  parentSpanId: -1
  startTime: 1669889035432
  endTime: 1669889035469
  operationName: "POST:/{coreName}/{txId}"
  spanLayer: Http
  componentId: 14
  tags {
    key: "url"
    value: "http://172.24.11.31:8080/jiaparts-support-api/jsc-gw/cum076"
  }
  tags {
    key: "http.method"
    value: "POST"
  }
}
service: "jsc_support_api"
serviceInstance: "d94b194ea8574f94836eec9637b0e57e@172.24.124.29"

b.由于公司项目是微服务模式,有统一的入口服务,查看上图,可以看到jsc_support_api就是入口服务,这个名称可以通过 serviceId 去service_traffic表查询出来。

如果 serviceId 是统一入口服务,就通过处理明文 data_binary_str 拿到请求的接口名称即api_name,上面的 cum076 即是接口名称。

c.创建 mysql 事件,每天完善记录数据的 api_name。因为入口服务已经解析出 api_name,通过该服务的 trace_id 就可以查出所有该请求的数据记录

UPDATE data_sync a INNER JOIN ( SELECT trace_id, api_name FROM data_sync t WHERE t.api_name IS NOT NULL ) b ON a.trace_id = b.trace_id SET a.api_name = b.api_name

至此,原始对比数据已经完成。

关键代码如下:

import org.apache.skywalking.apm.network.language.agent.v3.SegmentObject; //引入SegmentObject对象

   @Value("${string.servicenames}")
    String serviceNameApi;

    /**
     * 获取接口名称
     **/
    public String callApiName(String ref) {
        String apiName = null;
        String newStr = ref.substring(ref.indexOf("parentSpanId: -1"));//根节点
        if (newStr.contains("value: \"")) {//字符串分割获取接口名称
            String newStr2 = newStr.substring(newStr.indexOf("value: \""));
            String url = newStr2.split("\"")[1];
            apiName = url.split("/", 4)[3];
        }
        return apiName;
    }

    /**
     * 每天凌晨2开始执行
     **/
    @Scheduled(cron = "0 0 2 * * ?")
    public void statisticsFinishedTestTask() throws InvalidProtocolBufferException {
        //查找未同步数据
        boolean state = true;
        while (state) { //循环,列表为零时退出
            List<DataSync> segments = dataSyncService.getList(); //这里获取列表,并且每次3000条,因为数据量太大一次性获取全部时,导致了连接超时的bug
            if (segments.size() != 0) {
                for (DataSync segment : segments) {
                    byte[] dataBinaryByte = Base64.getDecoder().decode(segment.getDataBinary());//获取data_binary字段并解析
                    String serviceId = segment.getServiceId();
                    String serviceName = serviceTrafficService.selectOne(serviceId).getName();
                    SegmentObject segmentObject = SegmentObject.parseFrom(dataBinaryByte);//转成SegmentObject对象
                    segment.setDataBinaryStr(segmentObject.toString());//保存字符串
                    segment.setServiceName(serviceName);
                    if (serviceNameApi.contains(serviceName)) {
                        segment.setApiName(callApiName(segmentObject.toString()));//这里获取接口名称
                        dataSyncService.updateByTraceId(segment);
                    }
                    dataSyncService.updateByPrimaryKeySelective(segment);
                }
            } else {
                state = false;
            }
        }
    }
}

对应 xml 的 sql

<select id="getList"  resultMap="BaseResultMap">
     SELECT * FROM data_sync where data_binary_str is NULL LIMIT 3000
 </select>
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册