一、背景

任何系统都无法100%保证不出错误,线上系统报错之后,首先要做的就是在第一时间内找出问题,解决问题,定位线上问题最主要的途径就是看日志。

在单模块下根据日志排查问题,只需要直接搜索关键字就能很清晰地看到线上代码的执行情况。而随着现在越来越多的系统分布式化、微服务化,一个请求往往需要经过多个分布式模块协同处理,比如下面这个简单的分布式系统,购买一件商品的流程大致为:在web/h5/app端发送下单请求到网关(gateway);网关对请求进行过滤、包装,转发到业务模块(business);业务模块执行相关业务,在此需要根据具体业务逻辑调用用户模块(user)查询用户相关信息如用户名、收件地址等;调用商品模块(goods)查询商品信息如库存等;调用订单模块(order)生成订单;调用账务模块(account)查询优惠券等。


在这样的系统中,一旦下单失败,想要查看代码详细执行的情况,就得一个一个查看每个模块的日志,而且查找的关键字也可能不一样,比如查询用户模块的日志用用户名当关键字,查询商品模块用商品编码当关键字……这就很麻烦了。

二、分布式日志调用链追踪介绍

要解决上面的问题,可以在请求入口(比如上图中的网关模块gateway,甚至web/h5/app都可以)针对每一个请求生成一个requestId,后面整个执行链路中都带着这个requestId,利用这个requestId可以把整个过程中打出的相关日志连成一个串。当出现问题之后,在任意模块根据关键字找出requestId,如果相关模块部署在同一台机器上,可以利用tail -f 日志文件1.log 日志文件2.log 日志文件3.log |grep 'requestId的值'之类的方式查看调用链路的日志,比如查看一个用户登录时,在gateway、business、user模块打印的日志:

当然有ELK的话也可以通过ELK来查看。

三、分布式日志调用链追踪实现

以上只是一个把分布式日志“串”起来的一个思路,技术架构、部署方式不同的项目,具体实现方式肯定也不同。这里以以SpringBoot(Spring)+Dubbo为基础的系统来介绍一种实现方法。

1、在gateway模块生成requestId

首先需要在gateway模块生成一个requestId字符串,因为gateway模块调用business模块是通过dubbo调用,所以可以通过传参把requestId传递到business模块,但是这样对代码的入侵太严重了,服务调用者每次调用dubbo服务都需要把requestId放到参数中,所以这种方法pass掉!

这个问题,Dubbo的开发者们早就想到了,可以利用Dubbo的Filter来实现。

(1)首先在gateway模块的全局过滤器(自己实现的javax.servlet.Filter)中生成一个requestId字符串(尽量不重复),放到ThreadLocal(为了在gateway模块的其他地方打印日志时随用随取)中:

//定义一个全局静态的ThreadLocal
public static ThreadLocal<String> requestIdThreadLocal = new NamedThreadLocal<String>("requestId");
//把生成的requestId放到ThreadLocal中
String requestId=UUID.randomUUID().toString();
requestIdThreadLocal.set(requestId)

同时也放到dubbo的上下文中:

//定义一个Map,只能是Map<String, String>类型,可以存放一些字符类型的信息,比如dubbo调用者要向dubbo提供者传送的requestId
Map<String, String> context = new HashMap<String, String>();
context.put("requestId", requestId);
//把存储有requestId的map放到Dubbo的上下文中
RpcContext.getContext().setAttachments(context);

这时gateway模块在打印日志时(无论是配置的AOP,还是嵌入在代码里的日志),都可以直接从ThreadLocal中获取requestId。

(2)gateway模块(dubbo调用者)已经把requestId放到dubbo的Context中了,接下来就需要在business模块(dubbo提供者)从Context中获取requestId,怎么获取呢?用Dubbo的Filter来获取。

① 定义一个全局静态的ThreadLocal,为了在business模块其他地方打印日志时随用随取:

public static ThreadLocal<String> requestIdThreadLocal = new NamedThreadLocal<String>("requestId");

② 建一个实现com.alibaba.dubbo.rpc.Filter的过滤器,从dubbo的Context中接收requestId并放到ThreadLocal中:

public class DubboContextFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {Map<String, String> context = RpcContext.getContext().getAttachments();String requestId=context.get("requestId");requestIdThreadLocal.set(requestId);return invoker.invoke(invocation);}
}

③ 在配置文件的根目录(resources目录)建立名为META-INF.dubbo的文件夹,文件夹里建立名为com.alibaba.dubbo.rpc.Filter的文件,内容为:
“dubboContextFilter=DubboContextFilter的全路径类名”,比如:

dubboContextFilter=com.happycommunity.business.config.DubboContextFilter

④ 在Dubbo提供者的实现类的@com.alibaba.dubbo.config.annotation.Service注解中添加属性filter = “dubboContextFilter”。

这时business模块在打印日志时(无论是配置的AOP,还是嵌入在代码里的日志),都可以直接从ThreadLocal中获取requestId。

其他模块也一样,Dubbo服务的调用者把requestId放到Dubbo的Context中,Dubbo服务的提供者通过Dubbo的Filter从Context中获取requestId并存入ThreadLocal,画了个图流程大概如图所示:

上图中箭头指的就是requestId传递的路线。在gateway模块中,Servlet Filter拦截HTTP请求,对每个外部的请求生成一个requestId,存入ThreadLocal和Dubbo的Context,因为在同一个JVM中,该次请求执行的操作是都在一个线程中,在gateway模块的任意位置打日志都可以直接从ThreadLocal中获取requestId。

