Redis性能优化:使用Lua脚本编程,重写锁
mhr18 2024-11-05 10:25 24 浏览 0 评论
Lua 脚本功能是 Reids 2.6 版本的最大亮点, 通过内嵌对 Lua 环境的支持, Redis 解决了长久以来不能高效地处理 CAS (check-and-set)命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。本文将介绍如何使用Lua重写锁,和重写之前与重写之后的性能对比。
前期准备
本文使用的是Python Redis客户端,为了防止客户端并未为Redis2.6提供直接载入或者执行Lua脚本的功能,所以我们需要花费一点时间来创建一个脚本载入程序。
将脚本载入Redis,需要用到一个名为SCRIPT LOAD的命令,这个命令接受一个字符串格式的Lua脚本为参数,它会把脚本存储起来等待之后使用,然后返回被存储脚本的SHA1校验和。之后,用户只要调用EVALSHA命令,并输入脚本的SHA1校验和以及脚本所需的全部参数,就可以调用之前存储的脚本。
将脚本载入Redis的script_load函数
1、将SCRIPT LOAD命令返回的已载入脚本的SHA1校验和存储到一个列表里面,以便之后在call()函数内部对其进行修改。
2、在调用已载入脚本的时候,用户需要将Redis连接、脚本要处理的键以及脚本的其他参数传递给脚本。
3、程序只会在SHA1校验和未被缓存的情况下尝试载入脚本。
4、使用以缓存的SHA1校验和执行命令。
5、如果错误与脚本缺失无关,那么重新抛出异常。
6、当程序接收到脚本错误时,或者程序需要强制执行脚本时,它会使用EVAL命令直接执行给定的脚本。EVAL命令在执行完脚本之后,会自动把脚本缓存起来,而缓存产生的SHA1校验和跟使用EVALSHA命令缓存脚本产生的SHA1校验和是完全相同的。
7、返回一个函数,这个函数在被调用的时候会自动载入并执行脚本。
除了调用SCRIPT LOAD命令和EVALSHA命令之外,script_load()函数还会捕捉一个异常,当函数缓存了某个脚本的SHA1校验和,但是服务器却并没有存储这个SHA1校验和对应的脚本时,异常就会被抛出。
在服务器重启之后,或者用户执行了SCRIPT FLUSH命令,清空脚本缓存之后,又或者程序在不同的时间给函数提供了指向不同Redis服务器的连接时,这个异常都会出现。
当函数检测到脚本缺失的时候,它就会使用EVAL命令直接执行脚本,而EVAL命令,除了会执行脚本之外,还会将被执行的脚本缓存到Redis服务器里面。
除此之外,script_load()函数还允许用户通过force_eval参数来直接执行脚本,当我们需要在事务或者流水线里面执行脚本的时候,这个功能就会非常有用。
为什么要重写锁
第一个原因:
可以将CAS操作变为一个原子操作。这样做的主要目的是为了让Redis的集群服务器可以拒绝那些尝试在指定的分片上面,对不可用的键进行读取或者写入的脚本。
第二个原因:
减少网络通信次数。在处理Redis存储的数据时,程序可能需要一些数据,但这些数据没办法再最开始的调用中取得。其中的一个例子就是,从Redis获取一些散列值,然后使用这些值去访问存储在关系型数据库里面的信息,最后再把这些信息写入Redis里面。
基于以上这两个原因,我们需要使用Lua脚本重写锁实现。
重写锁实现
加锁操作首先生成一个ID,然后使用SETNX命令对键进行了有条件的设置操作,并在设置操作执行成功的时候,为键设置了过期时间。尽管加锁操作在概念上并不复杂,但程序还是需要处理各种失败和重试情况。
原版代码如下:
重写之前加锁实现源代码
1、128位随机标识符。
2、确保传给EXPIRE的都是整数。
3、获取锁并设置过期时间。
4、检查过期时间,并在有需要时对其进行更新。
使用Lua重写之后的代码:
重写之后加锁实现源代码
1、执行实际的锁获取操作,通过检查确保Lua调用已经执行成功。
2、检测锁是否已经存在。(提醒,Lua表格的索引是从1开始的。)
3、使用给定的过期时间以及标识符去设置键。
除了将之前的SETNX命令和EXPIRE命令替换成SETEX命令,从而确保客户端获取的锁总是具有过期时间之外,Lua脚本实现的加锁操作跟原来的加锁操作之间并无明显的不同。
接下来让我们乘胜前进,继续使用Lua脚本重写锁的释放操作。
锁释放操作首先要做的就是使用WATCH去监视代表锁的键,检查该键是否仍然存储着加锁时设置的标识符。如果是的话,程序就解除锁;如果不是的话,程序就说指定的锁已经丢失。
使用Lua重写的release_lock函数
1、调用负责释放锁的Lua函数。
2、检查锁是否匹配。
3、删除锁并确保脚本总是返回真值。
跟加锁操作不同,Lua版本的锁释放操作比原版更为简洁,因为程序无需再执行典型的WATCH/MULTI/EXEC步骤。
虽然减少代码量是一件非常好的事情,但是如果Lua版本的锁实现不能带来实际的性能提升,那么它的作用将是非常有限的。
为了测试原版锁实现和Lua锁实现之间的性能差异,我们给这两种锁实现的代码增加了一些指令,并通过测试代码分别执行1个、2个、5个和10个并行的进程,让这些进程反复不断的对锁执行获取操作和释放操作,然后记录两个版本的锁实现在十秒内执行锁获取操作的次数以及成功取得锁的次数。结果如图所示:
原版锁实现和Lua版本的锁实现在10秒内的性能对比
通过观察表中右边那一栏可以看到,在测试循环里面,Lua版本的锁实现在获取锁和释放锁方面的表现,要明显优于原版锁实现:在使用单个客户端的情况下,Lua锁的性能要高40%多;在使用两个客户端的情况下,Lua锁的性能要高87%;而在使用五个或者十个客户端的情况下,Lua锁的性能要高一倍以上。
通过对比中间栏和右边栏,我们还可以看到,由于Lua版本的锁实现,减少了加锁时所需的通信往返次数,所以Lua版本的锁实现在尝试获取锁时的速度比原版的锁要快得多。
除了性能变得更好之外,Lua版本的加锁操作和锁释放操作的代码也明显的变得更容易理解了,这使得我们可以很容易的验证这些代码的正确性。
总结
使用Lua脚本可以极大地提高性能,并对需要执行的操作进行大幅的简化。大家可以多尝试一下。
本文作者长期致力于互联网技术研究,擅长互联网相关知识包括高并发、大数据、架构、前后端语言、框架、算法、常见面试题等,欢迎关注。
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)