public class Publisher {

  private static final Logger logger = LoggerFactory.getLogger(Publisher.class);

  private final Jedis publisherJedis;

  private final String channel;

  public Publisher(Jedis publisherJedis, String channel) {
    this.publisherJedis = publisherJedis;
    this.channel = channel;
  }

  public void start() {
    logger.info("Type your message (quit for terminate)");
    try {
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

      while (true) {
        String line = reader.readLine();

        if (!"quit".equals(line)) {
          publisherJedis.publish(channel, line);
        } else {
          break;
        }
      }

    } catch (IOException e) {
      logger.error("IO failure while reading input, e");
    }
  }
}
/** 通信层一些辅助方法 */
public class RemotingHelper {

  private static final Logger LOGGER = LoggerFactory.getLogger(RemotingHelper.class);

  /** IP:PORT */
  public static SocketAddress string2SocketAddress(final String addr) {
    String[] s = addr.split(":");
    return new InetSocketAddress(s[0], Integer.valueOf(s[1]));
  }

  public static String parseChannelRemoteAddr(final Channel channel) {
    if (null == channel) {
      return "";
    }
    final SocketAddress remote = channel.remoteAddress();
    final String addr = remote != null ? remote.toString() : "";

    if (addr.length() > 0) {
      int index = addr.lastIndexOf("/");
      if (index >= 0) {
        return addr.substring(index + 1);
      }

      return addr;
    }

    return "";
  }

  public static void closeChannel(Channel channel) {
    final String addrRemote = RemotingHelper.parseChannelRemoteAddr(channel);
    channel
        .close()
        .addListener(
            new ChannelHandlerListener() {
              @Override
              public void operationComplete(Future future) throws Exception {
                LOGGER.info(
                    "closeChannel: close the connection to remote address[{}] result: {}",
                    addrRemote,
                    future.isSuccess());
              }
            });
  }
}
/** @author Robert HG ([email protected]) on 11/6/15. */
public class MinaHandler extends IoHandlerAdapter {

  private static final Logger LOGGER = LoggerFactory.getLogger(RemotingHelper.RemotingLogName);

  private AbstractRemoting remoting;
  private String sideType; // SERVER , CLIENT

  public MinaHandler(AbstractRemoting remoting) {
    this.remoting = remoting;
    if (remoting instanceof MinaRemotingClient) {
      sideType = "CLIENT";
    } else {
      sideType = "SERVER";
    }
  }

  @Override
  public void sessionCreated(IoSession session) throws Exception {
    final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(new MinaChannel(session));
    LOGGER.info("{} : sessionCreated {}", sideType, remoteAddress);
    super.sessionCreated(session);
  }

  @Override
  public void sessionOpened(IoSession session) throws Exception {
    Channel channel = new MinaChannel(session);
    final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(channel);
    LOGGER.info("{}: sessionOpened, the channel[{}]", sideType, remoteAddress);

    if (remoting.getChannelEventListener() != null) {
      remoting.putRemotingEvent(
          new RemotingEvent(RemotingEventType.CONNECT, remoteAddress, channel));
    }
  }

  @Override
  public void sessionClosed(IoSession session) throws Exception {
    com.github.ltsopensource.remoting.Channel channel = new MinaChannel(session);

    final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(channel);
    LOGGER.info("{}: sessionClosed, the channel[{}]", sideType, remoteAddress);

    if (remoting.getChannelEventListener() != null) {
      remoting.putRemotingEvent(new RemotingEvent(RemotingEventType.CLOSE, remoteAddress, channel));
    }
  }

  @Override
  public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
    com.github.ltsopensource.remoting.Channel channel = new MinaChannel(session);

    final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(channel);

    if (IdleStatus.BOTH_IDLE == status) {
      LOGGER.info("{}: IDLE [{}]", sideType, remoteAddress);
      RemotingHelper.closeChannel(channel);
    }

