应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

自实现分布式链路追踪方案ampampamp实践

  前言:
  排查问题是程序员的基本能力也是必须要会的,在开发环境,我们可以debug,但是一旦到了服务器上,就很难debug了,最有效的方式就是通过日志揪出bug,而一次请求的日志如果没有一个唯一的链路标识(我们下边称他为traceId),单靠程序员人工分析的话,费时费力,尤其是请求量高频的接口,更是雪上加霜,排查问题效率大打折扣,作为程序员,低效的方式是忍不了的!!!本文我将用一次实战演练,来演示常用框架中间件多服务之间如何传递traceId
  本文大概有如下内容:链路追踪简述和自实现思路单服务内如何实现链路id的输出垮服务调用时,实现链路id传递的各种方式(包含http(openFeign,httpClientrestTemplate)、rpc(motan、dubbo)、mq(RocketMq))异步调用时,如何解决log4j2自带的ThreadLocal丢失链路id问题起4个服务,进行调用,观察链路追踪的效果1、链路追踪实现简述
  所谓链路追踪,就是为了把整个请求链路从头到尾串起来,不管调用链路有多深,多复杂,只要将一次链路完整无误的串联起来,就是合格的链路追踪功能。
  业界不乏skywalkingzipkin等等链路追踪方面牛逼的框架,但是我们为了更轻量更灵活可控同时也是抱着学习心态,所以自己来实现链路追踪。
  首先想实现链路追踪,有两点是核心,实现了这两点,问题也就不大了traceId如何在本地(或者说单服务内)传递?在分布式环境中,traceId如何跨服务中间件传递?2、单体服务的链路追踪
  首先我们先讲下单服务内的链路传递
  作为java开发,最常用的就是slf4j来实现打印日志的功能(但是slf4j并不没有实现逻辑,因为slf4j整个的定义是一个日志门面,该包中并无具体的实现,实现都是在比如:logbacklog4j2等等日志实现框架中)
  slf4j的门面不仅给我们提供了打印日志的功能,还提供了org。slf4j。MDC类,该类的作用大概如下:
  映射诊断上下文(MappedDiagnosticContext,简称MDC)是一种工具,用于区分不同来源的交错日志输出。当服务器几乎同时处理多个客户机时,日志输出通常是交错的。MDC是基于每个线程进行管理的。
  上边这个官方解释,最重要的一句话就是MDC是基于每个线程进行管理的
  上边这个太官方,说下我个人对MDC的理解:他是一个日志的扩展,扩展的目的就是给每个线程输出的日志打上一个标记(一个线程只有一个标记且不能重复一般使用uuid即可),这样我们在查看日志时候,就可以根据这个标记来区分调用链路了
  ps:当然了,光往MDC中设置当前线程的链路id也是不行的你还得在log4j2。xml文件中,设置占位符,这样最终输出的日志才会带链路信息。如何设置会在2。1节有讲。
  从代码层面看下MDC做了啥:MDC类中通过一个MDCAdapter实例调用MDCAdapter的putgetremoveclear等方法。而putgetremoveclear具体的实现是不同厂商来做的比如我常用的log4j2包中就实现了MDCAdapter接口,实现在org。apache。logging。slf4j。Log4jMDCAdapter类中Log4jMDCAdapter类中使用了一个ThreadContext来执行putgetremoveclear逻辑,而ThreadContext中又是一个ThreadContextMapThreadContextMap是一个接口,有不同的实现其中默认的是DefaultThreadContextMap该类中维护了一个ThreadLocalMapString,StringlocalMap类型的成员变量其中map中的k,v就是你调用MDC。put(k,v);时传入的kv最终你调用MDC。put(k,v);时候传入的k和v会被放倒localMap这个ThreadLocal中去。在你给某个线程设置了key,value后,log4j2在打印日志时候,将会去log4j2。xml文件中找占位符等于key的,然后用value把占位符替换,从而打印输出了value(也就是traceId),至此实现了单服务内的链路追踪。
  把代码流程梳理下大概如下:MDC(类)MDCAdapter(接口)Log4jMDCAdapter(实现类之一)ThreadContext(类)ThreadContextMap(接口)DefaultThreadContextMap(MDC的几个操作都是往这个里边存取数据)ThreadLocalMapString,StringlocalMap;2。1、如何使用MDC来输出(单服务内的)链路信息?往MDC中设置keyvalue在log4j2。xml中设置占位符(注意:占位符名称要和MDC中的key一致)接着我们简单看下效果:(可以看到,每一个线程,都有一个唯一的链路id)
  上边我们说过MDC最终设置的key,value是放到DefaultThreadContextMap类中的ThreadLocalMapString,StringlocalMap这个里边的,也就是说里边的MapString,String是某个线程的本地副本(不懂线程本地副本的可以回顾下ThreadLocal的知识),有了这个知识基础,我们就不难理解为什么输出的日志是每个线程都有唯一的traceId了。
  举个例子来说就是:线程t1往MDC中设置的key,value,当log4j2打印t1的日志时,会找到当前(t1)线程设置的value,来把log4j2。xml中的占位符替换线程t2往MDC中设置key,value,当log4j2打印t2的日志时,会找到当前(t2)线程设置的value,来把log4j2。xml中的占位符替换
  ok,从上边可以了解到,单服务内通过slf4j提供的MDC功能,可以实现某服务内的链路追踪。
  我们都知道,所谓微服务,都是由各个小的单服务组合起来的,通过上边的描述,我们知道单服务内如何打印链路了,那么微服务间不管怎么调用,只要保证在请求最开始(一般是网关)生成一个链路id,在后续的调用中将这个链路id一层层传递下去,整个完整的调用链路追踪也就实现了。在下边小节,我们就看看traceId是怎么垮服务传递的。3、跨服务的链路追踪实现简述
  垮服务间调用,本质上就是各个服务之间进行通信的过程,一般情况下,微服务间常用通信方式有如下几种:(这里我们仅从应用层来看,如果从传输层看的话1和2都是tcp)rpchttp消息队列(mq)其他可能我不知道的在日常开发中我常用的:
  http框架有apache的httpclient,springcloud的openfeign,spring包中的resttemplate,okhttp(okhttp一般安卓用的比较多)。
  rpc框架的话一般是dubbo、motan其他的本人没怎么用过。
  mq的话我用过kafkarabbitmqrocketmq
  下边,我搭建了四个微服务,分别是网关服务studygateway,
  微服务消费服务studyconsumer后台管理服务studyadmin订单服务studyorder
  在这里我的调用链路大概如下图所示:
  如上图所示的链路,该次请求会经过网关,openFeign,异步调用,httpClient调用,mq这几个组件,而如何在这几个组件调用前传递traceId和调用后设置traceId成为垮服务进行链路追踪的关键。ps:dubbo和motan的我们就不演示了,会直接给出传递方案。
  铺垫了那么多,我们下边直接了当点不再啰嗦!3。1、在请求最前边(studygateway)添加过滤器
  在网关过滤器中生成并设置traceId到MDC(此刻网关服务的日志中将会打印traceId),同时通过header传递到下游服务。
  简单看下网关配置:
  3。2、在路由目标服务(studyconsumer)中添加过滤器(为了从请求头获取链路id)
  3。3、在studyconsumer服务添加feign拦截器(因为consumer要通过feign调用studyadmin服务)
  3。4、在studyadmin中添加过滤器(为了从请求头获取链路id)
  3。5、在httpClient工具中添加拦截器(对外调用时候往header设置链路id)
  3。6、在resttemplate中添加拦截器(对外调用时候往header设置链路id)
  注册拦截器到RestTemplate实例:
  3。7、在RocketMq发送前和消费前添加钩子
  发送前添加钩子:
  消费前添加钩子:
  分别往生产和消费实例对象中设置钩子:
  3。8、用ttl解决异步调用存在的问题(在这里我们也一并说了不再啰嗦)
  上边我们也说了,MDC底层DefaultThreadContextMap是用ThreadLocal来保存的链路信息,而ThreadLocal是同一个线程,才会有相同的副本数据,而当我们在项目中使用线程池时候,主线程和子线程肯定是不一样的,那么这种情况下就得考虑如何将主线程的值传递给子线程,让子线程也能记录traceId,从而保证链路不会断!
  值的一说的是jdk也想到了这个问题,提供了一个InheritableThreadLocal类,但是这个类并不适用于链路追踪场景,因为在异步调用场景下,是要保证每一次请求,都要将主线程的traceId传递给子线程,而InheritableThreadLocal只能是第一次时候传递,或者说他不是每次都传递给子线程更贴切,下边看下官方的描述:
  InheritableThreadLocal存在的问题:官方原话:使用InheritableThreadLocal时(ThreadContext可能并不总是自动传递给工作线程)
  由于线程池的复用机制,所以第n次请求时,线程池中线程所打印出的链路id,还是上次或者是上n次的链路id(我试验了确实如此),而我们真实希望是,线程池中线程打印的链路id保持和当前主线程中的链路id一致,换句话说:我们需要的是任务提交给线程池时的链路id传递到任务执行时。
  既然InheritableThreadLocal不满足需求,那么怎么办呢?看下边:
  在log4j2中,他底层是通过spi机制提供了对ThreadContextMap接口的扩展能力,不了解的可以去看看官网,而正好阿里开源了一个这个小框架ttl和ttlthreadcontextmap,ttlthreadcontextmap可以解决线程间的传递丢失问题(他内部也是使用的TransmittableThreadLocal也就是ttl来存储MDC的key和value)。ttlthreadcontextmap依赖java的spi机制,依靠spi机制,让log4j2在启动加载时,用log4j2。component。properties中log4j2。threadContextMap这个key对应的value作为ThreadContextMap接口的实现(也就是替换掉DefaultThreadContextMap这个默认实现),从而实现了线程间传递的功能。对ttl和ttlthreadcontextmap不熟悉的可以跳的github讲的很详细很清楚。
  TtlThreadContextMap内部使用TransmittableThreadLocal来存储MDC的key,value
  spi配置:
  而我们使用阿里这个工具也很简单首先maven引入(注意版本不清楚的去maven库看看)dependencygroupIdcom。alibabagroupIdtransmittablethreadlocalartifactIdversion2。14。2versiondependencydependencygroupIdcom。alibabagroupIdlog4j2ttlthreadcontextmapartifactIdexclusionsexclusiongroupIdorg。apache。logging。log4jgroupIdlog4japiartifactIdexclusionexclusionsversion1。4。0versionscoperuntimescopedependency
  在引入这个后,我什么也没配,如果我使用jdk的ThreadPoolExecutor或者spring的ThreadPoolTaskExecutor,都是可以实现链路传递的,但是我使用CompletableFuture的话,第一次请求的链路是对的,当第二次请求时候,CompletableFuture线程池中的打印链路信息还是第一次的,这个问题github上有说明,作者让使用javaagent来解决,果然在我配置javaagent后,CompletableFuture的链路信息每次都是正确的。在idea的VMoptions中配置:
  javaagent:Usershzz。m2repositorycomalibabatransmittablethreadlocal2。14。2transmittablethreadlocal2。14。2。jar
  即可解决CompletableFuture的链路id传递问题(这里我们最好是agent这样对代码无侵入,如果你使用TtlRunable修饰Runable的话对代码侵入比较多,维护起来也比较麻烦)
  4、跨服务异步链路追踪效果演示
  最后我们将四个服务启动,看下整体效果:
  postman调用:
  studygateway
  studyconsumer
  studyadmin
  tcpdump抓包看看第三方的请求头中是否含有了链路id:
  studyorder
  上边几张截图可以看到下边这个调用链通过链路id可以完美的串起来了!
  至此,垮服务进行链路追踪完成!
  最后我将TraceIdUtil代码粘出来。packagecom。xzll。common。util;importcom。xzll。common。constant。StudyConstant;importjodd。util。StringUtil;importorg。apache。commons。lang3。StringUtils;importorg。slf4j。MDC;importjavax。servlet。http。HttpServletRequest;importjava。util。UUID;Author:hzzDate:202322615:19:36Description:publicclassTraceIdUtil{publicstaticfinalStringREGEX;从header和参数中获取traceId从网关传入数据paramrequestHttpServletRequestreturntraceIdpublicstaticStringgetTraceIdByRequest(HttpServletRequestrequest){StringtraceIdrequest。getParameter(StudyConstant。TraceConstant。TRACEID);if(StringUtils。isBlank(traceId)){traceIdrequest。getHeader(StudyConstant。TraceConstant。TRACEID);}returntraceId;}publicstaticStringgetTraceIdByLocal(){returnMDC。get(StudyConstant。TraceConstant。TRACEID);}传递traceId至MDCparamtraceId链路idpublicstaticvoidsetTraceId(StringtraceId){if(StringUtil。isNotBlank(traceId)){MDC。put(StudyConstant。TraceConstant。TRACEID,traceId);}}构建traceIdreturnpublicstaticStringbuildTraceId(){returnUUID。randomUUID()。toString()。replaceAll(REGEX,StringUtils。EMPTY);}清理traceIdpublicstaticvoidcleanTraceId(){MDC。clear();}}5、结尾
  由于我的项目中没有使用motan和dubbo所以无法演示rpc调用,但是我接触的项目有,这里不粘完整代码了,直接给出答案,在motan中也是支持配置过滤器的,在调用前,通过过滤器往request的attachment中设置traceId来将traceId传递给服务提供者,在服务提供者中也可以添加过滤器,此时从attachment属性中取出traceId通过MDC。put(key,value)来将traceId设置进本服务。达到链路传递的效果。同理dubbo框架也是类似做法。
  ps:由于时间原因,我的四个服务中,有多处冗余代码,比如过滤器,这些类似公共的都可以抽出来搞一个starter或者是导入bean等等方式,来减少重复代码。由于本文重点讲述使用方面的东西,原理方面的不做过多解散,关于ttl和log4j2ttlthreadcontextmap,可以看看github的资料,已经讲的超级细了。另外如果像知道更多细节,需要从slf4j和log4j2的源码入手,相信会有更多的收获。你会发现每天使用的slf4jlog4j2里边居然这么多值的学习的地方!!!

