基于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 相关文章推荐
为Java项目添加Redis缓存的方法
May 18 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
详解Redis瘦身指南
May 26 Redis
深入理解redis中multi与pipeline
Jun 02 Redis
Redis主从配置和底层实现原理解析(实战记录)
Jun 30 Redis
浅谈Redis位图(Bitmap)及Redis二进制中的问题
Jul 15 Redis
Redis 常见使用场景
Aug 30 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
Redis数据同步之redis shake的实现方法
Apr 21 Redis
Redis高并发缓存架构性能优化
May 15 Redis
Redis批量生成数据的实现
Jun 05 Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 Redis
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
基于Redis过期事件实现订单超时取消
May 08 #Redis
Redis实现订单自动过期功能的示例代码
May 08 #Redis
redis 限制内存使用大小的实现
使用Redis实现秒杀功能的简单方法
You might like
初探PHP5
2006/10/09 PHP
利用discuz实现PHP大文件上传应用实例代码
2008/11/14 PHP
PHP 日期时间函数的高级应用技巧
2009/10/10 PHP
jquery 操作iframe的几种方法总结
2013/12/13 Javascript
node.js中的fs.linkSync方法使用说明
2014/12/15 Javascript
jquery实现submit提交表单
2015/02/03 Javascript
JS通过ajax动态读取xml文件内容的方法
2015/03/24 Javascript
Javascript中With语句用法实例
2015/05/14 Javascript
jQuery实现仿美橙互联两级导航菜单效果完整实例
2015/09/17 Javascript
JavaScript函数学习总结以及相关的编程习惯指南
2015/11/16 Javascript
JAVASCRIPT代码编写俄罗斯方块网页版
2015/11/26 Javascript
JS遍历数组和对象的区别及递归遍历对象、数组、属性的方法详解
2016/06/14 Javascript
浅谈addEventListener和attachEvent的区别
2016/07/14 Javascript
jquery.validate[.unobtrusive]和Bootstrap实现tooltip错误提示问题分析
2016/10/30 Javascript
jQuery模拟爆炸倒计时功能实例代码
2017/08/21 jQuery
基于vue中对鼠标划过事件的处理方式详解
2018/08/22 Javascript
Postman的下载及安装教程详解
2018/10/16 Javascript
angular 服务的单例模式(依赖注入模式下)详解
2018/10/22 Javascript
Javascript中Math.max和Math.max.apply的区别和用法详解
2020/08/24 Javascript
简单了解Django ORM常用字段类型及参数配置
2020/01/07 Python
基于logstash实现日志文件同步elasticsearch
2020/08/06 Python
Python定时任务框架APScheduler原理及常用代码
2020/10/05 Python
去除python中的字符串空格的简单方法
2020/12/22 Python
html5的自定义data-*属性与jquery的data()方法的使用
2014/07/02 HTML / CSS
Html5 FileReader实现即时上传图片功能实例代码
2014/09/01 HTML / CSS
英国领先的家庭时尚品牌:Peacocks
2018/01/11 全球购物
Happy Socks英国官网:购买五颜六色的袜子
2020/11/03 全球购物
了解AppleTalk协议吗
2014/04/01 面试题
JAVA高级程序员面试题
2013/09/06 面试题
驾驶员岗位职责
2014/01/29 职场文书
平安工地汇报材料
2014/08/19 职场文书
小学关爱留守儿童活动方案
2014/08/25 职场文书
2014年党员干部四风问题自我剖析材料
2014/09/29 职场文书
小学美术教学反思
2016/02/17 职场文书
SpringBoot实现quartz定时任务可视化管理功能
2021/08/30 Java/Android
Java spring定时任务详解
2021/10/05 Java/Android