redis lua执行原理,主从同步,动态变量

[Redis Slowlog]是排查性能问题关键监控指标。它是记录Redis queries运行时间超时特定阀值的系统。
这类慢查询命令被保存到Redis服务器的一个定长队列,最多保存slowlog-max-len(默认128)个慢查询命令。
当慢查询命令达到128个时,新产生的慢查询被加入前,会从队列中删除最旧的慢查询命令。

执行原理

创建并修改Lua环境

  • 1 创建基础Lua环境
  • 2 载入函数库
  • 3 创建全局表格Lua
  • 4 替换随机函数
  • 5 创建排序辅助函数
  • 6 创建redis.pcall函数
  • 7 全局环境保护
  • 8 修改后的Lua环境保存到服务器状态的Lua属性,等待脚本执行

Redis中带有不确定性的命令:

1

2

3

4

5

6

7

SINTER

SUNION

SDIFF

SMEMEBERS

HKEYS

HVALS

KEYS

注意:

  • Redis提供了排序函数,使用上述命令后,会返回相同的排序结果
  • Redis确保检查创建变量时,添加local等参数,但是没有禁止修改已经存在的全局变量,因此使用时应谨慎。
  • 由于Redis使用串行化的方式执行,因此某一特定时间,只有一个脚本能被放进Lua环境里面运行。

环境协作组件

环境组件:

  • 一个是用于执行Lua脚本的伪客户端
  • 一个用于保存Lua脚本的字典。

执行Lua脚本的伪客户端

使用redis.call或者redis.pcall执行Redis命令:

  • 1 将redis.call或者redis.pcall传给伪客户端
  • 2 伪客户端将执行的命令传给执行器
  • 3 执行器执行命令,返回给伪客户端
  • 4 伪客户端把结果返回给Lua环境
  • 5 Lua环境把结果返回给redis.call或者redis.pcall函数
  • 6 redis.call或者redis.pcall返回结果给调用者

lua_s字典

保存Lua脚本的SHA1【校验和】checksum,值是对应的脚本。Redis会把EVAL命令执行过的脚本,或者 LOAD加载的脚本都保存在字典中。

主从同步

字典的作用:实现 EXISTS命令;实现脚本的复制

EVAL命令的实现

  • 1 根据客户端给定的脚本,在Lua环境中定义Lua函数
  • 2 将客户端给定的脚本保存到lua_s字段,进一步使用
  • 3 执行刚刚在lua中定义的函数,执行客户端给定的lua脚本

通过函数保存传入的脚本好处:

  • 1 执行方便
  • 2 保持局部性
  • 3 如果定义过一次,只要使用校验和即可。

准备执行脚本:

  • 1 将EVAL命令传入的键名参数和脚本参数保存在KEYS数组和ARGV数组中
  • 2 装载超时钩子
  • 3 执行脚本函数
  • 4 移除钩子
  • 5 结果放入缓冲区
  • 5 垃圾回收

使用EVALSHA “xxx校验和”0 就可执行EVAL “return ‘hello world!’” 0

脚本管理命令实现

1

2

3

4

FLUSH 用于清除服务器中lua有关的脚本,释放lua_s字典,关闭现有的lua环境,并重新创建

EXISTS 输入SHA1校验和,判断是否存在

LOAD 与EVAL相同,创建对应的lua函数,存放到字典中

KILL 使用钩子定期检查脚本运行时间,如果没有执行过,可以使用 KILL杀掉;如果执行过,只能使用SHUTDOWN nosave停止服务器

 

动态变量

https://my.oschina.net/wangxu3655/blog/3125737?from=singlemessage

这个文章介绍了,当lua脚本出现动态变量时的处理方式

-- 当前时间

local now_time = redis.call('TIME');

-- 设置OPERATE_TIME值为当前秒数

redis.call('SET','OPERATE_TIME',now_time[1]);

 

Write commands not allowed after non deterministic commands.

Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode. 

翻译过来就是说, 写命令不被允许出现在‘非确定性命令’的后面, 请在脚本开始时调用redis.replicate_commands()来切换到命令复制模式.

修改后的lua脚本:

-- 开启单命令复制模式

redis.replicate_commands();

-- 当前时间

local now_time = redis.call('TIME');

-- 设置OPERATE_TIME值为当前秒数

redis.call('SET','OPERATE_TIME',now_time[1]);

再执行就不会报错, 然后我们到AOF文件下去查看一下, 我们发现追加的不是脚本内容, 而是MULTI...EXEC命令:

$1

0
*1
$5
MULTI
*3
$3
SET
$12
OPERATE_TIME
$10
1572855544
*1
$4
EXEC

回到脚本上来, 我们的脚本包含了TIME命令来获取系统时间, 这个命令在不同时间去执行返回的值肯定不一样,  如果在主从复制和AOF追加时, 直接复制整个脚本的内容, 那么肯定会造成执行时数据的不一致性. 

所以, 在执行Lua时Redis默认不允许动态的不确定性的变量存在. 如果存在, 则需要开启命令复制模式, 即只复制Lua脚本里包含的写命令, 所有的写命令会被包装在MULTI ... EXEC里.

Redis官方, 把默认的复制整个脚本内容的模式定义为whole scripts replication, 把只复制脚本里写命令的模式定义为 script effects replication. 

在命令复制模式下, 还可以选择是否对某个命令进行目标复制, 即是否需要复制到‘从服务器’和‘AOF文件’ , 如下:

redis.set_repl(redis.REPL_ALL) -- Replicate to AOF and slaves.
redis.set_repl(redis.REPL_AOF) -- Replicate only to AOF.
redis.set_repl(redis.REPL_SLAVE) -- Replicate only to slaves.
redis.set_repl(redis.REPL_NONE) -- Don't replicate at all.

下面这个slowlog的时间讲解很到位

https://my.oschina.net/u/3023401/blog/2231347

Redis Slowlog只计算命令的执行时间

如MySQL/MongoDB等常见数据库,慢查询的query_time都会包含命令所有耗时,包含锁等待这类时间; 而Redis的慢查询query_time只记录自己“被cpu服务的时间”,不包含排队等待、IO等待(如AOF SYNC)这类时间。
理解这点非常重要

参考:
    The Redis Slow Log is a system to log queries that exceeded a 
specified execution time. The execution time does not include I/O 
operations like talking with the client, sending the reply and so forth,
but just the time needed to actually execute the command (this is the only
stage of command execution where the thread is blocked and can not serve
other requests in the meantime).

Redis Single-threads的问题

Redis Server是单线程的处理(bgsave或aof重写时会Fork子进程处理),同一时间只能处理一个命令,并且是同步完成的。
所以RD和DBA在设计keyspace和访问模式时,应尽量避免使用耗时较大的命令。
在理想状态下,Redis单实例能处理8~10w的QPS, 如果大量的redis命令大量耗时大于1ms, 其实QPS只能达到1000基于几百。
Redis出现耗时大的命令,导致其他所有请求被阻塞等待,redis处理能力急剧退化,易导致整个服务链雪崩。

你可能感兴趣的:(Redis)