基于Redis实现分布式锁的方法(lua脚本版)


Posted in Redis onMay 12, 2021

1、前言

在Java中,我们通过锁来避免由于竞争而造成的数据不一致问题。通常我们使用synchronized 、Lock来实现。但是Java中的锁只能保证在同一个JVM进程内中可用,在跨JVM进程,例如分布式系统上则不可靠了。

2、分布式锁

分布式锁,是一种思想,它的实现方式有很多,如基于数据库实现、基于缓存(Redis等)实现、基于Zookeeper实现等等。为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件

  • 互斥性:在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁:即使客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 具有容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  • 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

 3、基于Redis实现分布式锁

以下代码实现了基于redis中间件的分布式锁。加锁的过程中为了保障setnx(设置KEY)和expire(设置超时时间)尽可能在一个事务中,使用到了lua脚本的方式,将需要完成的指令一并提交到redis中;

3.1、RedisConfig.java

package com.demo.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // key采用String的序列化方式
        template.setKeySerializer(new StringRedisSerializer());
        // value序列化方式采用jackson
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

}

3.2、RedisLockController.java

package com.demo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;

@RestController
@RequestMapping("/redis")
public class RedisLockController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @RequestMapping(value = "/lock/{key}/{uid}/{expire}")
    public Long lock(@PathVariable("key") String key, @PathVariable("uid") String uid, @PathVariable("expire") Integer expire) {
        Long result = null;
        try {
            //调用lua脚本并执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setResultType(Long.class);//返回类型是Long
            //lua文件存放在resources目录下的redis文件夹内
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_lock.lua")));
            result = redisTemplate.execute(redisScript, Arrays.asList(key), uid, expire);
            System.out.println("lock==" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @RequestMapping(value = "/unlock/{key}/{uid}")
    public Long unlock(@PathVariable("key") String key, @PathVariable("uid") String uid) {
        Long result = null;
        try {
            //调用lua脚本并执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setResultType(Long.class);//返回类型是Long
            //lua文件存放在resources目录下的redis文件夹内
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/redis_unlock.lua")));
            result = redisTemplate.execute(redisScript, Arrays.asList(key), uid);
            System.out.println("unlock==" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

3.3、redis_lock.lua

if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then
    return redis.call('expire',KEYS[1],ARGV[2])
else
    return 0
end

3.4、redis_unlock.lua

if redis.call("exists",KEYS[1]) == 0 then
    return 1
end

if redis.call('get',KEYS[1]) == ARGV[1] then
    return redis.call('del',KEYS[1])
else
    return 0
end

4、测试效果

key123为key,thread12345为value标识锁的主人,300为该锁的超时时间

加锁:锁主人为thread12345
http://127.0.0.1:8080/redis/lock/key123/thread12345/300

解锁:解锁人为thread123456
http://127.0.0.1:8080/redis/unlock/key123/thread123456

解锁:解锁人为thread12345
http://127.0.0.1:8080/redis/unlock/key123/thread12345

4.1、加锁,其他人解锁

基于Redis实现分布式锁的方法(lua脚本版)
基于Redis实现分布式锁的方法(lua脚本版)

thread12345加的锁,thread123456是解不了的,只有等thread12345自己解锁或者锁的超时时间过期

4.2、加锁,自己解锁

基于Redis实现分布式锁的方法(lua脚本版)
基于Redis实现分布式锁的方法(lua脚本版)

基于Redis实现分布式锁的方法(lua脚本版)

thread12345加的锁,thread12345自己随时可以解锁,也可以等锁的超时时间过期

5、总结

  •  使用Redis锁,会有业务未执行完,锁过期的问题,也就是锁不具有可重入性的特点。
  • 使用Redis锁,在尝试获取锁的时候,是非阻塞的,不满足在一定期限内不断尝试获取锁的场景。
  • 以上两点,都可以采用Redisson锁解决。

到此这篇关于基于Redis实现分布式锁的方法(lua脚本版)的文章就介绍到这了,更多相关Redis实现分布式锁内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
redis连接被拒绝的解决方案
Apr 12 Redis
redis限流的实际应用
Apr 24 Redis
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
基于Redis位图实现用户签到功能
May 08 Redis
浅谈redis缓存在项目中的使用
May 20 Redis
浅谈Redis中的RDB快照
Jun 29 Redis
Redis如何实现分布式锁
Aug 23 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
利用Redis实现点赞功能的示例代码
Jun 28 Redis
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
Redis实现订单自动过期功能的示例代码
May 08 #Redis
redis 限制内存使用大小的实现
使用Redis实现秒杀功能的简单方法
You might like
PHP读取文件内容后清空文件示例代码
2014/03/18 PHP
详解PHP的Yii框架中扩展的安装与使用
2016/04/01 PHP
php倒计时出现-0情况的解决方法
2016/07/28 PHP
PHP PDOStatement::errorInfo讲解
2019/01/31 PHP
PHP中迭代器的简单实现及Yii框架中的迭代器实现方法示例
2020/04/26 PHP
在jQuery1.5中使用deferred对象 着放大镜看Promise
2011/03/12 Javascript
js鼠标点击事件在各个浏览器中的写法及Event对象属性介绍
2013/01/24 Javascript
JavaScript实现网页图片等比例缩放实现代码及调用方式
2013/02/25 Javascript
jQuery操作Select选择的Text和Value(获取/设置/添加/删除)
2013/03/06 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
轻松实现Bootstrap图片轮播
2020/04/20 Javascript
jquery实现页面常用的返回顶部效果
2016/03/04 Javascript
JQuery EasyUI Layout 在from布局自适应窗口大小的实现方法
2016/05/28 Javascript
AngularJS 路由和模板实例及路由地址简化方法(必看)
2016/06/24 Javascript
推荐10款扩展Web表单的JS插件
2017/12/25 Javascript
浅谈Node.js 子进程与应用场景
2018/01/24 Javascript
Vue实现table上下移动功能示例
2019/02/21 Javascript
vue实现手机号码的校验实例代码(防抖函数的应用场景)
2019/09/05 Javascript
浅谈vue3中effect与computed的亲密关系
2019/10/10 Javascript
浅谈微信小程序列表埋点曝光指南
2019/10/15 Javascript
基于axios 的responseType类型的设置方法
2019/10/29 Javascript
你知道JavaScript Symbol类型怎么用吗
2020/01/08 Javascript
让python同时兼容python2和python3的8个技巧分享
2014/07/11 Python
python实现杨辉三角思路
2017/07/14 Python
详解Python使用Plotly绘图工具,绘制甘特图
2019/04/02 Python
详解python调用cmd命令三种方法
2019/07/08 Python
Series和DataFrame使用简单入门
2019/11/13 Python
Python多线程的退出控制实现
2020/08/10 Python
PyCharm2020最新激活码+激活码补丁(亲测最新版PyCharm2020.2激活成功)
2020/11/25 Python
巴基斯坦电子产品购物网站:Home Shopping
2017/09/14 全球购物
这76道Java面试题及答案,祝你能成功通过面试
2016/04/16 面试题
消防安全员岗位职责
2014/03/10 职场文书
关于感恩的演讲稿800字
2014/08/26 职场文书
学习心理学的体会
2014/11/07 职场文书
大连导游词
2015/02/12 职场文书
廉洁自律承诺书范文
2015/04/28 职场文书