redis-集群模式-lua脚本操作多个key的问题

问题描述:

fat压测环境中,突然出现大量redis相关报错:

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

原因分析:

开发、测试环境 redis 是公司内部搭建的单机版本,fat是模拟生产用的阿里云redis集群。导致同样的lua脚本在redis集群模式下出现错误。

在 Redis 集群模式下,需要确保 同一个Lua 脚本中的多个 key 能够位于同一个 hash 槽中!

问题解决:

1、有人提出,使用相同的key前缀,可以让有相同前缀的key分布在同一个hash槽中;(错误的方案

对于这一点,我是不认同的,因为这个无法绝对保证。

Redis 集群中,相同前缀的键并不一定会分配到同一个分片。Redis 集群采用哈希槽 (hash slot) 的方式来分配数据,每个键通过 CLUSTER KEYSLOT <key> 命令计算出它属于哪个槽。集群中的每个节点负责处理一部分槽。

但是,如果你是指同一个前缀的键在分配时会不会落到同一个节点上,那么答案是可能的,也可能不会。因为键的分配基于哈希槽,而槽的分配又基于键的哈克尔值。如果有相同前缀的键具有相似的哈克尔值,它们可能会落到相同的槽,进而被同一节点处理。但是,这并不是一个可靠的行为,因为哈克尔值的分布是不可预测的。

再一个,即使能满足需求,但是也会导致热点数据倾斜分布不均的问题。

2、使用哈希标签:{};(推荐

如果数据是不在一个slot下的键值,是不能使用mget,mset等多键操作的。
但是可以通过 {} 来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。

类似于shardingJdbc中的用于分片的字段。

比如我们一个业务中是缓存的用户相关的缓存信息,那么就可以 key1:{userId}、 key2:{userId} 这样来设计多个不同的redis key。这样即使lua脚本中操作多个key,按理也是应该按照业务是跟这个用户相关的缓存数据。这样就可以保证然多个当前用户相关的redis key 命中到同一个solt,就可以正常使用lua脚本控制请求层面的原子性了。

会伴随一个问题;

比如 一个lua脚本中 需要操作多种业务类型的数据,比如 一个是用户相关({userId})、一个是商品相关的{skuId}。如果存在用户跟sku维度的关联业务,那么可以考虑{userId+skuId}组合。

如果是分别独立的缓存场景,我想这个实现应该不是一个好的实现,不应该存在同一个lua脚本中操作两份不搭嘎的缓存数据。业务代码层面也没有按领域的维度进行合理的拆分,遇到这种场景,我们需要考虑的是是不是更好的划分服务边界会更好一些。利用其它的一些技术手段保证一致性或者原子性,合理拆分这个lua脚本中的复杂逻辑。

Redis集群模式下,使用lua脚本的方式与单机模式下基本相同,但需要注意一些细节。 1. 在集群模式下,需要使用`EVAL`命令来执行lua脚本,而不能使用`EVALSHA`命令。原因是在集群模式下,同一个key可能会被分配到不同的节点上,而`EVALSHA`需要在执行脚本之前将脚本的SHA1值发送到所有节点,但这些节点上可能并没有缓存该脚本的SHA1值。因此,为了保证能够在集群模式下正常使用lua脚本,需要每次使用`EVAL`命令来执行。 2. 在lua脚本中,需要使用`redis.call`和`redis.pcall`来调用Redis命令。其中,`redis.call`用于执行Redis命令,并且如果命令执行出错,会直接抛出异常;而`redis.pcall`则会将异常转换成返回值,方便在lua脚本中进行处理。 3. 在lua脚本中,可以使用`KEYS`和`ARGV`来获取传入的参数。其中,`KEYS`是一个数组,包含所有的key,而`ARGV`是一个数组,包含所有的value。 下面是一个示例的lua脚本,在集群模式下使用`EVAL`命令执行: ```lua -- 将key对应的值加上increment local key = KEYS[1] local increment = tonumber(ARGV[1]) local value = redis.call("incrby", key, increment) -- 如果key的值大于100,则将其设置为0 if value > 100 then redis.call("set", key, 0) end return value ``` 在执行该脚本时,需要指定key和increment两个参数。例如,使用以下命令将key为"counter"的值增加10: ``` EVAL "local key = KEYS[1]\nlocal increment = tonumber(ARGV[1])\nlocal value = redis.call(\"incrby\", key, increment)\nif value > 100 then\nredis.call(\"set\", key, 0)\nend\nreturn value" 1 "counter" 10 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进窄门见微光行远路

如果对你有比较大的帮助

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值