• 基于分布式锁解决分布式定时任务重复执行的方法


    遇到的问题及使用场景

    对将要过期(倒计时10天)的优惠券使用用户进行发短信提醒,每天上午10点发一次,发短信的服务有3个实例,如果不进行处理,用户每天会受到3条相同的短信,既影响用户体验,又浪费短信资源,那该怎么处理呢,我现在使用的Spring自带的定时任务组件Scheduled,可能Elastic-Job,XXL-Job开源框架已经很好的解决了这些问题,笔者只讨论是用Spring Task的情况。

    Redis分布式锁的构建

    @Slf4j
    public class RedisLock implements AutoCloseable {
    
        private RedisTemplate redisTemplate;
        private String key;
        private String value;
        //单位:秒
        private int expireTime;
    
        /**
         * @param redisTemplate
         * @param key
         * @param expireTime    过期时间
         */
        public RedisLock(RedisTemplate redisTemplate, String key, int expireTime) {
            this.redisTemplate = redisTemplate;
            this.key = key;
            this.expireTime = expireTime;
            this.value = UUID.randomUUID().toString();
        }
    
        /**
         * 获取分布式锁
         *
         * @return
         */
        public boolean getLock() {
            RedisCallback<Boolean> redisCallback = connection -> {
                //设置NX
                RedisStringCommands.SetOption setOption = RedisStringCommands.SetOption.ifAbsent();
                //设置过期时间
                Expiration expiration = Expiration.seconds(expireTime);
                //序列化key
                byte[] redisKey = redisTemplate.getKeySerializer().serialize(key);
                //序列化value
                byte[] redisValue = redisTemplate.getValueSerializer().serialize(value);
                //执行setnx操作(获取锁的操作)
                Boolean result = connection.set(redisKey, redisValue, expiration, setOption);
    
                // 也可以通过这种方式:
                //Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, redisValue);
                return result;
            };
    
            //获取分布式锁
            Boolean lock = (Boolean) redisTemplate.execute(redisCallback);
            return lock;
        }
    
        /**
         * 释放分布式锁
         *
         * @return
         */
        public boolean unLock() {
            String script = "if redis.call("get",KEYS[1]) == ARGV[1] then
    " +
                    "    return redis.call("del",KEYS[1])
    " +
                    "else
    " +
                    "    return 0
    " +
                    "end";
            RedisScript<Boolean> redisScript = RedisScript.of(script, Boolean.class);
            List<String> keys = Arrays.asList(key);
    
            Boolean result = (Boolean) redisTemplate.execute(redisScript, keys, value);
            if(result){
                log.info("线程{}释放锁成功",Thread.currentThread().getId(),result);
    
            }else{
                log.info("线程{}不能释放其他线程已抢到的锁,等待下一次定时任务执行",Thread.currentThread().getId());
            }
            return result;
        }
        
        @Override
        public void close() throws Exception {
            unLock();
        }
    
    }
    

    定时任务构建

    只举一个小的例子:

    @Service
    @Slf4j
    public class SchedulerService {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Scheduled(cron = "0/5 * * * * ?")
        public void sendSms() {
            try (RedisLock redisLock = new RedisLock(redisTemplate, "autoSms", 30)) {
                if (redisLock.getLock()) {
                    log.info("线程{}抢到了锁",Thread.currentThread().getId());
                    log.info("向138xxxxxxxx发送短信!");
                    Thread.sleep(3000);
                }else {
                    log.info("线程{}未抢到锁",Thread.currentThread().getId());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    任务执行分析

    源代码

    https://gitee.com/hardon123a/project-research/tree/master/redis/redis-distribute-lock

  • 相关阅读:
    RR调度(Round-robin scheduling)简单介绍
    iOS 应用内跳转到系统设置
    Python setup.py和MANIFEST.in文件
    HDU 1017 A Mathematical Curiosity (枚举水题)
    安卓获取软硬件信息并上传给server(Socket实现)
    杭电1869六度分离
    【原创】SM4password算法源代码接口具体解释
    MySQL经常使用命令--create命令使用
    移植opencv2.4.9到itop4412开发板
    Esper epl语句实验
  • 原文地址:https://www.cnblogs.com/huangjianping/p/15007627.html
Copyright © 2020-2023  润新知