    if (remoting.getChannelEventListener() != null) {
      RemotingEventType remotingEventType = null;
      if (IdleStatus.BOTH_IDLE == status) {
        remotingEventType = RemotingEventType.ALL_IDLE;
      } else if (IdleStatus.READER_IDLE == status) {
        remotingEventType = RemotingEventType.READER_IDLE;
      } else if (IdleStatus.WRITER_IDLE == status) {
        remotingEventType = RemotingEventType.WRITER_IDLE;
      }
      remoting.putRemotingEvent(new RemotingEvent(remotingEventType, remoteAddress, channel));
    }
  }

  @Override
  public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
    com.github.ltsopensource.remoting.Channel channel = new MinaChannel(session);

    final String remoteAddress = RemotingHelper.parseChannelRemoteAddr(channel);
    LOGGER.warn("{}: exceptionCaught {}. ", sideType, remoteAddress, cause);

    if (remoting.getChannelEventListener() != null) {
      remoting.putRemotingEvent(
          new RemotingEvent(RemotingEventType.EXCEPTION, remoteAddress, channel));
    }

    RemotingHelper.closeChannel(channel);
  }

  @Override
  public void messageReceived(IoSession session, Object message) throws Exception {
    if (message != null && message instanceof RemotingCommand) {
      remoting.processMessageReceived(new MinaChannel(session), (RemotingCommand) message);
    }
  }
}
/** @author Robert HG ([email protected]) on 5/17/15. */
public class RedisRegistry extends FailbackRegistry {

  private static final Logger LOGGER = LoggerFactory.getLogger(RedisRegistry.class);

  private final Map<String, JedisPool> jedisPools = new ConcurrentHashMap<String, JedisPool>();

  private String clusterName;
  private final ScheduledExecutorService expireExecutor =
      Executors.newScheduledThreadPool(
          1, new NamedThreadFactory("LTSRedisRegistryExpireTimer", true));
  private final ScheduledFuture<?> expireFuture;
  private final int expirePeriod;
  private boolean replicate;
  private final int reconnectPeriod;
  private final ConcurrentMap<String, Notifier> notifiers =
      new ConcurrentHashMap<String, Notifier>();
  private RedisLock lock;

  public RedisRegistry(AppContext appContext) {
    super(appContext);
    Config config = appContext.getConfig();
    this.clusterName = config.getClusterName();
    this.lock = new RedisLock("LTS_CLEAN_LOCK_KEY", config.getIdentity(), 2 * 60); // 锁两分钟过期

    JedisPoolConfig redisConfig = new JedisPoolConfig();
    // TODO 可以设置n多参数
    String address = NodeRegistryUtils.getRealRegistryAddress(config.getRegistryAddress());

    String cluster = config.getParameter("cluster", "failover");
    if (!"failover".equals(cluster) && !"replicate".equals(cluster)) {
      throw new IllegalArgumentException(
          "Unsupported redis cluster: "
              + cluster
              + ". The redis cluster only supported failover or replicate.");
    }
    replicate = "replicate".equals(cluster);

    this.reconnectPeriod =
        config.getParameter(
            ExtConfig.REGISTRY_RECONNECT_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RECONNECT_PERIOD);

    String[] addrs = address.split(",");
    for (String addr : addrs) {
      int i = addr.indexOf(':');
      String host = addr.substring(0, i);
      int port = Integer.parseInt(addr.substring(i + 1));
      this.jedisPools.put(addr, new JedisPool(redisConfig, host, port, Constants.DEFAULT_TIMEOUT));
    }

    this.expirePeriod =
        config.getParameter(ExtConfig.REDIS_SESSION_TIMEOUT, Constants.DEFAULT_SESSION_TIMEOUT);

    this.expireFuture =
        expireExecutor.scheduleWithFixedDelay(
            new Runnable() {
              public void run() {
                try {
                  deferExpired(); // 延长过期时间
                } catch (Throwable t) { // 防御性容错
                  LOGGER.error(
                      "Unexpected exception occur at defer expire time, cause: " + t.getMessage(),
                      t);
                }
              }
            },
            expirePeriod / 2,
            expirePeriod / 2,
            TimeUnit.MILLISECONDS);
  }

