分布式锁-lua脚本
// 工具类
@Component public class RedisLock { @Autowired private RedisTemplate redisTemplate; // 时间轮异步定时执行 private HashedWheelTimer timer = new HashedWheelTimer(); private DefaultRedisScript addTimeScript; { DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_lock.lua"))); this.addTimeScript = redisScript; } /*** * @Description: 获取锁并设置过期时间 * @Author: szc * @Date: 2023/8/15 19:52 * @Params [key, expireSecond] * @Return boolean */ public boolean tryLock(String key, long expireSecond){ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_lock.lua"))); Long isSuccess = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), expireSecond); return isSuccess != null && isSuccess == 1L; } /*** * @Description: 释放锁,删除key * @Author: szc * @Date: 2023/8/15 19:52 * @Params [key, expireSecond] * @Return boolean */ public boolean unLock(String key){ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_unlock.lua"))); Long isSuccess = (Long) redisTemplate.execute(redisScript, Arrays.asList(key)); return isSuccess != null && isSuccess == 1L; } /*** * @Description: 自增1 * @Author: szc * @Date: 2023/8/15 19:52 * @Params [key, expireSecond] * @Return boolean */ public boolean incrKey(String key, long expireSecond){ DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(); redisScript.setResultType(Long.class); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_incr_expire.lua"))); Long isSuccess = (Long) redisTemplate.execute(redisScript, Arrays.asList(key), expireSecond); Object num = redisTemplate.opsForValue().get(key); System.out.println("incrKey========="+num); return isSuccess != null && isSuccess == 1L; } /*** * @Description : key过期前未执行完,自动续期,增加过期时间 * @Author: szc * @Date: 2023/8/15 22:07 * @Params [key, time] * @Return void */ public void addTime(String key, long time) { timer.newTimeout( timerTask -> { //意思就是执行luaaa,key给KEYS[1],time给ARGV[1] Long execute = (Long) redisTemplate.execute(addTimeScript, Arrays.asList(key), time); if (execute == 0) { ThreadUtils.threadInfo("当前key还存在,自动续期。"); addTime(key, time); } else { ThreadUtils.threadInfo("key不存在了,不需要续期"); } }, time / 3, TimeUnit.SECONDS);//time的三分之一时间去执行看有没有key,有重置过期时间,没有就什么都不做。 } }
src\main\resources\redis\redis_incr_expire.lua
-- 每次增加1,设置过期时间 local result = tonumber(redis.call('incr', KEYS[1])) if(result == 1) then redis.call('expire', KEYS[1], ARGV[1]) return result else return result end
src\main\resources\redis\redis_lock.lua
-- 使用中的坑:需要重写redistemplate , 不然返回的都是0 -- eval "return tonumber(redis.call('setnx', 'lock_test', 'lock_test'))" 0 对应下一行执行 local result = tonumber(redis.call('setnx', KEYS[1], ARGV[1])) if(result == 1) then redis.call('expire', KEYS[1], ARGV[1]) return result else return result end
\src\main\resources\redis\redis_unlock.lua
-- 15 为设置的过期时间,可以java参数传递,使用ARGV[1]接收 if(redis.call('get', KEYS[1]) == '15') then return redis.call('del', KEYS[1]) else return 0 end
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
yml
spring: redis: host: localhost port: 6379 password:
使用
public class RedisLockController { @Autowired private RedisLock redisLock; @RequestMapping("lock") public void lock(){ long expireSecond = 15; String key = "lock_test"; boolean lock = redisLock.tryLock(key, expireSecond); if(lock){ try { // key过期前未执行完,自动续期,增加过期时间 redisLock.addTime(key,expireSecond); log.info("获取锁成功。。。。。。"); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); }finally { boolean unLock = redisLock.unLock(key); if(unLock){ log.info("释放锁成功!"); }else { log.info("释放锁失败!"); } } }else { log.info("获取锁失败。"); } } @RequestMapping("incrKey") public void incrKey(){ redisLock.incrKey("num_test", 60); }