产品需求:要求定时扫描超过10天,对抓中奖品进行处理的游戏记录,系统自动转换成娃娃币,同时在消费明细和消息中心提醒用户。
由于服务都是集群部署,定时任务会在各个服务器上重复执行,轻则浪费服务器资源,重则造成数据紊乱。
quartz定时任务提供了集群方式,但还需为其创建数据库表。spring schedule可以实现简单轻量级的定时任务,但无法保证只执行一次。
通过和架构师探讨得知,可以使用redis的分布式锁保证spring schedule集群只执行一次。
redis分布式锁是通过setnx命令实现的。该命令的作用是,当往redis中存入一个值时,会先判断该值对应的key是否存在,如果存在则返回0,如果不存在,则将该值存入redis并返回1。根据这个特性,我们在程序中,每次都调用setIfAbsent(该方法是setnx命令的实现)方法,来模拟是否获取到锁,如果返回true,则说明该key值不存在,表示获取到锁;如果返回false,则说明该key值存在,已经有程序在使用这个key值了,从而实现了类似加锁的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 1@Component
2@Configuration
3@EnableScheduling
4public class AutoConvertTask {
5 private static final Logger logger = LoggerFactory.getLogger(AutoConvertTask.class);
6
7 @Autowired
8 private RedisTemplate redisTemplate;
9
10 private static final String LOCK = "task-job-lock";
11
12 private static final String KEY = "tasklock";
13
14 @Scheduled(cron = "0 0 0 * * ? ")
15 public void autoConvertJob() {
16 boolean lock = false;
17 try {
18 lock = redisTemplate.opsForValue().setIfAbsent(KEY, LOCK);
19 logger.info("是否获取到锁:" + lock);
20 if (lock) {
21 List<GameHistory> historyList = historyService.findTenDaysAgoUntreated();
22 for (GameHistory history : historyList) {
23 update(history);
24 }
25 } else {
26 logger.info("没有获取到锁,不执行任务!");
27 return;
28 }
29 } finally {
30 if (lock) {
31 redisTemplate.delete(KEY);
32 logger.info("任务结束,释放锁!");
33 } else {
34 logger.info("没有获取到锁,无需释放锁!");
35 }
36 }
37
38 }
39
40}
41
42