对于很多系统而言,都有集群处理。在集群中使用quartz或者task处理任务的时候,一般有三种选择:
1. 多实例顺序执行
2. 单实例执行
3. 多实例并行执行
对于2,需要单独的实例进行部署运行,如果任务较多,需要独立的资源耗费较多,对于执行任务的实例要求就更高。
对于3,需要成熟的数据驱动式的任务分配,防止多实例重复执行。
2和3会在后续分享中进行。
对于多实例,同一数据库的情况下。创保目前考虑的是1情况。
redis对于并发较小的情况下,进行锁控制,效果是较为理想的。目前创保只有八台实例,适用于该方案。
1. 首先,redis提供了一个setnx方法,该方法执行后会返回key值在设置之前是否存在。
这是进行并发控制的基础,但并不够。
2. 在利用 setnx 进行 key 值设定后,如果instance发生异常(不仅仅是exception)会导致该锁无法正常释放。
所以,我们需要为锁设置一个失效时间,即expire命令。
由于redis有顺序执行命令的特性,如果我们setnx方法与expire命令并不是以一个原子粒度执行的话,将会导致一些不可控的现象发生。
所以,我们3. 使用了multi方法进行命令绑定,将setnx与expire进行绑定,保障同一原子粒度执行。
同时,4. 为了避免不必要的设置损耗,同时为了兼容对于锁的value值进行特别设置的情况,我们进行了exists判断。
整体代码进行整合,方法如下:
public boolean setLock(String key, int seconds, String value) throws BusinessServiceException {
boolean isBroken = false;
Jedis jedis = null;
try {
jedis = this.jedisPool.getResource();
boolean exists = jedis.exists(key);
if (exists) {
return false;
}
Transaction transaction = jedis.multi();
Response<Long> result = transaction.setnx(key, value);
transaction.expire(key, seconds);
transaction.exec();
return result.get() == 1;
} catch (Exception e) {
logger.error("redis getLock error" + e.getMessage());
logger.error(e);
return false;
} finally {
this.releaseJedis(jedis, isBroken);
}
}
同时,在使用该锁的时候,也有一些规范需要遵守。
在获取到锁之后,才能进行业务逻辑处理。
在业务逻辑处理之后,必须手动释放锁。
未获取到锁的instance不允许释放锁。
过期时间必须设置,推荐设置时间为业务逻辑执行时间的1.5倍左右或任务间隔的2~3倍左右。
示例代码如下:
boolean result = false;
try {
result = redisService.setLock(TIMER_NAME, EXPIRE_TIME, LOCK_STATUE);
} catch (BusinessServiceException e) {
logger.error("timer get redis lock error:" + e.getMessage(), e);
}
if (result) {
try {
//此处执行业务逻辑
} catch (Exception e) {
logger.error("syncPolicy error:" + e.getMessage(), e);
} finally {
try {
redisService.delRedisValue(TIMER_NAME);
} catch (BusinessServiceException e) {
logger.error("timer relase redis lock error:" + e.getMessage(), e);
}
}
}
对于2和3的方案,后续再进行分享。