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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
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的相关代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
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; } |