长期运行的基于Lua脚本的Redis事务问题 - scalegrid


Redis提供了两种处理事务的机制-基于MULTI / EXEC的事务Lua脚本评估。Redis Lua脚本是推荐的方法,在用法上相当流行。
部署了Lua脚本的我们的Redis客户经常报告此错误:
“BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE”
在本文中,我们将说明脚本的Redis事务属性,此错误的原因以及为什么我们必须在可能进行故障转移的Sentinel管理的系统上格外小心。
 
Redis Lua脚本的事务性
Redis的“事务”实际上不是传统上理解的事务-如果发生错误,脚本不会进行回退
Redis脚本的“原子性”通过以下方式保证:

  • 一旦脚本开始执行,所有其他命令/脚本将被阻止,直到脚本完成。因此,其他客户端要么看到脚本所做的更改,要么看不到。这是因为它们只能在脚本之前或之后执行。
  • 但是,Redis不会回滚,因此在脚本内发生错误时,该脚本已进行的任何更改都将保留,以后的命令/脚本将看到这些部分更改。
  • 由于脚本执行期间所有其他客户端均被阻止,因此至关重要的一点是,脚本的行为应能及时完成。


 
“ lua-time-limit”值
强烈建议脚本在一定时间内完成。Redis使用“ lua-time-limit”值以弱方式强制执行此操作。这是允许脚本运行的最大允许时间(以毫秒为单位)。默认值为5秒。这对于CPU限制的活动来说确实是很长的时间(脚本的访问权限受到限制,并且无法运行用于访问磁盘的命令)。
但是,如果脚本执行时间超过此时间,则不会被杀死。Redis重新开始接受客户端命令,但是以BUSY错误响应它们。
如果此时必须终止脚本,则有两个选项可用:

  • SCRIPT KILL命令可用于停止尚未执行任何写操作的脚本。
  • 如果脚本已经执行了写入服务器的操作,但仍必须终止,请使用SHUTDOWN NOSAVE完全关闭服务器。

通常最好等待脚本完成其操作。文档中提供了有关终止脚本执行和相关行为的方法的完整信息。
 
 Sentinel监控的高可用性系统上的行为
Sentinel管理的高可用性系统为此增加了新的障碍。实际上,此讨论适用于任何依赖轮询Redis服务器的运行状况的高可用性系统:
  • 长时间运行的脚本最初将阻止客户端命令。稍后,当“ lua-time-limit”通过时,服务器将开始以BUSY错误进行响应。
  • Sentinels将认为该节点不可用,如果该节点持续存在超过Sentinels上配置的毫秒级下降值,则它们将确定该节点已关闭。
  • 如果这样的节点是主节点,则将启动故障转移。副本节点可能会升级,并且可能开始接受来自客户端的新连接。
  • 同时,较早的母版最终将完成脚本的执行并重新联机。但是,Sentinel最终会将其重新配置为副本,并且它将开始与新的主服务器同步。脚本写入的所有数据都将丢失。
  •  

 
示范
我们建立了一个敏感的高可用性系统来演示此故障转移行为。该设置有2个Redis服务器在主/副本配置中运行,并由3个定点仲裁监控。
lua-time-limit值设置为500 ms,因此如果脚本运行时间超过500 ms,它将开始以错误响应客户端。的向下后毫秒上哨兵值被设置为5秒,使该报告错误的节点5秒后标记DOWN。
我们在主服务器上执行以下Lua脚本:
local i = 0
while (true)
do
local key = "Key-" .. i
local value =
"Value-" .. i
redis.call('set', key, value)
i = i + 1
redis.call('time')
end

这会将条目不断写入Redis主服务器。我们订阅其中一个Sentinel哨兵事件以观察行为。
该脚本在主服务器上启动:

$ redis-cli -a --eval test.lua
警告:在命令行界面上使用带有'-a'或'-u'选项的密码可能并不安全。

这是Sentinel上截断的活动序列:

3) "+vote-for-leader"
4)
"9096772621089bb885eaf7304a011d9f46c5689f 1"
1)
"pmessage"
2)
"*"
3)
"+sdown" <<< master marked DOWN
4)
"master test 172.31.2.48 6379"
1)
"pmessage"
2)
"*"
3)
"+odown"
4)
"master test 172.31.2.48 6379 quorum 3/2"
1)
"pmessage"
2)
"*"
3)
"-role-change" << role change initiated
4)
"slave 172.31.28.197:6379 172.31.28.197 6379 @ test 172.31.2.48 6379 new reported role is master"
1)
"pmessage"
2)
"*"
3)
"+config-update-from"
4)
"sentinel 9096772621089bb885eaf7304a011d9f46c5689f 172.31.2.48 26379 @ test 172.31.2.48 6379"
1)
"pmessage"
2)
"*"
3)
"+switch-master"
4)
"test 172.31.2.48 6379 172.31.28.197 6379"

后来,当旧的主master节点回到联机时,它将切换为复制次节点:

3) "-role-change"
4)
"slave 172.31.2.48:6379 172.31.2.48 6379 @ test 172.31.28.197 6379 new reported role is master"
1)
"pmessage"
2)
"*"
3)
"-sdown"
4)
"slave 172.31.2.48:6379 172.31.2.48 6379 @ test 172.31.28.197 6379"
1)
"pmessage"
2)
"*"
3)
"+role-change"
4)
"slave 172.31.2.48:6379 172.31.2.48 6379 @ test 172.31.28.197 6379 new reported role is slave"

通过脚本写入旧Master的所有数据都将丢失。
 
推荐建议

  • 您必须先了解长时间运行的脚本的特征,然后才能将其部署到生产环境中。
  • 如果您的脚本经常违反lua时间限制,则必须彻底检查脚本以进行可能的优化。您也可以将其分解为可以接受的持续时间。
  • 如果您必须运行违反lua时间限制的脚本,请考虑在其他客户端活动较少的时间段安排这些脚本。
  • lua-time-limit的值也可以增加。如果与脚本并行执行的其他客户端应用程序可以忍受接收到的极其延迟的响应,而不是“忙”错误并稍后重试,那么这将是一个可接受的解决方案。

Sentinel监视的高可用性系统的其他注意事项:
  • 如果脚本仅执行读取操作,并且您具有可用的副本,则可以将这些脚本移至副本。

毫秒后的Sentinel参数更改为一个值,该值将确保不启动故障转移。仅在仔细考虑后才能执行此操作,因为大幅增加该值将损害系统的高可用性特性。这也可能导致忽略真正的服务器故障。