在对于dubbo的路由机制和注册机制有所了解之后,我们来分析一下如何实现dubbo服务的不同环境隔离。
这一用法的大致思路为:
正常的测试环境中存在着api-consumer调用user-provider的这么一个调用关系,这里我们暂时称之为default版本。
假设某一天,业务方突然有一个a需求需要开发改动这个两个模块,此时就应该有这么一个关系链路出现,这里我们暂且称之为 version-a 版本。
但是由于version-a版本还是待完善阶段,可能还有一些影响主流程的bug存在,直接发布到测试环境替代掉default版本,可能会影响其他正在测试环境正常运作服务对于default版本的调用情况,因此version-a版本的影响需要被单独限制起来。
理想的情况下应该是这种情景:
那么如何实现这种单独的隔离方案呢?
目前自己所处的企业主要是采用dubbo作为微服务架构的基础,所以在进行不同服务环境的隔离时候需要对provider和consumer进行拆分设计。目前在dubbo这块的思路是基于对url配置总线的改造实现,保证不同版本的provider在写入url的时候,会结合需求分支注入一个git.branch的参数:
例如非稳定版本的测试环境中的provider 对应的配置总线中,可以在url的结尾中注入一个git.branch参数:
dubbo://192.168.43.227:9096/com.qiyu.dubbo.common.DubboService?anyhost=true&application=order-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&git.branch=master&interface=com.qiyu.dubbo.common.DubboService&methods=doTest&pid=63261®ister=true&release=2.7.3&side=provider&threadpool=fixed&threads=200×tamp=1609484179452 复制代码
然后在进行路由选择的时候,凡是非稳定版本的调用方只要在启动参数中携带有对应的git.branch参数方可实现对该provider的调用,否则都会调用到默认的稳定版本测试环境中。
对应的实现代码模块:
重写对应的zk注册工厂
package com.qiyu.dubbo.router.starter.zone.zk; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.registry.Registry; import org.apache.dubbo.registry.support.AbstractRegistryFactory; import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter; /** * @Author idea * @Date created in 3:20 下午 2020/11/26 */ public class ZoneAwareZookeeperRegisterFactory extends AbstractRegistryFactory { private ZookeeperTransporter zookeeperTransporter; /** * dubbo的spi自动具有依赖注入的功能 * * @param zookeeperTransporter */ public void setZookeeperTransporter(ZookeeperTransporter zookeeperTransporter) { this.zookeeperTransporter = zookeeperTransporter; } @Override public Registry createRegistry(URL url) { return new ZoneAwareZookeeperRegister(url,zookeeperTransporter); } } 复制代码
配合对dubbo.properties的改造实现相关模块的注入:
dubbo.registry.protocol=zoneAwareZookeeperRegisterFactory 复制代码
ps:注意,如果希望能够匹配到自定义的protocol,并且让dubbo服务在启动的时候注入到ZoneAwareZookeeperRegisterFactory的话,需要在配置注册地址的时候,将协议名称置空,例如这么写:
dubbo.registry.address=127.0.0.1:2181 复制代码
而不是:
dubbo.registry.address=zookeeper://127.0.0.1:2181 复制代码
这一部分可以在源代码的这里查询得到:
org.apache.dubbo.registry.RegistryService#register 复制代码
自定义注册服务的策略:
package com.qiyu.dubbo.router.starter.zone.zk; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.common.URL; import org.apache.dubbo.registry.zookeeper.ZookeeperRegistry; import org.apache.dubbo.remoting.zookeeper.ZookeeperTransporter; /** * @Author idea * @Date created in 3:18 下午 2020/11/26 */ @Slf4j public class ZoneAwareZookeeperRegister extends ZookeeperRegistry { public ZoneAwareZookeeperRegister(URL url, ZookeeperTransporter zookeeperTransporter) { super(url, zookeeperTransporter); } @Override public void doRegister(URL url) { String zone = System.getProperty("git.branch"); url = url.addParameter("git.branch", zone); log.info("当前注册的url为:{}" , url); super.doRegister(url); } } 复制代码
现在能够实现不同的provider在注册中心中配置总线url的不同,这样就能在路由router模块起到筛选的效果了。
主要思路:
自定义的一个cluster组件,该cluster组件加入一个叫做ZoneVersionSelectRouter的路由器。
package com.qiyu.dubbo.router.starter.zone.router; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.cluster.Cluster; import org.apache.dubbo.rpc.cluster.Directory; import org.apache.dubbo.rpc.cluster.Router; import org.apache.dubbo.rpc.cluster.RouterChain; import org.apache.dubbo.rpc.cluster.directory.AbstractDirectory; import org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker; import java.lang.reflect.Field; import java.util.List; /** * dubbo调用接口之前需要先通过cluster这个组件,在这里做重载,给他注入相关的router配置 * * @author idea * @date created in 10:22 下午 2020/11/21 */ public class ZoneAwareCluster implements Cluster { @Override public <T> Invoker<T> join(Directory<T> directory) throws RpcException { AbstractDirectory ad = (AbstractDirectory) directory; RouterChain routerChain = ad.getRouterChain(); try { Field routerField = routerChain.getClass().getDeclaredField("routers"); routerField.setAccessible(true); List<Router> routers = (List<Router>) routerField.get(routerChain); routers.add(new ZoneVersionSelectRouter()); return new FailoverClusterInvoker<>(directory); } catch (Exception e) { e.printStackTrace(); } return null; } } 复制代码
该路由组件可以根据consumer端的分支参数,选择性地调用不同的provider。
package com.qiyu.dubbo.router.starter.zone.router; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.common.URL; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcException; import org.apache.dubbo.rpc.cluster.Router; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.List; /** * 自定义的一个router组件: 基于zone参数进行不同版本选择的一个router组件 * * @author idea * @date created in 5:11 下午 2020/11/21 */ @Slf4j public class ZoneVersionSelectRouter implements Router { @Override public URL getUrl() { return null; } @Override public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException { //每次请求都会被切入到这个route模块 String consumerBranch = System.getProperty("git.branch"); log.info(this.getRouterName() + " consumerBranch is {} ", consumerBranch); Invoker providerInvoker = null; for (Invoker<T> invoker : invokers) { String providerBranch = invoker.getUrl().getParameter("git.branch"); if (StringUtils.isEmpty(providerBranch)) { providerInvoker = invoker; } else if (providerBranch.equals(consumerBranch)) { log.info(this.getRouterName() + " providerVersion matched was {} ", providerBranch); return invokers; } } log.info(this.getRouterName() + " providerVersion matched default version "); return Arrays.asList(providerInvoker); } @Override public boolean isRuntime() { return false; } @Override public boolean isForce() { return false; } @Override public int getPriority() { return -100; } private String getRouterName() { return " ====== ZoneVersionSelectRouter ====== "; } } 复制代码
记得要配置dubbo.properties文件:
dubbo.consumer.cluster=zoneAware 复制代码
最后便是spi配置文件的设置了:
RegisterFactory的spi拓展配置
zoneAwareZookeeperRegisterFactory=com.qiyu.dubbo.router.starter.zone.zk.ZoneAwareZookeeperRegisterFactory 复制代码
Cluster的spi拓展配置
zoneAware=com.qiyu.dubbo.router.starter.zone.router.ZoneAwareCluster 复制代码
启动的时候,加入-Dgit.branch参数,实现不同分支的流量隔离:
一个基本的dubbo服务隔离就基本完成了。
那么我们来进行一下总结,这一方案主要结合了哪些技术?
服务暴露过程中,配置总线的重写规则制定。
服务调用过程中,对于路由层的改造。
dubbo的spi机制理解与实践。