1.redis使ç¨lua
2.Redis 源码| Lua脚本
3.redis分布式锁的实现(setNx命令和Lua脚本)
4.🦋基于 redis 的简易滑动窗口实现
5.Redis中使用Lua脚本(二)之红包雨的抢夺
6.万字长文带你解读Redisson分布式锁的源码
redis使ç¨lua
redisä¸æ§è¡luaå¯ä»¥éè¿ä¸¤ç§æ¹å¼ï¼ç¬¬ä¸ç§æ¯å°luaèæ¬æå½ä»¤ç´æ¥ä½¿ç¨redisæ§è¡ï¼ç¬¬äºç§ç¸å½äºæèæ¬æå½ä»¤ä¿åå°redisä¸ï¼ç¶å使ç¨ä¸ä¸²shaç è°ç¨ï¼å¯ä»¥ç解为è°ç¨å½æ°ï¼
ä¾åï¼å¨redisä¸æ§è¡ï¼ï¼
è¾åºï¼
è¿éä¼ å ¥çkey个æ°ä¸º1ï¼æ以redisæ¯keyèworldæ¯åæ°
è¿ä¸ªæä½ç¸å½äºæèæ¬å è½½å°redisï¼å¾å°ä¸ä¸ªSHA1çæ ¡éªåï¼ç¶å使ç¨è¿ä¸ªSHA1ç æ¥è°ç¨å¯¹äºçLuaèæ¬ï¼é¿å æ¯æ¬¡å»åéLuaèæ¬ã
ä¾åï¼
æ§è¡evalsha
å¦ï¼
redisæä¾äºå 个å½ä»¤æ¥ç®¡çèæ¬
ç¨äºå°Luaèæ¬å è½½å°rediså åä¸
ç¨äºå¤æsha1å¼æ¯å¦å·²ç»å è½½å°rediså åä¸
è¿å个æ°
ç¨äºæ¸ é¤rediså åå·²ç»å è½½çææèæ¬
ç¨äºæææ£å¨æ§è¡çLuaèæ¬
å¦æLuaèæ¬æ¯è¾èæ¶ï¼çè³Luaèæ¬åå¨é®é¢ï¼é£ä¹æ¤æ¶Luaèæ¬çæ§è¡ä¼é»å¡redisï¼ç´å°èæ¬æ§è¡å®æ¯æè å¤é¨å¹²é¢å°å ¶ç»æ
æä¸ç¹éè¦æ³¨æï¼å¦æLuaèæ¬æ£å¨æ§è¡åæä½ï¼ script kill å½ä»¤ä¸ä¼çæï¼è¿æ¶åªè½çå¾ èæ¬æ§è¡ç»æï¼æä½¿ç¨ shutdown save åæredisæå¡
å¯åç rediså®æ¹ææ¡£
æ两ç§æ¹å¼å¯ä»¥è°ç¨
è¿ä¸¤ç§æ¹æ³é½å¯ä»¥è°ç¨ï¼åºå«æ¯call()æ¹æ³æ¯éå°å°±åæ¢æ§è¡åé¢çå 容并ç´æ¥è¿åé误ï¼èpcalléå°å¼å¸¸ä¼å¿½ç¥æ继ç»æ§è¡
å ¶ä»å½ä»¤å¯åçææ¡£è¿éä¸èµè¿°
ä¸ä¸ªä½¿ç¨Luaèæ¬æ§è¡redis scanå½ä»¤è¿è¡æ¹éå é¤çä¾åï¼æ件å为 del-batch.lua
è°ç¨
æ§è¡äºä¹åä¼å é¤ç¬¦åè§å TEST_KEY* çkey
è°ç¨ç»æ
Redis | Lua脚本
Redis通过其内置的Lua脚本功能,极大地扩展了其功能范围。源码这些脚本主要通过以下几个命令实现:EVAL和EVALSHA,源码前者用于执行脚本,源码后者则是源码针对预先缓存的脚本。在执行过程中,源码直播课堂源码报价lua.call()和lua.pcall()的源码区别在于错误处理策略,前者会返回错误信息,源码而后者会封装错误并返回。源码通过SCRIPT LOAD命令,源码开发者可以将自定义脚本存储在服务器中,源码然后使用EVALSHA来执行。源码
管理脚本的源码其他工具包括SCRIPT EXISTS检查脚本缓存状态,SCRIPT FLUSH用于清除所有缓存,源码而遇到超时情况,源码SCRIPT KILL则允许终止执行中的脚本。Redis的Lua环境还内置了丰富的函数库,如redis.log()用于写入日志,redis.shalhex()计算SHA1校验和,以及处理错误和状态的函数。
此外,bit包提供了bit.tohex()将数字转换为进制字符串的功能,struct包用于Lua值和C结构间的转换,struct.pack()打包值,struct.unpack()解包。cjson和cmsgpack则支持快速的JSON和MessagePack操作。调试方面,Lua脚本支持step和next命令,以及查看局部变量,断点设置和执行continue,动态断点有助于调试条件和循环。
Redis调试器提供了eval和redis命令,可以执行脚本,并通过trace查看调用链。例如,Lua脚本可以用于实现带身份验证的锁,如LPOPRPUSH命令,展示了Lua脚本在实际场景中的应用。
redis分布式锁的实现(setNx命令和Lua脚本)
分布式锁在多线程环境中用于保证同一时间仅有一个线程访问共享资源,特别是在分布式系统中,这一需求更为关键。Java中通过synchronized关键字和ReentrantLock实现本地锁,但在分布式架构中,需要采用分布式锁机制以确保不同节点的线程能够同步执行,避免数据冲突和重复操作,保障系统的python input源码一致性。
分布式锁的核心特点包括:互斥性、原子性、一致性以及可撤销性。其主要实现方式包括利用Redis等分布式缓存系统。本文主要探讨基于Redis的分布式锁实现,重点关注setnx+expire命令与Lua脚本的使用,同时提及更高级的Redlock算法以及Redisson等工具的实现。
### 1. 利用setnx+expire命令实现分布式锁(错误做法)
- **setnx**:用于设置键值,当键不存在时才设置,具备原子性。**expire**:设置键的过期时间,实现超时机制。
- **错误**:`setnx`和`expire`是分开执行的,不保证原子性。若`setnx`成功后应用异常或重启,锁无法过期。
### 2. 使用Lua脚本实现分布式锁
- **改善方案**:Lua脚本可一次性执行多个Redis命令,确保原子性。具体实现为:
lua
local key = KEYS[1]
local value = ARGV[1]
local timeout = tonumber(ARGV[2])
redis.call('setnx', key, value)
redis.call('expire', key, timeout)
### 3. 使用 `set key value [EX seconds][PX milliseconds][NX|XX]` 命令
- **优点**:Redis从2.6.版本起,`SET`命令增加了`NX`选项,实现与`setnx`命令相同的效果。
- **唯一性**:设置唯一值,通常使用UUID确保唯一性,避免共享资源冲突。
- **释放锁**:需验证锁的唯一值,通过Lua脚本实现原子性,代码如下:
lua
local key = KEYS[1]
local value = ARGV[1]
local expected = ARGV[2]
redis.call('get', key, function(err, stored_value)
if err then return nil end
if stored_value == expected then
redis.call('del', key)
else
return nil
end
end)
### 4. Redlock算法与Redisson实现
- **Redlock**:Redis作者Antirez提出,基于多个独立节点的分布式锁,提高可用性和可靠性。
- **Redisson**:提供易于使用的分布式锁实现,通过Redlock算法优化,支持Java和Netty非阻塞I/O,实现与JUC的Lock接口兼容。
### 5. Redis实现分布式锁的轮子
- **SpringBoot + Jedis + AOP**:构建简易分布式锁实现,包括自定义注解、AOP拦截器以及核心类实现,最终通过Controller层控制测试。
通过上述方法,我们可以实现基于Redis的分布式锁,确保分布式系统的数据一致性与线程安全。分布式锁的实现不仅涉及技术细节,还需考虑不同场景下的优化和扩展,如在Redis集群环境下处理锁冲突等问题,确保系统稳定性和高效性。
🦋基于 redis jsp源码打包的简易滑动窗口实现
本文将介绍如何基于 Redis 实现简易滑动窗口,以及在业务场景中的应用。通过 Redis 的 zset 结构和 Lua 脚本,我们可以实现动态计算特定时间窗口内的数值统计,从而满足不同业务需求。本文将从业务背景、实现原理、功能实现、限制与优化、关键代码、不足与性能等方面进行阐述。
业务背景:在实际业务中,我们常需要对实时数据流进行规则预警,例如在 5 分钟内失败 2 次,满足此条件时触发告警。通过使用 Redis 的 zset 结构,我们可以基于时间戳计算特定时间窗口内的数值统计,从而实现动态滑动窗口功能。
Redis 版本功能实现:通过 Lua 脚本实现 CAS(check-and-set)命令,实现对滑动窗口的动态更新。针对不同场景,本文详细描述了三种实现方式:统计时间窗口内是否达到预定阈值、统计时间窗口内是否达到预定阈值但不进行清理、以及只统计时间窗口内数值个数。
限制与优化:在使用 Lua 脚本时,需注意 Redis 集群版本对 Lua 脚本的限制,确保所有操作在相同 slot 进行。此外,Lua 脚本的使用需遵循规范,避免占用过多计算和内存资源。当面临高消耗命令、Redis 连接数量限制、高 QPS 或涉及大量复杂流计算时,应考虑使用 Flink 等流式计算中间件进行优化。
关键代码示例:实现滑动窗口计算的核心类,展示了如何利用 Redis API 和 Lua 脚本进行数据更新和统计。
不足与性能考虑:滑动窗口实现中可能出现重复 member 的问题,特别是在分布式场景下。此外,数据采集时间与服务器接收时间不一致可能影响计算准确性。在性能方面,需关注高消耗命令的使用、Redis 连接数量限制、QPS 和复杂流计算的处理能力。
总结:本文通过详细介绍 Redis 滑动窗口的实现原理、功能实现、rapid generator源码限制与优化策略,以及关键代码示例,为开发者提供了一种基于 Redis 实现简易滑动窗口的实用方法。同时,针对不足与性能考虑提供了相应的解决方案,帮助开发者在实际应用中更好地利用 Redis 进行实时数据处理。
Redis中使用Lua脚本(二)之红包雨的抢夺
需求介绍
想象一下类似答题抢红包的场景,一轮题目答完后会下起红包雨。目标是分析并实现此需求。初始化每轮次的红包雨批次ID,并将总金额拆分给每份红包。答题结束时,用户可参与抢红包,每人最多可抢3个。红包雨结束后,页面展示抢夺金额前N名的用户。
需求分析与概要设计
1. Redis结构与Key设计
红包ID:,用户ID列表:u1、u3、u3、u4、u5。Redis的Key包括:RedBagBatch:、RedBagBatch::Users、RedBagBatch::Limit。初始化时,将总金额拆分为个红包,金额随机分布,顺序随机。
红包雨的Lua脚本设计与模拟演示
1. 红包ID:,用户ID:u1、u3、u4、u5。
2. 通过Redis CLI操作,初始化个红包,金额为1-,随机排序。
实际使用Lua脚本
在项目中,利用Spring MVC、Spring和MyBatis环境,配合原生Jedis客户端。
1. 创建Service类,实现ApplicationListener接口,以在容器初始化时加载Lua脚本。webcruiser查看源码
2. 加锁加载Lua脚本:使用script load方法调用Redis服务端,获取脚本的SHA值,后续循环使用,减少资源消耗。
3. 执行Lua脚本:每次调用时,使用初始化产生的SHA值,通过evalsha方法执行脚本,并传入相应的keys和params。
伪代码示例:
万字长文带你解读Redisson分布式锁的源码
通过深入解读 Redisson 分布式锁的源码,我们了解到其核心功能在于实现加锁、解锁以及设置锁超时这三个基本操作。而分布式锁的实现,离不开对 Redis 发布订阅(pub/sub)机制的利用。订阅者(sub)通过订阅特定频道(channel)来接收发布者(pub)发送的消息,实现不同客户端间的通信。在使用 Redisson 加锁前,需获取 RLock 实例对象,进而调用 lock 或 tryLock 方法来完成加锁过程。
Redisson 中的 RLock 实例初始化时,会配置异步执行器、唯一 ID、等待获取锁的时间等参数。加锁逻辑主要涉及尝试获取锁(tryLock)和直接获取锁(lock)两种方式。tryLock 方法中,通过尝试获取锁并监听锁是否被释放来实现锁的获取和等待逻辑。这通过调用底层命令(整合成 Lua 脚本)与 Redis 进行交互来实现。Redis 的 Hash 结构被用于存储锁的持有情况,hincrby 命令用于在持有锁的线程释放锁时调整计数,确保锁的可重入性。
解锁逻辑相对简单,通过调用 unlock 方法,Redisson 使用特定的 Lua 脚本命令来判断锁是否存在,是否为当前线程持有,并相应地执行删除或调整锁过期时间的操作。
此外,Redisson 支持 RedLock 算法来提供一种更鲁棒的锁实现,通过多个无关联的 Redis 实例(Node)组成的分布式锁来防止单点故障。尽管 RedLock 算法能一定程度上提高系统可靠性,但并不保证强一致性。因此,在业务场景对锁的安全性有较高要求时,可采取业务层幂等处理作为补充。
Redisson 的设计遵循了简化实现与高效性能的原则,通过 Lua 脚本与 Redis 的直接交互来实现分布式锁的原子操作。在源码中,通过巧妙利用并发工具和网络通信机制,实现了分布式锁的高效执行。尽管 Redisson 在注释方面可能稍显不足,但其源码中蕴含的并发与网络通信的最佳实践仍然值得深入学习与研究。
详解事务模式和Lua脚本,带你吃透Redis 事务
Redis事务包含两种模式:事务模式和Lua脚本。
本文分享自华为云社区《一文讲透 Redis 事务》,作者:勇哥java实战分享。
Redis事务包含如下命令:
事务包含三个阶段:
这里有一个疑问?在开启事务的时候,Redis key 可以被修改吗?
在事务执行 EXEC 命令之前 ,Redis key 依然可以被修改。
在事务开启之前,我们可以 watch 命令监听 Redis key 。在事务执行之前,我们修改 key 值 ,事务执行失败,返回nil 。
通过上面的例子,watch 命令可以实现类似乐观锁的效果 。
Redis的事务模式具备如下特点:
但Lua脚本更具备实用场景,它是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果。
Lua脚本模式的身影几乎无处不在,比如分布式锁、延迟队列、抢红包等场景。
Redis的事务包含如下命令:
事务包含三个阶段:
下面展示一个事务的例子。
在事务执行 EXEC 命令前,客户端发送的操作命令错误,比如:语法错误或者使用了不存在的命令。
在这个例子中,我们使用了不存在的命令,导致入队失败,整个事务都将无法执行 。
事务操作入队时,命令和操作的数据类型不匹配 ,入队列正常,但执行 EXEC 命令异常 。
这个例子里,Redis 在执行 EXEC 命令时,如果出现了错误,Redis 不会终止其它命令的执行,事务也不会因为某个命令执行失败而回滚 。
综上,我对 Redis 事务原子性的理解如下:
也就是:Redis 事务在特定条件下,才具备一定的原子性 。
数据库的隔离性是指:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
事务隔离分为不同级别 ,分别是:
首先,需要明确一点:Redis 并没有事务隔离级别的概念。这里我们讨论 Redis 的隔离性是指:并发场景下,事务之间是否可以做到互不干扰。
我们可以将事务执行可以分为EXEC 命令执行前和 EXEC 命令执行后两个阶段,分开讨论。
在事务执行之前 ,Redis key 依然可以被修改。此时,可以使用WATCH机制来实现乐观锁的效果。
因为 Redis 是单线程执行操作命令, EXEC 命令执行后,Redis 会保证命令队列中的所有命令执行完 。 这样就可以保证事务的隔离性。
数据库的持久性是指 :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Redis 的数据是否持久化取决于 Redis 的持久化配置模式 。
综上,redis 事务的持久性是无法保证的 。
一致性的概念一直很让人困惑,在我搜寻的资料里,有两类不同的定义。
我们先看下下维基百科上一致性的定义:
Consistency ensures that a transaction can only bring the database from one valid state to another, maintaining database invariants: any data written to the database must be valid according to all defined rules, including constraints, cascades, triggers, and any combination thereof. This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct. Referential integrity guarantees the primary key – foreign key relationship.
在这段文字里,一致性的核心是“约束”,“any data written to the database must be valid according to all defined rules ”。
如何理解约束?这里引用知乎问题如何理解数据库的内部一致性和外部一致性,蚂蚁金服 OceanBase 研发专家韩富晟回答的一段话:
“约束”由数据库的使用者告诉数据库,使用者要求数据一定符合这样或者那样的约束。当数据发生修改时,数据库会检查数据是否还符合约束条件,如果约束条件不再被满足,那么修改操作不会发生。
关系数据库最常见的两类约束是“唯一性约束”和“完整性约束”,表格中定义的主键和唯一键都保证了指定的数据项绝不会出现重复,表格之间定义的参照完整性也保证了同一个属性在不同表格中的一致性。
“ Consistency in ACID ”是如此的好用,以至于已经融化在大部分使用者的血液里了,使用者会在表格设计的时候自觉的加上需要的约束条件,数据库也会严格的执行这个约束条件。
所以事务的一致性和预先定义的约束有关,保证了约束即保证了一致性。
我们细细品一品这句话:This prevents database corruption by an illegal transaction, but does not guarantee that a transaction is correct。
写到这里可能大家还是有点模糊,我们举经典转账的案例。
我们开启一个事务,张三和李四账号上的初始余额都是元,并且余额字段没有任何约束。张三给李四转账元。张三的余额更新为 - , 李四的余额更新为。
从应用层面来看,这个事务明显不合法,因为现实场景中,用户余额不可能小于 0 , 但是它完全遵循数据库的约束,所以从数据库层面来看,这个事务依然保证了一致性。
Redis 的事务一致性是指:Redis 事务在执行过程中符合数据库的约束,没有包含非法或者无效的错误数据。
我们分三种异常场景分别讨论:
综上所述,在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。
这本书是分布式系统入门的神书。在事务这一章节有一段关于 ACID 的解释:
Atomicity, isolation, and durability are properties of the database,whereas consistency (in the ACID sense) is a property of the application. The application may rely on the database’s atomicity and isolation properties in order to achieve consistency, but it’s not up to the database alone. Thus, the letter C doesn’t really belong in ACID.
原子性,隔离性和持久性是数据库的属性,而一致性(在 ACID 意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母 C 不属于 ACID 。
很多时候,我们一直在纠结的一致性,其实就是指符合现实世界的一致性,现实世界的一致性才是事务追求的最终目标。
为了实现现实世界的一致性,需要满足如下几点:
我们通常称 Redis 为内存数据库 , 不同于传统的关系数据库,为了提供了更高的性能,更快的写入速度,在设计和实现层面做了一些平衡,并不能完全支持事务的 ACID。
Redis 的事务具备如下特点:
从工程角度来看,假设事务操作中每个步骤需要依赖上一个步骤返回的结果,则需要通过 watch 来实现乐观锁 。
Lua 由标准 C 编写而成,代码简洁优美,几乎在所有操作系统和平台上都可以编译,运行。Lua 脚本可以很容易的被 C/C ++ 代码调用,也可以反过来调用 C/C++ 的函数,这使得 Lua 在应用程序中可以被广泛应用。
Lua 脚本在游戏领域大放异彩,大家耳熟能详的《大话西游II》,《魔兽世界》都大量使用 Lua 脚本。Java 后端工程师接触过的 api 网关,比如Openresty ,Kong 都可以看到 Lua 脚本的身影。
从 Redis 2.6.0 版本开始, Redis内置的 Lua 解释器,可以实现在 Redis 中运行 Lua 脚本。
使用 Lua 脚本的好处 :
Redis Lua 脚本常用命令:
命令格式:
说明:
简单实例:
下面演示下 Lua 如何调用 Redis 命令 ,通过redis.call()来执行了 Redis 命令 。
"hello world"
使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。
思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。
EVALSHA 命令基本语法如下:
实例如下:
从定义上来说,Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。
不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。
Lua 脚本是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果。
Lua 脚本模式的身影几乎无处不在,比如分布式锁、延迟队列、抢红包等场景。
不过在编写 Lua 脚本时,要注意如下两点:
在Redis中如何使用Lua脚本
Redis中的Lua脚本是一种强大的工具,它允许在数据库层面上直接执行逻辑,无需客户端和服务器之间的数据传输,尤其在C/S架构下处理复杂操作时。作为NoSQL数据库Redis的重要特性,Lua脚本广泛应用于缓存管理和服务器内部逻辑处理。
在Redis中,有两种方式执行Lua脚本:eval和evalsha。eval允许使用key列表和参数列表传递灵活性,将脚本内容作为字符串发送给服务端执行,而evalsha则先将脚本加载到服务器,通过SHA1校验和执行,节省了每次发送脚本的开销,提升了效率。你可以使用script load命令将脚本加载,并通过evalsha调用。
在编写Lua脚本时,可以借助redis.call和redis.pcall函数访问Redis,两者区别在于处理错误的方式。lua脚本的日志输出和调试功能也十分实用,如Lua Script Debugger和Redis的日志级别控制。
SpringBoot应用中,通过RedisTemplate可以方便地执行Lua脚本,但需要注意配置和可能遇到的序列化问题。同时,尽管Lua脚本强大,但需谨慎使用,因为它可能会阻塞服务器,导致性能问题,特别是在分布式环境中。
总的来说,掌握Lua脚本在Redis中的使用是互联网行业中Java开发者必备的技能,通过合理的配置和应用,可以有效提升Redis的性能和灵活性。