You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@rocketmq.apache.org by GitBox <gi...@apache.org> on 2020/06/12 02:19:16 UTC

[GitHub] [rocketmq] gaoyf opened a new issue #2090: 4.7.1 中的 #2042 修复不彻底

gaoyf opened a new issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090


   **BUG REPORT**
   看到刚release的4.7.1中的#2042 修复了NameServer更新时可能导致的问题我很开心,因为之前我也遇到了,但是看了下修复代码,发现并没有完全解决这个问题。
   #### 一 先说一下我遇到的问题:
   我的NameServer列表采用的是域名发现的方式,以便热更新。但有一次在更新NameServer列表后,导致客户端无法生产和消费,经调查日志发现问题如下:
   1 假设起初域名对应的NameServer列表为a,b,**客户端选择了a与之建立通道**。
   2 随后更新域名映射的NameServer列表为c,d,**但是a,b两个NameServer实例没有下线**。
   3 broker感知到了域名映射变更,故摘除a,b,不再向其注册。
   4 **客户端持有的NameServer列表也更新了,但是客户端与a的通道依然通信正常。**
   但是此时NameServer a上的topic路由信息已经都没了,导致无法正常生产消费。
   
   #### 二 再说一下为啥#2042 没有解决上面的问题:
   试想一下下面的情景:
   1 线程1执行[NettyRemotingClient.getAndCreateNameserverChannel()](https://github.com/apache/rocketmq/blob/rocketmq-all-4.7.1/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java#L411)方法的这行代码[final List<String> addrList = this.namesrvAddrList.get();](https://github.com/apache/rocketmq/blob/rocketmq-all-4.7.1/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java#L420)后,其已经取到了addrList列表。
   2 此时假如正好NameServer列表变了,更新线程也执行完了[NettyRemotingClient.updateNameServerAddressList(List<String> addrs)](https://github.com/apache/rocketmq/blob/rocketmq-all-4.7.1/remoting/src/main/java/org/apache/rocketmq/remoting/netty/NettyRemotingClient.java#L336)的变更方法。
   3 注意,此时线程1持有的addrList依然是旧的,那么很可能依然选择一个旧的NameServer实例。
   4 假如线程1选择的旧NameServer实例没有下线,就会带来之前一样的问题。
   
   之前遇到这个问题(**从域名映射移除的NameServer实例应该马上关闭**)我只认为是操作不当,既然官方认为真是问题,那么就应该彻底修复。
   
   #### 三 我的修复方案:
   1 增加校验方法:
   ```
   private boolean isValid(String address) {
       if(address == null) {
           return false;
       }
       List<String> addrList = namesrvAddrList.get();
       if(addrList == null) {
           return true;
       }
       if(addrList.contains(address)) {
           return true;
       }
       log.info("name server address is invalid: {}", address);
       return false;
   }
   ```
   2 在chooseNameServer实例时进行校验,修改getAndCreateNameserverChannel()方法:
   ```
   private Channel getAndCreateNameserverChannel() throws RemotingConnectException, InterruptedException {
       String addr = this.namesrvAddrChoosed.get();
       if (isValid(addr)) { // 校验客户端持有的地址是否合法
           ChannelWrapper cw = this.channelTables.get(addr);
           if (cw != null && cw.isOK()) {
               return cw.getChannel();
           }
       } else {
           namesrvAddrChoosed.set(null); // 非法置空
       }
   
       final List<String> addrList = this.namesrvAddrList.get();
       if (this.lockNamesrvChannel.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
           try {
               addr = this.namesrvAddrChoosed.get();
               if (isValid(addr)) { // 校验客户端持有的地址是否合法
                   ChannelWrapper cw = this.channelTables.get(addr);
                   if (cw != null && cw.isOK()) {
                       return cw.getChannel();
                   }
               }
   
               if (addrList != null && !addrList.isEmpty()) {
                   for (int i = 0; i < addrList.size(); i++) {
                       int index = this.namesrvIndex.incrementAndGet();
                       index = Math.abs(index);
                       index = index % addrList.size();
                       String newAddr = addrList.get(index);
   
                       this.namesrvAddrChoosed.set(newAddr);
                       log.info("new name server is chosen. OLD: {} , NEW: {}. namesrvIndex = {}", addr, newAddr, namesrvIndex);
                       Channel channelNew = this.createChannel(newAddr);
                       if (channelNew != null) {
                           return channelNew;
                       }
                   }
                   throw new RemotingConnectException(addrList.toString());
               }
           } finally {
               this.lockNamesrvChannel.unlock();
           }
       } else {
           log.warn("getAndCreateNameserverChannel: try to lock name server, but timeout, {}ms", LOCK_TIMEOUT_MILLIS);
       }
   
       return null;
   }
   ```
   
   代码修改的地方如注释,这样即使客户端持有了不在NameServer列表中的链接,也会释放掉。


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] gaoyf edited a comment on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
gaoyf edited a comment on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-654047550


   > namesrv-a上的topic路由信息没了,client查询的时候namesrv会返回“No topic route info in name server for the topic”异常,client有一个数据保护机制是当查询异常时不会更新本地缓存的路由,所以至少会保留有一个broker关于这个topic的路由,理论上不会出现因为没有路由而收发不了消息的问题。
   
   @wqliang 对,你说的`至少会保留有一个broker关于这个topic的路由` 没错,但是注意,我说的是 无法**正常**生产消费,比如下面的场景:
   1. 对于生产者(只持有一个broker的路由,此时集群管理员可能不知道处于这样状态的生产者),如果这个时候broker更新(升级或下线)将影响消费发送。
   2. 对于消费者,同样的问题,某个实例可能只持有一个或几个broker的路由,那么这个实例将不再向其他broker(未持有路由的broker)发送心跳,这样导致的问题就是,其他同组的其他实例rebalance时拉取的clientId列表中将不包含这个有问题的实例,结果就是消息被重复消费。
   
   > 因此,上述修改也只是尽量加快client端对变化的生效,仍然没有解决更新步调不一致,broker先于client生效的问题。
   
   注意,官方之前的修复代码#2042,并不能完全解决上面说的有问题的情况,我最上面已经描述过了。而我说的解决方案,是最终一致性,即最终肯定会保持一致。
   
   > 提供一个思路,client和broker更新namesrv地址列表分开两个接口管理。。。
   
   另外,这个思路很不错,但是增加了运维的困难度。如果能在代码层面兼容岂不是更好些。
   
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] ShannonDing commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
ShannonDing commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-643084849


   link #2042 


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] gaoyf edited a comment on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
gaoyf edited a comment on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-654047550


   > namesrv-a上的topic路由信息没了,client查询的时候namesrv会返回“No topic route info in name server for the topic”异常,client有一个数据保护机制是当查询异常时不会更新本地缓存的路由,所以至少会保留有一个broker关于这个topic的路由,理论上不会出现因为没有路由而收发不了消息的问题。
   
   @wqliang 对,你说的`至少会保留有一个broker关于这个topic的路由` 没错,但是注意,我说的是 无法**正常**生产消费,比如下面的场景:
   1. 对于生产者(只持有一个broker的路由,此时集群管理员可能不知道处于这样状态的生产者),如果这个时候broker更新(升级或下线)将影响消费发送。
   2. 对于消费者,同样的问题,某个实例可能只持有一个或几个broker的路由,那么这个实例将不再向其他broker(未持有其路由的broker)发送心跳,这样导致的问题就是,其他同组的其他实例rebalance时拉取的clientId列表中将不包含这个有问题的实例,结果就是消息被重复消费。
   
   > 因此,上述修改也只是尽量加快client端对变化的生效,仍然没有解决更新步调不一致,broker先于client生效的问题。
   
   注意,官方之前的修复代码#2042,并不能完全解决上面说的有问题的情况,我最上面已经描述过了。而我说的解决方案,是最终一致性,即最终肯定会保持一致。
   
   > 提供一个思路,client和broker更新namesrv地址列表分开两个接口管理。。。
   
   另外,这个思路很不错,但是增加了运维的困难度。如果能在代码层面兼容岂不是更好些。
   
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] Garlichead edited a comment on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
Garlichead edited a comment on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-653275464


   > #### 一 先说一下我遇到的问题:
   > 我的NameServer列表采用的是域名发现的方式,以便热更新。但有一次在更新NameServer列表后,导致客户端无法生产和消费
   
   
   请教下 您这里说的 nameserver列表采用域名发现的方式 具体是什么意思哦? 为什么要 更新域名映射的NameServer列表为c,d,这具体是您的什么业务操作场景呢?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] Garlichead commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
