需求
为了保障SaaS云服务的稳定性,缩小故障范围,资源隔离或者说故障隔离是一种常用手段。在应用层按照隔离的粒度一般可以分为:线程级别的隔离、服务级别的隔离。
有这么一个功能依赖了第三方的服务B,这个三方服务分布在各个省和地区,我们有一个微服务A封装了和B的通信细节,专门负责和这个三方服务B的通信,其它业务服务只和A服务通信即可。 由于各省接口稳定性不一样,所以就需要有一种隔离机制来应对故障区域,防止A服务的线程资源被故障区域的请求占满,从而影响其它 正常区域接口的访问。
方案
如果使用线程隔离的方案,则如下:
如果使用服务级别的隔离方案,则如下:
今天说下如何通过Dubbo实现服务级别的隔离
实现
服务A是可以处理所有区域的请求的,但是我们为了实现按区域独立部署,实现所谓的隔离就需要给服务A的每个实例打上一个区域的标签,让调用方的请求按照区域流向不同的服务实例,通过Dubbo如何实现呢?
其实挺简单的,我之前写过一篇 关于Dubbo应用的分布式调试方案的文章,实现的思路 大同小异,在服务提供者身上打上标签,通过服务消费者端 自定义Dubbo路由策略去实现。
具体步骤如下:
1、提供者端服务A的每个实例上打上区域编码的标签,有了这个标签才有可能让消费方去按区域编码路由。
具体如何打标签呢,之前也说过了,可以在 provider标签下自定义parameter参数
<dubbo:provider>
<dubbo:parameter key="areaCode" value="${areaCode:}"/>
</dubbo:provider>
2、消费者端 自定义路由策略,通过扩展SPI接口 RouterFactory 、Router 即可
public class AreaCodeRouter implements Router {
private static final Logger logger = LoggerFactory.getLogger(AreaCodeRouter.class);
private final URL url;
public AreaCodeRouter (URL url){
this.url = url;
}
@Override
public URL getUrl() {
return this.url;
}
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
if (invokers != null && invokers.size() != 0) {
try {
//获取需要自定义路由的areaCode
String areaCode = TraceUtil.getAreaCode();
List<Invoker<T>> result = new ArrayList();
while (true){
Iterator iterator = invokers.iterator();
while(iterator.hasNext()) {
Invoker<T> invoker = (Invoker)iterator.next();
URL invokerUrl = invoker.getUrl();
//获取提供者携带的AreaCode参数
String providerAreaCode = invokerUrl.getParameter("areaCode");
if(areaCode ==null){
result.add(invoker);
}else{
//添加所有符合AreaCode内容一致的提供者
if (providerAreaCode !=null&&providerAreaCode.equals(areaCode )) {
result.add(invoker);
}
}
}
if (result.size()==0&&areaCode!=null) {
//不存在areaCode内容一致的提供者服务,就不区分区域了,可以走所有的服务
areaCode =null;
}else{
//存在areaCode 对应的提供者服务
break;
}
}
return result;
} catch (Throwable throwable) {
logger.error("Failed to execute areaCode router rule: " + this.getUrl() + ", invokers: " + invokers + ", cause: " + throwable.getMessage(), throwable);
}
}
return invokers;
}
@Override
public int compareTo(Router o) {
return 0;
}
}
public class AreaCodeRouterFactory implements RouterFactory {
@Override
public Router getRouter(URL url) {
return new AreaCodeRouter(url);
}
}
3、消费者端SPI配置
META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.RouterFactory的配置
areaCodeRouterFactory=com.xxx.dubbo.router.AreaCodeRouterFactory
4、消费者端路由策略生效
<dubbo:registry address="xxx" group="xxx" >
<dubbo:parameter key="router" value="areaCodeRouterFactory" />
</dubbo:registry>
5、消费者端areaCode标签的传递
调用方在发起RPC之前,将areaCode参数值绑定到当前线程上(通过ThreadLocal传递),这样在路由策略中才能取出使用
总结
主要利用了Dubbo的自定义传参和参数优先级覆盖机制,才能在消费者端 invoker 的url中获取到 提供者端 自定义的areaCode参数,不得不说Dubbo确实挺强大的。