随着业务越来越复杂,系统也随之进行各种拆分,特别是随着微服务架构和容器技术的兴起,看似简单的一个应用,后台可能有几十个甚至几百个服务在支撑;一个前端的请求可能需要多次的服务调用最后才能完成;当请求变慢或者不可用时,我们无法得知是哪个后台服务引起的,这时就需要解决如何快速定位服务故障点,今天介绍的分布式服务链路追踪就能很好的解决这样的问题。
它可以做什么呢?
- 用户行为链路:如浏览页面,观看视频,购买商品,收藏,评论等等行为。
- 服务链路追踪:快速定位异常
- 大数据AI画像:一个链路非业务的动态行为数据,才是最贴近用户的素材
服务链路追踪的基本出发点——记录足迹
- (1)入口处生成链路标识traceId
- (2)传递traceId参数给下层业务方法
- (3)各方法内部记录访问的信息
但是这种方式,违背了我们面向对象原则:高内聚、低耦合。严重的和业务代码耦合在一起。链路追踪不能成为我们的主流业务方法,增值性的服务不能影响我们的主流业务(异常、耗时)
单系统链路追踪-优化方案
我们可以采用ThreadLocal变量来存储traceId(关于threadLocal使用自行百度),traceId就可以在当前线程的范围内访问。
/**
* @desc
* @author lizehao
* @company 连连支付
* @date 2018年12月30日上午10:40:49
*/
public class TraceDataHolder {
private static ThreadLocal<String> instanceHolder = new ThreadLocal<String>();
public static void setTraceId() {
String traceId = instanceHolder.get();
if (StringUtils.isEmpty(traceId)) {
traceId = UUID.randomUUID().toString();
instanceHolder.set(traceId);
}
}
public static String getTraceId() {
return instanceHolder.get();
}
}
在程序的访问入口处,调用TrackDataHolder.setTraceId()即可,获取traceId调用TrackDataHolder.getTraceId().
还可以进一步进行优化,入口处我们可以采用基于AOP的实现,也可以采用基于注解的方式。
这样方式在单系统中是可以的,但是在分布式系统,比如说dubbo之间的服务调用,它可不认这一套,所以我们还需要进一步进行优化。
如何让traceId的传递不侵入业务?
MDC是log日志系统支持的一个功能,底层支持threadLocal变量。并且可以在打印日志的时候,获取该变量。
在方法的入口处,通过MDC.put("traceId","****")进行设置,日志格式采用 %X{traceId},代码里通过MDC.get("traceId")获取。
通过MDC设置值也可以通过AOP、注解实现。
但是在dubbo服务之间,traceId如何传递的呢?我们就需要用到dubbo的Filter。
link-trace项目
/**
* @desc
* @author lizehao
* @company 连连支付
* @date 2018年8月23日下午6:32:26
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TrackAnnotation {
}
/**
* @desc
* @author lizehao
* @company
* @date 2018年12月30日下午8:58:00
*/
@Aspect
@Component
public class TrackAspect {
public final static String TRACK_ID = "trackId";
@Around("@annotation( com.lizh.link.track.demo.annotation.TrackAnnotation )")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
MDC.put(TRACK_ID, UUID.randomUUID().toString());
return pjp.proceed();
}
}
通过dubbo的SPI机制,扩展Filter
/**
* @desc
* @author lizehao
* @company
* @date 2018年12月30日下午9:43:59
*/
@Slf4j
@Activate(group = "provider")
public class MDCFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String trackId = RpcContext.getContext().getAttachment(TrackAspect.TRACK_ID);
MDC.put(TrackAspect.TRACK_ID, trackId);
log.info("---------MDC:[{}]--------", trackId);
return invoker.invoke(invocation);
}
}
/**
* @desc
* @author lizehao
* @company
* @date 2018年12月30日下午9:44:04
*/
@Slf4j
@Activate(group = "consumer")
public class RPCFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// RpcContext.getContext().setAttachments(MDC.getContext());
log.info("---------RPC:[{}]--------", MDC.get(TrackAspect.TRACK_ID));
RpcContext.getContext().setAttachment(TrackAspect.TRACK_ID, (String) MDC.get(TrackAspect.TRACK_ID));
return invoker.invoke(invocation);
}
}
/src/main/resources/META-INF/dubbo下新建一个文件,文件名为接口的全类名(com.alibaba.dubbo.rpc.Filter)
mdc=com.lizh.link.trace.demo.log.MDCFilter
rpc=com.lizh.link.trace.demo.log.RPCFilter
服务的消费方:
#################Dubbo 服务提供者配置#################
spring.dubbo.application.name=order-consumer
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.consumer.filter=rpc
spring.dubbo.scan=com.lizh
核心配置:spring.dubbo.consumer.filter=rpc
/**
* @desc
* @author lizehao
* @company
* @date 2018年12月30日下午9:11:34
*/
@Slf4j
@Controller
public class OrderController {
@Reference
private OrderService orderService;
@ResponseBody
@TrackAnnotation
@RequestMapping("/order")
public Order findOne(long id) {
log.info("前置开始查询订单信息[{}]", id);
Order order = orderService.findOrderById(id);
log.info("订单信息[{}]", JSON.toJSONString(order));
return order;
}
}
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger- [%X{trackId}]- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger-[%X{trackId}]- %msg%n
核心配置:@TrackAnnotation
服务的提供方
#################Dubbo \u670D\u52A1\u63D0\u4F9B\u8005\u914D\u7F6E#################
spring.dubbo.application.name=order-provider
spring.dubbo.registry.address=zookeeper://127.0.0.1:2181
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
spring.dubbo.scan=com.lizh
spring.dubbo.provider.filter=mdc
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger- [%X{trackId}]- %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger-[%X{trackId}]- %msg%n
运行结果:
消费方:
2018-12-31 10:10:44 INFO com.lizh.order.demo.controller.OrderController- [9b1851dc-f9e6-444a-bce5-613974220450]- 前置开始查询订单信息[111]
2018-12-31 10:10:44 INFO com.lizh.link.track.demo.log.RPCFilter- [9b1851dc-f9e6-444a-bce5-613974220450]- ---------RPC:[9b1851dc-f9e6-444a-bce5-613974220450]--------
2018-12-31 10:10:44 INFO com.lizh.order.demo.controller.OrderController- [9b1851dc-f9e6-444a-bce5-613974220450]- 订单信息[{"id":111,"name":"订单_293","price":"23"}]
提供方:
2018-12-31 10:10:44 INFO com.lizh.link.track.demo.log.MDCFilter- [9b1851dc-f9e6-444a-bce5-613974220450]- ---------MDC:[9b1851dc-f9e6-444a-bce5-613974220450]--------
2018-12-31 10:10:44 INFO com.lizh.order.demo.service.OrderServiceImpl- [9b1851dc-f9e6-444a-bce5-613974220450]- 查询订单[111]对象......