SpringBoot+Redis实现分布式锁

 一:Lua脚本

加锁:

--[[
思路:
   1.用2个局部变量接受参数
   2.由于redis内置lua解析器,执行加锁命令
   3.如果加锁成功,则设置超时时间
   4.返回加锁命令的执行结果
]]
local key = KEYS[1]
local value = KEYS[2]

local rs1 = redis.call('SETNX',key,value)
if rs1 == true
then
   redis.call('SETEX', key,3600, value)
end

return rs1

解锁:

--[[
思路:
   1.接受redis传来的参数
   2.判断是否是自己的锁,是则删掉
   3.返回结果值
]]
local key = KEYS[1]
local value = KEYS[2]

if redis.call('get',key) == value
then
    return redis.call('del',key)
else
    return false
end

SpringBoot测试类:

@SpringBootTest
public class TestApplicationTests {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisTemplate redisTemplate;
    private static String LOCK_PREFIX = "lock_";
    private static String lockPath = "lock.lua";
    private static String unlockPath = "unlock.lua";

    public void execJob() throws IOException {
        //1.先去获取锁
        String key = LOCK_PREFIX + "001";
        String value = "job002";
        Boolean rs = lock(key, value,lockPath);
        System.out.println(rs);
        try {
            if (!rs) {
                String string = redisTemplate.opsForValue().get(key).toString();
                logger.info(string);
            } else {
                logger.info("加锁成功,休息5秒");
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            logger.info("释放锁");
            Boolean rs1 = lock(key, value,unlockPath);
            System.out.println(rs1);
        }

    }


    public Boolean lock(String key, String value,String luaPath) throws IOException {
        DefaultRedisScript<Boolean>   lockScript = new DefaultRedisScript<>();
        ClassPathResource resource = new ClassPathResource(luaPath);
        ResourceScriptSource source = new ResourceScriptSource(resource);
        lockScript.setScriptSource(source);
        lockScript.setResultType(Boolean.class);
        Boolean result = (Boolean) redisTemplate.execute(lockScript, Arrays.asList(key, value));
        return result;
    }


    @Test
   public void contextLoads() throws IOException {
        execJob();
    }

}

 

java日志

true
2020-04-16 14:54:45.823  INFO 14988 --- [           main] com.example.test.TestApplicationTests    : 加锁成功,休息5秒
2020-04-16 14:54:50.823  INFO 14988 --- [           main] com.example.test.TestApplicationTests    : 释放锁
true

在睡眠的5秒钟,一直查keys,发现先有锁,后面会释放锁

27.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\block_001"
2) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x0212"
127.0.0.1:6379>

 二 Lua的字符串

/**
 * @author WGR
 * @create 2020/4/16 -- 16:50
 */
@SpringBootTest
public class RedisTest {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisTemplate redisTemplate;
    private static String LOCK_PREFIX = "lock_";
    private static String lockPath = "lock.lua";
    public static final String UNLOCK_LUA;
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == KEYS[2] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return false ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    public void execJob() throws IOException {
        //1.先去获取锁
        String key = LOCK_PREFIX + "001";
        String value = "job002";
        Boolean rs = lock(key, value,lockPath);
        try {
            if (!rs) {
                String string = redisTemplate.opsForValue().get(key).toString();
                logger.info(string);
            } else {
                logger.info("加锁成功,休息5秒");
                Thread.sleep(5000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放锁
            logger.info("释放锁");
            DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(UNLOCK_LUA,Boolean.class);
            Boolean result = (Boolean)redisTemplate.execute(redisScript, Arrays.asList(key, value));
            System.out.println(result);
        }

    }


    public Boolean lock(String key, String value,String luaPath) throws IOException {
        DefaultRedisScript<Boolean> lockScript = new DefaultRedisScript<>();
        ClassPathResource resource = new ClassPathResource(luaPath);
        ResourceScriptSource source = new ResourceScriptSource(resource);
        lockScript.setScriptSource(source);
        lockScript.setResultType(Boolean.class);
        Boolean result = (Boolean) redisTemplate.execute(lockScript, Arrays.asList(key, value));
        return result;
    }


    @Test
    public void contextLoads() throws IOException {
        execJob();
    }
}
2020-04-16 17:03:51.214  INFO 17376 --- [           main] com.example.test.RedisTest               : 加锁成功,休息5秒
2020-04-16 17:03:56.214  INFO 17376 --- [           main] com.example.test.RedisTest               : 释放锁
true

注:execute可以换成多个参数的 

 

 

        //注意脚本中KYS[l]和KYS[2] 的写法,它们代表客户端传递的第一个键和第二个键,
        //而ARGV[l]和ARGV[2]则表示客户端传递的第一个和第二个参数
posted @ 2020-04-16 17:10  天宇轩-王  阅读(1160)  评论(0编辑  收藏  举报