采坑经验:Dubbo 2.6.x版本中隐式参数attachment的错误使用方式,稍不注意就会出现生产事故

avengerEug

前言

  • 接触dubbo分布式框架的开发也有一段时间了,其中为了解决项目中遇到的一些杂症,还特意学习了一下Dubbo服务暴露和服务引入的一些源码知识点。最近在项目开发的过程中,有使用到了dubbo的隐式参数技术点,但发现了几个在使用上非常容易出错并且一出错就是生产事故的,现在记录一下。

一、了解Dubbo隐式参数之前先了解下Dubbo的上下文信息

  • 什么是Dubbo的上下文信息?这里总结下自己的理解:

    上下文中存放的是当前调用过程中所需的环境信息。所有的配置信息都将转换成URL的参数。RpcContext类就是Dubbo的上下文,但是它仅仅是一个ThreadLocal级别的临时状态记录器,当接收到RPC请求或发起RPC请求时,RpcContext的状态都会变化。比如:A调用B、B再调用C的情况下。B机器的RpcContext会有如下的情况发生:在B调用C之前,RpcContext记录的是A调B的信息,在B调用C之后,RpcContext记录的是B调C的信息。
  • 比如:我们想要获取到服务调用者的host相关信息,那么我们可以在服务提供者中获取当前消费此服务的消费者的host信息,其代码如下所示:

    // 获取调用方的host信息
    String serverIP = RpcContext.getContext().getRemoteHost();

二、Dubbo上下文携带的隐式参数attachment

  • 不知道各位在开发的时候,有没有遇到一种需要额外传递给下游服务的参数(比如标识当前用户请求的jwt、记录分布式系统全链路跟踪的全局traceId)。当有这方面的需求时,我们不可能修改方法的参数签名,这与业务耦合了。此时,可能就需要使用Dubbo的隐式参数attachment了。什么是attachment?可以把它认定为Dubbo协议中的一个扩展点。就有点类似于Http协议,我们可以自定义请求头信息。而在Dubbo中,我们也可以自定义RPC请求中的参数。
  • 举个例子:用户在执行下单这个业务,最终肯定会经过后台的订单服务、库存服务等服务。现在有个需求:在订单服务中,要明确知道这个订单是哪个用户创建的。在库存服务中,要明确知道这个商品最终是用户的哪个操作导致减少的。整个需求里面有个核心:就是要知道操作者是谁!假设项目用的是jwt技术来记录用户的状态,那么订单服务和库存服务就必须要知道这个jwt字符串,将jwt解码后,就能知道当前请求是由哪个用户发起的。在这样的一个场景中,使用dubbo的隐式参数可以达到上述的目的。实现的伪代码如下所示:

    RpcContext.getContext().setAttachment("jwt", "xxxxxxxxxxjwt字符串xxxxxxxx");
    // dubbo rpc 调用库存服务:减少库存
    warehouseService.decrement();
    // dubbo rpc 调用订单服务:创建订单
    orderService.create();

这里先总结下attachment在使用上的几个特点:

1、key名称不能以小驼峰命名,下游服务序列化后,会将key名称变成全小写(Dubbo 2.6.x版本,在2.7.x版本被修复了)

2、隐式参数设置后,仅在第一次RPC请求生效,后续的RPC请求将无法获取到隐式参数

因为attachment有上述的两个特点,因此我们很容易如下的两个错误:

易犯错误1易犯错误2
我们在warehouseService.decrement()的下游服务中能顺利的从attachment中获取jwt参数,而在orderService.create()的下游服务中已经无法顺利的从attachment中获取jwt参数了在本例中,添加到attachment中的key为jwt,是ok的。但如果我们把key设置成大驼峰的命名方式,比如:userJwt。在经过Dubbo的一系列处理后,在warehouseService.decrement()下游服务中的rpcContext对象中的attachment中的key已经变成了userjwt,已经无法获取到key为jwt的参数了。

三、筛选出最优的解决方案

  • 针对错误1,我们有三个实现方案,其对应的方案策略如下所示:

    方案优点缺点
    方案1:在每一次发起RPC之前,都手动执行一次RpcContext.getContext().setAttachment("jwt", "xxxxxxxxxxjwt字符串xxxxxxxx");代码能解决问题,但不是最优方案增加编码的复杂度和代码的重复度。
    方案2:使用spring的aop 的before机制,在执行rpc发起远程服务之前,先把jwt放入到attachment中能解决问题dubbo的远程调用对象本身就很重量,现在再添加一层代理,不利于定位问题。
    方案3:使用Dubbo的filter机制,在对指定远程服务添加一层filter,filter的逻辑就是将jwt放入到attachment中去比较好的一种解决方案,充分利用到了Dubbo框架自身提供的filter扩展。这也是比较通用的解决方案,全链路追踪的traceId也是这么玩的。(推荐代码阅读性不高,filter同aop一样,都是解耦的,不利于定位问题。
  • 针对错误2,在不对源码进行扩展的情况下,最简单的方式就是修改key的命名方式,这里可以使用两种方式:

    方式1参考Dubbo源码的org.apache.dubbo.common.constants.CommonConstants类中对添加到URL中的key的命令方式,多个单词用.做区分
    方式2单词与单词间使用自定义的符号做分隔,比如_,#等符号。这种方式也可以区分于key是自己添加的还是Dubbo框架自带的。

四、总结

  • 避坑指南:有涉及到隐式参数的代码改动时,一定要多测试。若某个环节被忽略,很容易造成生产事故
  • 如果你觉得我的文章有用的话,欢迎点赞和关注。
  • I'm a slow walker, but I never walk backwards
阅读 155

I am a slow walker, but I never walk backwards.

1 声望
3 粉丝
0 条评论
你知道吗?

I am a slow walker, but I never walk backwards.

1 声望
3 粉丝
宣传栏