当业务需要在一次数据库交互中原子性地执行多个命令时,例如实现分布式锁、限流器或进行条件更新,多次网络往返会增加延迟并引入竞态条件风险。Orca(兼容Redis协议)提供的Lua脚本功能,允许您将多个Redis兼容命令封装在一个脚本中,由数据库原子化执行,从而有效解决此类问题,提升复杂操作的执行效率与数据一致性。
功能简介
Orca(兼容Redis协议)的Lua脚本功能,允许您像在Redis中一样执行Lua脚本。其核心优势在于原子性和高性能。
原子性:脚本内的所有命令作为一个不可分割的单元执行。脚本执行期间,不会有其他命令或脚本插入,保证了操作的原子性,避免了部分执行导致的中间状态。
高性能:
减少网络开销:将多个命令打包在单个脚本中一次性发送给数据库,显著减少了客户端与服务器之间的网络往返次数。
脚本缓存:通过
SCRIPT LOAD命令将脚本预加载到集群内存中,并获得一个SHA1校验和。后续可通过EVALSHA命令,仅发送此简短的SHA1值来调用脚本,进一步降低网络传输的数据量。
适用范围
集群版本:需为MySQL 8.0.2,且内核小版本需为8.0.2.2.33及以上版本。
连接地址:需使用读写模式为可读可写(自动读写分离)的Orca地址执行Lua脚本命令。
不支持的命令:以下命令无法在Lua脚本中通过
redis.call()或redis.pcall()执行,否则将返回错误。事务控制命令:
WATCH、UNWATCH、MULTI、EXEC、DISCARD阻塞命令:
BLPOP、BRPOPLua脚本命令:
EVAL、EVAL_RO、EVALSHA、EVALSHA_RO、SCRIPT订阅发布命令:
SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE连接管理命令:
AUTH、HELLO、CLIENT
基本语法
以下是操作Lua脚本的核心命令。更多信息请参见Redis官网Lua 脚本相关命令和Scripting with Lua。
命令 | 语法 | 说明 |
|
| 直接执行给定的Lua脚本。这是最基础的执行方式。参数说明:
|
|
| 只读模式下执行Lua脚本。适用于读写分离场景,确保脚本不会执行写操作。若脚本中包含写命令,将返回错误。参数说明与 |
|
| 通过脚本的SHA1校验和执行已缓存的脚本。如果脚本未缓存,将返回 |
|
| 只读模式下通过脚本的SHA1校验和执行已缓存的脚本。适用于读写分离场景,确保脚本不会执行写操作。若脚本中包含写命令,将返回错误。参数说明与 |
|
| 将脚本加载到缓存中,并返回其SHA1校验和,以便后续通过 |
|
| 检查一个或多个SHA1校验和对应的脚本是否存在于缓存中。存在返回1,不存在返回0。 |
|
| 终止当前正在执行的Lua脚本。Orca支持终止任意脚本(包括写脚本),并会自动回滚该脚本已产生的数据变更,保证数据一致性。 |
|
| 清空当前集群中的所有Lua脚本缓存。
|
操作示例
准备测试数据:
SET polardb orca测试命令:
EVAL
EVAL "return redis.call('GET', KEYS[1])" 1 polardb返回示例:
"orca"EVAL_RO
EVAL_RO "return redis.call('GET', KEYS[1])" 1 polardb返回示例:
"orca"SCRIPT LOAD
SCRIPT LOAD "return redis.call('GET', KEYS[1])"返回示例:
"d3c21d0c2b9ca22f82737626a27bcaf5d288f99f"EVALSHA
EVALSHA d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返回示例:
"orca"EVALSHA_R
EVALSHA_RO d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返回示例:
"orca"SCRIPT EXISTS
SCRIPT EXISTS d3c21d0c2b9ca22f82737626a27bcaf5d288f99f ffffffffffffffffffffffffffffffffffffffff返回示例:
1) (integer) 1 2) (integer) 0SCRIPT FLUSH
警告该命令会清空集群中的所有Lua脚本缓存(
SCRIPT LOAD/EVAL产生的缓存都会被移除)。清空后,所有依赖
EVALSHA/EVALSHA_RO的调用都可能返回NOSCRIPT错误,需要重新加载脚本并重试。若缓存脚本较多,
SCRIPT FLUSH可能阻塞集群较长时间,建议在业务低峰期执行。建议您在客户端/应用侧保留脚本源代码,并实现
NOSCRIPT的自动恢复机制(重新SCRIPT LOAD或使用EVAL重新注册)。
SCRIPT FLUSH返回示例:
OK
常见使用场景
配置并执行一个原子计数器
避免并发INCR导致的竞态,确保计数器严格递增且可带条件重置。
流程概述
编写Lua脚本。
加载至集群缓存。
通过
EVALSHA复用执行。
SCRIPT LOAD + EVALSHA减少网络传输量,提升性能与幂等性。脚本内容不暴露于请求体。
操作步骤
编写脚本(本地保存):
-- counter.lua:若 key 不存在则设为 0,再 +1;返回新值 if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])加载脚本并获取SHA1:
SCRIPT LOAD "if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])" -- 返回结果 "b547eabbcde73b25330442e4f4e4dc1783b91241"(推荐)复用执行:
EVALSHA b547eabbcde73b25330442e4f4e4dc1783b91241 1 my_counter -- 返回结果 (integer) 1 -- 再次执行返回结果 (integer) 2
执行纯查询脚本
确认脚本不含写操作:检查是否调用
SET、DEL、HSET、LPUSH等任意写命令。若存在,禁止使用EVAL_RO。说明若脚本含写命令,将返回错误
(error) ERR Write commands are not allowed from read-only scripts.。使用
EVAL_RO执行:EVAL_RO "return {redis.call('SET', KEYS[1]), redis.call('TTL', KEYS[1])}" 1 polardb -- 返回结果 1) "orca" 2) (integer) -1
终止异常运行的Lua脚本
防止长耗时脚本阻塞集群。中断执行,系统将自动回滚全部变更,保持数据一致性。执行SCRIPT KILL:
仅能终止当前正在执行的脚本,不支持指定SHA1终止。
SCRIPT KILL
-- 返回结果
OK性能优化实践
为降低Lua脚本对集群的阻塞影响,并避免集群缓存大量功能重复的脚本导致内存占用过高,建议遵循以下实践:
超时限制:单个Lua脚本的默认执行超时时间为300秒。
避免编写过大的Lua脚本,防止占用过多的内存。
避免在Lua脚本中长时间、大批量写入数据。
阻塞风险:Lua脚本在集群中以原子方式执行。在执行期间,会阻塞其他命令与脚本,因此应控制写入批量与执行时长,以避免长循环或大范围遍历。如有必要,应将复杂逻辑拆分为多个独立脚本进行执行。
脚本缓存非持久化:集群运行时Lua脚本缓存不会被清除,集群重启、主备切换(HA)或执行
SCRIPT FLUSH命令后会清理Lua脚本缓存,需要重新注册。脚本编写规范:使用
KEYS[]与ARGV[]传递参数,避免将参数硬编码在脚本中。减少网络流量:使用
SCRIPT LOAD + EVALSHA组合,获得最佳性能并减少网络流量。
操作示例
加载脚本:在应用初始化或首次使用时,通过
SCRIPT LOAD将脚本加载到集群并获取其SHA1值。您的应用程序应在本地保存此SHA1值。# 加载一个设置键值的脚本 SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # 返回示例 "55b22c0d0cedf3866879ce7c854970626dcef0c3"执行脚本:后续通过
EVALSHA命令,使用上一步获取的SHA1值来执行脚本。# 使用缓存的脚本设置 k1 = v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 # 使用同一个缓存的脚本设置 k2 = v2 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2处理
NOSCRIPT错误:如果EVALSHA返回NOSCRIPT错误,应用程序应捕获此错误,然后自动切换回使用EVAL或SCRIPT LOAD命令执行一次脚本将脚本缓存至集群中。EVAL:在执行的同时会自动将脚本重新缓存。SCRIPT LOAD:将脚本加载到集群并获取其SHA1值。
清理Lua脚本的内存占用:当您在不需要执行Lua脚本时,可执行
SCRIPT FLUSH命令清除Lua脚本缓存。若集群中缓存的Lua脚本过多,该命令可能会阻塞集群较长时间,建议在业务低峰期执行。
常见问题
如何处理
NOSCRIPT No matching script. Please use EVAL.错误?问题原因:这个错误表示您尝试使用
EVALSHA或EVALSHA_RO命令执行一个不存在于集群缓存中的脚本。通常由集群重启、主备切换或执行了SCRIPT FLUSH导致。
解决方案:您的客户端代码需要具备错误处理逻辑。当捕获到NOSCRIPT错误时,应改用EVAL或SCRIPT LOAD命令重新执行该脚本将脚本缓存至集群中,后续的EVALSHA调用即可恢复正常。Lua脚本执行超时怎么办?
脚本默认超时时间为300秒。超时通常意味着脚本逻辑过于复杂或处理的数据量过大。
解决方案:优化脚本逻辑,避免在脚本内执行低效的循环或大规模数据遍历。
如果一个脚本执行时间过长,可以通过
SCRIPT KILL命令终止它。PolarDB会回滚该脚本已做的所有修改。将复杂的业务逻辑拆分为多个更小、更快的脚本分步执行。
为什么使用
EVAL_RO执行脚本会报错ERR Write commands are not allowed from read-only scripts.?EVAL_RO和EVALSHA_RO是只读模式,严禁执行任何写操作命令(如SET、HSET或DEL等)。此错误明确指出您的脚本中包含了写命令。
解决方案:检查并移除脚本中的所有写命令。
如果您确实需要执行写操作,请改用
EVAL或EVALSHA命令。