通过redis选主进程
选主进程的方式有很多,完善点的方案就是zookeeper来做。这里介绍的是一种基于redis选主的方案,快速开发,快速实现。Springside4里有个MasterElector类,细读代码后整理出自己的总结如下。1. 业务进程启动时生成hostId,hostId的规则是“主机名-随机数”,一台主机部署多个进程实例的情况,也可以改为“主机名-端口”。2. 业务进程调
选主进程的方式有很多,完善点的方案就是zookeeper来做。这里介绍的是一种基于redis选主的方案,快速开发,快速实现。
Springside4里有个MasterElector类,细读代码后整理出自己的总结如下。
1. 业务进程启动时生成hostId,hostId的规则是“主机名-随机数”,一台主机部署多个进程实例的情况,也可以改为“主机名-端口”。
2. 业务进程调用jedis.get(masterKey)获取masterFromRedis。
3. 如果masterFromRedis为空,调用setnx(masterKey, hostId)方法设值,方法返回值大于0当前进程选为主进程,并且expire(masterKey,expireSeconds)设置key的有效期。Setnx返回值小于等于0时不能选为主进程。
如果masterFromRedis等于hostId,当前进程选为主进程,并且expire(masterKey, expireSeconds)设置key的有效期。
4. 按时间间隔intervalSeconds定期执行第二步和第三步。
当get(masterKey)之后获取到的masterFromRdis为空,在setnx之前,有其他进程已经Set了masterKey的值,本进程再调用setnx返回0失败.reids api文档有详细介绍:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。
intervalSeconds定时时间间隔设置短些,可以减少异常情况下没有主的情况。
附上springside4的代码
public class MasterElector implementsRunnable {
publicstatic final String DEFAULT_MASTER_KEY = "master";
privatestatic Logger logger = LoggerFactory.getLogger(MasterElector.class);
privatestatic AtomicInteger poolNumber = new AtomicInteger(1);
privateScheduledExecutorService internalScheduledThreadPool;
privateScheduledFuture electorJob;
privateint intervalSeconds;
privateJedisTemplate jedisTemplate;
privateint expireSeconds;
privateString hostId;
privateAtomicBoolean master = new AtomicBoolean(false);
privateString masterKey = DEFAULT_MASTER_KEY;
publicMasterElector(JedisPool jedisPool, int intervalSeconds, int expireSeconds) {
jedisTemplate= new JedisTemplate(jedisPool);
this.expireSeconds= expireSeconds;
this.intervalSeconds= intervalSeconds;
}
/**
* 发挥目前该实例是否master
*/
publicboolean isMaster() {
returnmaster.get();
}
/**
* 启动抢注线程, 自行创建scheduler线程池.
*/
publicvoid start() {
internalScheduledThreadPool= Executors.newScheduledThreadPool(1,
Threads.buildJobFactory("Master-Elector-"+ poolNumber.getAndIncrement() + "-%d"));
start(internalScheduledThreadPool);
}
/**
* 启动抢注线程, 使用传入的scheduler线程池.
*/
publicvoid start(ScheduledExecutorService scheduledThreadPool) {
if(intervalSeconds >= expireSeconds) {
thrownew IllegalArgumentException("periodSeconds must less than expireSeconds.periodSeconds is "
+intervalSeconds + " expireSeconds is " + expireSeconds);
}
hostId= generateHostId();
electorJob= scheduledThreadPool.scheduleAtFixedRate(new WrapExceptionRunnable(this), 0,intervalSeconds,
TimeUnit.SECONDS);
logger.info("masterElectorstart, hostName:{}.", hostId);
}
/**
* 停止分发任务,如果是自行创建的threadPool则自行销毁。
*/
publicvoid stop() {
electorJob.cancel(false);
if(internalScheduledThreadPool != null) {
Threads.normalShutdown(internalScheduledThreadPool,5, TimeUnit.SECONDS);
}
}
/**
* 生成host id的方法哦,可在子类重载.
*/
protectedString generateHostId() {
Stringhost = "localhost";
try{
host= InetAddress.getLocalHost().getHostName();
}catch (UnknownHostException e) {
logger.warn("cannot get hostName", e);
}
host= host + "-" + new SecureRandom().nextInt(10000);
returnhost;
}
@Override
publicvoid run() {
jedisTemplate.execute(newJedisActionNoResult() {// NOSONAR
@Override
publicvoid action(Jedis jedis) {
StringmasterFromRedis = jedis.get(masterKey);
logger.debug("masteris {}", masterFromRedis);
//if master is null, the cluster just start or the master had crashed, try toregister myself
//as master
if(masterFromRedis == null) {
//use setnx to make sure only one client can register as master.
if(jedis.setnx(masterKey, hostId) > 0) {
jedis.expire(masterKey,expireSeconds);
master.set(true);
logger.info("masteris changed to {}.", hostId);
return;
}else {
master.set(false);
return;
}
}
//if master is myself, update the expire time.
if(hostId.equals(masterFromRedis)) {
jedis.expire(masterKey,expireSeconds);
master.set(true);
return;
}
master.set(false);
}
});
}
/**
* 如果应用中有多种master,设置唯一的mastername
*/
publicvoid setMasterKey(String masterKey) {
this.masterKey= masterKey;
}
//for test
publicvoid setHostId(String hostId) {
this.hostId= hostId;
}
}
更多推荐
所有评论(0)