dubbo系列七、dubbo tag路由擴展


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進行參數綁定。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM