文章目录

  • dubbo tag路由扩展
  • 1.前言
  • 2.tag路由扩展
  • 2.1.consumer端设置
  • 2.2.provider端设置
  • 2.3.总结
  • 2.4.dubbo属性自动装配说明
  • 2.5.springboot Binder使用说明

dubbo tag路由扩展

1.前言

dubbo tag路由用着简单清晰,工作中我们常使用tag路由进行流量隔离,比如多套测试环境,使用dubbo治理平台通过路由规则又麻烦,但是tag路由有两个问题:

1.写着有点麻烦,每次调用要显示的RpcContext.getContext().setAttachment("dubbo.tag", "xxx");,才行,那么有没有办法可以只是设置下配置就可以实现呢?

2.在consumer一个方法内多处请求provider,第一次请求consumer 端的 dubbo.tag 通过 dubbo 的 attachment 携带给 provider 端,但是请求结束就被ConsumerContextFilter清空了attachment ,因此第二次开始就没有了dubbo.tag携带,这个问题有没方便办法解决?

2.tag路由扩展

针对上述问题:可以写个consumer端filter,每次从环境获取dubbo.tag设置到attachment ,但是,每个项目组都这么做,也麻烦,不够方便和统一,下面介绍一种简单方法,通过配置项

2.1.consumer端设置

consumer请求provider的流程是:

MockClusterInvoker->FailoverClusterInvoker->RegistryDirectory获取引用的Invoker->TagInvoker路由过滤Invoker集合->负载均衡选取Invoker->filter chain->DubboInvoker

那么设置在哪里呢?当然在filter chain可以实现,但是前面也说了,不够统一和方便,那么就只能是在FailoverClusterInvoker前面了,那么设置个TagInvoker,如何生成呢?就使用TagClusterWrapper了,wrapper是个装饰对象,在加载默认的FailoverCluster的时候dubbo spi会自动加载。代码如下

@Component
@ConfigurationProperties(prefix = "zzz.dubbo")
public class DubboProperties {
	/** 统一服务端和消费端Tag */
    private String tag;
    /** 是否强制根据tag选择服务 */
    private Boolean tagforce;
    //标签路由
    public static String consumerTag;
    public static Boolean consumerTagforce;
    
    public String getTag() {
        return tag;
    }

    public void setTag(String tag) {
        this.tag = tag;
        this.consumerTag = tag;
    }

    public Boolean getTagforce() {
        return tagforce;
    }

    public void setTagforce(Boolean tagforce) {
        this.tagforce = tagforce;
        this.consumerTagforce = tagforce;
    }
}

public class TagClusterWrapper implements Cluster {
//需要在META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.Cluster文件内配置tagClusterWrapper=org.pangu.client.dubbo.TagClusterWrapper
    private Cluster cluster;

    public TagClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {//RegistryProtocol.doRefer(Cluster, Registry, Class<T>, URL)操作内cluster.join(directory)调用
        Invoker<T> targetInvoker = cluster.join(directory);

        return new TagInvoker<>(targetInvoker);
    }
}

public class TagInvoker<T> implements Invoker<T> {
    private static final Logger logger = LoggerFactory.getLogger(TagInvoker.class);

    private Invoker<T> invoker;

    public TagInvoker(Invoker<T> invoker) {
        this.invoker = invoker;
    }

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        if (StringUtils.hasText(DubboProperties.consumerTag)) {
            RpcContext context = RpcContext.getContext();
            if (!context.getAttachments().containsKey(Constants.TAG_KEY)) {
                context.setAttachment(Constants.TAG_KEY, DubboProperties.consumerTag);
            }
        }
        if (DubboProperties.consumerTagforce != null && DubboProperties.consumerTagforce) {
            RpcContext context = RpcContext.getContext();
            if (!context.getAttachments().containsKey(Constants.FORCE_USE_TAG)) {
                context.setAttachment(Constants.FORCE_USE_TAG, "true");
            }
        }
        Result result;
        try {
            result = invoker.invoke(invocation);
        } catch (RpcException e) {
            String message = e.getMessage();
            if (message != null && message.contains("No provider available for")) {
                RpcContext context = RpcContext.getContext();
                String consumerTag = context.getAttachments().get(Constants.TAG_KEY);
                if (consumerTag != null) {
                    logger.error("No provider available with Tag : {}", consumerTag);
                } else if ("true".equals(context.getAttachments().get(Constants.FORCE_USE_TAG))) {
                    logger.error("'tag-force' equals true, you must specify a tag name");
                }
            }
            throw e;
        }
        return result;
    }

    //其它@Override忽略
    
}

