分布式锁优化之 使用lua脚本改造分布式锁保证判断和删除的原子性(优化之LUA脚本保证删除的原子性)

文章目录

1、lua脚本入门

Lua 教程 | 菜鸟教程https://www.runoob.com/lua/lua-tutorial.html\\

redis提供了一组命令行直接可以执行lua脚本。

redis中的lua脚本是有原子性:因为是通过一个指令执行的

sh 复制代码
[root@localhost ~]# docker exec -it spzx-redis redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> eval script numkeys key [key ...] arg [arg ...]

script:lua 脚本字符串

numkeys:需要传递的key列表的元素数量

key:指定具体key列表,元素之间用空格区分,用KEYS[Iindex从1开始]

arg:指定具体arg列表,元素之间用空格区分,用ARGV[index从1开始]

sh 复制代码
127.0.0.1:6379> eval "return 'hello world!'" 0
"hello world!"

1.1、变量:弱类型

复制代码
全局变量:a = 5
局部变量:local a = 5   redis中的lua脚本只支持局部变量
sh 复制代码
127.0.0.1:6379> eval "local a=5 return a" 0
(integer) 5

1.2、流程控制

sh 复制代码
127.0.0.1:6379> eval "if 10>20 then return 10 else return 20 end" 0
(integer) 20

要修正你的脚本,你应该使用 KEYS 和 ARGV 而不是 keys 和 argv(注意大小写)。KEYS 数组包含了传递给脚本的键名(key names),而 ARGV 数组包含了传递给脚本的参数(arguments)。

sh 复制代码
127.0.0.1:6379> eval "if keys[1]>argv[1] then return keys[2] else return argv[2] end" 2 10 20 30 40
(error) ERR Error running script (call to f_6913b8a1459b5bd1de1628cd76db221048464f0a): @enable_strict_lua:15: user_script:1: Script attempted to access nonexistent global variable 'keys'
127.0.0.1:6379> eval "if KEYS[1]>ARGV[1] then return KEYS[2] else return ARGV[2] end" 2 10 20 30 40
"40"
sh 复制代码
127.0.0.1:6379> eval "return {10,20,30,40}" 0
1) (integer) 10
2) (integer) 20
3) (integer) 30
4) (integer) 40
sh 复制代码
127.0.0.1:6379> eval "return {KEYS[1],KEYS[3],ARGV[1],ARGV[3]}" 4 10 20 30 40 50 60 70 80 90 
1) "10"
2) "30"
3) "50"
4) "70"

1.3、在lua中执行redis指令

redis.call('指令名称',...)

sh 复制代码
127.0.0.1:6379> eval "return redis.call('set','lock','333')" 0
OK
127.0.0.1:6379> get lock
"333"
sh 复制代码
127.0.0.1:6379> eval "return redis.call('get','lock')" 0
"333"
sh 复制代码
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 lock 123
OK
127.0.0.1:6379> get lock
"123"

1.4、实战:先判断是否自己的锁,如果是才能删除

sh 复制代码
eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"
sh 复制代码
eval "if redis.call('get',KEYS[1]) == ARGV[1] 
then 
	return redis.call('del',KEYS[1]) 
else 
	return 0 
end"
sh 复制代码
127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock 4343323
(integer) 0
sh 复制代码
127.0.0.1:6379> eval "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 lock 123
(integer) 1

2、AlbumInfoApiController --》testLock()

java 复制代码
@Tag(name = "专辑管理")
@RestController
@RequestMapping("api/album/albumInfo")
@SuppressWarnings({"unchecked", "rawtypes"})
public class AlbumInfoApiController {

	@GetMapping("test/lock")
	public Result testLock() {
		this.albumInfoService.testLock();
		return Result.ok("测试分布式锁案例");
	}
	
}

3、AlbumInfoServiceImpl --》testLock()

