package com.appgree.core.redis.provider; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.pool2.BasePooledObjectFactory; import org.apache.commons.pool2.PooledObject; import org.apache.log4j.Logger; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPubSub; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.util.Pool; /** * The Class RedisSentinelPool. */ public class RedisSentinelPool extends Pool<Jedis> { /** The logger. */ private static Logger logger = Logger.getLogger(RedisSentinelPool.class); /** * The Class HostAndPort. */ public class HostAndPort { /** The host. */ String host; /** The port. */ int port; /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof HostAndPort) { final HostAndPort that = (HostAndPort) obj; return this.port == that.port && this.host.equals(that.host); } return false; } /** * Gets the host. * * @return the host */ public String getHost() { return host; } /** * Gets the port. * * @return the port */ public int getPort() { return port; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return host + ":" + port; } } /** * The Class PoolEntry. */ private class PoolEntry { /** The pool. */ private JedisPool pool; /** The master. */ private HostAndPort master; /** The notifiables. */ private List<RedisSentinelNotifiable> notifiables = new ArrayList<RedisSentinelNotifiable>(); /** * Adds the notifiable. * * @param notifiable the notifiable */ private void addNotifiable(RedisSentinelNotifiable notifiable) { this.notifiables.add(notifiable); } /** * Gets the notifiables. * * @return the notifiables */ private List<RedisSentinelNotifiable> getNotifiables() { return this.notifiables; } /** * Gets the pool. * * @return the pool */ private JedisPool getPool() { return pool; } /** * Gets the master. * * @return the master */ private HostAndPort getMaster() { return master; } /** * Sets the master. * * @param master the master * @param pool the pool */ private void setMaster(HostAndPort master, JedisPool pool) { this.pool = pool; this.master = master; } } // the master host map /** The pool map. */ private Map<String, PoolEntry> poolMap = new HashMap<String, RedisSentinelPool.PoolEntry>(); /** * A factory for creating jedis objects. */ private static class jedisFactory extends BasePooledObjectFactory<Jedis> { @Override public PooledObject<Jedis> makeObject() throws Exception { return null; } @Override public Jedis create() throws Exception { return null; } @Override public PooledObject<Jedis> wrap(Jedis obj) { return null; } } /** * The Class JedisPubSubAdapter. */ public class JedisPubSubAdapter extends JedisPubSub { /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onMessage(java.lang.String, java.lang.String) */ @Override public void onMessage(String channel, String message) { } /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onPMessage(java.lang.String, java.lang.String, java.lang.String) */ @Override public void onPMessage(String pattern, String channel, String message) { } /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onPSubscribe(java.lang.String, int) */ @Override public void onPSubscribe(String pattern, int subscribedChannels) { } /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onPUnsubscribe(java.lang.String, int) */ @Override public void onPUnsubscribe(String pattern, int subscribedChannels) { } /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onSubscribe(java.lang.String, int) */ @Override public void onSubscribe(String channel, int subscribedChannels) { } /* * (non-Javadoc) * * @see redis.clients.jedis.JedisPubSub#onUnsubscribe(java.lang.String, int) */ @Override public void onUnsubscribe(String channel, int subscribedChannels) { } } /** The pool config. */ private final JedisPoolConfig poolConfig; /** The sentinels. */ private Set<String> sentinels; /** The sentinels threads. */ private static Set<Thread> sentinelsThreads = new HashSet<Thread>(); /** The shutdown. */ private static volatile boolean shutdown = false; /** The pool timeout. */ private int poolTimeout; /** * Instantiates a new redis sentinel pool. * * @param poolConfig the pool config * @param sentinels the sentinels * @param poolTimeout the pool timeout */ public RedisSentinelPool(JedisPoolConfig config, Set<String> sentinels, int poolTimeout) { super(config, new jedisFactory()); this.poolConfig = null; this.sentinels = sentinels; this.poolTimeout = poolTimeout; // life cycle RedisSentinelPool.shutdown = false; // initializes sentinel listener initSentinels(); } /** * Unitialize sentinels threads. */ public static void uninit() { RedisSentinelPool.shutdown = true; // interrupt all threads for (Thread thread : RedisSentinelPool.sentinelsThreads) { thread.interrupt(); } // releases threads references RedisSentinelPool.sentinelsThreads.clear(); } /** * Inits a new notifiable for a new master. * * @param masterName the master name * @param notifiable the notifiable */ public void init(String masterName, RedisSentinelNotifiable notifiable) { HostAndPort master = getMasterByName(masterName); if (master == null) { log("Error initializing master" + masterName); return; } // adds a new entry initPool(masterName, master, notifiable); } /* * (non-Javadoc) * * @see redis.clients.util.Pool#destroy() */ public synchronized void destroy() { for (PoolEntry entry : poolMap.values()) { if (entry.getPool() != null) { entry.getPool().destroy(); } } } /** * Gets the current host master. * * @param masterName the master name * @return the current host master */ public synchronized HostAndPort getCurrentHostMaster(String masterName) { PoolEntry entry = poolMap.get(masterName); if (entry == null) { log("Error retrieving host and port for mastername " + masterName); return null; } return entry.getMaster(); } // Overwrite Pool<Jedis> /** * Gets the resource. * * @param masterName the master name * @return the resource */ public synchronized Jedis getResource(String masterName) { PoolEntry entry = poolMap.get(masterName); if (entry == null || entry.getPool() == null) { log("Error retrieving jedis pool for mastername " + masterName); return null; } return entry.getPool().getResource(); } /** * Inits the pool. * * @param masterName the master name * @param master the master * @param newNotifiable the new notifiable */ private synchronized void initPool(String masterName, HostAndPort master, RedisSentinelNotifiable newNotifiable) { if (masterName == null || master == null) { logger.error("Configuration error: null parameter in initPool"); return; } PoolEntry entry = poolMap.get(masterName); if (newNotifiable == null && entry.getMaster().equals(master)) { log("Master already notified. Ignoring new event for " + master); return; } // checks if already set if (entry != null && entry.getMaster() != null && entry.getMaster().equals(master)) { log("Pool already created. Reusing pool for master " + master); } else { JedisPool pool = new JedisPool(poolConfig, master.host, master.port, this.poolTimeout); if (entry == null) { log("Created pool: " + master); entry = new PoolEntry(); poolMap.put(masterName, entry); } // sets the new master entry.setMaster(master, pool); } if (newNotifiable != null) { // adds a new handler entry.addNotifiable(newNotifiable); // if we are processing a new notified we just notify the new one try { newNotifiable.poolChanged(entry.getPool()); } catch (Throwable e) { logger.error("Error notifying pool changing for mastername " + masterName, e); } return; } // notifies all handlers for (RedisSentinelNotifiable notifiable : entry.getNotifiables()) { try { notifiable.poolChanged(entry.getPool()); } catch (Throwable e) { logger.error("Error notifying pool changing for mastername " + masterName, e); } } } /** * Gets the master by name. * * @param masterName the master name * @return the master by name */ private HostAndPort getMasterByName(String masterName) { HostAndPort master = null; outer: while (RedisSentinelPool.shutdown == false) { for (String sentinel : this.sentinels) { final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":"))); final Jedis jedis = new Jedis(hap.host, hap.port, this.poolTimeout); try { if (master == null) { List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); if (masterAddr == null || masterAddr.size() < 1) { throw new RuntimeException("Error retrieving master name. Can't find master name from redis sentinel: " + masterName); } master = toHostAndPort(masterAddr); jedis.disconnect(); break outer; } } catch (JedisConnectionException e) { log("Cannot connect to sentinel running @ " + hap + ". Trying next one."); } finally { if (jedis != null) { jedis.close(); } } } try { log("All sentinels down, cannot determinate where is " + masterName + " master is running... sleeping 1000ms."); Thread.sleep(1000); } catch (InterruptedException e) { logger.warn("Thread interrupted.", e); } } log("Got master running at " + master + "."); return master; } /** * Inits the sentinels. */ private void initSentinels() { log("Starting sentinel listeners. Number of sentinels = " + this.sentinels.size()); for (String sentinel : this.sentinels) { log("Initializing sentinel thread monitor for sentinel " + sentinel); final HostAndPort hap = toHostAndPort(Arrays.asList(sentinel.split(":"))); Thread newThread = new Thread() { public void run() { while (RedisSentinelPool.shutdown == false) { final Jedis jedis = new Jedis(hap.host, hap.port, poolTimeout); try { jedis.subscribe(new JedisPubSubAdapter() { @Override public void onMessage(String channel, String message) { // System.out.println(channel + ": " + message); // +switch-master: mymaster 127.0.0.1 6379 127.0.0.1 6380 log("Sentinel " + hap + " published: " + message + "."); final String[] switchMasterMsg = message.split(" "); masterChangeHandler(switchMasterMsg[0], toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4]))); } }, "+switch-master"); } catch (JedisConnectionException e) { log("Lost connection to " + hap + ". Sleeping 5000ms."); try { Thread.sleep(5000); } catch (InterruptedException e1) { log("Thread interrupted"); } } catch (Throwable e) { if (RedisSentinelPool.shutdown) { log("Terminanting sentinel monitoring thread"); } // not interrupted. Dumps error logger.error("Error in sentinel monitoring thread", e); } finally { if (jedis != null) { jedis.close(); } } } }; }; // adds the thread to the set set RedisSentinelPool.sentinelsThreads.add(newThread); // start threads newThread.setDaemon(true); newThread.start(); } } /** * Master change handler. * * @param masterName the master name * @param newMaster the new master */ private void masterChangeHandler(String masterName, HostAndPort newMaster) { // re-initializes initPool(masterName, newMaster, null); } /** * Log. * * @param msg the msg */ private void log(String msg) { logger.info("Jedis Sentinel Pool: " + msg); } /** * Return resource object. * * @param masterName the master name * @param resource the resource */ public synchronized void returnResourceObject(String masterName, final Jedis resource) { PoolEntry entry = poolMap.get(masterName); if (entry == null || entry.getPool() == null) { log("Error retrieving pool entry for mastername " + masterName); } entry.getPool().returnResourceObject(resource); } /** * To host and port. * * @param getMasterAddrByNameResult the get master addr by name result * @return the host and port */ private HostAndPort toHostAndPort(List<String> getMasterAddrByNameResult) { final HostAndPort hap = new HostAndPort(); hap.host = getMasterAddrByNameResult.get(0); hap.port = Integer.parseInt(getMasterAddrByNameResult.get(1)); return hap; } /** * Gets the pool. * * @param masterName the master name * @return the pool */ public synchronized JedisPool getPool(String masterName) { PoolEntry entry = poolMap.get(masterName); if (entry == null || entry.getPool() == null) { log("Error retrieving pool entry for mastername " + masterName); } return entry.getPool(); } }