dubbo源码学习(三)-Router
在学习router之前先了解下灰色发布的概念
灰度发布
概念:灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度期:灰度发布开始到结束期间的这一段时间,称为灰度期。
过程:
1.首先在192.168.22.58和192.168.22.59两台机器上启动Provider,然后启动Consumer
2.假设我们要升级192.168.22.58服务器上的服务,接着我们去dubbo的控制台配置路由,切断192.168.22.58的流量,配置完成并且启动之后,就看到此时只调用192.168.22.59的服务
3.假设此时你在192.168.22.58服务器升级服务,升级完成后再次将启动服务
router主要有三个实现类,分别是ConditionRouter,MockInvokersSelector,ScriptRouter
主要学习ConditionRouter(条件路由):
条件路由主要就是根据dubbo管理控制台配置的路由规则来过滤相关的invoker,当我们对路由规则点击启用的时候,就会触发RegistryDirectory类的notify方法(在directory也讲到在服务订阅的时候会触发notify方法,另外还有修改路由规则的时候会触发);
其实对于Router来说,重要的就是他是怎么过滤的。具体的源码可以跟着http://dubbo.apache.org/zh-cn/docs/source_code_guide/router.html文档走一边源码,借用大佬画的一个图可以看清楚大致的源码执行过程
提高URL,随便也学习下dubbo的url规则:
protocol:一般是 dubbo 中的各种协议 如:dubbo thrift http zk
username/password:用户名/密码
host/port:主机/端口
path:接口名称
parameters:参数键值对
dubbo://192.168.1.6:20880/moe.cnkirito.sample.HelloService?timeout=3000
描述一个 dubbo 协议的服务
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=democonsumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=1214&qos.port=33333×tamp=15457219946
描述一个zookeeper 注册中心
consumer://30.5.120.217/org.apache.dubbo.demo.DemoService?application=democonsumer&category=consumers&check=false&dubbo=2.0.2&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=1209&qos.port=33333&side=consumer×tamp=1545721827784
描述一个消费者
public final class URL implements Serializable {
private static final long serialVersionUID = -6256557181054799417L;
/** 协议 (例如: dubbo:// zookeeper://) */
private final String protocol;
/** 用户名 (例如: dubbo://hadluo) */
private final String username;
/** 密码 */
private final String password;
/** 地址 */
private final String host;
/** 端口 */
private final int port;
/** 上下文路径 */
private final String path;
/** 附加参数 */
private final Map parameters;
/** 缓存 优化 当前URL的 toString() */
private volatile transient String string;
public URL(String protocol, String username, String password, String host, int port, String path,
Map parameters) {
if ((username == null || username.length() == 0) && password != null && password.length() > 0) {
throw new IllegalArgumentException("Invalid url, password without username!");
}
this.protocol = protocol;
this.username = username;
this.password = password;
this.host = host;
this.port = (port < 0 ? 0 : port);
this.path = path;
// trim the beginning "/"
while (path != null && path.startsWith("/")) {
path = path.substring(1);
}
if (parameters == null) {
parameters = new HashMap();
} else {
parameters = new HashMap(parameters);
}
// 不可修改的 map
this.parameters = Collections.unmodifiableMap(parameters);
}
public static URL valueOf(String url) {
// dubbo://hadluo:[email protected]:20881/context/path?version=1.0.0&application=morgan&noValue
if (url == null || (url = url.trim()).length() == 0) {
throw new IllegalArgumentException("url == null");
}
String protocol = null;
String username = null;
String password = null;
String host = null;
int port = 0;
String path = null;
Map parameters = null;
int i = url.indexOf("?"); // seperator between body and parameters
if (i >= 0) {
// 以 & 分隔 version=1.0.0&application=morgan&noValue
String[] parts = url.substring(i + 1).split("\\&");
parameters = new HashMap();
for (String part : parts) {
part = part.trim();
if (part.length() > 0) {
int j = part.indexOf('=');
if (j >= 0) {
// 是 key=val 形式的
parameters.put(part.substring(0, j), part.substring(j + 1));
} else {
// 只有 val形式的
parameters.put(part, part);
}
}
}
// url 变成 : dubbo://hadluo:[email protected]:20881/context/path?
url = url.substring(0, i);
}
i = url.indexOf("://");
if (i >= 0) {
if (i == 0)
throw new IllegalStateException("url missing protocol: \"" + url + "\"");
// 取 dubbo
protocol = url.substring(0, i);
// url变成 hadluo:[email protected]:20881/context/path
url = url.substring(i + 3);
} else {
// case: file:/path/to/file.txt
i = url.indexOf(":/");
if (i >= 0) {
if (i == 0)
throw new IllegalStateException("url missing protocol: \"" + url + "\"");
protocol = url.substring(0, i);
url = url.substring(i + 1);
}
}
i = url.indexOf("/");
if (i >= 0) {
// context/path
path = url.substring(i + 1);
// url变成 hadluo:[email protected]:20881
url = url.substring(0, i);
}
i = url.indexOf("@");
if (i >= 0) {
// hadluo:1234
username = url.substring(0, i);
int j = username.indexOf(":");
if (j >= 0) {
// 1234
password = username.substring(j + 1);
// hadluo
username = username.substring(0, j);
}
// 127.0.0.1:20881
url = url.substring(i + 1);
}
i = url.indexOf(":");
if (i >= 0 && i < url.length() - 1) {
// 20881
port = Integer.parseInt(url.substring(i + 1));
// 127.0.0.1
url = url.substring(0, i);
}
if (url.length() > 0)
host = url;
return new URL(protocol, username, password, host, port, path, parameters);
}
public String toString() {
if (string != null) {
return string;
}
// 打印成 string ,并缓存起来
return string = buildString();
}
private String buildString() {
StringBuilder buf = new StringBuilder();
if (protocol != null && protocol.length() > 0) {
buf.append(protocol);
buf.append("://");
}
if (username != null && username.length() > 0) {
buf.append(username);
if (password != null && password.length() > 0) {
buf.append(":");
buf.append(password);
}
buf.append("@");
}
if (host != null && host.length() > 0) {
buf.append(host);
if (port > 0) {
buf.append(":");
buf.append(port);
}
}
if (path != null && path.length() > 0) {
buf.append("/");
buf.append(path);
}
buildParameters(buf);
return buf.toString();
}
private void buildParameters(StringBuilder buf) {
if (this.parameters != null && this.parameters.size() > 0) {
boolean first = true;
for (Map.Entry entry : new TreeMap(this.parameters).entrySet()) {
if (entry.getKey() != null && entry.getKey().length() > 0) {
if (first) {
// 第一个 参数 是添加 ?
buf.append("?");
first = false;
} else {
// 后面参数 是 &连接
buf.append("&");
}
buf.append(entry.getKey());
buf.append("=");
buf.append(entry.getValue() == null ? "" : entry.getValue().trim());
}
}
}
}
}