基于redis分布式锁解决定时任务重复问题

由于使用for update来实现分布式锁在高并发时,会造成数据库压力过大,所以可以使用redis的setNX特性来实现分布式锁。

封装redis分布式锁

package com.example.distributelock.lock;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.Expiration;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;

@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\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        RedisScript<Boolean> redisScript = RedisScript.of(script,Boolean.class);
        List<String> keys = Arrays.asList(key);

        Boolean result = (Boolean)redisTemplate.execute(redisScript, keys, value);
        log.info("释放锁的结果:"+result);
        return result;
    }


    @Override
    public void close() throws Exception {
    
    
        unLock();
    }
}

实现AutoCloseable接口并重写close()方法实现释放锁的操作,这样就不需要在finally中去释放锁。

在controller中使用redis分布式锁

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("redisLock")
    public String redisLock(){
    
    
        log.info("我进入了方法!");
        try (RedisLock redisLock = new RedisLock(redisTemplate,"redisKey",30)){
    
    
            if (redisLock.getLock()) {
    
    
                log.info("我进入了锁!!");
                Thread.sleep(15000);
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        log.info("方法执行完成");
        return "方法执行完成";
    }

直接将实例化RedisLock写在try的括号中,这是java1.7之后的特性,如果获得锁就进入锁,然后sleep模拟业务操作。

解决定时任务重复执行的问题

package com.example.distributelock.service;

import com.example.distributelock.lock.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@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("向138xxxxxxxx发送短信!");
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

}

猜你喜欢

转载自blog.csdn.net/qq_31776219/article/details/113822008