java 复制代码
    @Override
    public  void testLock(){
        // 加锁:set k v nx ex 3
        String uuid = UUID.randomUUID().toString();
        Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        if (!lock) {
            try {
                // 获取锁失败,进行自旋
                Thread.sleep(50);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else {
            // 获取锁成功,执行业务
            //this.redisTemplate.expire("lock", 3, TimeUnit.SECONDS);

            Object numObj = this.redisTemplate.opsForValue().get("num");
            if (numObj == null) {
                this.redisTemplate.opsForValue().set("num", 1);
                return;
            }
            Integer num = Integer.parseInt(numObj.toString());
            this.redisTemplate.opsForValue().set("num", ++num);

            // 解锁
            // 先判断是否是自己的锁,如果是则删除
            //if (StringUtils.equals(uuid, this.redisTemplate.opsForValue().get("lock").toString())) {
            //    this.redisTemplate.delete("lock");
            //}
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                        "return redis.call('del',KEYS[1]) " +
                    "else " +
                        "return 0 " +
                    "end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
        }
    }

压力测试肯定也没有问题。自行测试

启动多个运行实例:

redis中的值重新改为0。

sh 复制代码
[root@localhost ~]# ab -n 5000 -c 100 http://192.168.74.1:8500/api/album/albumInfo/test/lock
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.74.1 (be patient)
Completed 500 requests
Completed 1000 requests
Completed 1500 requests
Completed 2000 requests
Completed 2500 requests
Completed 3000 requests
Completed 3500 requests
Completed 4000 requests
Completed 4500 requests
Completed 5000 requests
Finished 5000 requests


Server Software:        
Server Hostname:        192.168.74.1
Server Port:            8500

Document Path:          /api/album/albumInfo/test/lock
Document Length:        76 bytes

Concurrency Level:      100
Time taken for tests:   49.543 seconds
Complete requests:      5000
Failed requests:        674
   (Connect: 0, Receive: 0, Length: 674, Exceptions: 0)
Write errors:           0
Total transferred:      2353370 bytes
HTML transferred:       383370 bytes
Requests per second:    100.92 [#/sec] (mean)
Time per request:       990.856 [ms] (mean)
Time per request:       9.909 [ms] (mean, across all concurrent requests)
Transfer rate:          46.39 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   2.6      1      81
Processing:     5  948 2587.5     14   33884
Waiting:        5  948 2587.5     14   33883
Total:          6  950 2587.7     16   33887

Percentage of the requests served within a certain time (ms)
  50%     16
  66%    197
  75%    627
  80%   1054
  90%   2846
  95%   5011
  98%   8277
  99%  12678
 100%  33887 (longest request)
相关推荐
predisw2 小时前
kafka connect 大概了解
分布式·kafka
计算机毕设定制辅导-无忧学长4 小时前
RabbitMQ 快速上手:安装配置与 HelloWorld 实践(二)
分布式·rabbitmq·ruby
啾啾Fun4 小时前
【Java微服务组件】分布式协调P1-数据共享中心简单设计与实现
java·分布式·微服务
梦想画家7 小时前
Scrapy进阶实践指南:从脚本运行到分布式爬取
分布式·scrapy·数据工程
东阳马生架构8 小时前
Seata源码—5.全局事务的创建与返回处理二
分布式·seata·分布式事务
莱茵不哈哈9 小时前
OpenResty 深度解析:构建高性能 Web 服务的终极方案
nginx·lua·kong·openresty·conf
掘金-我是哪吒10 小时前
分布式微服务系统架构第133集:运维服务器6年经验,高并发,大数据量系统
运维·服务器·分布式·微服务·系统架构
尘世壹俗人12 小时前
hadoop.proxyuser.代理用户.授信域 用来干什么的
大数据·hadoop·分布式
白露与泡影13 小时前
基于Mongodb的分布式文件存储实现
分布式·mongodb·wpf
追风赶月、14 小时前
【Redis】redis用作缓存和分布式锁
redis·分布式·缓存