Lua功能设计

lua版本

redis-for-cloud引用版本:5.1.5 2006 redis6.0.9引用版本:5.1.5 最新版本:5.4.1 2020.6 所以使用5.1.5

实现细节中思考的问题:

随机数

随机数需要不同实例相同随机值,参考Srand48rand.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等造成的脚本一致性问题。

命令:

  1. EVAL
  2. EVALSHA
  3. SCRIPT LOAD
  4. SCRIPT EXISTS
  5. SCRIPT FLUSH
  6. SCRIPT KILL

lua调用redis:

  1. redis.call()
  2. redis.pcall()
  3. redis.error_reply()
  4. redis.status_reply()
  5. redis.replicate_commands()
  6. redis.breakpoint()
  7. redis.debug()
  8. redis.set_repl()
  9. redis.log()
  10. redis.sha1hex()
  11. redis.error_reply()
  12. redis.status_reply()
  13. redis.set_repl()

多kvstore

cluster模式限制跨slot,也就限制了跨store。

debug功能

  1. script debug no
  2. script debug yes 事务不提交,其他线程要能处理普通请求,要能处理lua请求
  3. script debug sync 事务要提交
  4. 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功能开发

任务

  1. 学习lua语法,学习lua嵌入c的语法
  2. 学习redis处理lua的逻辑。
  3. 引入lua代码,lua环境初始化,编译运行。
  4. 随机数,参考Srand48,rand.h
  5. sha,参考sha1.h
  6. 限制对全局变量的修改,参考scriptingEnableGlobalsProtection
  7. 对redis命令的返回结果排序
  8. 伪客户端
  9. redis.call()等接口
  10. 锁改造:所有支持lua的命令会调用到的锁接口,都要支持类似递归锁的逻辑。
  11. 事务改造:所有支持lua的命令改为使用sessionCtx内公共的事务
  12. 6个lua命令,其中evalsha功能不保证复制,搬迁,回档的一致性(可以考虑暂不支持evalsha)。
  13. 复制(可以考虑只支持效果复制)
  14. debug功能(可以考虑暂不支持)
  15. lua相关测试+测例
  16. 锁改造和事务改造测试+测例