基于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为什么要提供pipeline功能
Jun 22 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redisson实现Redis分布式锁的几种方式
Aug 07 Redis
Redis 常见使用场景
Aug 30 Redis
详解redis在微服务领域的贡献
Oct 16 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
解决Redis启动警告问题
Feb 24 Redis
redis sentinel监控高可用集群实现的配置步骤
Apr 01 Redis
muduo TcpServer模块源码分析
Apr 26 Redis
Redis 限流器
May 15 Redis
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
Redis实现订单自动过期功能的示例代码
May 08 #Redis
redis 限制内存使用大小的实现
使用Redis实现秒杀功能的简单方法
You might like
UCenter Home二次开发指南
2009/05/28 PHP
php中$this-&amp;gt;含义分析
2009/11/29 PHP
PHP中把stdClass Object转array的几个方法
2014/05/08 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
在IE,Firefox,Safari,Chrome,Opera浏览器上调试javascript
2008/12/02 Javascript
让innerText在firefox火狐和IE浏览器都能用的写法
2011/05/14 Javascript
分享一道笔试题[有n个直线最多可以把一个平面分成多少个部分]
2012/10/12 Javascript
利用Javascript判断操作系统的类型实现不同操作系统下的兼容性
2013/01/29 Javascript
Extjs实现进度条的两种便捷方式
2013/09/26 Javascript
jQuery点击自身以外地方关闭弹出层的简单实例
2013/12/24 Javascript
jquery插件hiAlert实现网页对话框美化
2015/05/03 Javascript
javascript最基本的函数汇总
2015/06/25 Javascript
axios post提交formdata的实例
2018/03/16 Javascript
解决vue项目使用font-awesome,build后路径的问题
2018/09/01 Javascript
解决betterScroll在vue中存在图片时,出现拉不动的问题
2018/09/27 Javascript
在vue项目中正确使用iconfont的方法
2018/09/28 Javascript
详解vue-cli3 中跨域解决方案
2019/04/10 Javascript
微信小程序canvas实现签名功能
2021/01/19 Javascript
[54:28]EG vs OG 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/18 DOTA
python处理图片之PIL模块简单使用方法
2015/05/11 Python
初学python的操作难点总结(新手必看篇)
2017/08/03 Python
浅谈Python中重载isinstance继承关系的问题
2018/05/04 Python
python2 与 python3 实现共存的方法
2018/07/12 Python
Django配置celery(非djcelery)执行异步任务和定时任务
2018/07/16 Python
详解Python中的测试工具
2019/06/09 Python
Pytorch中.new()的作用详解
2020/02/18 Python
PyTorch安装与基本使用详解
2020/08/31 Python
css3设置box-pack和box-align让div里面的元素垂直居中
2014/09/01 HTML / CSS
三陽商会官方网站:Sanyo iStore
2019/05/15 全球购物
如何估计一张表的大小(假设该表中有1万条数据)
2016/03/27 面试题
解释一下ruby中的特殊方法与特殊类
2013/02/26 面试题
团员的自我评价
2013/12/01 职场文书
投标授权委托书范文
2014/08/02 职场文书
焦裕禄观后感
2015/06/03 职场文书
母亲去世追悼词
2015/06/23 职场文书
浅析Redis Sentinel 与 Redis Cluster
2021/06/24 Redis