Redis集群中的 CROSSSLOT Keys Error

场景 Redis单节点没有问题,切换到Redis Cluster后业务上某一功能出错: CROSSSLOT Keys in request don't hash to the same slot 出错的代码: 1 2 3 4 5 6 7 8 var ctx = context.TODO() _, err := uq.queueImpl.rc.TxPipelined(ctx, func(pip redis.Pipeliner) error { cmd := pip.LPush(ctx, uq.key, dest...) if cmd.Err() != nil { return cmd.Err() } return pip.SAdd(context.Background(), uq.setkey, dest...).Err() }) 这段代码的逻辑是向 list 中push一条数据,再向一个 set 加入数据,两步操作通过 pipline 在同一个事务中完成。 问题分析 错误的大概意思就是: 请求中跨槽的key没有被hash到相同的hash槽中。通过代码分析,事务中的两次操作的key并不相同,他们没有被hash到同一个hash槽从而出现上述错误。 什么是hash槽 Redis Cluster 规范中的 Key distribution model(key分布模型)说明如下: Redis集群中将key的存储空间划分为16384个slot,整个集群可以支持最大16384个主节点,实际上建议不超过1000个节点。每一个主节点又可以处理16384个 hash slot。整个集群将hash slot分布于不同的节点组成高可用集群,单个节点有多个副本以便在节点故障时将重新平衡节点,达到高可用的目的。关于可用性保证,可以看这里。 如下的公式用于计算key的hash slot: HASH_SLOT = CRC16(key) mod 16384 Redis将相同hash值的slot分布到同一个node下,如下图所示: 图片出自这里 可以看出,hash槽(slot)就是一个整数,通过key计算得来,它的作用就是决定key存储于哪一个节点中。 为什么会出现CROSSSLOT错误 主要原因是应用程序尝试在多个键上运行命令,但操作中涉及的键不在同一个哈希槽中,这会导致集群中不允许的“CROSSSLOT”操作。 比如,使用Set的SUION命令时,如果多个key的 hash slot 不在集群中的同一个node上,则会出现CROSSSLOT错误。 前边场景中,在同一个事务中操作多个key,集群环境下必须要保证这些被操作的key必须被hash到同一个slot,否则同样会抛出CROSSSLOT错误。 Redis这么做的主要原因还是在于避免分布式数据被破坏的风险,而且在同一个事务下或者同一个命令中操作多个跨节点的key,会因网络等因素带来性能损耗,所以Redis禁止这么做。如果有这种场景,Redis也提供了解决方案:使用Hash Tags。 ...

2023-10-17 · 1 min · 131 words · Hank

使用Redis实现分布式锁

