学习
实践
活动
工具
TVP
写文章

重写 spring-cloud-consul 服务发现逻辑

需求

覆盖 spring-cloud-consul (以下简称 consul,注意区分它并不是日常说的 HashiCorp consul ) 的服务发现逻辑,以满足特殊场景需求

不允许直接改源码

分析

consul 中有两处用到了服务发现逻辑

ConsulDiscoveryClient: 这个类用的场景比较少,一般是别的组件或者系统需要自己获取服务注册的一些信息时,手动注入该类,然后直接使用

ConsulServerList: 我们日常通过 feign 或者 ribbon 间接使用服务发现时,调用的就是这个类的 getServers() 方法

我们今天重点看一下ConsulServerList

不出意外,ConsulServerList 是被注入到 Spring 中使用的,而且注入条件是 ConditionalOnMissingBean:

解决办法一

自己实现 ServerList,并且让它在原生 ConsulServerList 初始化前被注入

运行结果

虽然我们已经定义好了 MyConsulServerList, 但是程序运行时,还是去初始化 ConsulServerList

而且我们发现原生 ConsulServerList 对于serviceId 的处理很奇怪

对的,你没有看错,serviceId 是放在成员变量里的

这就有一个问题,绝大多数系统都是不止一个服务提供方,那 serverId 放在成员变量里,就证明 ConsulServerList 不可能是单例

可是也没有看到明显的 scope 配置,那这个 Bean 是什么时候被初始化的呢

多例的 ConsulServerList

经过断点可知,每当请求一个新的 service 时,ConsulRibbonClientConfiguration.ribbonServerList() 就会被执行一次

而触发该方法的并不是 consul, 而是 ribbon

核心调用链和代码如上文所示。大致步骤如下:

feign 拿着 serviceId 去问 ribbon 要服务端配置

ribbon 根据 serviceId 去 RibbonClientConfiguration 的上下文中找是否存在这个名称的 ConsulServerList bean

如果存在就返回

如果不存在,就注册一个,然后刷新上下文初始化

spring 怎么知道要刷新哪个上下文

它是通过 @RibbonClient 注解来定位的

这里就不再展开,具体可以参考代码:RibbonClientConfigurationRegistrar.class

解决办法二

禁用原有的 ConsulRibbonClientConfiguration: spring.cloud.consul.ribbon.enabled:true

自定义自己的 ConsulRibbonClientConfiguration: MyConsulRibbonClientConfiguration

自定义自己的 ConsulServerList: MyConsulServerList

给自己的逻辑添加入口: @ConditionalOnExpression("$==false")

具体实现代码参考: https://github.com/bishion/microService/tree/master/myConsulTool

思考

为什么 ribbon 要这么大费周章地去给每个服务方定义一个单独的 ServerList 呢? 明明在调用的时候,将 serviceId 传给服务发现逻辑就可以了

这是因为 ribbon 的一个需求决定的: 服务配置个性化

ribbon 支持对某一个服务单独配置负载,比如负载算法,是否重试等,当然也包括服务发现逻辑

为每一个服务实例化一个服务发现逻辑,可以最大化地将自由交给实现方。只是目前的 consul 并未用到这个功能而已

后记

为每个服务懒加载一个独有的实例,ribbon 的实现很巧妙,值得学习

关于 ribbon 对于服务个性化配置的实现,找机会另开一题

如有错误或不当之处,欢迎留言指正

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180603G167SG00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

同媒体快讯

扫码关注腾讯云开发者

领取腾讯云代金券