基于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 相关文章推荐
Redis6.0搭建集群Redis-cluster的方法
May 08 Redis
使用Redis实现秒杀功能的简单方法
May 08 Redis
Redis数据结构之链表与字典的使用
May 11 Redis
redis哨兵常用命令和监控示例详解
May 27 Redis
压缩Redis里的字符串大对象操作
Jun 23 Redis
浅谈redis整数集为什么不能降级
Jul 25 Redis
Redis入门教程详解
Aug 30 Redis
Spring Boot实战解决高并发数据入库之 Redis 缓存+MySQL 批量入库问题
Feb 12 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
Redis中key的过期删除策略和内存淘汰机制
Apr 12 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
Redis实战之Lettuce的使用技巧详解
Dec 24 Redis
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
Redis实现订单自动过期功能的示例代码
May 08 #Redis
redis 限制内存使用大小的实现
使用Redis实现秒杀功能的简单方法
You might like
YB217、YB235、YB400浅听
2021/03/02 无线电
实例(Smarty+FCKeditor新闻系统)
2007/01/02 PHP
PHP容易被忽略而出错陷阱 数字与字符串比较
2011/11/10 PHP
ThinkPHP中U方法的使用浅析
2014/06/13 PHP
php数组键值用法实例分析
2015/02/27 PHP
实例介绍PHP中zip_open()函数用法
2019/02/15 PHP
laravel框架数据库操作、查询构建器、Eloquent ORM操作实例分析
2019/12/20 PHP
JQuery 无废话系列教程(二) jquery实战篇上
2009/06/23 Javascript
再谈javascript面向对象编程
2012/03/18 Javascript
javascript静态页面传值的三种方法分享
2013/11/12 Javascript
事件委托与阻止冒泡阻止其父元素事件触发
2014/09/02 Javascript
JavaScript原生节点操作小结
2017/01/17 Javascript
微信小程序 引入es6 promise
2017/04/12 Javascript
jQuery实现为动态添加的元素绑定事件实例分析
2018/09/07 jQuery
JavaScript和TypeScript中的void的具体使用
2019/09/12 Javascript
重置Redux的状态数据的方法实现
2019/11/18 Javascript
Python内置函数的用法实例教程
2014/09/08 Python
Python算术运算符实例详解
2017/05/31 Python
Python序列化基础知识(json/pickle)
2017/10/19 Python
python+opencv轮廓检测代码解析
2018/01/05 Python
python跳过第一行快速读取文件内容的实例
2018/07/12 Python
Python实现针对json中某个关键字段进行排序操作示例
2018/12/25 Python
python判断自身是否正在运行的方法
2019/08/08 Python
详细整理python 字符串(str)与列表(list)以及数组(array)之间的转换方法
2019/08/30 Python
python3.7环境下安装Anaconda的教程图解
2019/09/10 Python
Python Selenium安装及环境配置的实现
2020/03/17 Python
python 爬虫网页登陆的简单实现
2020/11/30 Python
英国床和浴室商场:Bed & Bath Emporium
2018/05/20 全球购物
学习十八大报告感言
2014/02/04 职场文书
2014年学校德育工作总结
2014/12/05 职场文书
清洁工个人总结
2015/03/04 职场文书
面试复试通知单
2015/04/24 职场文书
车辆管理制度范本
2015/08/05 职场文书
CSS中em的正确打开方式详解
2021/04/08 HTML / CSS
CentOS下安装Jenkins的完整步骤
2022/04/07 Servers
Spring Boot 实现 WebSocket
2022/04/30 Java/Android