记一次Dubbo版本升级历程

359 阅读2分钟

1.背景

发现Dubbo低版本安全问题,要求各业务团队升级dubbo版本到安全的版本(2.7.22及以上版本)

原因是因为dubbo泛型调用时存在反序列化漏洞,可能导致恶意代码执行。

2.Dubbo 2.7.1 -> 2.7.23 的几个较大的更新

1.服务配置中心模型(2.7.3)

2.消费端线程模型(2.7.5)

老的线程池模型

  1. 业务线程发出请求,拿到一个 Future 实例。
  2. 业务线程紧接着调用 future.get 阻塞等待业务结果返回。
  3. 当业务数据返回后,交由独立的 Consumer 端线程池进行反序列化等处理,并调用 future.set 将反序列化后的业务结果置回。
  4. 业务线程拿到结果直接返回

2.7.5 版本引入的线程池模型

  1. 业务线程发出请求,拿到一个 Future 实例。
  2. 在调用 future.get() 之前,先调用 ThreadlessExecutor.wait(),wait 会使业务线程在一个阻塞队列上等待,直到队列中被加入元素。
  3. 当业务数据返回后,生成一个 Runnable Task 并放入 ThreadlessExecutor 队列 业务线程将 Task 取出并在本线程中执行:反序列化业务数据并 set 到 Future。
  4. 业务线程拿到结果直接返回

这样,相比于老的线程池模型,由业务线程自己负责监测并解析返回结果,免去了额外的消费端线程池开销。

3.结果值缓存(2.7.7)

使用url转字符串作为key,预设三种缓存实现:

  • org.apache.dubbo.cache.support.lru.LruCacheFactory
  • org.apache.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
  • org.apache.dubbo.cache.support.jcache.JCacheFactory

3.升级遇到的问题

3.1 序列化冲突

与低版本(2.7.1)的provider通讯报序列化解析失败。

原因:

  1. oppo-framework重写kryo序列化协议和protobuff序列化协议
  2. 其中重写的protobuff协议用的contentTypeId在后续dubbo版本更新中被使用。
  3. dubbo 2.7.1版本通过在/META-INF/dubbo-internal/com.xxxxx 自定义序列化协议会覆盖原生的实现。但是在高版本(2.7.x)开始,类加载器增加overrwrite属性,只有为true才能覆盖原生实现。而默认/META-INF/dubbo-internal/ 目录下的类加载策略的overwrite = false,如果出现重复key,则对新的key打印一行错误日志。
  4. pt

dubbo 协议

Dubbo 数据包分为消息头和消息体,消息头用于存储一些元信息,比如魔数(Magic),数据包类型(Request/Response),消息体长度(Data Length)等。消息体中用于存储具体的调用消息,比如方法名称,参数列表等。下面简单列举一下消息头的内容。

  1. 类加载策略

  • 指定加载目录
  • 选择类加载器
  • 指定的包不被加载
  • 是否支持覆盖

解决方法:

  1. 无视新增 contentTypeId,继续将该 contentTypeId 当作 protostuff 序列化实现处理。
  2. 将原本放在 /META-INF/dubbo-internal/org.apache.dubbo.common.serialize.Serialization SPI 配置文件改为放到/META-INF/dubbo/org.apache.dubbo.common.serialize.Serialization 下,该目录下的类加载策略 overwitte = true,如果发现有重复的实现则覆盖。