基于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 27 Redis
Redis高级数据类型Hyperloglog、Bitmap的使用
May 24 Redis
详解Redis集群搭建的三种方式
May 31 Redis
redis不能访问本机真实ip地址的解决方案
Jul 07 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
使用redis生成唯一编号及原理示例详解
Sep 15 Redis
SpringBoot整合Redis入门之缓存数据的方法
Nov 17 Redis
redis的list数据类型相关命令介绍及使用
Jan 18 Redis
解决 Redis 秒杀超卖场景的高并发
Apr 12 Redis
Redis高并发缓存架构性能优化
May 15 Redis
Redis特殊数据类型HyperLogLog基数统计算法讲解
Jun 01 Redis
Redis实现订单过期删除的方法步骤
Jun 05 Redis
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
Redis实现订单自动过期功能的示例代码
May 08 #Redis
redis 限制内存使用大小的实现
使用Redis实现秒杀功能的简单方法
You might like
PHP入门教程之会话控制技巧(cookie与session)
2016/09/11 PHP
PHP实现对数组分页处理实例详解
2017/02/07 PHP
PHP正则表达式处理函数(PCRE 函数)实例小结
2019/05/09 PHP
Thinkphp5.0框架使用模型Model的获取器、修改器、软删除数据操作示例
2019/10/11 PHP
ThinkPHP5.1验证码功能实现的示例代码
2020/06/08 PHP
如何用javascript去掉字符串里的所有空格
2007/02/08 Javascript
JavaScript高级程序设计(第3版)学习笔记12 js正则表达式
2012/10/11 Javascript
js实现的切换面板实例代码
2013/06/17 Javascript
JS在TextArea光标位置插入文字并实现移动光标到文字末尾
2013/06/21 Javascript
jquery checkbox实现单选小例
2013/11/27 Javascript
js分页代码分享
2014/04/28 Javascript
Node.js 服务器端应用开发框架 -- Hapi.js
2014/07/29 Javascript
jQuery实现tag便签去重效果的方法
2015/01/20 Javascript
jQuery实现页面顶部显示的进度条效果完整实例
2015/12/09 Javascript
使用jQuery中的wrap()函数操作HTML元素的教程
2016/05/24 Javascript
基于input动态模糊查询的实现方法
2017/12/12 Javascript
JS+HTML5 canvas绘制验证码示例
2018/12/05 Javascript
微信小程序云开发实现增删改查功能
2019/05/17 Javascript
layui点击左侧导航栏,实现不刷新整个页面,只刷新局部的方法
2019/09/25 Javascript
js实现倒计时秒杀效果
2020/03/25 Javascript
如何基于JS截获动态代码
2019/12/25 Javascript
Python爬虫模拟登录带验证码网站
2016/01/22 Python
详细介绍Python进度条tqdm的使用
2019/07/31 Python
解决Pymongo insert时会自动添加_id的问题
2020/12/05 Python
Python爬虫UA伪装爬取的实例讲解
2021/02/19 Python
html2 canvas生成清晰的图片实现打印功能
2019/09/23 HTML / CSS
武汉瑞得软件笔试题
2015/10/27 面试题
网站编辑求职信
2013/10/17 职场文书
大学军训感言1000字
2014/02/25 职场文书
2015年党小组工作总结
2015/05/26 职场文书
汉语拼音教学反思
2016/02/22 职场文书
导游词之襄阳古城
2019/09/27 职场文书
如何使用Python实现一个简易的ORM模型
2021/05/12 Python
Java实现多线程聊天室
2021/06/26 Java/Android
一定要知道的 25 个 Vue 技巧
2021/11/02 Vue.js
聊聊Lombok中的@Builder注解使用教程
2021/11/17 Java/Android