/******************************************************************************* * Copyright (c) 2005, 2014 springside.github.io * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ package org.springside.modules.nosql.redis.service.elector; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.SecureRandom; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springside.modules.nosql.redis.JedisTemplate; import org.springside.modules.nosql.redis.JedisTemplate.JedisActionNoResult; import org.springside.modules.nosql.redis.JedisUtils; import org.springside.modules.nosql.redis.pool.JedisPool; import org.springside.modules.utils.Threads; import org.springside.modules.utils.Threads.WrapExceptionRunnable; import redis.clients.jedis.Jedis; /** * Master选举实现, 基于setNx()与expire()两大API. * 与每次使用setNx争夺的分布式锁不同,Master用setNX争夺Master Key成功后,会不断的更新key的expireTime,保持自己的master地位,直到自己倒掉了不能再更新为止。 * 其他Slave会定时检查Master Key是否已过期,如果已过期则重新发起争夺。 * * 其他服务可以随时调用isMaster(),查询自己是否master, 与MasterElector的内部定时操作是解耦的。 * * 在最差情况下,可能有两倍的intervalSecs内集群内没有Master。 * * @author calvin */ public class MasterElector implements Runnable { public static final String DEFAULT_MASTER_KEY = "master"; private static Logger logger = LoggerFactory.getLogger(MasterElector.class); private ScheduledExecutorService internalScheduledThreadPool; private ScheduledFuture electorJob; private int intervalSecs; private int expireSecs; private JedisTemplate jedisTemplate; private String hostId; private String masterKey = DEFAULT_MASTER_KEY; private AtomicBoolean master = new AtomicBoolean(false); public MasterElector(JedisPool jedisPool, int intervalSecs) { this.jedisTemplate = new JedisTemplate(jedisPool); this.intervalSecs = intervalSecs; this.expireSecs = intervalSecs + (intervalSecs / 2); } /** * 返回当前实例是否master。 */ public boolean isMaster() { return master.get(); } /** * 启动抢注线程, 自行创建scheduler线程池. */ public void start() { internalScheduledThreadPool = Executors.newScheduledThreadPool(1, Threads.buildJobFactory("Master-Elector-" + masterKey + "-%d")); start(internalScheduledThreadPool); } /** * 启动抢注线程, 使用传入的scheduler线程池. */ public void start(ScheduledExecutorService scheduledThreadPool) { hostId = generateHostId(); electorJob = scheduledThreadPool.scheduleAtFixedRate(new WrapExceptionRunnable(this), 0, intervalSecs, TimeUnit.SECONDS); logger.info("masterElector for {} start, hostName:{}.", masterKey, hostId); } /** * 停止抢注线程, * 如果是master, 则主动注销key. * 如果是自行创建的threadPool则自行销毁,最多5秒超时. */ public void stop() { if (master.get()) { jedisTemplate.del(masterKey); } electorJob.cancel(false); if (internalScheduledThreadPool != null) { Threads.normalShutdown(internalScheduledThreadPool, 5, TimeUnit.SECONDS); } } /** * 生成host id的方法,可在子类重载. */ protected String generateHostId() { String host = "localhost"; try { host = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { logger.warn("can not get hostName, use localhost as default.", e); } host = host + "-" + new SecureRandom().nextInt(10000); return host; } @Override public void run() { try { jedisTemplate.execute(new JedisActionNoResult() { @Override public void action(Jedis jedis) { String masterFromRedis = jedis.get(masterKey); logger.debug("master {} is {}", masterKey, masterFromRedis); // 如果masterKey返回值为空,证明集群刚重启 或master已crash,尝试注册为Master. if (masterFromRedis == null) { // 使用setnx,保证只有一个Client能注册为Master. if (JedisUtils.isStatusOk(jedis.set(masterKey, hostId, "NX", "EX", expireSecs))) { master.set(true); logger.info("master {} is changed to {}.", masterKey, hostId); return; } else { master.set(false); return; } } // 如果我已是master,更新key的超时时间 if (hostId.equals(masterFromRedis)) { jedis.expire(masterKey, expireSecs); master.set(true); } else { master.set(false); } } }); } catch (Throwable e) { // catch any exception, because the scheduled thread will break if the exception thrown outside. logger.error("Unexpected error occurred in task", e); } } /** * 如果应用中有多种master,设置唯一的master name。 */ public void setMasterKey(String masterKey) { this.masterKey = masterKey; } // for test void setHostId(String hostId) { this.hostId = hostId; } }