Lua功能设计
lua版本
redis-for-cloud
引用版本:5.1.5 2006 redis6.0.9
引用版本:5.1.5 最新版本:5.4.1 2020.6 所以使用5.1.5
实现细节中思考的问题:
随机数
随机数需要不同实例相同随机值,参考Srand48
,rand.h
多线程情况下,redisSrand48
需要改造成类,对每个线程生成一个对象,每次调用前需要设置种子为0.
sha
SHA-1,参考sha1.h
全局变量
限制对全局变量的修改,参考scriptingEnableGlobalsProtection
数据类型转换
Lua 数据类型和 Redis 数据类型之间转换 参见: https://www.cnblogs.com/yanwei-wang/p/6000691.html LUA转redis,参考luaRedisGenericCommand()
redis转LUA,参考redisProtocolToLuaType()
排序
保证命令结果的一致性,需要对redis命令返回结果进行排序。 参考:luaSortArray()
gc
定期调用lua_gc回收
伪客户端
lua调用tendis命令的伪客户端fclient evalCommand
开始的时候创建fclient,redis.call
的时候从lua环境取出命令和参数设置到fclient。 命令处理完成,转换redis类型为lua类型,并推送到lua栈顶table中。在栈顶上调用luaSortArray
完成排序。
原子性
script kill 原生的redis为了保证原子性,有数据修改的时候不能kill,需要shutdown nosave
才行。由于redis的aof里面会有脏数据,而tendis的解决方案如下: tendis需要在事务里面实现原子性,事务不能太大。 tendis在处理script kill
的时候,无法定位到具体执行实例,只能全部一起kill,或者传入sha然后kill全部sha相等的实例。
锁
只支持脚本外传入key,evalcommand
里面就对所有key先上锁。 具体command上锁的时候,利用伪客户端SessionCtx::isLockedByMe()
提供的递归锁功能。 目前只有AquireKeyLock()
接口有这个功能,所有上锁接口都要改造支持,因为redis.call
可能调用很多命令,这些命令可能需要chunk锁和db锁。
事务
为了保证整个lua脚本的原子性,需要一个事务处理所有的请求。 因为每个store需要自己的事务,所以可以用SessionCtx::createTransaction()
创建事务接口。 对于不同的命令,会有很多自己创建事务的情况,要全部改成使用sessionCtx内公共的事务。 多个store的事务之间理论上也需要保证原子性,但是可以限制不能跨slot。 对evalcommand
外部传入的key进行跨slot校验,在命令结束的时候,可以check一下SessionCtx::_txnMap
里面是否只有一个元素。 但是有的命令可能并不需要传入key,而是对chunk或者store进行修改操作,所以要确认现在command里面的flag是否设置正确的CMD_NOSCRIPT标记,并确认是否会对chunk和store进行修改。
lua_scripts字典
主要涉及主从复制、回档、搬迁、cluster模式的failover等造成的脚本一致性问题。
命令:
EVAL
EVALSHA
SCRIPT LOAD
SCRIPT EXISTS
SCRIPT FLUSH
SCRIPT KILL
lua调用redis:
redis.call()
redis.pcall()
redis.error_reply()
redis.status_reply()
redis.replicate_commands()
redis.breakpoint()
redis.debug()
redis.set_repl()
redis.log()
redis.sha1hex()
redis.error_reply()
redis.status_reply()
redis.set_repl()
多kvstore
cluster模式限制跨slot,也就限制了跨store。
debug功能
script debug no
script debug yes 事务不提交,其他线程要能处理普通请求,要能处理lua请求
script debug sync 事务要提交
ldbState (lua debug状态信息)需要多实例,每个lua环境一个。
脚本复制
脚本完全复制:直接复制lua代码段。需要阻止每个命令内部生成binlog,然后对整个脚本生成一个特殊的binlog。(可以暂不支持) 脚本效果复制:复制lua执行过程中调用的redis写命令。在tendis中则是复用binlog同步。 选择性复制:在调用redis.replicate_commands()
设置效果复制的情况下,调用redis.set_repl(redis.REPL_NONE)
等实现只复制一部分写命令的功能。(可以暂不支持) 用户在lua内调用redis.replicate_commands()
将会切换为效果复制,需要在写命令之前进行设置。 参见: https://juejin.cn/post/6858654481572331534
revision
暂时未发现有啥影响
多线程
建立多个lua_State环境,需要确认是否有全局变量,随机函数之类的影响,需要确认内存占用。 为了解耦,lua_State不要放到workpool的类里面。新建LuaStatePool类。 获取lua_State需要上锁,为了降低锁竞争,需要分桶。
主从同步
效果复制,完全复制 lua字典也要保证主从同步。
定点回档
备份时lua字典也要存入快照,回档时lua字典也要加载回内存。
搬迁
lua命令字典也要搬迁,但是命令是与slot无关的。
Lua功能开发
任务
- 学习lua语法,学习lua嵌入c的语法
- 学习redis处理lua的逻辑。
- 引入lua代码,lua环境初始化,编译运行。
- 随机数,参考Srand48,rand.h
- sha,参考sha1.h
- 限制对全局变量的修改,参考scriptingEnableGlobalsProtection
- 对redis命令的返回结果排序
- 伪客户端
- redis.call()等接口
- 锁改造:所有支持lua的命令会调用到的锁接口,都要支持类似递归锁的逻辑。
- 事务改造:所有支持lua的命令改为使用sessionCtx内公共的事务
- 6个lua命令,其中evalsha功能不保证复制,搬迁,回档的一致性(可以考虑暂不支持evalsha)。
- 复制(可以考虑只支持效果复制)
- debug功能(可以考虑暂不支持)
- lua相关测试+测例
- 锁改造和事务改造测试+测例