  private void deferExpired() {
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
      JedisPool jedisPool = entry.getValue();
      try {
        Jedis jedis = jedisPool.getResource();
        try {
          for (Node node : new HashSet<Node>(getRegistered())) {
            String key = NodeRegistryUtils.getNodeTypePath(clusterName, node.getNodeType());
            if (jedis.hset(
                    key, node.toFullString(), String.valueOf(SystemClock.now() + expirePeriod))
                == 1) {
              jedis.publish(key, Constants.REGISTER);
            }
          }
          if (lock.acquire(jedis)) {
            clean(jedis);
          }
          if (!replicate) {
            break; //  如果服务器端已同步数据,只需写入单台机器
          }
        } finally {
          jedis.close();
        }
      } catch (Throwable t) {
        LOGGER.warn(
            "Failed to write provider heartbeat to redis registry. registry: "
                + entry.getKey()
                + ", cause: "
                + t.getMessage(),
            t);
      }
    }
  }

  private void clean(Jedis jedis) {
    // /LTS/{集群名字}/NODES/
    Set<String> nodeTypePaths =
        jedis.keys(NodeRegistryUtils.getRootPath(appContext.getConfig().getClusterName()) + "/*");
    if (CollectionUtils.isNotEmpty(nodeTypePaths)) {
      for (String nodeTypePath : nodeTypePaths) {
        // /LTS/{集群名字}/NODES/JOB_TRACKER
        Set<String> nodePaths = jedis.keys(nodeTypePath);
        if (CollectionUtils.isNotEmpty(nodePaths)) {
          for (String nodePath : nodePaths) {
            Map<String, String> nodes = jedis.hgetAll(nodePath);
            if (CollectionUtils.isNotEmpty(nodes)) {
              boolean delete = false;
              long now = SystemClock.now();
              for (Map.Entry<String, String> entry : nodes.entrySet()) {
                String key = entry.getKey();
                long expire = Long.parseLong(entry.getValue());
                if (expire < now) {
                  jedis.hdel(nodePath, key);
                  delete = true;
                  if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn(
                        "Delete expired key: "
                            + nodePath
                            + " -> value: "
                            + entry.getKey()
                            + ", expire: "
                            + new Date(expire)
                            + ", now: "
                            + new Date(now));
                  }
                }
              }
              if (delete) {
                jedis.publish(nodePath, Constants.UNREGISTER);
              }
            }
          }
        }
      }
    }
  }

  @Override
  protected void doRegister(Node node) {
    String key = NodeRegistryUtils.getNodeTypePath(clusterName, node.getNodeType());
    String expire = String.valueOf(SystemClock.now() + expirePeriod);
    boolean success = false;
    NodeRegistryException exception = null;
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
      JedisPool jedisPool = entry.getValue();
      try {
        Jedis jedis = jedisPool.getResource();
        try {
          jedis.hset(key, node.toFullString(), expire);
          jedis.publish(key, Constants.REGISTER);
          success = true;
          if (!replicate) {
            break; //  如果服务器端已同步数据,只需写入单台机器
          }
        } finally {
          jedis.close();
        }
      } catch (Throwable t) {
        exception =
            new NodeRegistryException(
                "Failed to register node to redis registry. registry: "
                    + entry.getKey()
                    + ", node: "
                    + node
                    + ", cause: "
                    + t.getMessage(),
                t);
      }
    }
    if (exception != null) {
      if (success) {
        LOGGER.warn(exception.getMessage(), exception);
      } else {
        throw exception;
      }
    }
  }

  @Override
  protected void doUnRegister(Node node) {
    String key = NodeRegistryUtils.getNodeTypePath(clusterName, node.getNodeType());
    boolean success = false;
    NodeRegistryException exception = null;
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
      JedisPool jedisPool = entry.getValue();
      try {
        Jedis jedis = jedisPool.getResource();
        try {
          jedis.hdel(key, node.toFullString());
          jedis.publish(key, Constants.UNREGISTER);
          success = true;
          if (!replicate) {
            break; //  如果服务器端已同步数据,只需写入单台机器
          }
        } finally {
          jedis.close();
        }
      } catch (Throwable t) {
        exception =
            new NodeRegistryException(
                "Failed to unregister node to redis registry. registry: "
                    + entry.getKey()
                    + ", node: "
                    + node
                    + ", cause: "
                    + t.getMessage(),
                t);
      }
    }
    if (exception != null) {
      if (success) {
        LOGGER.warn(exception.getMessage(), exception);
      } else {
        throw exception;
      }
    }
  }

  @Override
  protected void doSubscribe(Node node, NotifyListener listener) {

    List<NodeType> listenNodeTypes = node.getListenNodeTypes();
    if (CollectionUtils.isEmpty(listenNodeTypes)) {
      return;
    }
    for (NodeType listenNodeType : listenNodeTypes) {
      String listenNodePath = NodeRegistryUtils.getNodeTypePath(clusterName, listenNodeType);

      Notifier notifier = notifiers.get(listenNodePath);
      if (notifier == null) {
        Notifier newNotifier = new Notifier(listenNodePath);
        notifiers.putIfAbsent(listenNodePath, newNotifier);
        notifier = notifiers.get(listenNodePath);
        if (notifier == newNotifier) {
          notifier.start();
        }
      }

      boolean success = false;
      NodeRegistryException exception = null;
      for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
        JedisPool jedisPool = entry.getValue();
        try {
          Jedis jedis = jedisPool.getResource();
          try {
            doNotify(
                jedis,
                Collections.singletonList(listenNodePath),
                Collections.singletonList(listener));
            success = true;
            break; // 只需读一个服务器的数据

          } finally {
            jedis.close();
          }
        } catch (Throwable t) {
          exception =
              new NodeRegistryException(
                  "Failed to unregister node to redis registry. registry: "
                      + entry.getKey()
                      + ", node: "
                      + node
                      + ", cause: "
                      + t.getMessage(),
                  t);
        }
      }
      if (exception != null) {
        if (success) {
          LOGGER.warn(exception.getMessage(), exception);
        } else {
          throw exception;
        }
      }
    }
  }

  @Override
  protected void doUnsubscribe(Node node, NotifyListener listener) {}

  @Override
  public void destroy() {
    super.destroy();
    try {
      expireFuture.cancel(true);
    } catch (Throwable t) {
      LOGGER.warn(t.getMessage(), t);
    }
    try {
      for (Notifier notifier : notifiers.values()) {
        notifier.shutdown();
      }
    } catch (Throwable t) {
      LOGGER.warn(t.getMessage(), t);
    }
    for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
      JedisPool jedisPool = entry.getValue();
      try {
        jedisPool.destroy();
      } catch (Throwable t) {
        LOGGER.warn(
            "Failed to destroy the redis registry client. registry: "
                + entry.getKey()
                + ", cause: "
                + t.getMessage(),
            t);
      }
    }
  }

  private ConcurrentHashMap<String /*key*/, List<String>> cachedNodeMap =
      new ConcurrentHashMap<String, List<String>>();

  private void doNotify(
      Jedis jedis, Collection<String> keys, Collection<NotifyListener> listeners) {
    if (CollectionUtils.isEmpty(keys) && CollectionUtils.isEmpty(listeners)) {
      return;
    }

    for (String key : keys) {

      Map<String, String> values = jedis.hgetAll(key);
      List<String> currentChildren =
          values == null ? new ArrayList<String>(0) : new ArrayList<String>(values.keySet());
      List<String> oldChildren = cachedNodeMap.get(key);

      // 1. 找出增加的 节点
      List<String> addChildren = CollectionUtils.getLeftDiff(currentChildren, oldChildren);
      // 2. 找出减少的 节点
      List<String> decChildren = CollectionUtils.getLeftDiff(oldChildren, currentChildren);

      if (CollectionUtils.isNotEmpty(addChildren)) {
        List<Node> nodes = new ArrayList<Node>(addChildren.size());
        for (String child : addChildren) {
          Node node = NodeRegistryUtils.parse(child);
          nodes.add(node);
        }
        for (NotifyListener listener : listeners) {
          notify(NotifyEvent.ADD, nodes, listener);
        }
      }
      if (CollectionUtils.isNotEmpty(decChildren)) {
        List<Node> nodes = new ArrayList<Node>(decChildren.size());
        for (String child : decChildren) {
          Node node = NodeRegistryUtils.parse(child);
          nodes.add(node);
        }
        for (NotifyListener listener : listeners) {
          notify(NotifyEvent.REMOVE, nodes, listener);
        }
      }
      cachedNodeMap.put(key, currentChildren);
    }
  }

  private void doNotify(Jedis jedis, String key) {
    for (Map.Entry<Node, Set<NotifyListener>> entry :
        new HashMap<Node, Set<NotifyListener>>(getSubscribed()).entrySet()) {
      doNotify(
          jedis, Collections.singletonList(key), new HashSet<NotifyListener>(entry.getValue()));
    }
  }

  private class NotifySub extends JedisPubSub {

    private final JedisPool jedisPool;

    public NotifySub(JedisPool jedisPool) {
      this.jedisPool = jedisPool;
    }

    @Override
    public void onMessage(String key, String msg) {
      if (LOGGER.isInfoEnabled()) {
        LOGGER.info("redis event: " + key + " = " + msg);
      }
      if (msg.equals(Constants.REGISTER) || msg.equals(Constants.UNREGISTER)) {
        try {
          Jedis jedis = jedisPool.getResource();
          try {
            doNotify(jedis, key);
          } finally {
            jedis.close();
          }
        } catch (Throwable t) {
          LOGGER.error(t.getMessage(), t);
        }
      }
    }
  }

  // 用这个线程来监控redis是否可用
  private volatile String monitorId;
  private volatile boolean redisAvailable = false;

  private class Notifier extends Thread {

    private final String listenNodePath;

    private volatile Jedis jedis;

    private volatile boolean running = true;

    public Notifier(String listenNodePath) {
      super.setDaemon(true);
      super.setName("LTSRedisSubscribe");
      this.listenNodePath = listenNodePath;
      if (monitorId == null) {
        monitorId = listenNodePath;
      }
    }

    @Override
    public void run() {
      try {
        while (running) {
          int retryTimes = 0;
          for (Map.Entry<String, JedisPool> entry : jedisPools.entrySet()) {
            try {
              JedisPool jedisPool = entry.getValue();
              jedis = jedisPool.getResource();
              if (listenNodePath.equals(monitorId) && !redisAvailable) {
                redisAvailable = true;
                appContext.getRegistryStatMonitor().setAvailable(redisAvailable);
              }
              try {
                retryTimes = 0;
                jedis.subscribe(new NotifySub(jedisPool), listenNodePath); // 阻塞
                break;
              } finally {
                jedis.close();
              }
            } catch (Throwable t) { // 重试另一台
              LOGGER.warn(
                  "Failed to subscribe node from redis registry. registry: " + entry.getKey(), t);
              if (++retryTimes % jedisPools.size() == 0) {
                // 如果在所有redis都不可用,需要休息一会,避免空转占用过多cpu资源
                sleep(reconnectPeriod);
                if (listenNodePath.equals(monitorId) && redisAvailable) {
                  redisAvailable = false;
                  appContext.getRegistryStatMonitor().setAvailable(redisAvailable);
                }
              }
            }
          }
        }
      } catch (Throwable t) {
        LOGGER.error(t.getMessage(), t);
      }
    }

    public void shutdown() {
      try {
        running = false;
        jedis.disconnect();
      } catch (Throwable t) {
        LOGGER.warn(t.getMessage(), t);
      }
    }
  }
}