在单体Java应用中,由于代码运行于同一个JVM,使用实现资源加锁是比较容易的,例如使用synchronized或ReentrantLock加锁来控制并发访问;但是在分布式系统中,多个分布式系统之间也需要控制并发访问,由于处于不同的JVM,此时就不能简单使用java的锁机制来进行控制。这种跨进程或者跨服务器的加锁,需要额外使用全局的获取锁的服务,就是本文探讨的分布式锁。 1. 为什么需要分布式锁 分布式锁解决的问题:保证分布式系统的共享资源在某一时刻只被一个客户端访问,保证数据的准确性。 举个例子: 如图所示,订单服务下单前需要保证库存足够,库存服务首先会检查库存充足,然后在将订单的库存数量锁定,如果此时管理系统对库存数量进行了修改,那么由于跨系统的并发操作可能操作库存数据的不正确。此时,对库存的操作就需要考虑分布式锁,将库存锁定,暂时不能更改。 这样一来,对库存的更改和扣减操作使用同一把锁来锁定,每次只有一个客户端能够操作成功,要么订单服务先扣减服务,要么管理系统先修改库存,反正两个不能同时进行。 2. 分布式锁的现有方案 分布式锁的整体思路是:在分布式系统中,有一个全局的东西(中间件或服务),各个服务需要加锁时都向它获取锁,然后给锁一个标记(例如锁的名称),如果标记相同则认为是同一把锁,这样就可以控制各个系统的资源共享。 目前,分布式锁的方案大致有以下几种: 基于Zookeeper的临时节点 基于Redis的SET命令 这里仅仅讨论Redis的分布式锁实现。 3. Redis实现分布式锁的原理 基于Redis来实现分布式锁,其原理很简单:在Redis中设置一个Key,表示加锁,如果其他系统来加锁时发现这个Key已经存在,表示已经加了锁,则它获取锁失败,然后它再不断重试加锁,直到加锁成功,然后才能执行后续业务逻辑。释放锁也很简单,直接将这个KEY删除即可。 锁一旦被创建,就必须能够释放,否则会引起死锁(其他系统永远获取不到锁),一般会使用Redis的过期机制,让KEY一段时间后自动过期以避免死锁。 加锁时,过程如下: 首先,使用SET命令来为某一个KEY(可以作为锁名称)设置一个唯一的值,仅当KEY不存在时才能加锁成功,如果KEY存在则设置失败,表明锁已经存在; 其次,为该KEY设置一个过期时间,来避免死锁问题; 释放锁时,先获取锁是否存在,如果存在则调用DEL命令删除该KEY。 无论是加锁,还是释放锁,都需要保证命令的原子性执行(要么都成功,要么都失败,试想一下,如果加锁时SET命令成功,然后在调用EXPIRE命令设置过期时间,未完成时Redis宕机了,会造成死锁)。例如,加锁时,SET命令和设置过期时间需要为一个原子命令,Redis已经提供了原子命令,如下: // NX是指如果key不存在就成功,key存在返回false,PX指定过期时间,单位毫秒 SET anyLock unique_value NX PX 30000 释放锁时,获取锁和删除KEY为一个原子操作,Redis没有提供获取KEY然后DEL的原子命令,这里需要用到LUA脚本以保证原子性: // 执行LUA脚本保证原子性,先获取锁,然后调用DEL删除 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end 需要注意的是,加锁时设置的KEY值value必须是唯一的,这是因为在释放锁时需要获取到该值以便验证释放锁的客户端和加锁的客户端是同一客户端,如果value值不唯一则可能客户端A加了锁,但是由客户端B给释放了,引起业务混乱而没有达到加锁的目的。 4. 三种部署方式下的锁问题 Redis有三种部署方式,每种方式下的分布式锁都存在一些问题: 1、单机部署 这种方式下,很明显的缺点就是单点问题,Redis故障了,那么分布式锁就不能使用了。 2、Master-Slave + Sentinel模式 主从+哨兵模式,主节点挂了,哨兵会重新选择一个从节点作为主节点,数据会复制到从节点上,但是复制过程需要一定的时间,如果主节点挂了,它上边的锁可能还没有复制到从节点上,就会造成锁丢失。 3、Cluster模式 集群部署模式,同理,在某一个节点上的锁可能还没有复制到其他节点上,同样会造成锁丢失。 使用Redis的分布式锁,其优点是性能很高,支持高并发分布式锁场景,缺点则是如果加锁失败,需要不断循环重试加锁,消耗资源,另外,Redis集群下可能造成锁丢失的极端情况,对于这种情况,Redis的作者也考虑到了,他提出了RedLock算法,具体可以看 这里。 5. 使用Redisson的分布式锁 一般而言,不推荐自己实现Redis分布式锁,因为需要考虑诸如锁重入等多种情况,Java的Redisson框架已经为我们提供了分布式锁的支持。 Redisson是一个Java版的Redis Client,提供了大量的基于Redis的分布式特性支持,例如 分布式锁、分布式任务调度、分布式远程服务、分布式集合等等,Redisson官网: https://redisson.org/ 要使用Redisson的分布式锁非常简单,基本的代码如下: RLock lock = redisson.getLock("anyLock"); lock.lock(); // do something …… lock.unlock(); ...

2019-10-17 · 2 min · 392 words · Hank

redis desktop manager桌面工具

链接 http://pan.baidu.com/s/1i53VLmX 密码 vmm6 比较好用的Redis可视化界面工具,支持Windows 10, Mac OS X 10.12, Ubuntu 16, LinuxMint | Fedora | CentOS | OpenSUSE等平台。 github地址: https://github.com/uglide/RedisDesktopManager/ 官网: http://redisdesktop.com 界面截图: 可支持控制台: 支持按key过滤查找功能:

2017-11-23 · 1 min · 26 words · Hank