专栏电商日志财经减肥爱情
投稿投诉
爱情常识
搭配分娩
减肥两性
孕期塑形
财经教案
论文美文
日志体育
养生学堂
电商科学
头戴业界
专栏星座
用品音乐

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

  前言:
  排查问题是程序员的基本能力也是必须要会的,在开发环境,我们可以debug,但是一旦到了服务器上,就很难debug了,最有效的方式就是通过日志揪出bug,而一次请求的日志如果没有一个唯一的链路标识(我们下边称他为traceId),单靠程序员人工分析的话,费时费力,尤其是请求量高频的接口,更是雪上加霜,排查问题效率大打折扣,作为程序员,低效的方式是忍不了的!!! 本文我将用一次实战演练,来演示常用框架/中间件/多服务 之间如何传递traceId
  本文大概有如下内容:链路追踪简述和自实现思路单服务内如何实现链路id的输出垮服务调用时,实现链路id传递的各种方式 ( 包含http(openFeign,httpClient restTemplate)、rpc(motan、 dubbo)、mq(RocketMq) )异步调用时,如何解决log4j2自带的ThreadLocal丢失链路id问题起4个服务,进行调用,观察链路追踪的效果1、链路追踪实现简述
  所谓链路追踪,就是为了 把整个请求链路从头到尾串起来,不管调用链路有多深,多复杂,只要将一次链路完整无误的串联起来,就是合格的链路追踪功能。
  业界不乏skywalking zipkin 等等链路追踪方面牛逼的框架,但是我们为了更轻量更灵活可控同时也是抱着学习心态,所以自己来实现链路追踪。
  首先想实现链路追踪,有两点是核心,实现了这两点,问题也就不大了traceId 如何在本地 (或者说单服务内) 传递?在分布式环境中,traceId如何跨服务/中间件 传递?2、单体服务 的链路追踪
  首先我们先讲下单服务内的链路传递
  作为java开发,最常用的就是slf4j来实现打印日志的功能(但是slf4j并不没有实现逻辑,因为 slf4j整个的定义是一个日志门面,该包中并无具体的实现,实现都是在 比如:logback log4j2等等日志实现框架中)
  slf4j的门面不仅给我们提供了打印日志的功能,还提供了 org.slf4j.MDC 类, 该类的作用大概如下:
  映射诊断上下文(Mapped Diagnostic Context,简称MDC)是一种工具,用于区分不同来源的交错日志输出。当服务器几乎同时处理多个客户机时,日志输出通常是交错的。 MDC是基于每个线程进行管理的 。
  上边这个官方解释,最重要的一句话就是 MDC是基于每个线程进行管理的
  上边这个太官方,说下我个人对MDC的理解:他是一个日志的扩展,扩展的目的就是给 每个线程 输出的日志打上一个标记(一个线程只有一个标记且不能重复一般使用uuid即可),这样我们在查看日志时候,就可以根据这个标记来区分调用链路了
  ps: 当然了, 光往MDC中设置当前线程的链路id也是不行的你还得在log4j2.xml文件中,设置占位符,这样最终输出的日志才会带链路信息。如何设置会在 2.1节 有讲。
  从代码层面看下 MDC做了啥:MDC类中通过一个 MDCAdapter实例调用MDCAdapter的put get remove clear等方法。而put get remove clear 具体的实现是不同厂商来做的 比如我常用的log4j2包中就实现了 MDCAdapter 接口,实现在 org.apache.logging.slf4j.Log4jMDCAdapter类中Log4jMDCAdapter类中使用了一个 ThreadContext来执行put get remove clear逻辑,而ThreadContext 中又是一个 ThreadContextMap ThreadContextMap是一个接口 ,有不同的实现其中默认的是 DefaultThreadContextMap 该类中维护了一个 ThreadLocal> localMap 类型的成员变量 其中map中的k,v 就是你调用MDC.put(k,v); 时传入的k v最终你调用MDC.put(k,v);时候传入的k和v会被放倒 localMap这个ThreadLocal中去。在你给某个线程设置了key,value后 ,log4j2在打印日志时候,将会去log4j2.xml文件中找 占位符等于 key的,然后用value把占位符替换,从而打印输出了value(也就是traceId),至此实现了单服务内的链路追踪。
  把代码流程梳理下大概如下:MDC(类) 	MDCAdapter(接口) 		Log4jMDCAdapter(实现类之一) 			ThreadContext(类) 				ThreadContextMap(接口) 					DefaultThreadContextMap                                                 //(MDC的几个操作都是往这个里边存取数据) 						ThreadLocal> localMap;2.1、如何使用MDC来输出(单服务内的)链路信息?往MDC中设置key value在log4j2.xml 中设置占位符(注意:占位符名称要和MDC中的key一致)接着我们简单看下效果:(可以看到,每一个线程,都有一个唯一的链路id )
  上边我们说过 MDC最终设置的key,value 是放到 DefaultThreadContextMap类中的 ThreadLocal> localMap 这个里边的,也就是说 里边的Map是某个线程的本地副本(不懂线程本地副本的可以回顾下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 的话我用过kafka rabbitmq rocketmq
  下边,我搭建了四个微服务,分别是网关服务 study-gateway,
  微服务消费服务 study-consumer 后台管理服务 study-admin 订单服务 study-order
  在这里我的 调用链路 大概如下图所示:
  如上图所示的链路,该次请求会经过 网关,openFeign,异步调用,httpClient调用,mq 这几个组件,而如何在这几个组件 调用前传递traceId 和 调用后设置traceId 成为垮服务进行链路追踪的关键。 ps: dubbo和motan的我们就不演示了,会直接给出传递方案。
  铺垫了那么多,我们下边直接了当点不再啰嗦!3.1、在请求最前边(study-gateway)添加过滤器
  在网关过滤器中生成并设置traceId到MDC(此刻网关服务的日志中将会打印traceId) ,同时通过header传递到下游服务。
  简单看下网关配置:
  3.2、在路由目标 服务(study-consumer)中添加过滤器(为了从请求头获取链路id)
  3.3、在study-consumer服务添加feign拦截器(因为consumer要通过feign调用study-admin服务)
  3.4、在study-admin中添加过滤器(为了从请求头获取链路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 和 ttl-thread-context-map ,ttl-thread-context-map 可以解决线程间的传递丢失问题(他内部也是使用的TransmittableThreadLocal也就是ttl来存储MDC 的key和value)。ttl-thread-context-map 依赖java的spi机制,依靠spi机制,让log4j2 在启动加载时,用log4j2.component.properties中 log4j2.threadContextMap这个key 对应的 value作为ThreadContextMap 接口的实现(也就是替换掉DefaultThreadContextMap这个默认实现),从而实现了线程间传递的功能。对ttl和ttl-thread-context-map不熟悉的可以跳的github 讲的很详细很清楚。
  TtlThreadContextMap内部使用TransmittableThreadLocal来存储MDC的key,value
  spi配置:
  而我们使用阿里这个工具也很简单首先maven引入(注意版本 不清楚的去maven库看看)    com.alibaba    transmittable-thread-local    2.14.2      com.alibaba    log4j2-ttl-thread-context-map                     org.apache.logging.log4j          log4j-api               1.4.0    runtime 
  在引入这个后,我什么也没配,如果我使用jdk的 ThreadPoolExecutor 或者spring的 ThreadPoolTaskExecutor,都是可以实现链路传递的,但是我使用 CompletableFuture的话,第一次请求的链路是对的,当第二次请求时候,CompletableFuture线程池中的打印链路信息还是第一次的,这个问题github上有说明,作者让使用javaagent来解决,果然在我配置javaagent后,CompletableFuture 的链路信息每次都是正确的。在idea 的 VM options 中配置:
  -javaagent:/Users/hzz/.m2/repository/com/alibaba/transmittable-thread-local/2.14.2/transmittable-thread-local-2.14.2.jar
  即可解决 CompletableFuture的链路id传递问题(这里我们最好是agent这样对代码无侵入,如果你使用TtlRunable修饰Runable的话 对代码侵入比较多,维护起来也比较麻烦)
  4、跨服务+异步 链路追踪效果演示
  最后我们将四个服务启动,看下整体效果:
  postman调用:
  study-gateway
  study-consumer
  study-admin
  tcpdump抓包看看第三方的请求头中是否含有了链路id:
  study-order
  上边几张截图可以看到下边这个调用链 通过链路id 可以完美的串起来了!
  至此,垮服务进行链路追踪完成!
  最后我将TraceIdUtil代码粘出来。package com.xzll.common.util;  import com.xzll.common.constant.StudyConstant; import jodd.util.StringUtil; import org.apache.commons.lang3.StringUtils; import org.slf4j.MDC;  import javax.servlet.http.HttpServletRequest; import java.util.UUID;  /**  * @Author: hzz  * @Date: 2023/2/26 15:19:36  * @Description:  */ public class TraceIdUtil {     public static final String REGEX = "-";     /**     * 从header和参数中获取traceId     * 从网关传入数据     *     * @param request  HttpServletRequest     * @return traceId     */    public static String getTraceIdByRequest(HttpServletRequest request) {       String traceId = request.getParameter(StudyConstant.TraceConstant.TRACE_ID);       if (StringUtils.isBlank(traceId)) {          traceId = request.getHeader(StudyConstant.TraceConstant.TRACE_ID);       }       return traceId;    }     public static String getTraceIdByLocal() {       return MDC.get(StudyConstant.TraceConstant.TRACE_ID);    }     /**     * 传递traceId至MDC     *     * @param traceId  链路id     */    public static void setTraceId(String traceId) {       if (StringUtil.isNotBlank(traceId)) {          MDC.put(StudyConstant.TraceConstant.TRACE_ID, traceId);       }    }     /**     * 构建traceId     * @return     */    public static String buildTraceId() {       return UUID.randomUUID().toString().replaceAll(REGEX, StringUtils.EMPTY);    }     /**     * 清理traceId     */    public static void cleanTraceId() {       MDC.clear();    } }5、结尾
  由于我的项目中没有使用motan和dubbo所以无法演示rpc调用,但是我接触的项目有,这里不粘完整代码了 ,直接给出答案,在motan中也是支持配置过滤器的 ,在调用前,通过过滤器往request的attachment中设置traceId来将traceId传递给服务提供者,在服务提供者中也可以添加过滤器,此时从attachment属性中取出traceId通过MDC.put(key,value)来将traceId设置进本服务。达到链路传递的效果。 同理dubbo框架也是类似做法。
  ps:由于时间原因,我的四个服务中,有多处冗余代码,比如过滤器,这些类似公共的都可以抽出来搞一个starter或者是导入bean等等方式,来减少重复代码。由于本文重点讲述使用方面的东西,原理方面的不做过多解散,关于ttl和 log4j2-ttl-thread-context-map ,可以看看github的资料,已经讲的超级细了。另外如果像知道更多细节,需要从slf4j和log4j2的源码入手,相信会有更多的收获。你会发现每天使用的slf4j log4j2 里边居然这么多值的学习的地方!!!

换个方式赏月中国科学家展现嫦娥五号月壤微观之美中国科学家与艺术家合作,在中秋节开启全新的赏月模式通过高科技手段,让人们领略细如粉末的月尘中隐藏的美丽微观世界。这是7月7日拍摄的嫦娥五号月壤中的一颗玄武岩碎片的正交偏光显微照片。统治了地球1亿7千万年,恐龙却没有进化成高等智慧生物,为何?自寒武纪生物大爆发之后,日月轮转沧海桑田,地球食物链顶端的霸主更换了一代又一代,在这场战役当中,有靠着运气登顶的食素霸主水龙兽,也有一直在努力但是永远都是老二的鲨鱼。水龙兽时代的构儿童趣味折纸教程,一起来折这些蔬菜水果吧,建议收藏今天给大家带来蔬果折纸教程合集,其中有草莓,香蕉,樱桃,桃子,茄子,蘑菇,柿子折纸,非常适合小朋友们学习,可以收藏起来慢慢学哦!如果喜欢我的教程,别忘了点击关注哦,关注小曦,从此折无球可打?从最可能下家到无意签约,为什么绿凯不愿签下安东尼呢17年休赛期,安东尼保罗乔治威少三大巨星联手,企图在西部季后赛大干一场。可尽管这一年他们的纸面阵容足够强大,常规赛战绩也勉强挤进西部前四,可最终却还是在季后赛翻船了。18年季后赛首火箭掘金2换2交易!湖人爵士三套交易方案曝出!博扬下家三选一?湖人与爵士商讨的三套交易方案!爵士向湖人提交了两份有关威少的交易报价!方案一爵士用康利克拉克森和比斯利,交易威少和2个首轮签。方案二爵士用康利博扬和盖伊,交易威少和2个首轮。而湖人11,范志毅惊出一身冷汗,国足没让人失望,功臣曾闪耀亚冠U20国足的比赛,着实把范志毅惊出一身冷汗。所幸的是这次国足没有让球迷失望,最后的补时阶段依靠2个点球,以31战胜了缅甸。2个点球都是由一人制造,他曾帮助山东泰山闪耀亚冠。北京时间(猜想)宇宙若是三维空间,我们则生活在伪三维空间。设立宇宙整体是三维,闭合面为360度。我们生活的区域空间是小于闭合面360度的类似漏斗。用一张纸描述,把纸切成一个圆,在吧纸切成两半穿过中骁龙8性能机大集结看看这四款手机究竟哪个更好用手机中国导购最近各家手机厂商们都发了一些性能非常强劲但价格相对实惠的机型,但这也让许多朋友在选择的时候有些为难了。一加AcePro作为性能机,在日常使用和游戏方面确实能胜任,但拍照3千元价位女生用来拍照的手机推荐头条创作挑战赛1。motoX30Pro8128起步价3499依旧是熟悉的骁龙8,性能这块不用担心。屏幕是1080P,144赫兹国产维新诺屏幕。4600毫安时电池搭配125瓦超级快充三星旗舰GalaxyS22大跳水降价到手仅需3999文万怡飘责编吕东兴总编唐迪三星GalaxyS22在京东商城原先需要4199,现在再次降价优惠200到手3999,优惠活动不等人选择三星GalaxyS22的八大购机理由全新一代骁龙8医疗AI浪潮国产走高端,外企下基层导读为了抢夺丢掉的市场份额,跨国企业主动降级产品功能向县域推进。不过,这个过程仍然面临很多挑战一方面,企业是否有相应的下沉渠道。第二,是否有合适的产品向下推进。这要求企业必须要对县
we确认垫底,V5竟然还有可能获得复活甲?却要看别人脸色目前LPL的局势,依旧存在着很多不确定的因素,没到最后一刻,谁都不知道现在到底是什么样的。就像近期TES跟V5的比赛当中,很多人都认为TES会拿下胜利,结果最终是V5直接20战胜了四两拨千斤!解析白云山大健康板块王老吉牵手王者荣耀的深思熟虑今夏,在王老吉和王者荣耀联合举办得不怕上火做夏日王者跨界营销发布会上,双方对外发布了2套全新联名ip定制罐王老吉x王者荣耀夏日王者(立绘系列)和王老吉x王者荣耀夏日王者(突破系列)这些菜吃了对人体的好处你知道吗?文鹤轩伯毅我们时常听说多吃蔬菜水果,多吃蔬菜水果对人有益,那么我们应该吃什么蔬菜水果呢?吃了这些蔬菜水果到底有什么好处呢?多吃就一定对人体好吗?蔬菜含有丰富的纤维和维生素,能起到改1味常见的药来泡水,能通气,消淤堵,还能化痰散结节身体淤堵,肝气郁结,痰瘀互结这些问题在现在很多人的身上越来越常见了。今天给大家分享一味中药,哪味中药呢?这味中药是可以通气消淤化痰消积食的中药,帮助我们清理身体瘀和堵的一味中药!还长期喝电热水壶烧的水,可能会给身体带来什么危害?水是万物之源,是生产之根,生物之本,其作用至关重要,维持体内生理健康,运转,运输血液,人可以三天不吃饭,但做不到三天不喝水。,一旦缺水,各项机能器官遭受打击重大,免疫力下降,病毒细长期吃六味地黄丸的人,身体怎样了?医生4个变化或等着他我国药物有中药和西药之分,对于这两种药物来说,各有各的好处,一般情况中药是由草药为原料经过简单加工制作而成的药物,其最大程度保留了草药的药效,像我们日常中比较常见的六味地黄丸这就属更年期为何要补钙和维生素?更年期要不要补充钙和维生素?答案是需要的。骨质疏松是女性进入中老年后常见的一种病症。防治的重要途径就是保持足够的钙摄入。女性体内的钙质从40岁前后开始就支出大于收入了,因此,一般从天气转凉,建议少吃2瓜,多吃3肉,补足营养,秋冬身体不怕冷人不管走到哪一步,总得找点乐子,想一点办法。是的,美食就是我找的乐子,想的办法!小谈食刻和你一起认真对待吃下的食物!导读不知道大家有没有感觉到,最近凉快了很多,看来古人说的话,确实三餐这么吃现代人已不再为吃饱发愁,而是开始关注如何吃好。1食物多样,谷类为主,粗细搭配。2多吃蔬菜水果和薯类。3每天吃奶类豆类或其制品。4常吃适量的鱼禽蛋和瘦肉。5减少烹调油用量,膳食清淡少赛季报销!绿军迎来噩耗,打勇士的法宝没有了要说上赛季表现最令人惊艳的队伍,那绝对非绿军莫属了!他们在上半个赛季一直徘徊东部第8第9的情况下,下半个赛季迎来了大爆发,排名一路上升,直接杀到了东部第2位,更是在季后赛中接连淘汰NBA三消息哈登撩球迷女友,西蒙斯取消婚约,勇士或退役5人球衣近日NBA大部分的球星都在积极的训练,距离新赛季的到来还有一个多月的时间,球员们必须加强训练,培养队员与队员之间的默契,才能够在新的赛季朝着更高的目标前进。北京时间的9月2日,有消
友情链接:快好知快生活快百科快传网中准网文好找聚热点快软件