正式任命!32岁丁宁亮相新岗位助力国乒,刘国梁欣慰,世乒赛稳北京时间8月27日,国乒一线队主力正在成都积极备战世乒赛,本次成都世乒赛团体比赛将会在9月份于成都开赛,目前还有一个月左右的备战时间,本次比赛作为巴黎奥运周期的第一次世界顶级的……足坛上帝逼王之王兹拉坦伊布拉希莫维奇你第一次走进阿贾克斯时,与队友说的一句话是是什么?我是伊布,你TM又是谁?我就像默罕默德阿里,每次每次他说会在第四回合将对方击倒,都不会食言。没有我的世界杯不值得一……拒绝29队,加盟湖人坐稳先发!紫金军该庆幸和你签下的是两年合闹了半天,等了接近两个月,最终杜兰特和篮网重新达成一致,回归篮网,这也就是意味着,篮网不会选择兜售欧文,他们没理由再次惹怒杜兰特,如此一来,篮网也就正式确立了下赛季以三巨头为核……殷桃中餐厅造型,每一套都看似随性,却在不经意间透着女人味殷桃五官柔和,眼里带魅,她的美远非当下网红风能比拟,一个充满东方古典美的女星。《中餐厅》作为一款慢综艺节目,从其开播到现在就受到了广大观众的喜欢,几乎老少皆宜,在慢综艺节……许昌市新增一家A股上市公司近日,河南硅烷科技发展股份有限公司(以下简称硅烷科技)发布公告,确定其A股IPO网下、网上申购时间为2022年9月19日,发行价格为5。66元股,初始发行股份数量为7823万股……爱情公寓播出的13年有人嫁入豪门,有人被捕,有人离世我们的童年回忆《爱情公寓》距今已播出13年了。在《爱情公寓》热播之时,收视率秒杀各大连续剧。尤其是《爱情公寓4》开播后,首天播放量就突破了两亿。由此可见,这种……把养老保险缴费档次由60提高至100会有多大作用?算一算今天有朋友咨询表示,其家人按照灵活就业人员保险参保年限较短,已缴费13年。男性未来60岁退休时,预计缴费年限也就是27年。在缴费年限少的情况下,想领更高的养老金,可能就只……孩子的科学锻炼,需要家长的高质量陪伴(特别家教1081期)为了在特殊时期为家长提供特别的家庭教育指导,全国妇联推出了特殊时期特别家教微信栏目,家长可以通过家庭教育微课学习家庭教育知识。孩子的科学锻炼,需要家长的高质量陪伴(特别家教10……半生烟雨半生忧,一念心轻万事休鱼离开了水会死掉,但是水离开了鱼会变得更清澈。不要以为你放不下的人也同样放不下你,人生其实没有什么放不下的,不要把自己搞得太累,也不要为了睡得很香的人而失眠。这世上最憋屈……鼻子上挤出一粒粒的白色的东西是什么?很多人喜欢挤鼻子,能挤出一些白色的东西,像这个样子:有些又黄又硬:用夹子也可以夹出一条条的东西:夹出来一颗颗的油脂颗粒这些油脂粒学名叫开放性粉刺,也就是……入睡困难?多梦易醒?用这3个小方法,快速改善失眠大家好,我是素问宫娜医生。夏季最容易引起睡眠障碍,原因多归于心、脾两脏。夏季属火,在脏为心,心火旺则人烦热,燥扰不宁,心神不安。长夏属土,在脏为脾,脾土湿,运化失司……隐入尘烟语录尘归于尘,土归于土。一切的存在都是命数隐入尘烟还是草编的驴好,不吃草,也不被人使唤。被风刮来刮去,麦子能说个啥?被飞过的麻雀啄食,麦子能说个啥?被自己家的驴啃了,麦……
给自己一个音乐小空间丹拿新意境系列Emit10书架音箱其实丹拿属于那种不太需要多做介绍的HiFi品牌了,无论是深入人心的各款经典音箱产品,还是最近几年和其他数码品牌推出的优质联名合作款,都让主流消费者对这家单麦老牌大厂有所了解。不……极简生活(109)被夸了衰老的比较慢方法简单有人夸我了有一个多年的同事,异性的,很老实的一个人,今天吃饭时擦身而过,忽然说:你怎么不显老呀?!保养得这么到位!我第一反应是回应:真得吗?然后,我第二反应是……清朝有一个女子效仿花木兰,女扮男装去从军,结果很让人意外中国历史上帼不让须眉的女英雄不胜枚举,殷商时期的妇好是我们中国历史上第一位有史可查的女将军,为商王武丁征战四方;战国时期钟离春军政才能卓著,被齐王封后,留下有事钟无艳,无……小米11Pro连续四天霸榜,其它品牌羡慕吗?早在双十一开打之前业内就预测今年又是小米苹果唱二人转,其它品牌都是来陪榜的。因为最终的结果肯定是小米获得销量冠军,苹果获得销售额冠军。从现在的趋势看这个预测非常的准确,特别是对……这才是凉拌芹菜花生正确做法,早上配粥,晚上下酒,太香了大家好,这里是【刘一手美食】,关注老刘,每天分享一道好吃又实用的家常菜1、花生长于滋养补益,可延年益寿,所以民间又称长生果,并且和黄豆一样被誉为植物肉、素中之荤2、……十年了,娱乐圈依然没有比大S婚礼更搞笑的瓜来源丨小风小浪id丨vistawenyu大家好,我是马英俊,乌蝇哥手下最擅长吐槽男人的美男子。大S和汪小菲的婚变疑云演到这儿,大概率是雷声大雨点小了。最……如何看待俞敏洪直播带货大家好,我是羽翼课堂创始人Benny,作为从事教育营销行业17年的老兵,我总在想,新东方曾经作为教育行业龙头,因为教育政策的改革,不得已停止中小学班课业务,同时伴随着大量……网赚APP之死那是一个魔幻的年代。点开APP,目光所及皆是捡钱的机会,走路能赚钱,喝水能赚钱,甚至睡觉都能赚钱。每天只需简单地做任务、领金币即可兑现,一天赚杯奶茶钱不成问题。虽然……励志语录努力不是为了跟别人比是为了有更好的自己励志语录Nov9(1)努力,有意义!世界上唯一可以不劳而获的就是贫穷,唯一可以无中生有的是梦想,没有哪件事是不动手就可以实现的。(2)……前三季度31省份房地产开发投资数据出炉!17地增速超全国中新经纬10月22日电(张猛)日前,国家统计局发布前三季度31省份房地产开发投资数据。中新经纬梳理发现,前三季度,广东、江苏房地产开发投资均超万亿,17省份增速超全国。资……中国篮坛帅帅杨鸣她照顾着这个家非常辛苦,我很感谢她父母的影响是一个人成长的动力和助力,只有父母竭力支持,孩子才会更有斗志和激情,那些走向成功的舞台,在事业上取得成就的孩子,都有一对扶着他们,鼓舞着他们走的父母。人生有时一帆风顺……周琦被排挤了?队友特意站出朝他竖中指,渴望融入球队却挑错方式对篮球有关注的球迷朋友们知道,在与新疆男篮闹僵之后,周琦成为CBA中最具争议的球员之一,然而这个与新疆男篮之间的矛盾并没有得到有效的调和,周琦还因此放弃了在国内的发展机会,选择……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网