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:
[email protected]


Reply via email to