Redis Lua脚本异常

项目中使用到Redis缓存中间件,同时基于Lua脚本来实现一些锁的功能。项目在测试环境执行时没什么问题,但当发布到生产环境时会抛出如下异常:

 Error: ERR 'EVAL' command keys must in same slot

或如下异常:

org.springframework.dao.InvalidDataAccessApiUsageException: ERR 'EVAL' command keys must in same slot. channel: [id: 0x10457fd7...

原因分析

由于测试环境对服务要求没那么严格,因此测试服务器中的redis为单机配置,而生产服务器为了确保高可用,使用了三主三从的模式。这就导致了测试与生产环境的差异。而导致Lua脚本出错正是因为这种差异。

Redis集群数据共享时,使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现:一个Redis集群包含16384个哈希槽(hash slot), 数据库中的每个键都属于这16384个哈希槽的其中一个, 集群使用公式 CRC16(key)%16384来计算键key属于哪个槽, 其中CRC16(key)语句用于计算键key的CRC16校验和 。

Lua脚本为保证事务,传入的key必须是在同一个slot(槽)中,如果不在同一个slot中将返回以下错误信息(command keys must in same slot)。

而在集群环境下,它会将数据自动分布到不同的节点(虚拟的16384个slot)。它数据是通过计算key路由分发,所以只要key一样,则一定会被分到同一个slot。

解决方案

Redis的配置里面,有叫hash_tag的属性,相关配置示例如下:

beta:
  listen: 127.0.0.1:22122
  hash: fnv1a_64
  hash_tag: "{}"
  distribution: ketama
  auto_eject_hosts: false
  timeout: 400
  redis: true
  servers:
   - 127.0.0.1:6380:1 server1
   - 127.0.0.1:6381:1 server2
   - 127.0.0.1:6382:1 server3
   - 127.0.0.1:6383:1 server4

Hash Tag是用于hash的部分字符串开始和结束的标记,例如”{}”、”$$”等。当一个key包含hash_tag时,便不对整个key做hash,而仅对hash_tag包括的字符串做hash。

比如,我们原来的key为“key_user_id_123”,其中当User变化时,“123”会为不同的值,则有可能分配到不同的槽中。如果为了保证它们能够分配到相同的槽中,只需改为“{key_user_id}_123”,这样计算槽的时候只会使用大括号内的固定部分了。

既然是固定部分,那么每次计算必然会分配到同一个槽中,上述异常也就可以避免了。

其他建议

针对该问题,我们可以想到一些其他的解决方案,比如都采用单机版,这个强烈不建议。同时呢,建立大家在实战时尽量保持开发、测试、生产环境的基本一致性。



Redis执行Lua脚本报错:ERR ‘EVAL’ command keys must in same slot.插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:https://choupangxia.com/2020/10/24/redis-lua/