背景
当我们的项目为了达到负载均衡或者备份的目的,服务通常会启动两个及以上的实例,但是如果服务中有定时任务,定时任务又不需要启动多个,只需要单实例运行,因此造成了矛盾。本主要讲解水平扩展后的服务,如何做定时任务的单实例互斥运行
环境
- springboot:在springboot环境下
- redis:配合redis进行
思路
- 利用spring的@Scheduled机制在项目中开启定时任务
- springboot集成redis后得到StringRedisTemplate
- 在定时任务执行前获取redis的锁
- 判断是否获取到锁?
- 如果获取到锁,设置锁的有效期(防止程序意外停止),执行任务,最后释放锁
- 如果未获取到锁,表明锁已经被其他的服务实例抢走,实例放弃定时任务
关键代码如下
/**
* 分布式定时任务的测试
* @author mateng
*/
@Component
public class SchedueService {
private static final Logger logger = LoggerFactory.getLogger(SchedueService.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
private final String key = "job";
private final String value = "running";
@Scheduled(fixedRate=2000)
public void testaaa() {
boolean lock = false;
try {
// 获取锁
lock = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
logger.debug("是否获取到锁:" + lock);
if (lock) {
// 如果在执行任务的过程中,程序突然挂了,为了避免程序因为中断而造成一直加锁的情况产生,20分钟后,key值失效,自动释放锁,
stringRedisTemplate.expire(key, 20, TimeUnit.MINUTES);
//执行定时任务
doSomething();
} else {
logger.debug("没有获取到锁,不执行任务!");
return;
}
} finally {// 无论如何,最终都要释放锁
if (lock) {// 如果获取了锁,则释放锁
stringRedisTemplate.delete(key);
logger.debug("任务结束,释放锁!");
} else {
logger.debug("没有获取到锁,无需释放锁!");
}
}
}
private void doSomething() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
展望
后期章节中将会讲解真正的分布式定时任务的实现,将一个定时任务拆解到多个服务中进行同时的定时任务执行