基于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的几个过期策略
May 27 Redis
Redis可视化客户端小结
Jun 10 Redis
redis实现的四种常见限流策略
Jun 18 Redis
Redis Cluster集群动态扩容的实现
Jul 15 Redis
基于Redis的List实现特价商品列表功能
Aug 30 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 Redis
解决Redis启动警告问题
Feb 24 Redis
解决linux下redis数据库overcommit_memory问题
Feb 24 Redis
Redis实现一个账号只能登录一个设备
Apr 19 Redis
Redis特殊数据类型bitmap位图
Jun 01 Redis
一文教你快速生成MySQL数据库关系图
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
dedecms 制作模板中使用的全局标记图文教程
2007/03/11 PHP
php实现可以设置中奖概率的抽奖程序代码分享
2014/01/19 PHP
文本框的字数限制功能jquery插件
2009/11/24 Javascript
IE6中使用position导致页面变形的解决方案(js代码)
2011/01/09 Javascript
JS实现div内部的文字或图片自动循环滚动代码
2013/04/19 Javascript
iframe子页面与父页面在同域或不同域下的js通信
2014/05/07 Javascript
node.js中的path.delimiter方法使用说明
2014/12/09 Javascript
QQ登录背景闪动效果附效果演示源码下载
2015/09/22 Javascript
sso跨域写cookie的一段js脚本(推荐)
2016/05/25 Javascript
js 将input框中的输入自动转化成半角大写(税号输入框)
2017/02/16 Javascript
js实现瀑布流效果(自动生成新的内容)
2017/03/16 Javascript
JavaScript使用原型和原型链实现对象继承的方法详解
2017/04/05 Javascript
JavaScript基于扩展String实现替换字符串中index处字符的方法
2017/06/13 Javascript
Bootstrap 模态框自定义点击和关闭事件详解
2018/08/10 Javascript
jquery的$().each和$.each的区别
2019/01/18 jQuery
JS常见错误(Error)及处理方案详解
2020/07/02 Javascript
python opencv旋转图像(保持图像不被裁减)
2018/07/26 Python
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
Python实现的拉格朗日插值法示例
2019/01/08 Python
Python合并同一个文件夹下所有PDF文件的方法
2019/03/11 Python
Python中numpy模块常见用法demo实例小结
2019/03/16 Python
Flask教程之重定向与错误处理实例分析
2019/08/01 Python
windows10 pycharm下安装pyltp库和加载模型实现语义角色标注的示例代码
2020/05/07 Python
GUESS Factory加拿大:牛仔裤、服装及配饰
2019/09/20 全球购物
药品质量检测应届生求职信
2013/11/14 职场文书
关于打架的检讨书
2014/01/17 职场文书
三年大学生活自我鉴定
2014/01/21 职场文书
个人党性剖析材料
2014/02/03 职场文书
读书演讲主持词
2014/03/18 职场文书
2015年党员个人剖析材料
2014/12/18 职场文书
工作岗位职责范本
2015/02/15 职场文书
2015年幼儿园教研活动总结
2015/03/25 职场文书
2015年学习部工作总结范文
2015/03/31 职场文书
《金色的草地》教学反思
2016/02/17 职场文书
浅谈自定义校验注解ConstraintValidator
2021/06/30 Java/Android
css之clearfix的用法深入理解(必看篇)
2023/05/21 HTML / CSS