motan源码分析十:流量切换
motan提供了流量切换的功能,可以实现把一个group的流量切换到另一个group(一个或多个服务都可以)。大家可以使用tomcat部署motan的管理工具,并设置几个组,例如可以参考demo代码:motan_demo_server_commandRegistry.xml。分析源码时可以发现,流量切换是在客户端完成的,与服务端没什么关系,在实际的工作中,可以解决很多问题,例如:某个集群出了问题,可以马上将流量切换到其它集群;在系统升级的过程中,将带升级集群的流量切换到其它集群,实现了24小时随时升级等。
1.motan的流量切换是通过command来实现的,每次我们在motan管理器上进行设置的时候,其实是写入信息到注册中心的command节点,而motan又监听了这些command节点,下面是motan的客户端监听command相关的代码
protected void subscribeCommand(final URL url, final CommandListener commandListener) {
try {
clientLock.lock();//对clientLock进行上锁
ConcurrentHashMap<CommandListener, IZkDataListener> dataChangeListeners = commandListeners.get(url);//数据变更监听器
if (dataChangeListeners == null) {
commandListeners.putIfAbsent(url, new ConcurrentHashMap<CommandListener, IZkDataListener>());
dataChangeListeners = commandListeners.get(url);
}
IZkDataListener zkDataListener = dataChangeListeners.get(commandListener);
if (zkDataListener == null) {
dataChangeListeners.putIfAbsent(commandListener, new IZkDataListener() {//增加新的listener
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
commandListener.notifyCommand(url, (String) data);//调用commandListener的notifyCommand方法
LoggerUtil.info(String.format("[ZookeeperRegistry] command data change: path=%s, command=%s", dataPath, (String) data));
} @Override
public void handleDataDeleted(String dataPath) throws Exception {
commandListener.notifyCommand(url, null);
LoggerUtil.info(String.format("[ZookeeperRegistry] command deleted: path=%s", dataPath));
}
});
zkDataListener = dataChangeListeners.get(commandListener);
} String commandPath = ZkUtils.toCommandPath(url);
zkClient.subscribeDataChanges(commandPath, zkDataListener);//向zookeeper注册监听事件
LoggerUtil.info(String.format("[ZookeeperRegistry] subscribe command: path=%s, info=%s", commandPath, url.toFullStr()));
} catch (Throwable e) {
throw new MotanFrameworkException(String.format("Failed to subscribe %s to zookeeper(%s), cause: %s", url, getUrl(), e.getMessage()), e);
} finally {
clientLock.unlock();
}
}
2.CommandServiceManager实现了上节中的commandListener
public void notifyCommand(URL serviceUrl, String commandString) {
LoggerUtil.info("CommandServiceManager notify command. service:" + serviceUrl.toSimpleString() + ", command:" + commandString); if (!MotanSwitcherUtil.isOpen(MOTAN_COMMAND_SWITCHER) || commandString == null) {//判断命令开关是否打开
LoggerUtil.info("command reset empty since swither is close.");
commandString = "";
} List<URL> finalResult = new ArrayList<URL>();
URL urlCopy = serviceUrl.createCopy();//serviceurl的副本 if (!StringUtils.equals(commandString, commandStringCache)) {
commandStringCache = commandString;
commandCache = RpcCommandUtil.stringToCommand(commandStringCache);//将字符串转换为命令
Map<String, Integer> weights = new HashMap<String, Integer>(); if (commandCache != null) {
commandCache.sort();
finalResult = discoverServiceWithCommand(refUrl, weights, commandCache);
} else {
// 如果是指令有异常时,应当按没有指令处理,防止错误指令导致服务异常
if (StringUtils.isNotBlank(commandString)) {
LoggerUtil.warn("command parse fail, ignored! command:" + commandString);
commandString = "";
}
// 没有命令时,只返回这个manager实际group对应的结果
finalResult.addAll(discoverOneGroup(refUrl)); } // 指令变化时,删除不再有效的缓存,取消订阅不再有效的group
Set<String> groupKeys = groupServiceCache.keySet();
for (String gk : groupKeys) {
if (!weights.containsKey(gk)) {
groupServiceCache.remove(gk);
URL urlTemp = urlCopy.createCopy();
urlTemp.addParameter(URLParamType.group.getName(), gk);
registry.unsubscribeService(urlTemp, this);
}
}
} else {
LoggerUtil.info("command not change. url:" + serviceUrl.toSimpleString());
// 指令没有变化,什么也不做
return;
} for (NotifyListener notifyListener : notifySet) {
notifyListener.notify(registry.getUrl(), finalResult);
} // 当指令从有改到无时,会触发取消订阅所有的group,需要重新订阅本组的service
if ("".equals(commandString)) {
LoggerUtil.info("reSub service" + refUrl.toSimpleString());
registry.subscribeService(refUrl, this);
}
}
3.discoverServiceWithCommand的相关代码
public List<URL> discoverServiceWithCommand(URL serviceUrl, Map<String, Integer> weights, RpcCommand rpcCommand, String localIP) {
if (rpcCommand == null || CollectionUtil.isEmpty(rpcCommand.getClientCommandList())) {
return discoverOneGroup(serviceUrl);
} List<URL> mergedResult = new LinkedList<URL>();
String path = serviceUrl.getPath();//获取路径 List<RpcCommand.ClientCommand> clientCommandList = rpcCommand.getClientCommandList();
boolean hit = false;
for (RpcCommand.ClientCommand command : clientCommandList) {
mergedResult = new LinkedList<URL>();
// 判断当前url是否符合过滤条件
boolean match = RpcCommandUtil.match(command.getPattern(), path);
if (match) {
hit = true;
if (!CollectionUtil.isEmpty(command.getMergeGroups())) {
// 计算出所有要合并的分组及权重
try {
buildWeightsMap(weights, command);
} catch (MotanFrameworkException e) {
LoggerUtil.warn("build weights map fail!" + e.getMessage());
continue;
}
// 根据计算结果,分别发现各个group的service,合并结果
mergedResult.addAll(mergeResult(serviceUrl, weights));
} else {
mergedResult.addAll(discoverOneGroup(serviceUrl));
} LoggerUtil.info("mergedResult: size-" + mergedResult.size() + " --- " + mergedResult.toString()); if (!CollectionUtil.isEmpty(command.getRouteRules())) {
LoggerUtil.info("router: " + command.getRouteRules().toString()); for (String routeRule : command.getRouteRules()) {
String[] fromTo = routeRule.replaceAll("\\s+", "").split("to"); if (fromTo.length != 2) {
routeRuleConfigError();
continue;
}
String from = fromTo[0];
String to = fromTo[1];
if (from.length() < 1 || to.length() < 1 || !IP_PATTERN.matcher(from).find() || !IP_PATTERN.matcher(to).find()) {
routeRuleConfigError();
continue;
}
boolean oppositeFrom = from.startsWith("!");
boolean oppositeTo = to.startsWith("!");
if (oppositeFrom) {
from = from.substring(1);
}
if (oppositeTo) {
to = to.substring(1);
}
int idx = from.indexOf('*');
boolean matchFrom;
if (idx != -1) {
matchFrom = localIP.startsWith(from.substring(0, idx));
} else {
matchFrom = localIP.equals(from);
} // 开头有!,取反
if (oppositeFrom) {
matchFrom = !matchFrom;
}
LoggerUtil.info("matchFrom: " + matchFrom + ", localip:" + localIP + ", from:" + from);
if (matchFrom) {
boolean matchTo;
Iterator<URL> iterator = mergedResult.iterator();
while (iterator.hasNext()) {
URL url = iterator.next();
if (url.getProtocol().equalsIgnoreCase("rule")) {
continue;
}
idx = to.indexOf('*');
if (idx != -1) {
matchTo = url.getHost().startsWith(to.substring(0, idx));
} else {
matchTo = url.getHost().equals(to);
}
if (oppositeTo) {
matchTo = !matchTo;
}
if (!matchTo) {
iterator.remove();
LoggerUtil.info("router To not match. url remove : " + url.toSimpleString());
}
}
}
}
}
// 只取第一个匹配的 TODO 考虑是否能满足绝大多数场景需求
break;
}
} List<URL> finalResult = new ArrayList<URL>();
if (!hit) {
finalResult = discoverOneGroup(serviceUrl);
} else {
finalResult.addAll(mergedResult);
}
return finalResult;
}
motan源码分析十:流量切换的更多相关文章
- C# DateTime的11种构造函数 [Abp 源码分析]十五、自动审计记录 .Net 登陆的时候添加验证码 使用Topshelf开发Windows服务、记录日志 日常杂记——C#验证码 c#_生成图片式验证码 C# 利用SharpZipLib生成压缩包 Sql2012如何将远程服务器数据库及表、表结构、表数据导入本地数据库
C# DateTime的11种构造函数 别的也不多说没直接贴代码 using System; using System.Collections.Generic; using System.Glob ...
- netty源码分析(十八)Netty底层架构系统总结与应用实践
一个EventLoopGroup当中会包含一个或多个EventLoop. 一个EventLoop在它的整个生命周期当中都只会与唯一一个Thread进行绑定. 所有由EventLoop所处理的各种I/O ...
- motan源码分析六:客户端与服务器的通信层分析
本章将分析motan的序列化和底层通信相关部分的代码. 1.在上一章中,有一个getrefers的操作,来获取所有服务器的引用,每个服务器的引用都是由DefaultRpcReferer来创建的 pub ...
- jQuery 源码分析(十四) 数据操作模块 类样式操作 详解
jQuery的属性操作模块总共有4个部分,本篇说一下第3个部分:类样式操作部分,用于修改DOM元素的class特性的,对于类样式操作来说,jQuery并没有定义静态方法,而只定义了实例方法,如下: a ...
- ABP源码分析十:Unit Of Work
ABP以AOP的方式实现UnitOfWork功能.通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类 ...
- ABP源码分析十二:本地化
本文逐个分析ABP中涉及到locaization的接口和类,以及相互之间的关系.本地化主要涉及两个方面:一个是语言(Language)的管理,这部分相对简单.另一个是语言对应得本地化资源(Locali ...
- ABP源码分析十四:Entity的设计
IEntity<TPrimaryKey>: 封装了PrimaryKey:Id,这是一个泛型类型 IEntity: 封装了PrimaryKey:Id,这是一个int类型 Entity< ...
- ABP源码分析十五:ABP中的实用扩展方法
类名 扩展的类型 方法名 参数 作用 XmlNodeExtensions XmlNode GetAttributeValueOrNull attributeName Gets an attribu ...
- ABP源码分析十六:DTO的设计
IDTO:空接口,用于标注Dto对象. ComboboxItemDto:用于combobox/list中Item的DTO NameValueDto<T>/NameValueDto:用于na ...
随机推荐
- Java设计模式(学习整理)---工厂模式
1.工厂模式 1.1 为什么使用工厂模式? 因为工厂模式就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时 ...
- java基础之反射机制
一.概念 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为jav ...
- 执行hadoop fs -ls时出现错误RuntimeException: core-site.xml not found
由于暴力关机,Hadoop fs -ls 出现了下图问题: 问题出现的原因是下面红框框里面的东西,我当时以为从另一个节点下载一个conf.cloudera.yarn文件就能解决问题,发现不行啊,于是删 ...
- 【POJ1823】【线段树】Hotel
Description The "Informatics" hotel is one of the most luxurious hotels from Galaciuc. A l ...
- 中级Perl 第三章课后习题
3. 10. 1. 练习1 [25 分钟] 读当前目录的文件列表并转换成全路径.不能用shell 命令或外部程序读当前目 录.Perl 的File::Spec 和Cwd 两个模块对这个程序有帮助.每个 ...
- 浏览器兼容性判定写法格式(ie)
条件注释判断浏览器<!--[if !IE]><!--[if IE]><!--[if lt IE 6]><!--[if gte IE 6]> <!- ...
- 百度分享 ajax 或分页后显示不出问题解决方案
自从用了AJAX后,JS重新加载问题就如家常便饭般层出不穷啊.没有系统学习过js感觉亚历山大. 百度后,还是找到了解决办法. 百度分享创建了一个全局对象window._bd_share_main.通过 ...
- 在vs code中使用ftp-sync插件实现客户端与服务器端代码的同步
在vs code中使用ftp-sync插件实现客户端与服务器端代码的同步 下载安装 vscode-ftp-sync 插件. 安装方法1. Ctrl+Shift+P 输入 ext install [插件 ...
- jquery ZeroClipboard实现黏贴板功能,兼容所有浏览器
两天前听了一个H5的分享,会议上有一句话,非常有感触:不是你不能,而是你对自己的要求太低.很简单的一句话,相信很多事情不是大家做不到,真的是对自己的要求太低,如果对自己要求多一点,那么你取得的进步可能 ...
- 运行在TQ2440开发板上以及X86平台上的linux内核编译
一.运行在TQ2440开发板上的linux内核编译 1.获取源码并解压 直接使用天嵌移植好的“linux-2.6.30.4_20100531.tar.bz2”源码包. 解压(天嵌默认解压到/opt/E ...