dubbo是一款非常优秀的服务治理型RPC框架,dubbo的优秀在于,庞大的架构体系、精湛的模块设计、灵活的SPI设计、丰富的组件实现,博主做微服务技术选型考察dubbo时,非常惊叹在那个年代别人就已经能够产出如此优秀的项目,以至于后面每逢别人说想要学习架构设计时,我都会推荐他读读dubbo的代码,学习下dubbo的架构设计原则。常说dubbo不仅仅是一款RPC框架,是因为他的服务治理特性相对于RPC通讯来说更加的突出,这个特性让我在2017年选型的时候果断选择了他,那个时候dubbo官方还没产出spring boot starter,而我们的项目大部分完成了从spring mvc改造到spring boot项目。为了简化开发集成dubbo组件,我们基于dubbo2.5.6版本自研了一套spring-boot-dubbo-starter组件,并且自定义dubbo服务暴露和引入的注解, 自定义了dubbo的配置装载方式。当时没有专业的运维、搭建高可用zk也费资源,为了简单方便少维护一个组件、当时我们直接选了redis(阿里云高可用实例)作为dubbo的注册中心。以上就是我们这次升级dubbo的背景情况
由于我们目前维护了自己的spring-boot-dubbo-starter,所以在做升级时,我们产生了两种不同的升级方案,并且都做了完整的验证。
为了做到开发侧基本无感知升级到2.7.4.1版本,我们做了两件事情
在做注解兼容时也考虑过两个方案,一个是在自研的starter上做兼容dubbo2.7.4.1的处理,一个是在官方2.7.4.1的starter上兼容我们的处理。后面果断选择了后者,因为dubbo2.7.4.1版本对于我们来说是个黑盒子不知道有哪些改动,正向兼容难度比较大,反向兼容却要容易的多。 我们将原来自研组件里的自定义注解,保留包路径完整的拷贝到官方的starter项目中,然后将 ReferenceAnnotationBeanPostProcessor和ServiceAnnotationBeanPostProcessor从dubbo的spring模块中挪了出来,做了兼容自定义注解的处理。这个地方再次夸下 dubbo的设计,dubbo在捐赠给apache后,包名都改了,为了兼容老的alibaba包下的注解,服务暴露和服务引入都做了非常简易的注解兼容设计。 得益于此,我们在做自定义注解兼容处理时非常轻松就搞定了。
ReferenceAnnotationBeanPostProcessor的构造器传入自定义注解:
public ReferenceAnnotationBeanPostProcessor() { super(AutowiredDubbo.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class); }
ServiceAnnotationBeanPostProcessor扫描时添加自定义注解支持
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class)); /** * Add the compatibility for legacy Dubbo's @Service * * The issue : https://github.com/apache/dubbo/issues/4330 * @since 2.7.3 */ scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class)); // 兼容@DubboService注解 scanner.addIncludeFilter(new AnnotationTypeFilter(DubboService.class));
最后修改DubboAutoConfiguration中的服务暴露和服务引入处理器为我们魔改的实现即可
自研的自定义配置加载以spring.dubbo.打头的,而官方是以dubbo.打头的,区别如下:
自研的配置: spring.dubbo.application.name = xxx spring.dubbo.registry.address = xxx spring.dubbo.protocol.port = -1 官方starter配置 dubbo.application.name = xxx dubbo.registry.address = xxx dubbo.protocol.port = -1
为了做到配置兼容,修改了dubbo starter配置加载逻辑,去掉了spring.打头,修改DubboUtils中的filterDubboProperties,如:
public static SortedMap<String, Object> filterDubboProperties(ConfigurableEnvironment environment) { SortedMap<String, Object> dubboProperties = new TreeMap<>(); Map<String, Object> properties = EnvironmentUtils.extractProperties(environment); for (Map.Entry<String, Object> entry : properties.entrySet()) { String propertyName = entry.getKey(); if (propertyName.startsWith(DUBBO_PREFIX + PROPERTY_NAME_SEPARATOR) && entry.getValue() != null) { dubboProperties.put(propertyName, entry.getValue().toString()); } if (propertyName.startsWith("spring." + DUBBO_PREFIX + PROPERTY_NAME_SEPARATOR) && entry.getValue() != null) { propertyName = propertyName.substring(7); dubboProperties.put(propertyName, entry.getValue().toString()); } } return Collections.unmodifiableSortedMap(dubboProperties); }
最后打包上传到私服,开发只需要升级下jar的版本,配置和代码都不用动就可以升级到2.7.4.1版本的dubbo,可能魔改的地方不止上面贴的这些代码,这里只是引出思路,这个方案到这里结束了,这个方案的优点是对开发比较透明 因为迁移到nacos的步骤是一样的,第二个方案会谈到
最终讨论下来,考虑到内部维护版本,当官方升级时联动升级会比较麻烦,不如,直接痛一次全线改造代码,改造配置,采用了官方的starter直接升级,这样,后面有版本升级不用在投入人力维护自研的和官方的一致。
官方dubbo starter依赖
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.4.1</version> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <!-- 注意,引入dubbo官方依赖后,需要同时挪除我们维护的starter包-->
1、官方的starter默认的服务引入会校验服务是否存在,不存在就抛异常,会影响应用启动,可添加全局的配置,覆盖默认行为,配置如下:dubbo.consumer.check=false
2、自研starter中@AutowiredDubbo 的timeout等参数的单位为秒,官方注解@Reference的参数单位为毫秒,如以前配置为timeout=30, 则在官方starter中只有30毫秒的超时时间。
3、在使用多注册中心时,dubbo会从两个注册中心同时引入服务,虽然你的URL是完全一样的,也会在本地产生两个服务实例,所以,当你的容错模式为广播模式(cluster="Broadcast")或者并行模式(cluster="Forking")时就会产生消费者一次触发,生产者收到两次的问题。而默认的集群策略为 Failover,会正常的走随机负载的方式调用,不会有这种问题。如果有广播模式、或者并行模式的使用,可以通过设置nacos注册中心,只注册不消费。配置方式如下,等所以服务都迁移到nacos上后及时移除这个配置: dubbo.registries.nacos.parameters.subscribe = false
去掉spring.前缀即可,注意,升级官方starter后,需要新增一个配置,用来设置redis的连接池大小,官方默认的8个, dubbo.registries.redis.parameters.max.total = 200
下面示例了升级后的dubbo配置:
dubbo.application.name = xxx dubbo.protocol.port = -1 dubbo.provider.timeout = 300000 dubbo.consumer.check = false dubbo.registries.nacos.address = nacos://xxx:80 dubbo.registries.redis.address = redis://xxx:6379 dubbo.registries.redis.parameters.max.total = 200
利用dubbo支持多注册中心的功能,分两个阶段完成平滑的从redis迁移到nacos,第一阶段,全线升级修改配置为双注册中心,第二阶段,摘掉redis注册中心完成过渡,配置方式如下:
dubbo.registries.nacos.address=nacos://xxx:80 dubbo.registries.redis.address=redis://xx:6379
注意一些问题
dubbo.registry.parameters.db.index = 2
dubbo.registry.parameters.namespace = xxxxxx
dubbo升级的方案虽然简单,但是真正升级平滑过渡不是一蹴而就的,期间还是遇到了很多问题,这是一个不断优化稳定的过程。截止目前我们还没全线铺开上生产,只是个别应用推上生产做验证,升级有风险,需要小心又谨慎