docs: @Local
      public CompletableFuture<Integer> sendAction(final WebSocketAction action, final Serializable... userids) {
          if (userids == null || userids.length < 1) return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
          if (this.localEngine != null && this.sncpNodeAddresses == null) { //本地模式且没有分布式
              return this.localEngine.sendLocalAction(action, userids);
          }
          CompletableFuture<Integer> rsfuture;
          if (userids.length == 1) {
              rsfuture = sendOneUserAction(action, userids[0]);
          } else {
              String[] keys = new String[userids.length];
              final Map<String, Serializable> keyuser = new HashMap<>();
              for (int i = 0; i < userids.length; i++) {
                  keys[i] = SOURCE_SNCP_USERID_PREFIX + userids[i];
                  keyuser.put(keys[i], userids[i]);
              }
              tryAcquireSemaphore();
              CompletableFuture<Map<String, Collection<InetSocketAddress>>> addrsFuture = sncpNodeAddresses.getCollectionMapAsync(InetSocketAddress.class, keys);
              if (semaphore != null) addrsFuture.whenComplete((r, e) -> releaseSemaphore());
              rsfuture = addrsFuture.thenCompose((Map<String, Collection<InetSocketAddress>> addrs) -> {
                  if (addrs == null || addrs.isEmpty()) {
                      if (logger.isLoggable(Level.FINER)) logger.finer("websocket not found userids:" + JsonConvert.root().convertTo(userids) + " on any node ");
                      return CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY);
                  }
                  Map<InetSocketAddress, List<Serializable>> addrUsers = new HashMap<>();
                  addrs.forEach((key, as) -> {
                      for (InetSocketAddress a : as) {
                          addrUsers.computeIfAbsent(a, k -> new ArrayList<>()).add(keyuser.get(key));
                      }
                  });
                  if (logger.isLoggable(Level.FINEST)) {
                      logger.finest("websocket(localaddr=" + localSncpAddress + ", userids=" + JsonConvert.root().convertTo(userids) + ") found action-userid-addrs: " + addrUsers);
                  }
                  CompletableFuture<Integer> future = null;
                  for (Map.Entry<InetSocketAddress, List<Serializable>> en : addrUsers.entrySet()) {
                      Serializable[] us = en.getValue().toArray(new Serializable[en.getValue().size()]);
                      future = future == null ? sendOneAddrAction(en.getKey(), action, us)
                          : future.thenCombine(sendOneAddrAction(en.getKey(), action, us), (a, b) -> a | b);
                  }
                  return future == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : future;
              });
          }
          return rsfuture == null ? CompletableFuture.completedFuture(RETCODE_GROUP_EMPTY) : rsfuture;
      }