当Dubbo服务请求到business模块时,因为不在一个JVM中,就不能直接跟gateway模块似的直接从ThreadLocal中获取requestId了,所以需要用Dubbo的Filter在接收到Dubbo请求之后,执行方法之前,从Context中获取到requestId并存入当前线程(business接收到gateway的dubbo请求后重新开启了一个新的线程来处理业务逻辑)的ThreadLocal中,后续在任意位置打日志都可以直接从ThreadLocal中获取requestId。

(注:上文中的代码仅为示例代码,并不完整,完整的demo可参考:https://github.com/DannyHoo/happycommunity)

dubbo分布式日志调用链追踪相关推荐

  1. 快狗打车CTO沈剑:低成本搞定分布式调用链追踪系统

    本文根据沈剑老师在[2020 Gdevops全球敏捷运维峰会]现场演讲内容整理而成. 讲师介绍 沈剑,到家集团技术VP&技术委员会主席,快狗打车CTO,互联网架构技术专家,"架构师之 ...

  2. 面试精讲之面试考点及大厂真题 - 分布式专栏 22 分布式系统下调用链追踪技术

    22 分布式系统下调用链追踪技术 我们有力的道德就是通过奋斗取得物质上的成功:这种道德既适用于国家,也适用于个人. --罗素 引言 一个复杂的分布式系统,用户发起一个请求,这个请求可能调用几十到几百个 ...

  3. 分布式 - 分布式系统下调用链追踪技术

    不啰嗦,我们直接开始! 引言 一个复杂的分布式系统,用户发起一个请求,这个请求可能调用几十到几百个服务,经过很多业务层,而每个业务又是多个机器集群,一个请求具体被随机到哪台机器上又无法确定,如果最后用 ...

  4. Dubbo分布式日志追踪

    很多互联网公司都用的dubbo分布式框架进行微服务的开发,一个大系统往往会被拆分成很多不同的子系统,并且子系统还会部署多台机器,当其中一个系统出问题了,查看日志十分麻烦 所以我们需要一个固定的流程ID ...

  5. 微服务调用链追踪中心搭建 1

    概述 一个完整的微服务系统包含多个微服务单元,各个微服务子系统存在互相调用的情况,形成一个 调用链.一个客户端请求从发出到被响应 经历了哪些组件.哪些微服务.请求总时长.每个组件所花时长 等信息我们有 ...

  6. 调用链追踪系统在伴鱼:理论篇

    本文将调用链追踪系统的设计维度归结于以下 5 个:调用链数据模型.元数据结构.因果关系.采样策略以及数据可视化.我们可以把这 5 个维度当作一个分析框架,用它帮助我们在理论上解构市面上任意一个调用链追 ...

  7. 调用链追踪系统在伴鱼:实践篇

    我们介绍了伴鱼在调用链追踪领域的调研工作,本篇继续介绍伴鱼的调用链追踪实践.在正式介绍前,简单交代一下背景:2015 年,在伴鱼服务端起步之时,技术团队就做出统一使用 Go 语言的决定.这个决定的影响 ...

  8. 微服务调用链追踪框架Skywalking,看完你就懂了!

    思维导图 文章已收录Github精选,欢迎Star:https://github.com/yehongzhi/learningSummary 概述 **skywalking**又是一个优秀的国产开源框 ...

  9. Istio 调用链追踪与指标收集

    Istio 调用链追踪与指标收集 引言 调用链追踪 安装Zipkin Istio 启用调用链追踪 暴露zipkin服务 自定义采样率 使用全局配置 针对Deployment设置 指标收集 dashbo ...

最新文章

  1. 一文概述2017年深度学习NLP重大进展与趋势
  2. dbus-launch(转)
  3. C# 从不是创建控件 的线程访问它
  4. C语言再学习 -- 详解C++/C 面试题 1
  5. audio隐藏下载按钮
  6. vue获取编辑器纯文字_vue中使用富文本编辑器
  7. Java switch-case语句用法
  8. JavaScript常用注释规范
  9. 2020身高体重标准表儿童_2020儿童身高标准表出炉,10岁长到1米4才合格,你家娃达标了吗...
  10. 微信小程序UI库组件库合集
  11. PI控制器概念 笔记
  12. Myeclipse之回退版本
  13. CentOS设置开机自动执行指定命令
  14. mma8653驱动编程
  15. 安卓系统或安卓机顶盒如何安装entware来搭建liunx系统环境
  16. linux 运行go文件路径,go程序部署到linux上运行-Go语言中文社区
  17. cocos studio
  18. linux下phy接光模块,C6638,linux mac to mac 模式(没PHY),接SFP+模块,通过光纤(30cm)与PC连接。在PC显示该网络链路为10Gbps,网口问题...
  19. 移动Web开发技巧汇总
  20. WebService-CXF

热门文章

  1. word2vec简要教程
  2. 递归折半查找法 c语言程序,折半查找法的递归和非递归形式
  3. 帮忙写一个满天星菜单风格代码
  4. tp5 童攀_TP5.1+swoole+redis+nginx实战开发B2B2C多商户商城系统
  5. 根服务器能当蜘蛛种子网站吗,网站被对方镜像有什么危害,该怎么处理?_蜘蛛技巧_超级蜘蛛池...
  6. 航空发动机原理复习之计算题总结(三)
  7. 牛客训练赛10 B栈和排序(栈)
  8. 【不正经科普】从Windows11看数据安全——你的数据真的安全吗?
  9. zookeeper设置retries
  10. Unity3d 鼠标的事件GetMouseButtonDown()、GetMouseButton()、GetMouseButtonUp()