全链路调用日志跟踪
介绍
框架支持分布式,一个由客户端(玩家)发起的请求到游戏服务器后,可能会经过多个不同的游戏逻辑服产生最后的请求结果。
产生的问题
- 每一次请求都有可能会形成一条复杂的分布式调用链路,那么排查问题的难度也随之上升。
- 或者说当有大量的玩家在线操作时,其他线程的日志也会一起输出并穿行在其中,导致很难筛选出指定请求的全部相关日志。
为了解决上述问题,我们需要对请求链路进行追踪,配合 MDC 机制,在日志模板中加入 traceId 标识。 在请求到达游戏服务器时,为每个请求分配一个唯一标识。 在打印日志时,traceId 标识会出现在日志中,这样就能追踪到该请求的全部链路了。
打印预览
图中高亮的地方,表示同一个请求所打印的日志。 以往,我们会通过 userId 来区分请求的唯一性,这种方式只适合中少量的请求中,在大量请求中并不适用。这是因为:
- 当有大量的玩家在线操作时,其他线程的日志也会一起输出并穿行在其中,导致很难筛选出指定请求的全部相关日志。
- 同一用户可能在短时间内请求多次,这样就很难区分该日志是属于哪个请求的。
而全链路调用日志跟踪正是解决该问题的,全链路调用日志跟踪可以为每个请求分配一个唯一标识,并记录在日志中。 通过唯一标识可以快速的在日志中过滤出指定请求的信息。
框架提供的全链路调用日志跟踪特性更是强大,支持跨机器、跨进程。 简单的说,从玩家的请求进来到结束,无论该请求经过了多少个游戏逻辑服,都能精准记录。
下图展示的是在跨服调用下的截图
全链路调用日志跟踪在跨服调用下很实用
logback.xml
code 4,配置中,ioGameTraceId 表示打印 traceId 。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="log.pattern"
value="%d{HH:mm:ss.SSS} %green([%thread]) [%X{ioGameTraceId}] %highlight(%-5level) %cyan(%logger{5}).%M\(%F:%L\) %m%n"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
如何使用
将 TraceIdInOut 插件添加到业务框架中。
BarSkeletonBuilder builder = ...;
builder.addInOut(new TraceIdInOut());
开启 traceId 特性
使用全链路调用日志跟踪特性时,还需要做一些简单的配置,因为并不是所有的项目都需要该特性。
IoGameGlobalConfig.openTraceId = true;
该配置需要在游戏对外服中设置,因为游戏对外服是玩家请求的入口。
traceId 生成策略
对于 traceId 的生成策略,框架提供了默认实现(只合适单个游戏对外服)。
如果你启动了多个游戏对外服,这里建议你重新实现 traceId 生成策略这部分的逻辑,或者使用一些第三方库。
UUID 生成策略
使用 JDK 内置的 UUID 生成策略。
public void config() {
TraceKit.setDefaultTraceIdSupplier(() -> UUID.randomUUID().toString());
}
Mongo ObjectId 生成策略
see https://mvnrepository.com/artifact/org.mongodb/bson
使用 MongoDB id 为生成策略。
public void config() {
TraceKit.setDefaultTraceIdSupplier(() -> new ObjectId().toString());
}
其他生成策略
小结
全链路调用日志跟踪,可以很好的解决开头所描述的问题,并且在框架中使用该特性是简单的。