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