Dubbo 路由规则的过滤器实现

This commit is contained in:
YunaiV
2020-07-21 08:07:11 +08:00
parent e6201b00c1
commit 1dadf93449
24 changed files with 160 additions and 571 deletions

View File

@@ -0,0 +1,25 @@
package cn.iocoder.mall.dubbo.config;
import cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class DubboWebAutoConfiguration implements WebMvcConfigurer {
// ========== 拦截器相关 ==========
@Override
public void addInterceptors(InterceptorRegistry registry) {
try {
registry.addInterceptor(new DubboRouterTagWebInterceptor()).order(-1000);
} catch (NoSuchBeanDefinitionException e) {
// logger.warn("[addInterceptors][无法获取 AccessLogInterceptor 拦截器,因此不启动 AccessLog 的记录]");
}
}
}

View File

@@ -0,0 +1,62 @@
package cn.iocoder.mall.dubbo.core.filter;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
/**
* 基于 Dubbo 标签路由规则(http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html),实现如下功能:
* 1. 本地开发调试时,在带有 Dubbo Tag 的情况下,优先调用指定 Tag 的服务提供者。这样,我们可以将本地启动的服务提供者打上相应的 Tag即可优先调用本地
* 2. TODO 优化点:蓝绿发布、灰度发布
*
* 实现逻辑为:
* 1. 对于 Consumer 方,在调用 Provider 时,会将 {@link DubboRouterTagContextHolder} 中的 Tag 通过 Dubbo 隐式传参。
* 同时Dubbo 自带 {@link org.apache.dubbo.rpc.cluster.router.tag.TagRouter},会根据该参数,会选择符合该 Tag 的 Provider。
* 2. 对于 Provider 方,在通过 Dubbo 隐式传参获得到 Tag 时,会设置到 {@link DubboRouterTagContextHolder} 中。
* 这样,在 Provider 作为 Consumer 角色时,调用其它 Provider 时,可以继续实现标签路由的功能。
*/
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
public class DubboRouterTagFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 消费端
if (RpcContext.getContext().isConsumerSide()) {
// 设置 Dubbo Tag 到 Dubbo 隐式传参
String dubboTag = DubboRouterTagContextHolder.getTag();
boolean hasDubboTag = StringUtils.hasText(dubboTag);
if (hasDubboTag) {
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
}
// 继续调用
try {
return invoker.invoke(invocation);
} finally {
// 解决极端情况下,本地 injvm 调用时,消费端会调用 DubboRouterTagContextHolder.clear() 上下文,导致消费端也被清理了,因为在同一个 JVM 进程内。
if (hasDubboTag) {
DubboRouterTagContextHolder.setTag(dubboTag);
}
}
// 提供端
} else {
// 从 Dubbo 隐式传参获得 Dubbo Tag
String dubboTag = invocation.getAttachment(CommonConstants.TAG_KEY);
boolean hasDubboTag = StringUtils.hasText(dubboTag);
if (hasDubboTag) {
invocation.setAttachment(CommonConstants.TAG_KEY, dubboTag);
}
// 继续调用
try {
return invoker.invoke(invocation);
} finally {
// 清理
if (hasDubboTag) {
DubboRouterTagContextHolder.clear();
}
}
}
}
}

View File

@@ -1,4 +0,0 @@
/**
* 占坑
*/
package cn.iocoder.mall.dubbo.core;

View File

@@ -0,0 +1,25 @@
package cn.iocoder.mall.dubbo.core.router;
/**
* Dubbo 路由 Tag 的上下文
*
* @see cn.iocoder.mall.dubbo.core.filter.DubboRouterTagFilter
* @see cn.iocoder.mall.dubbo.core.web.DubboRouterTagWebInterceptor
*/
public class DubboRouterTagContextHolder {
private static ThreadLocal<String> tagContext = new ThreadLocal<>();
public static void setTag(String tag) {
tagContext.set(tag);
}
public static String getTag() {
return tagContext.get();
}
public static void clear() {
tagContext.remove();
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.mall.dubbo.core.web;
import cn.iocoder.common.framework.util.StringUtils;
import cn.iocoder.mall.dubbo.core.router.DubboRouterTagContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Dubbo 路由标签的 Web 拦截器,将请求 Header 中的 {@link #HEADER_DUBBO_TAG} 设置到 {@link DubboRouterTagContextHolder} 中。
*
* @see cn.iocoder.mall.dubbo.core.filter.DubboRouterTagFilter
*/
public class DubboRouterTagWebInterceptor implements HandlerInterceptor {
private static final String HEADER_DUBBO_TAG = "dubbo-tag";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String tag = request.getHeader(HEADER_DUBBO_TAG);
if (StringUtils.hasText(tag)) {
DubboRouterTagContextHolder.setTag(tag);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
DubboRouterTagContextHolder.clear();
}
}