Java 实现分布式服务的调用链跟踪
作者:果果果果果 发布时间:2023-11-25 10:24:55
目录
为什么要实现调用链跟踪?
如何实现?
第一步,看图、看场景,用户浏览器的一次请求行为所走的路径是什么样的
第二步,实现。不想看代码可直接拉最后看结果和原理
测试一下结果:
为什么要实现调用链跟踪?
随着业务的发展,所有的系统最终都会走向服务化体系,微服务的目的一是提高系统的稳定性,二是提高持续交付的效率,为什么能提高这两项不是今天讨论的内容。
当然这也不是绝对的,如果业务还在MVP验证,团队规模小个人觉得完全没必要微服务化、单体应用是比较好的选择。作者是有经历过从单体应用到1000+应用的增长经历,也是见证了公司从初创到上市的过程,对于系统阶段和业务阶段的匹配还是有比较深的感受的。
服务拆分后带来的问题是什么呢?服务的依赖关系复杂后,对于问题的排查也增加了复杂度,当然站在更高的角度来看拆分带来的不只是排错复杂性的提升,工程效率、组织协作也都会带来新的挑战。
回到主题,如何快速查询整个请求链路上的日志并呈现出来是解决排查问题复杂度的根本方法,这就是今天我们要讲的内容,如何自己来实现一个全链路跟踪。
如何实现?
第一步,看图、看场景,用户浏览器的一次请求行为所走的路径是什么样的
如上图、省略了4层和7层的LB,请求直接到gateway->A->B 那如何把个request关联起来呢?从时序上来看我们只要在gateway生成一个traceId然后层层透传,那么每一次的request的我们就能通过traceid关联查询出来了。
如何透传、如何记录呢?或者说如何透传、如何记录让各应用的开发人员无需关注呢?
第二步,实现。不想看代码可直接拉最后看结果和原理
如何传递,这里我们使用定义统一的Request类,所有的api层需要使用这个规范,代码如下:
public class Request<T> implements Serializable {
//header:携带需要传递的信息
private RequestHeader header;
//业务参数
private T bizModel;
//...省略get set
}
public class RequestHeader implements Serializable {
//调用链唯一ID
private String traceId;
//当前用户Id
private String userId;
//上游调用方appId
private String callAppId;
//...省略get set
}
有了这个Request之后,我们在网关层每次都生成traceId, 然后在各服务之间传递就能做到调用链的关联了。我们继续看个各应用应该如何定义服务和使用
@ApiMethod
@PostMapping("/test")
@ApiOperation(value = "test", notes = "", response = String.class)
public Response<ExampleRespDTO> test(@RequestBody Request<ExampleReqDTO> req) {
ExampleRespDTO exampleRespDTO = new ExampleRespDTO();
exampleRespDTO.setName(req.getBizModel().getName());
//输出当前应用的header信息
System.out.println("上游的traceId:"+RequestContext.getHeader().getTraceId());
System.out.println("上游的callAppId:"+RequestContext.getHeader().getCallAppId());
System.out.println("上游的userId:"+RequestContext.getHeader().getUserId());
/***
* 模拟调用其他应用服务
* 通过RPCRequest 来构建request对象
*/
Request<OtherAppServiceReqDTO> otherAppServiceReqDTORequest =RPCRequest.createRequest(new OtherAppServiceReqDTO());
//输出下游应用的header信息
System.out.println("调用下游的traceId:"+otherAppServiceReqDTORequest.getHeader().getTraceId());
System.out.println("调用下游的callAppId:"+otherAppServiceReqDTORequest.getHeader().getCallAppId());
System.out.println("调用下游的userId:"+otherAppServiceReqDTORequest.getHeader().getUserId());
return Response.successResponse(exampleRespDTO);
}
看完上面代码的同学,应该看到了有一个模拟调用其他服务的地方,这里主要解决的是服务和服务之间的调用header传递的问题,这里封装了一个createRequest的方法,其主要内容还是把当前应用的requestHeader 赋值给请求其他服务的request上。这也是一个测试接口,最后面有测试的结果
public class RPCRequest {
public static <T> Request<T> createRequest(T requestData){
Request<T> request = new Request();
RequestHeader requestHeader=new RequestHeader();
requestHeader.setTraceId(RequestContext.getHeader().getTraceId());
requestHeader.setUserId(RequestContext.getHeader().getUserId());
requestHeader.setCallAppId(AppConfig.CURRENT_APP_ID);
request.setHeader(requestHeader);
request.setBizModel(requestData);
return request;
}
}
当前request中的header存在什么地方呢,我们看一下RequestContext的代码
public class RequestContext {
private static ThreadLocal<RequestHeader> threadLocal=new ThreadLocal<>();
public static void setHeader(RequestHeader header){
threadLocal.set(header);
}
public static RequestHeader getHeader(){
return threadLocal.get();
}
public static void clear(){
threadLocal.remove();
}
}
header是什么时候放进去的呢?这里就是AOP该发挥作用的时候了,直接看代码
public class ApiHandler {
public ApiHandler() {
}
public Response handleApiMethod(ProceedingJoinPoint pjp, ApiMethod apiMethod) {
//获取上游调用方的request header
Object[] args = pjp.getArgs();
Request request = (Request) args[0];
RequestHeader header = request.getHeader();
//将header加入到当前request 到ThreadLocal保存
RequestContext.setHeader(header);
Response response = null;
try {
//构建response header
ResponseHeader responseHeader = new ResponseHeader();
responseHeader.setTraceId(RequestContext.getHeader().getTraceId());
//执行service方法
response = (Response) pjp.proceed(args);
response.setHeader(responseHeader);
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
//清除ThreadLocal中当前请求的header 对象
RequestContext.clear();
}
return response;
}
}
不想看代码的,直接看下图,原理比较简单,浅黄色为AOP作用,接口执行前和执行后,其中reqeuest和header的定义在第1段代码
这里没有介绍如何收集数据和查询展示,比较简单的办法是使用logback打本地日志,然后通过agent抽到集中式日志进行查询展示,例如ELK。
测试一下结果:
1、接口文档
2、执行结果
来源:https://juejin.cn/post/6969990175619285028


