背景
伴随着业务的复杂,异步任务伴随着我们的系统构建,通常1个http请求过来之后,是拆出多个任务,多线程执行,最终等待合并结果然后返回。现有的日志和skywarlking默认的功能无法涵盖场景。
方案的缺点
日志方法最大的问题是记录日志的时候无法准确搜索,例如请求里都是alice,短时间内触发了3次,通过alice能查询出多个片段,我们得根据上下文推断出请求的状态,但是推断的过程就因人而异了。最终的结论往往是有偏差的。 skywarlking默认只记录同步的状态。无法记录异步的情况。导致只能明白主流程,但是对于流程的细节,无法提供支持。
分析
这里的核心点是并发情况下引发的,对于日志,我们只要做好唯一的一起id标记,就可以满足。skywarlking我们解决的就是异步的时候怎么串链路的问题。为了比较好的配合,我们需要和2者串起来。
skywarlking的异步支持
skywarlking主要是在异步的方法上记录到相关的信息,这里的方式就是做传递,目前有2种方式。 第一种是任务比较复杂。类型可能包括传统的的runnable,还有stream表达式,例如在stream中设置一个function。
对于如上的场景,我们的改动方式比较大,需要每一个提交的地方都进行改动。改动的方式直接使用skywarking的包装类。
RunnableWrapper.of()
CallableWrapper.of()
SupplierWrapper.of()
FunctionWrapper.of()
ConsumerWrapper.of()
通过包装就可以返回一个可以被追踪的对象。然后传入我们的提交器就可以。
第二种比较单一,是我们有一些抽象的过程,已经有了一些例如公共的任务类。这种可以直接使用skywarlking的注解。改动的话相对可控,直接在类上加注解即可。 案例如下:
@TraceCrossThread
public static class MyTest<String> implements Callable<String> {}
通过上面的2种方式,我们就可以把异步的任务在 skywarlking上展示出来。
日志的支持
因为要把skywarlking和日志关联起来,所以要用到skywarlking的id来作为串联的方式。
String traceId = TraceContext.traceId();
日志的方法我们选择MDC. 我们要在异步提交的时候,指定提交的traceId。
MDC.put('traceId', traceId);
在日志的输出的地方,增加id的配置。
[traceID:%X{traceID}]
skywarlking的traceId的唯一的,在提交任务的时候,要把对应的信息传递给task,在task的执行之前组织成一个map结构。在执行完之后清理。重点清理一定要做,否则信息会带给下一个任务。执行的目标大概如下
MDC.setContextMap(context);
try
{ runnable.run(); }
finally { MDC.clear(); }
只要把traceId通过context传递下去,日志就可以输出成功。
通过线程之间传递traceId可以实现,唯一的信息传递,日志的打印就可以根据id进行搜索。解决了准确性的问题。
最终版本
我们通过skywarlking来完成大节点的观测,虽然http中有异步的操作,我们依旧可以通过链路的追踪来找到最长的环节。在环节的开始和结束时间对比上,还可以看到调度的区别。具体每个异步任务的细节,则通过日志观测,通过把traceId传递,可以在skywarlking上拿到id进行日志的检索,拿到具体的细节信息,完全了解现在的任务状态。