以vertx为基础的微服务构架下实现requestId的解决思路
2016-08-31 00:00
211 查看
摘要: 一个web请求对应一个线程情况下,利用log4j的MDC功能,将requestId绑定在线程变量下,每次log4j记录日志时,从线程变量上获取requestId,并记录到日志文件中。
一个web请求涉及多个线程时,在vertx下,处理过程可以理解为一个线程处理web数据,并将处理后的结果传递给下一个线程,然后重复这个过程直到获取最终的数据。只要在将数据传递给下一个线程的过程中将requestId透明地传给下一个处理线程,就可以实现类型单线程下的requestId记录。
本文主要是介绍lambda表示式如何实现requestId的传递。至于跨网络的数据传递,只能重新重新规定传递的数据结构。
于是准备开始为日志附加requestId,但是发现这个功能不是很简单。
下面的讨论中也不会涉及如何生成一个requestId,会把精力集中在requestId如何在日志中记录,同时如何在多个线程中传递。
在传统的J2EE框架下,这个问题很简单,使用log4j的MDC功能就行。简单来说就是每次一个web请求到来后,生成一个requestId并且绑定到线程上,因为J2EE下对这次请求的处理都是在同一个线程中,所以保证在请求过程中可以随时获取到requestId,可以很容易实现requestId的记录。
举个例子来说:
在web请求入口中:
在log4j的配置文件中,调整日志的输出模式
最后在一个请求中输出日志
发现一个requestId出现了日志中(假设这次请求的requestId值为XXXXX),日志输出如下
这种设计下对开发人员的开发影响基本为0,性能消耗极小。
但是使用vert.x后,一次web请求中,已经涉及很多个线程进行处理,线程变量已经无法覆盖整个过程了。此时好像也会遇到线程并发的问题。
虽然一个web请求涉及了多个线程,但是一个好消息是在整个请求过程requestId是不会发生改变,这保证了线程安全问题。同时也符合lambda表达式的一个特性,这个特性在是解决方案中会用到。
vertx的一个web请求过程中,多个线程执行过程中是通过数据作为连接的,只要能够在线程切换过程中,将requestId传递过去并将其放到MDC中,也就能实现log4j记录时,日志出现requestId。
其实问题的本质就是如何在实现线程切换执行过程中保证requestId的传递?这里说的线程切换不是操作系统下的线程切换,一个线程拿到上一个线程对web请求数据的处理的结果定义为一次线程切换,通常情况下都是通过lambda表达式实现。
这里lambda表达式内可以获取lambda表达式定义时的外部变量,该变量在lambda内会变成final类型,这个函数闭包的一个特性,这个特性某种意义上使得lambda表达式获得了额外的参数。
定义一个封装方法实现requestId的传递。
经过包装的代码就能输出requestId了
对于初入门的开发人员可能无法识别那些lambda表达式会导致线程切换,手动通过对lambda增加wrap函数也比较烦,可以考虑重新实现会导致线程切换的接口,接口中使用wrap方法。
对于通过网络传递数据的接口,只能在传递数据中增加requestId属性,传递到另外的处理线程中。实现的方式也可以考虑重新实现接口,在发送端增加requestId属性,在接收端解析requestId属性,并将其放到MDC中。
通过这种方式可以实现类似J2EE下单线程MDC的requestId的功效。
一个web请求涉及多个线程时,在vertx下,处理过程可以理解为一个线程处理web数据,并将处理后的结果传递给下一个线程,然后重复这个过程直到获取最终的数据。只要在将数据传递给下一个线程的过程中将requestId透明地传给下一个处理线程,就可以实现类型单线程下的requestId记录。
本文主要是介绍lambda表示式如何实现requestId的传递。至于跨网络的数据传递,只能重新重新规定传递的数据结构。
##问题背景
最近认识到requestId在微服务架构排错上的重要性,因为上线后,发现没有requestId的日志很难跟踪到用户的一次行为,同时跨服务间的调用bug很难跟踪和排查。于是准备开始为日志附加requestId,但是发现这个功能不是很简单。
##问题本质
假设系统的基础日志框架是log4j。对于采用slj4j+log4j的日志框架,因为slj4j是一个外观模式,是对log4j的一次封装,这种日志框架也适合以下讨论。下面的讨论中也不会涉及如何生成一个requestId,会把精力集中在requestId如何在日志中记录,同时如何在多个线程中传递。
在传统的J2EE框架下,这个问题很简单,使用log4j的MDC功能就行。简单来说就是每次一个web请求到来后,生成一个requestId并且绑定到线程上,因为J2EE下对这次请求的处理都是在同一个线程中,所以保证在请求过程中可以随时获取到requestId,可以很容易实现requestId的记录。
举个例子来说:
在web请求入口中:
String requestId=generateRequestId(); //生成requestId MDC.put("r",requestId); //log4j的功能,将requestId绑定到当前线程中。
在log4j的配置文件中,调整日志的输出模式
log4j.appender.stdout.layout.ConversionPattern=%X{r} %m %n //%X{r}表示输出MDC中绑定名r的线程变量值
最后在一个请求中输出日志
logger.info("test output")
发现一个requestId出现了日志中(假设这次请求的requestId值为XXXXX),日志输出如下
XXXXX test output
这种设计下对开发人员的开发影响基本为0,性能消耗极小。
但是使用vert.x后,一次web请求中,已经涉及很多个线程进行处理,线程变量已经无法覆盖整个过程了。此时好像也会遇到线程并发的问题。
虽然一个web请求涉及了多个线程,但是一个好消息是在整个请求过程requestId是不会发生改变,这保证了线程安全问题。同时也符合lambda表达式的一个特性,这个特性在是解决方案中会用到。
vertx的一个web请求过程中,多个线程执行过程中是通过数据作为连接的,只要能够在线程切换过程中,将requestId传递过去并将其放到MDC中,也就能实现log4j记录时,日志出现requestId。
其实问题的本质就是如何在实现线程切换执行过程中保证requestId的传递?这里说的线程切换不是操作系统下的线程切换,一个线程拿到上一个线程对web请求数据的处理的结果定义为一次线程切换,通常情况下都是通过lambda表达式实现。
##解决思路
实现过程中涉及一个上面说所的lambda表达式的特性,这里先简单介绍(假设读者已经基本了解lambda表达式是什么了),以vertx的eventbus.send为String outsideValue="this is a outside lambda value"; eventbus.send("test.requestId", res->{ logger.info(outsideValue); // 这里会输出 this is a outside lambda value });
这里lambda表达式内可以获取lambda表达式定义时的外部变量,该变量在lambda内会变成final类型,这个函数闭包的一个特性,这个特性某种意义上使得lambda表达式获得了额外的参数。
定义一个封装方法实现requestId的传递。
public Handler<AsyncResult<T>> <T> wrap(Handler<AsyncResult<T>> source){ String requestId=MDC.get("r"); // 获取当前线程的requestId,此时作为额外的参数 return res->{ MDC.put("r",requestId); //设置执行线程的requestId线程变量,source执行中可以获取该值 source.handler(res); } }
经过包装的代码就能输出requestId了
String outsideValue="this is a outside lambda value"; eventbus.send("test.requestId", wrap(res->{ logger.info(outsideValue); // 这里会输出XXXXX this is a outside lambda value }));
对于初入门的开发人员可能无法识别那些lambda表达式会导致线程切换,手动通过对lambda增加wrap函数也比较烦,可以考虑重新实现会导致线程切换的接口,接口中使用wrap方法。
对于通过网络传递数据的接口,只能在传递数据中增加requestId属性,传递到另外的处理线程中。实现的方式也可以考虑重新实现接口,在发送端增加requestId属性,在接收端解析requestId属性,并将其放到MDC中。
通过这种方式可以实现类似J2EE下单线程MDC的requestId的功效。
相关文章推荐
- [转载]在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分 .
- 实现音乐播放器后台Service服务一直存在的解决思路
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分(转)
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 实现音乐播放器后台Service服务一直存在的解决思路
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分(来源:http://blog.csdn.net/yangjundeng/archive/2005/03/17/321920.aspx)
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- C#中使用异步Socket编程实现TCP网络服务的CS的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)----基础类库部分
- C#使用异步Socket实现TCP网络服务的CS的通讯构架(二)使用方法
- php生成SessionID和图片校验码的思路和实现
- 问题解决: Attribute value request.getParameter("id") is quoted with " which must be escaped when used within the value
- 服务基础构架和待配信息介绍
- npkcrypt 服务启动失败解决方法;事件来源: Service Control Manager;事件 ID: 7000
- 在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架
- 转贴(电脑报):Windows Server 2003网络服务实现与管理基础(图略)