猜你喜欢
- 什么是树?简单认识树 在生活中,有杨树,石榴树,枣树,而在计算机中的树呢,是一种非线性结构,是由 n(n>=0) 个有限节点
- 本文实例为大家分享了java实现简单快递系统的具体代码,供大家参考,具体内容如下创建四个类Express,Locker, User, Adm
- 如何:对 Windows 窗体控件进行线程安全调用访问 Windows 窗体控件本质上不是线程安全的。 如果有两个或多个线程操作某一控件的状
- 本文实例为大家分享了winform可拖动的自定义Label控件,供大家参考,具体内容如下效果预览:实现步骤如下:(1)首先在项目上右击选择:
- 手机二维码扫码登录已经成为了现代互联网时代的一种普遍的登录方式。它的出现,极大地方便了用户登录的流程,减少了用户输入用户名和密码的麻烦。在二
- 本文实例讲述了C#实现图片切割的方法。分享给大家供大家参考,具体如下:图片切割就是把一幅大图片按用户要求切割成多幅小图片。dotnet环境下
- 上转型对象:子类创建对象 并将这个对象引用赋值给父类的对象。语法格式:Father f=new Son();注意事项:上转型对象是由子类创建
- Java 序列化技术可以使你将一个对象的状态写入一个Byte 流里,并且可以从其它地方把该Byte 流里的数据读出来,重新构造一个相同的对象
- 一、什么叫做匿名类?匿名类就是没有名字的类。匿名类不能被引用,只能再创建的时候用new语句来声明。二、匿名类的优势以及应用场景;1、匿名类型
- 在学习 Spring Mvc 过程中,有必要来先了解几个关键参数:@Controller:在类上注解,则此类将编程一个控制器,在项目启动 S
- 使用IDEA配置Maven搭建开发框架ssm教程一、配置Maven环境1.下载Maven:下载链接2.下载完成解压压缩包并创建本地仓库文件夹
- eMMC主要是针对手机和平板电脑等产品的内嵌式存储器,由于其在封装中集成了一个控制器,且提供标准接口并管理闪存等优势,越来越受到Androi
- 1.抽象类与抽象方法:(1)使用关键字abstract修饰的类,称为抽象类.(2)抽象类只是用到一个类所具有的行为,不能单独通过创建对象来使
- 本篇将从以下几个方面讲述反射的知识:class 的使用方法的反射构造函数的反射成员变量的反射一、什么是class类在面向对象的世界里,万物皆
- Cookie1. 概念是服务器通知客户端保存键值对的一种技术。cookie 是 servlet(服务器) 发送到 Web 浏览器(客户端)的
- 有很多制作精良的APP都自带点击音效,那么如何简单的来实现这一效果,这里需要使用到的一个概念叫做SoundPool,这个类主要用于播放一些比
- JetBrains JVM Debugger Memory View plugin在我最近的研发活动期间寻找新的工具,以提高我的开发经验,使
- java.util.Scanner类是一个简单的文本扫描类,它可以解析基本数据类型和字符串。它本质上是使用正则表达式去读取不同的数据类型。J
- 高效检索海量信息(经典查找算法)是现代信息世界的基础设施。我们使用符号表描述一张抽象的表格,将信息(值)存储在其中,然后按照指定的键来搜索并
- 这是一个自定义view画圆,对于初学自定义view的小伙伴这是一个很好的帮助。 看图代码:package sjx.com.custonvie