服务路由包含一条路由规则,路由规则决定了服务消费者的调用目标,即规定了服务消费者可调用哪些服务提供者。
服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由,筛选出符合路由规则的服务提供者。
一、过程
RegistryDirectory的notify方法中调用:
- toRouters():路由配置(url)->路由器实例(Router)
- setRouters()
- toRouters()
/**
* 把路由配置转化成具体路由器
* @param urls
* @return null : no routers ,do nothing
* else :routers list
*/
private List<Router> toRouters(List<URL> urls) {
List<Router> routers = new ArrayList<Router>();
if (urls == null || urls.size() < 1) {
return routers;
}
if (urls != null && urls.size() > 0) {
for (URL url : urls) {
if (Constants.EMPTY_PROTOCOL.equals(url.getProtocol())) {
continue;
}
String routerType = url.getParameter(Constants.ROUTER_KEY);
if (routerType != null && routerType.length() > 0) {
//设置url的protocol为路由类型
url = url.setProtocol(routerType);
}
try {
//根据routerType获取对应的路由器工厂,然后获取具体路由器。然后放入路由器集合
//dubbo默认实现有file,script,condition三种类型工厂对应三种路由类型
//这些都是在RouterFactory$Adpative类完成适配的,RouterFactory$Adpative类是dubbo spi机制动态编码,编译生成的
Router router = routerFactory.getRouter(url);
if (!routers.contains(router))
routers.add(router);
} catch (Throwable t) {
logger.error("convert router url to router error, url: " + url, t);
}
}
}
return routers;
}
- setRouters()
/***
* 设置路由器
* @param routers
*/
protected void setRouters(List<Router> routers) {
// copy list
routers = routers == null ? new ArrayList<Router>() : new ArrayList<Router>(routers);
//append url router
//获取路由key
//这里感觉是从consumer端配置router的路由规则,但是consumer没有router配置的地方???
//官方文档给出<dubbo:protocol router="xxx" />配置方式,但是并没有实现。
String routerkey = url.getParameter(Constants.ROUTER_KEY);
if (routerkey != null && routerkey.length() > 0) {
//根据routerkey获取三种路由工厂的其中一种
RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
//再由路由工厂获取具体类型路由器
routers.add(routerFactory.getRouter(url));
}
// append mock invoker selector
// 追加mock invoker 路由器,
routers.add(new MockInvokersSelector());
//排序后MockInvokersSelector会放在最后
//MockInvokersSelector路由器,是dubbo对mock调用支持的一部分,稍后看下源码
Collections.sort(routers);
this.routers = routers;
}
二、分类
Dubbo 目前提供了三种服务路由实现,分别为条件路由 ConditionRouter、脚本路由 ScriptRouter 和标签路由 TagRouter。其中条件路由是我们最常使用的,标签路由是一个新的实现,暂时还未发布,该实现预计会在 2.7.x 版本中发布。
1. ConditionRouter 条件路由
condition类型路由器是由condition路由工厂获取,ConditionRouterFactory是RouterFactory 通过spi机制的一种实现。
public class ConditionRouterFactory implements RouterFactory {
public static final String NAME = "condition";
//返回ConditionRouter类型路由器
public Router getRouter(URL url) {
return new ConditionRouter(url);
}
}
(1)ConditionRouter()构造方法
构造函数,初始化并解析路由规则,分成两个部分:whenCondition 和 thenCondition
- 初始化路由规则类型:Map<String, MatchPair>
- 解析路由规则:parseRule()
/**
* ConditionRouter 类生命
* 实现了Comparable接口,是为了路由排序用的
* @author william.liangf
*/
public class ConditionRouter implements Router, Comparable<Router>{
private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);
private static Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
//路由器的信息来源:url
private final URL url;
//路由器优先级,在多个路由排序用的
private final int priority;
//是否强制执行路由规则,哪怕没有合适的invoker
private final boolean force;
//存放consumer路由规则
private final Map<String, MatchPair> whenCondition;
//存放provider路由规则
private final Map<String, MatchPair> thenCondition;
/***
* pulibc 构造函数,为各个属性赋值
* @param url
*/
public ConditionRouter(URL url) {
this.url = url;
this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
this.force = url.getParameter(Constants.FORCE_KEY, false);
try {
//通过rule key获取路由规则字串
String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
if (rule == null || rule.trim().length() == 0) {
throw new IllegalArgumentException("Illegal route rule!");
}
//把字符串里的"consumer." "provider." 替换掉,方便解析
rule = rule.replace("consumer.", "").replace("provider.", "");
int i = rule.indexOf("=>");
//以"=>"为分割线,前面是consumer规则,后面是provider 规则
String whenRule = i < 0 ? null : rule.substring(0, i).trim();
String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
//parseRule 方法解析规则,放在Map<String, MatchPair>里
Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
// NOTE: When条件是允许为空的,外部业务来保证类似的约束条件
//解析构造的规则放在condition变量里
this.whenCondition = when;
this.thenCondition = then;
} catch (ParseException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
- MatchPair类
/***
* MatchPiar 数据结构,它包含类具体匹配方法isMatch
* 是个私有,静态类,为什么静态??
*/
private static final class MatchPair {
//允许列表,内部用Set集合类型,防止重复。
final Set<String> matches = new HashSet<String>();
//拒绝规则
final Set<String> mismatches = new HashSet<String>();
//具体执行匹配规则的方法
private boolean isMatch(String value, URL param) {
//只有允许项目
if (matches.size() > 0 && mismatches.size() == 0) {
for (String match : matches) {
//value只要满足一项,允许,就为匹配,通过
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
//只有不允许项目
if (mismatches.size() > 0 && matches.size() == 0) {
//value是要满足一项,不允许。就是不匹配
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
//必须全部不满足不匹配。才通过
return true;
}
if (matches.size() > 0 && mismatches.size() > 0) {
//when both mismatches and matches contain the same value, then using mismatches first
//当匹配项和不匹配项包含同样的值,不匹配项优先
for (String mismatch : mismatches) {
if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
return false;
}
}
for (String match : matches) {
if (UrlUtils.isMatchGlobPattern(match, value, param)) {
return true;
}
}
return false;
}
return false;
}
}
- parseRule()
/**
* 解析规则字串解析后放到map里
* 这里有个数据结构类MatchPair 用set放着,允许规则matches,和不允许规则mismatches,他们都是set结构
* @param rule
* @return
* @throws ParseException
*/
private static Map<String, MatchPair> parseRule(String rule)
throws ParseException {
Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
if (StringUtils.isBlank(rule)) {
return condition;
}
// 匹配或不匹配Key-Value对
MatchPair pair = null;
// 多个Value值
Set<String> values = null;
//用java的正则表达式Pattern的matcher去分割字符串到Matcher类,然后逐个匹配。
//这个要熟悉正则,和Matcher,Pattern
final Matcher matcher = ROUTE_PATTERN.matcher(rule);
while (matcher.find()) { //逐个匹配
String separator = matcher.group(1);
String content = matcher.group(2);
// 表达式开始
if (separator == null || separator.length() == 0) {
pair = new MatchPair();
condition.put(content, pair);
}
// KV开始
else if ("&".equals(separator)) {
if (condition.get(content) == null) {
pair = new MatchPair();
condition.put(content, pair);
} else {
pair = condition.get(content);
}
}
// KV的Value部分开始
else if ("=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values = pair.matches;
values.add(content);
}
// KV的Value部分开始
else if ("!=".equals(separator)) {
if (pair == null)
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values = pair.mismatches;
values.add(content);
}
// KV的Value部分的多个条目
else if (",".equals(separator)) { // 如果为逗号表示
if (values == null || values.size() == 0)
throw new ParseException("Illegal route rule \""
+ rule + "\", The error char '" + separator
+ "' at index " + matcher.start() + " before \""
+ content + "\".", matcher.start());
values.add(content);
} else {
throw new ParseException("Illegal route rule \"" + rule
+ "\", The error char '" + separator + "' at index "
+ matcher.start() + " before \"" + content + "\".", matcher.start());
}
}
return condition;
}
(2)route()路由方法
根据路由规则对,调用方(一个)和服务提供方(多个)执行路由规则。让符合规则的调用方,可以调用; 让不符合规则的调用方不能调用。让符合规则的服务提供方,留着服务提供者列表;让不符合路由规则的服务提供方,从服务者列表中除去。
- route()
/***
* 路由方法,执行路由规则
* @param invokers
* @param url refer url
* @param invocation
* @param <T>
* @return
* @throws RpcException
*/
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
throws RpcException {
if (invokers == null || invokers.size() == 0) {
return invokers;
}
try {
//前置条件不匹配,说明consumer不在限制之列。说明,路由不针对当前客户,这样就全部放行,所有提供者都可以调用。
//这是consumer的url
if (!matchWhen(url, invocation)) {
return invokers;
}
List<Invoker<T>> result = new ArrayList<Invoker<T>>();
//thenCondition为null表示拒绝一切请求
if (thenCondition == null) {
logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
return result;
}
for (Invoker<T> invoker : invokers) {
//调用放匹配才放行。服务提供者,只要符合路由才,能提供服务,这里的url改为invoker.getUrl()
//都不匹配,这里result可能是空的。
if (matchThen(invoker.getUrl(), url)) {
result.add(invoker);
}
}
if (result.size() > 0) {
return result;
} else if (force) {
//force强制执行路由。哪怕result是空的,也要返回给上层方法。如果为false,最后放回所有的invokers,等于不执行路由
logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
return result;
}
} catch (Throwable t) {
logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
}
return invokers;
}
理由规则的执行。matchWhen()和matchThen()
- matchWhen()对调用方的匹配
/***
* 对服务调用方的匹配
* @param url
* @param invocation
* @return
*/
boolean matchWhen(URL url, Invocation invocation) {
return whenCondition == null || whenCondition.isEmpty() || matchCondition(whenCondition, url, null, invocation);
}
- matchThen()对服务提供者的匹配
/***
* 对服务提供者的匹配
* @param url
* @param param
* @return
*/
private boolean matchThen(URL url, URL param) {
return !(thenCondition == null || thenCondition.isEmpty()) && matchCondition(thenCondition, url, param, null);
}
- matchCondition()
/***
* 具体执行匹配规则,遍历condition里所有的规则,执行规则。
* @param condition
* @param url
* @param param
* @param invocation
* @return
*/
private boolean matchCondition(Map<String, MatchPair> condition, URL url, URL param, Invocation invocation) {
Map<String, String> sample = url.toMap();
boolean result = false;
for (Map.Entry<String, MatchPair> matchPair : condition.entrySet()) {
String key = matchPair.getKey();
String sampleValue;
//get real invoked method name from invocation
//路由规则支持到方法级别
if (invocation != null && (Constants.METHOD_KEY.equals(key) || Constants.METHODS_KEY.equals(key))) {
sampleValue = invocation.getMethodName();//获取方法名
} else {//key 是host 获取host值
sampleValue = sample.get(key);
}
if (sampleValue != null) {
//调用MatchPair的isMatch方法
if (!matchPair.getValue().isMatch(sampleValue, param)) {
return false;
} else {
result = true;
}
} else {
//not pass the condition
//如果sampleValue没有值,但匹配项有值。不通过
if (matchPair.getValue().matches.size() > 0) {
return false;
} else {
result = true;
}
}
}
return result;
}
路由器的排序
/***
* 路由器的排序
* @param o
* @return
*/
public int compareTo(Router o) {
if (o == null || o.getClass() != ConditionRouter.class) {
return 1;
}
ConditionRouter c = (ConditionRouter) o;
//如果优先级相同,通过rul字符串比较
return this.priority == c.priority ? url.toFullString().compareTo(c.url.toFullString()) : (this.priority > c.priority ? 1 : -1);
}
2. ScriptRouter 脚本路由
3. TagRouter 标签路由
参考:
https://my.oschina.net/u/146130/blog/1581069
https://my.oschina.net/u/146130/blog/1592235
https://www.jianshu.com/p/21d6750e391a