Redis简单扩容经验谈

随着访问量增加,Redis的CPU使用率达到90%+,成为性能瓶颈。为解决此问题,尝试了多种方案:使用twemproxy进行代理,实现无代码改动的扩容,但twemproxy也成为新的瓶颈。最后采取读写分离结合多个Redis实例,有效缓解压力。关键在于理解业务场景,选择合适的技术方案。
team中的一个同学在其项目中使用了Redis作为缓存,将热点数据存放在Redis中。为了提升性能,写Redis时采用了管道的方式,平时使用时,Redis的性能、资源使用都能符合项目需求,但当访问量增加的时候,Redis的QPS还能满足要求,但CPU使用率高的时候已经达到90%+,平时只有30%+,而众所周知,Redis是单进程的,只能占用1个CPU核,跑满了也就100%,无法利用机器的多核,而当CPU跑到100%时,必然会造成性能瓶颈。怎么解决?

方案一:
首先想到的是,增加Redis服务器的数量,在客户端对存储的key进行hash运算,存入不同的Redis服务器中,读取时,也进行相同的hash运算,找到对应的Redis服务器,可以解决问题,但是不好的地方:
第一,客户端要改动代码;
第二、需要客户端记住所有的Redis服务器的地址;
这个方案可以使用,但能不能不用改动代码就能实现扩容呢?

方案二:
搭建一个集群,由于Redis服务器使用的版本低于3.0,不支持集群,只能通过使用代理,就想到了有名的Redis代理twemproxy。
twemproxy的性能也是杠杠滴,虽然是代理,但它对访问性能的影响非常小,连Redis作者都推荐它。
twemproxy使用方便,对于一个新手来说,不到一个小时就能学会使用,而且关键是不用改动客户端代码,几乎支持所有的Redis命令和管道操作,只需要改下客户端的配置文件中配置的Redis的IP和PORT,由原来的Redis的IP和Port改成twemproxy服务的IP和PORT。
客户端不需要考虑hash的问题,这些twemproxy会做,客户端就像操作一台Redis一样。
上面用了“几乎”这个词,因为有些命令,比如"keys *"就不支持
很快部署了好了twemproxy和后面跟
### Redis Hash 扩容机制原理及实现 #### 1. Redis Hash 的底层结构 Redis 中的 Hash 数据结构在底层实现上与 Java 的 HashMap 类似,采用数组加链表的形式。当发生哈希碰撞时,元素会被追加到链表上[^1]。Hash 的存储基于字典(dict)实现,而字典是 Redis 中的核心数据结构之一。 #### 2. 渐进式 Rehash 机制 Redis 的 Hash 在扩容时采用了渐进式 Rehash 的方式。这种方式避免了单次扩容对性能造成较大影响。具体来说,Redis 的字典包含两个哈希表 `ht[0]` 和 `ht[1]`,初始状态下所有数据都存储在 `ht[0]` 中。当触发扩容时,Redis 不会立即完成整个 Rehash 过程,而是逐步将数据从 `ht[0]` 迁移到 `ht[1]`[^5]。 - 每次操作(如读、写)时,如果检测到正在 Rehash,则会将当前槽位的数据迁移到 `ht[1]`。 - 当所有槽位的数据迁移完成后,`ht[1]` 成为新的主表,`ht[0]` 被清空并重置为备用表[^5]。 #### 3. 触发扩容的条件 Redis 的 Hash 扩容机制由字典的 `_dictExpand` 方法控制。当满足以下条件时,会触发扩容: - 当前字典中存储的键值对数量超过了负载因子与当前哈希表大小的乘积。 - 如果当前正在执行 Rehash,则不会再次触发扩容,直到 Rehash 完成[^4]。 负载因子的默认值为 5,意味着当哈希表中的节点数达到哈希槽数量的 5 倍时,会触发扩容[^3]。 #### 4. 扩容过程的实现细节 以下是 Redis Hash 扩容的主要步骤: - **创建新哈希表**:根据新的大小创建 `ht[1]`,其大小通常是当前哈希表大小的两倍。 - **逐步迁移数据**:在每次操作时检查是否需要迁移当前槽位的数据。如果需要,则将该槽位的所有键值对从 `ht[0]` 移动到 `ht[1]`。 - **完成迁移**:当所有槽位的数据迁移完成后,将 `ht[1]` 设置为主表,并将 `ht[0]` 清空并重置为备用表[^5]。 #### 5. 扩容与性能的关系 渐进式 Rehash 的设计使得 Redis 的 Hash 扩容对性能的影响较小。相比一次性完成 Rehash,这种逐步迁移的方式可以避免因大量数据移动而导致的延迟峰值。然而,在高并发场景下,如果 Rehash 操作频繁发生,仍可能对性能产生一定影响。 ```python # 示例代码:模拟 Redis 渐进式 Rehash 的伪代码 def rehash(dict): if dict.rehash_idx == -1: dict.ht[1] = create_new_hash_table(dict.ht[0].size * 2) dict.rehash_idx = 0 while dict.rehash_idx < dict.ht[0].size: move_slot_data(dict.ht[0], dict.ht[1], dict.rehash_idx) dict.rehash_idx += 1 # 模拟每次操作只处理一个槽位 if some_condition(): break if dict.rehash_idx >= dict.ht[0].size: dict.ht[0], dict.ht[1] = dict.ht[1], None dict.rehash_idx = -1 ``` #### 6. 缩容机制 Redis 的 Hash 缩容机制与扩容类似,同样是通过 `_dictExpand` 方法实现。缩容的触发条件是当前哈希表的使用率低于一定阈值,此时会将哈希表缩小以节省内存[^4]。缩容过程中同样会逐步迁移数据,直到完成所有槽位的迁移。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值