前面说了使用 skywalking 进行全链路跟踪,一个 api 请求,我们可以根据trace_id获取到请求的所有记录,测试在正常测试的过程中,会产生跟踪日志,我们可以将关键日志作为原始对比数据。
查看 skywalking 的 mysql 数据库,研究源码,可以看到,在 segment 表中,data_binary 字段就是前端显示的对应的链路数据,但是这个字段是加密的。
起初,我原本考虑加个字段,然后修改一下源码。在生成日志的时候直接写入明文,但在调试过后发现不行,新增字段后写入就会报错,请教吴大后回复说这是我特殊的需求。
于是使用使用如下方案:
mysql 定时同步至 data_sync 表用的事件,写法:
忽略重复项进行同步
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 就可以查出所有该请求的数据记录
关键代码如下:
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>