转载

dubbo微服务之间流水号的隐式传递

做开发的人都知道流水号这个概念,有业务流水号,交易流水号,请求流水号等等,各种流水号。

无论是啥名字的流水号,目的都是为了在某个维度,让一系列动作有一个唯一的标识。后面方便查日志,查问题。系统间交互可以防止扯皮。

比如交易流水号,唯一标识一笔交易,这边所说的交易可以是无业务含义的请求,也可以是账务交易。如果是标识无业务含义的请求。一般会在交易开始时生成一个32位或者64位的唯一编码。这个编码会在交易的纵向流程中传递,可以用参数传递,也可以放到上下文 Context 中,还可以放到 ThreadLocal 中。

在单体应用 Singleton Application 中,对流水号的处理比较简单,无论是参数传递还是 ThreadLocal 都没有什么难度。

在微服务环境下,一笔交易会经过多次微服务之间的交互,并且需要把流水号传递下去,它的传递链路可能是这样的(服务A) --> (服务B --> )(服务C) --> (服务D) -->(服务E),使用 dubbo 作为 RPC 框架。有2种传递流水号的方式(当然这只是我自己觉得,可能还有更好的处理方式),下面详细说说。

参数传递法

这种方式很容易理解,就是把流水号作为接口的一个参数 (String jnl) 。这种方式结构简单,实现起来也很简单。但是有诸多问题。

1.几乎所有暴露的接口服务都会有这么个参数。不好看。

2.交易流水号,实际与业务本身相关性比较低。微服务之间的参数应该尽量与业务强相关,而不是占用单独的参数位置来传递与业务无关的东西。

3.这样的方式,维护起来麻烦,写啥服务都得加这么个参数,移植性也很差。

我个人不喜欢这样的做法。

利用dubbo的Attachment做隐式传递

还是模拟刚刚的传递过程,把一个32位的Jnl在服务间传递。(服务A) --> (服务B --> )(服务C)

dubbo微服务之间流水号的隐式传递

1.A服务是入口,生成32位流水号 Jnl

2.A服务把Jnl放到 ThreadLocal

3.在调用B系统之前,把 JnlThreadLocal 取出并且放入 Invocation Attachment 中(这个过程由 Dubbo Filter 做)

4.B系统接收 Invocation 信息,并且反序列化。从 Attacment 中取出 Jnl 放入 ThreadLocal 中(这个过程由 Dubbo Filter 做)

5.B系统调用C系统时重复刚刚的几步操作。

简单理解:在运行过程中, Jnl 始终从一个系统的 ThreadLocal 转移到另一个系统的 ThreadLocal 中。转移过程通过取和放都通过 Dubbo Filter 来完成。

这个过程涉及到3个角色。

ThreadLocal

Consumer 一方的 Filter ,作用是读取 ThreadLocal 中的Jnl放入 Invocation Attachment

Privoder 一方 Filter ,作用是读取 Invocation Attachment 中的Jnl放入 ThreadLocal

下面看代码实现

public class AccessJnlUtil {

   // 获取
   public static String getAccessJnl() {
      return (String) RpcContext.getContext().get("AccessJnl");
   }
	
   // 放入
   public static void setAccessJnl(String accessJnl) {
      RpcContext.getContext().set("AccessJnl", accessJnl);
   }

   public static void deleteAccessJnl() {
      RpcContext.getContext().remove("AccessJnl");
   }
}
复制代码

AccessJnlUtil 是用来对AccessJnl做put和set操作的。利用了 Dubbo RpcContextRpcContext 本身就是 ThreadLocal 的封装,不熟悉的话可以追踪下源码,这边不再赘述。

public class AccessJnlConsumerFilter implements Filter {

	@Override
	public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
		invocation.getAttachments().put("AccessJnl", AccessJnlUtil.getAccessJnl());
		return invoker.invoke(invocation);
	}
}
复制代码

AccessJnlConsumerFilter 是消费方的 Filter ,在实际调用 invoke 之前,把 AccessJnlThreadLocal 中取出,放到 Invocation Attachment 中。

public class AccessJnlProviderFilter implements Filter {

   @Override
   public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
      AccessJnlUtil.setAccessJnl(RpcContext.getContext().getAttachment("AccessJnl"));
      return invoker.invoke(invocation);
   }
}
复制代码

AccessJnlProviderFilter 是消费方的 Filter ,在实际调用 invoke 之前,把 AccessJnlInvocation Attachment 中取出,放到 ThreadLocal 中。

然后就是把两个 Filter 配置到系统中,具体配置方式可以参考 Dubbo文档

总结

利用 dubboAttachment 做隐式传递。我个人比较喜欢这样的处理方式。相对优雅。

原文  https://juejin.im/post/5eaeb68be51d454de344c4cf
正文到此结束
Loading...