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

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: std::move和std::forward都是C++中的类型转换函数,但它们作用不同。std::move将一个左值转换为右值,以使之能够进行移动操作,从而避免产生不必要的复制行为,提高程序的性能;而std::forward则是转发函数模板,将原本传入的参数保持其属性,比如如果传入的是左值,则转发后仍为左值,传入的是右值,则转发后仍为右值。这两个函数在模板编程、移动操作、完美转发等场景中常常被使用。 ### 回答2: std::move和std::forward是C++11新加入的两个重要的语言特性,它们都是在实现移动语义时使用的,std::move用于强制将左值转换为右值引用,从而支持移动构造和移动赋值操作;std::forward则用于完美转发,在模板中传递参数的引用类型。本文将对这两个函数进行源码分析。 1. std::move源码分析 std::move的定义非常简单,只是将其参数强制转换为右值引用类型: template<typename T> typename std::remove_reference<T>::type&& move(T&& t) noexcept { return static_cast<typename remove_reference<T>::type&&>(t); } 可以看到,它使用了std::remove_reference模板来去除T的引用。然后使用static_cast将t强制转换成右值引用类型,并返回。 当我们调用std::move时,实际上就是将一个左值转换为右值引用,以达到优化移动语义的目的。例如: std::vector<int> v1; std::vector<int> v2 = std::move(v1); //移动构造 在上面的代码中,我们将v1移动到v2中,这样就可以避免复制(通过重用已分配的内存)。注意,这只在T类型实现了移动构造函数的情况下才适用。 2. std::forward源码分析 std::forward的作用是在模板中完美转发参数。它需要通过保留参数类型和值类别的方式,将参数转发给别的函数。它的定义如下: template <typename T> constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept { return static_cast<T&&>(t); } template <typename T> constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept { static_assert(!std::is_lvalue_reference<T>::value, "template argument substituting T is an lvalue reference type"); return static_cast<T&&>(t); } 可以看到,std::forward是通过两个模板函数来实现的,一个是左值引用,一个是右值引用。前者需要保留参数的左值引用,而后者需要判断参数是否是左值引用类型,如果是,则编译时会给出错误。 在两个函数中,都使用了std::remove_reference模板来删除参数类型的引用,然后使用static_cast将参数转换成相应的引用类型。例如: void foo(std::string& str) { bar(std::forward<std::string>(str)); } 在这个例子中,str是函数参数的左值引用,我们希望将它转发给bar函数,保留其左值引用。如果我们使用std::move,则会将str的值类别转换成右值引用,这样可能会导致编译错误或实现不必要的复制。因此,我们应该使用std::forward来完美转发str,并且保留其左值引用类型。 总结 std::move和std::forward是在C++11中加入的两个重要的语言特性,用于实现移动语义。std::move将左值转换为右值引用以支持移动构造和移动赋值操作,而std::forward则用于完美转发,在模板中传递参数的引用类型。它们的实现都依赖于std::remove_reference模板进行参数类型的去除引用操作,并使用static_cast将参数转换为相应的引用类型。学习和掌握这两个函数对我们理解现代C++编程有很大的帮助。 ### 回答3: std::move和std::forward是C++11标准中非常重要的两个函数模板,它们都可以用于C++中的移动语义,以提高程序的性能。在实际开发中,std::move和std::forward经常被用来优化参数传递和返回值语句,并且它们的使用方法与意义相似。 1. std::move std::move函数的定义如下所示: ```cpp template <typename T> typename remove_reference<T>::type&& move(T&& arg) noexcept; ``` 该函数的作用是将对象的左值转化为右值,从而进行移动操作。它接受一个模板参数T,然后将该参数转化为它对应的右值引用。如果T是左值类型,那么move函数返回该类型的一个右值引用。 在源码中,我们可以看到move函数实际上是将传入的参数arg强制转化为右值引用类型,并且使用了noexcept关键字,表示该函数是不抛出异常的。在移动语义中,对象的资源被移动而非复制,因此在move函数中使用右值引用类型可以避免无谓的复制操作,提高程序性能。 2. std::forward std::forward函数的定义如下所示: ```cpp template <typename T> T&& forward(typename remove_reference<T>::type& arg) noexcept; template <typename T> T&& forward(typename remove_reference<T>::type&& arg) noexcept; ``` 该函数的作用是针对完美转发进行优化,即按照形参的值类型来进行参数传递,以避免多次拷贝构造或移动构造,并且保留了右值特性。在语义上,它可以保证实参的值类型被完全保留,无论是左值还是右值。 在源码中,我们可以看到forward函数的模板参数为typename T,并且该函数定义了两个重载版本,分别用于左值和右值类型。当T为左值类型时,我们希望将实参arg作为左值类型进行传递,因此我们使用左值引用类型;当T为右值类型时,实参arg被认为是右值类型,因此我们使用右值引用类型。在使用forward进行参数传递时,根据函数模板参数的值类型自动匹配对应的重载版本,从而实现完美转发。 综上所述,std::move和std::forward是C++11中非常重要的函数模板,它们可以有效地提高程序性能,减少不必要的复制操作,并且针对完美转发进行了优化,具有非常广泛的应用场景。在实际开发中,我们需要根据语义需求来选择合适的函数进行参数传递和返回值处理,以达到优化程序性能的目的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值