使用方法,在属性文件配置zzz.dubbo.tag=xxx即可。

这样consumer端每次请求就变为了MockClusterInvoker->TagInvoker->FailoverClusterInvoker,在TagInvoker内给增加tag,使用就很方便了。

2.2.provider端设置

上述设置,需要在代码进行硬编码@Service(tag="xxx"),硬编码不好,也可以通过属性来设置tag,比如dubbo.provider.tag=xxx,这样就可以。但是,有没有更傻瓜式的操作,consumer和provider端都设置比如zzz.dubbo.tag=xxx,就会给dubbo provider service增加tag,这样设置也都相同。实际provider端这样设置就等同生成一个ProviderConfig。代码如下

@Component
public class TagDubboConfigBeanCustomizer implements DubboConfigBeanCustomizer{

	@Autowired
	private DubboProperties dubboProperties;//和consumer端的DubboProperties相同
	@Override
	public int getOrder() {
		return 0;
	}

	@Override
	public void customize(String beanName, AbstractConfig dubboConfig) {
		if (dubboConfig instanceof ProviderConfig) {
            providerConfig((ProviderConfig) dubboConfig);
        }
	}
	
	private void providerConfig(ProviderConfig providerConfig) {
        if (StringUtils.isEmpty(providerConfig.getTag())
                && StringUtils.hasText(dubboProperties.getTag())) {
            providerConfig.setTag(dubboProperties.getTag());//给provider设置tag
        }
    }
}

这样在provider端设置属性zzz.dubbo.tag=xxx即可给provider增加tag为xxx,和consumer端保持一致。

2.3.总结

consumer和provider端代码可以提取作为被依赖的基础jar,当然作为基础依赖,不能直接在TagDubboConfigBeanCustomizer加上@Component,而是要使用自动装配进行注册为bean。

2.4.dubbo属性自动装配说明

在进行provider更改属性进行配置,涉及到了DubboConfigBeanCustomizer,这个用得少,记录下。

dubbo springboot自动装配开启DubboAutoConfiguration,DubboAutoConfiguration内又@EnableDubboConfig,因此注册bean DubboConfigConfiguration.Single,其上注释为

@EnableDubboConfigBindings({
    @EnableDubboConfigBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.module", type = ModuleConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
    @EnableDubboConfigBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class)
})
public static class Single {

}
//处理了@EnableDubboConfigBindings,那么@EnableDubboConfigBinding的import就不会执行。因为开启的是@EnableDubboConfigBindings,因此EnableDubboConfigBinding的import就不会执行

@EnableDubboConfigBindings 引入 DubboConfigBindingsRegistrar,注册dubbo ConfigBean和DubboConfigBindingBeanPostProcessor,其中dubbo

Configbean就是ApplicationConfig、ProviderConfig等,DubboConfigBindingBeanPostProcessor则是用于属性绑定,比如默认dubbp.provider开头属性绑定到ProviderConfig上。当然也支持自定义,因此自定义DubboConfigBeanCustomizer就可以实现把自定义的属性进行绑定到ProviderConfig。

2.5.springboot Binder使用说明

在dubbo自动装配启动过程中,使用到了配置的绑定,就是使用的Binder,因此记录下Binder。

Binder的使用其实比较简单 有点类似注解ConfigurationProperties的作用,都是将属性绑定到某个具体的对象上。 但是有一点区别 ConfigurationProperties是在容器启动时绑定的,而Binder是我们手动编码动态的绑定上去的。

DubboProperties dubboProperties = Binder.get(environment).bind(“zzz.dubbo”, DubboProperties.class).orElse(new DubboProperties());

比如要对dubbo进行封装,使用我们自定义的属性配置,那么就使用Binder进行参数绑定。