Garlichead commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-653275464


   > #### 一 先说一下我遇到的问题:
   > 我的NameServer列表采用的是域名发现的方式,以便热更新。但有一次在更新NameServer列表后,导致客户端无法生产和消费
   请教下 您这里说的 nameserver列表采用域名发现的方式 具体是什么意思哦? 为什么要 更新域名映射的NameServer列表为c,d,这具体是您的什么业务操作场景呢?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] wqliang commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
wqliang commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-653734347


   该ISSUE描述的问题,我理解本质上是因为client、broker实例之间感知到namesrv地址列表变化并且生效的步调不一致,谁先生效谁后生效的问题。而@gaoyf 遇到的是broker先生效client后生效的场景,broker不再向namesrv-a报心跳,导致namesrv-a清除了该broker的路由,而此时client还连着namesrv-a。
   但对于分析中说的“但是此时NameServer a上的topic路由信息已经都没了,导致无法正常生产消费”我理解有点不一样,namesrv-a上的topic路由信息没了,client查询的时候namesrv会返回“No topic route info in name server for the topic”异常,client有一个数据保护机制是当查询异常时不会更新本地缓存的路由,所以至少会保留有一个broker关于这个topic的路由,理论上不会出现因为没有路由而收发不了消息的问题。
   因此,上述修改也只是尽量加快client端对变化的生效,仍然没有解决更新步调不一致,broker先于client生效的问题。


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] gaoyf commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
gaoyf commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-654076734


   > > #### 一 先说一下我遇到的问题:
   > > 我的NameServer列表采用的是域名发现的方式,以便热更新。但有一次在更新NameServer列表后,导致客户端无法生产和消费
   > 
   > 请教下 您这里说的 nameserver列表采用域名发现的方式 具体是什么意思哦? 为什么要 更新域名映射的NameServer列表为c,d,这具体是您的什么业务操作场景呢?
   
   请自行参考官方[最佳实践](https://github.com/apache/rocketmq/blob/master/docs/cn/best_practice.md#51-%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%AF%BB%E5%9D%80%E6%96%B9%E5%BC%8F)里的说明。


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] gaoyf commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
gaoyf commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-654047550


   > namesrv-a上的topic路由信息没了,client查询的时候namesrv会返回“No topic route info in name server for the topic”异常,client有一个数据保护机制是当查询异常时不会更新本地缓存的路由,所以至少会保留有一个broker关于这个topic的路由,理论上不会出现因为没有路由而收发不了消息的问题。
   
   @wqliang 对,你说的`至少会保留有一个broker关于这个topic的路由` 没错,可是这样会导致如下问题:
   1. 对于生产者(只持有一个broker的路由,此时集群管理员可能不知道处于这样状态的生产者),如果这个时候broker更新(升级或下线)将影响消费发送。
   2. 对于消费者,同样的问题,某个实例可能只持有一个或几个broker的路由,那么这个实例将不再向其他broker(未持有路由的broker)发送心跳,这样导致的问题就是,其他同组的其他实例rebalance时拉取的clientId列表中将不包含这个有问题的实例,结果就是消息被重复消费。
   
   > 因此,上述修改也只是尽量加快client端对变化的生效,仍然没有解决更新步调不一致,broker先于client生效的问题。
   
   注意,官方之前的修复代码#2042,并不能完全解决上面说的有问题的情况,我最上面已经描述过了。而我说的解决方案,是最终一致性,即最终肯定会保持一致。
   
   > 提供一个思路,client和broker更新namesrv地址列表分开两个接口管理。。。
   
   另外,这个思路很不错,但是增加了运维的困难度。如果能在代码层面兼容岂不是更好些。
   
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] gaoyf edited a comment on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
gaoyf edited a comment on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-654047550


   > namesrv-a上的topic路由信息没了,client查询的时候namesrv会返回“No topic route info in name server for the topic”异常,client有一个数据保护机制是当查询异常时不会更新本地缓存的路由,所以至少会保留有一个broker关于这个topic的路由,理论上不会出现因为没有路由而收发不了消息的问题。
   
   @wqliang 对,你说的`至少会保留有一个broker关于这个topic的路由` 没错,但是注意,我说的是 无法**正常**生产消费,比如下面的场景:
   1. 对于生产者(只持有一个broker的路由,此时集群管理员可能不知道处于这样状态的生产者),如果这个时候broker更新(升级或下线)将影响消费发送。
   2. 对于消费者,同样的问题,某个实例可能只持有一个或几个broker的路由,那么这个实例将不再向其他broker(未持有路由的broker)发送心跳,这样导致的问题就是,同组的其他实例rebalance时拉取的clientId列表中将不包含这个有问题的实例,结果就是消息被重复消费。
   
   > 因此,上述修改也只是尽量加快client端对变化的生效,仍然没有解决更新步调不一致,broker先于client生效的问题。
   
   注意,官方之前的修复代码#2042,并不能完全解决上面说的有问题的情况,我最上面已经描述过了。而我说的解决方案,是最终一致性,即最终肯定会保持一致。
   
   > 提供一个思路,client和broker更新namesrv地址列表分开两个接口管理。。。
   
   另外,这个思路很不错,但是增加了运维的困难度。如果能在代码层面兼容岂不是更好些。
   
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [rocketmq] wqliang commented on issue #2090: The same issue as #2042 during the hot update of name servers.

Posted by GitBox <gi...@apache.org>.
wqliang commented on issue #2090:
URL: https://github.com/apache/rocketmq/issues/2090#issuecomment-653734646


   提供一个思路,client和broker更新namesrv地址列表分开两个接口管理,并且client接口返回的列表是broker接口返回的列表的子集。有namesrv变更时,先更新client接口的返回,等2个周期(client查询接口的周期)后在更新broker接口的值,使得过程平滑。同理,上线新namesrv实例顺序则是反过来,先加到broker列表里,带broker都上报过心跳后,在加到client列表里。


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org