Redis是我们日常开发中最常用的缓存和存储中间件,单机Redis用起来很简单,但是当数据量和访问量越来越大,单机扛不住的时候,就需要用Redis集群了。Redis集群看起来简单,但是真正用起来,坑还是很多的,我这两年在项目中用了好几次Redis集群,踩了不少坑,也积累了一些实战经验。今天就来总结一下Redis集群的踩坑经历和实战经验。
一、Redis集群的基本原理
在讲踩坑之前,先简单回顾一下Redis集群的基本原理。Redis Cluster是Redis官方在3.0版本推出的分布式集群方案,它的核心是数据分片和故障转移。
数据分片方面,Redis集群把所有数据分成16384个槽(slot),每个节点负责一部分槽。当你写入一个key的时候,Redis会用CRC16算法对key计算一个值,然后对16384取模,得到这个key属于哪个槽,然后把这个key写到负责这个槽的节点上。读取的时候也是一样,先计算key属于哪个槽,然后去对应的节点读取。
这样做的好处是可以水平扩展,数据量变大了,加节点就行,数据会自动迁移到新节点上。而且客户端可以连接任意一个节点,节点会自动把请求转发到负责对应槽的节点,对客户端来说比较透明。
故障转移方面,Redis集群的每个主节点都可以有多个从节点,主节点负责读写,从节点负责同步数据。如果主节点挂了,集群会自动进行选举,从从节点里选出一个新的主节点,继续提供服务,整个过程一般在几秒到十几秒内完成,不需要人工干预。
了解了基本原理,下面就来讲讲我踩过的那些坑。
二、坑一:集群搭建时的节点数和槽分配
第一个坑是在集群搭建的时候,节点数和槽分配的问题。
刚开始用Redis集群的时候,我以为至少要6个节点(3主3从)才能搭建集群,因为官方文档里的例子就是6个节点。后来才知道,其实Redis集群最少只需要3个主节点就能搭建,从节点不是必须的,但是生产环境一定要配从节点,不然主节点挂了就没法故障转移了。
还有一个坑是槽分配的问题,刚开始搭建集群的时候,我用的是自动分配槽,redis-trib.rb会自动把16384个槽平均分配给各个主节点。但是后来加节点的时候,自动迁移槽的过程很慢,而且会影响性能,因为迁移的时候要把数据从旧节点搬到新节点,占用带宽和CPU。
后来我的经验是,搭建集群的时候,尽量一次规划好节点数,不要频繁加节点。如果确实需要加节点,尽量在业务低峰期操作,而且要控制迁移的速度,不要影响正常业务。还有就是,槽的分配要尽量均匀,不要出现某个节点负责的槽特别多,某个特别少的情况,不然会出现数据倾斜,某个节点压力特别大,其他节点很闲。
还有一个坑是,Redis集群不支持处理多个key的命令,比如mset、mget、事务、Lua脚本等等,如果这些命令涉及的key不在同一个槽里,就会报错。所以设计key的时候要注意,需要一起操作的key,要确保它们在同一个槽里。Redis提供了hash tag的功能,就是在key里用{}括起来一部分,计算槽的时候只算{}里的内容,这样就能让不同的key分配到同一个槽里。比如user:{1001}:name和user:{1001}:age,它们的hash tag都是1001,所以会分配到同一个槽里,就可以一起操作了。
三、坑二:故障转移和主从切换
第二个坑是故障转移和主从切换的问题。
有一次线上出了个问题,一个主节点因为服务器故障挂了,集群自动进行了故障转移,把一个从节点提升成了主节点,业务很快就恢复了,看起来一切正常。但是过了一会儿,我们发现有一部分数据读不到了,查了半天,发现是新提升的主节点和原来的主节点数据不一致,有一部分数据还没同步过去,原来的主节点就挂了,所以这部分数据丢了。
这是因为Redis的主从复制是异步的,主节点写入数据之后,马上就返回成功了,不会等从节点同步完成。如果主节点刚写完数据,还没来得及同步到从节点就挂了,那这部分数据就丢了。这是Redis集群的一个特点,也是一个坑,它保证的是高可用,不是强一致性,如果你的业务对数据一致性要求很高,那就要注意了,可能需要用其他方案。
后来我们的解决方案是,对于重要的数据,写入之后,用wait命令等待从节点同步完成,再返回成功,这样就能保证数据至少同步到了一个从节点,就算主节点挂了,数据也不会丢。当然,这样会影响写入性能,因为要等从节点同步,所以要根据业务的重要性来权衡,重要的数据用wait,不重要的数据就不用。
还有一个坑是,主从切换的时候,会有短暂的不可用时间,大概几秒到十几秒,这段时间集群在进行选举和切换,不能处理请求。如果你的业务对可用性要求很高,不能接受这几秒的不可用,那就要在客户端做重试,或者用其他高可用方案。我们的做法是在客户端做了重试,请求失败的时候自动重试几次,大部分时候重试的时候切换已经完成了,就能成功了。
还有一个坑是,从节点不能处理读请求,默认情况下,Redis集群的从节点是不处理读请求的,所有读请求都由主节点处理。如果你想让从节点也处理读请求,实现读写分离,需要在客户端配置readonly模式,而且从节点的数据可能有延迟,因为复制是异步的,所以对于一致性要求高的读请求,还是要走主节点。
四、坑三:性能和内存问题
第三个坑是性能和内存的问题。
Redis集群的性能比单机高吗?理论上是的,因为数据分散到多个节点,读写压力也分散了,整体吞吐量更高。但是实际使用的时候,发现有时候集群的性能还不如单机,特别是对于小数据量、高并发的场景。
原因是,Redis集群的请求转发有开销。客户端连接任意一个节点,如果这个key不在这个节点上,节点会把请求转发到负责这个key的节点,然后再把结果返回给客户端,这个转发过程有网络开销,会增加延迟。如果客户端连接的节点刚好负责这个key,那就不用转发,延迟和单机差不多。但是如果key分布在不同的节点,就会有很多转发,延迟就会增加。
后来我们的解决方案是,在客户端使用集群感知的客户端,比如JedisCluster、Lettuce这些,它们会缓存槽和节点的映射关系,请求的时候直接连接负责对应槽的节点,不用转发,这样延迟就低了很多。所以用Redis集群的时候,一定要用支持集群的客户端,不要用普通的客户端,不然性能会很差。
还有一个坑是内存问题。Redis是内存数据库,所有数据都存在内存里,集群虽然能分散数据,但是每个节点的内存还是有限的。而且Redis集群有一个特点,就是每个节点都要存储整个集群的槽和节点的映射关系,还有其他元数据,这些会占用一部分内存。另外,Redis的内存碎片率也会影响实际可用内存,有时候数据量不大,但是内存占用很高,就是因为内存碎片。
我们的经验是,每个节点的内存使用率不要超过70%,留30%的余量,因为Redis在做RDB持久化或者AOF重写的时候,会fork子进程,这时候会有写时复制,内存占用会临时增加,如果内存不够,可能会导致fork失败,或者触发OOM killer把Redis进程杀掉。还有就是要定期检查内存碎片率,如果碎片率太高,可以在低峰期做一次内存整理,或者重启节点。
还有一个坑是大key和热key的问题。大key就是value特别大的key,比如一个list里有几百万个元素,或者一个string有几十MB。大key会导致数据倾斜,这个key所在的节点压力特别大,而且迁移的时候很慢,还会阻塞Redis。热key就是访问量特别大的key,所有请求都打到这个key所在的节点,导致这个节点压力特别大,其他节点很闲。
对于大key,我们的解决方案是拆分,把一个大key拆成多个小key,分散到不同的节点上。对于热key,我们的解决方案是在客户端做本地缓存,把热key缓存到客户端本地,减少对Redis的请求,或者用多个副本,把热key复制到多个节点,分散压力。
五、坑四:持久化和备份
第四个坑是持久化和备份的问题。
Redis集群的持久化和单机差不多,也是RDB和AOF两种方式。但是集群的备份比单机麻烦,因为数据分散在多个节点上,你不能只备份一个节点,要备份所有节点的数据,而且要保证备份的一致性。
刚开始我们备份的时候,就是分别登录每个节点,执行bgsave,然后把RDB文件拷出来。但是这样做有个问题,就是各个节点的备份时间点不一样,数据不一致,如果要恢复的话,恢复出来的数据可能不是同一个时间点的,会有问题。
后来我们的解决方案是,用redis-trib.rb的import功能,或者用其他备份工具,在同一个时间点对所有节点做备份,保证数据一致性。还有就是,备份的时候要在业务低峰期操作,因为bgsave会fork子进程,占用内存和CPU,影响性能。
还有一个坑是,集群恢复的时候,不能直接把RDB文件拷到节点里启动,因为集群的节点信息、槽分配信息都存在节点里,直接拷RDB文件会导致集群信息混乱。正确的恢复方式是,先搭建一个新的空集群,然后用redis-trib.rb的import功能,把备份的数据导入到新集群里,这样集群会自动分配槽和节点信息。
还有就是,一定要定期测试备份恢复,很多人只是做了备份,但是从来没有测试过恢复,等到真的需要恢复的时候,才发现备份有问题,恢复不了,那就晚了。我们的经验是,每个月做一次备份恢复测试,确保备份是可用的。
六、写在最后
Redis集群:踩坑总结与实战经验。
以上就是我这两年来用Redis集群踩过的一些坑,以及积累的一些实战经验,包括集群搭建、故障转移、性能内存、持久化备份等几个方面。当然,Redis集群的坑远不止这些,还有很多细节需要在实际使用中慢慢积累。
总的来说,Redis集群是一个不错的分布式缓存和存储方案,能满足大部分场景的需求,但是它也不是银弹,有它的局限性,比如不支持多key操作、异步复制可能丢数据、故障转移有短暂不可用等等。在使用之前,要充分了解它的特点和局限性,根据业务需求选择合适的方案,不要为了用集群而用集群,单机能搞定的就不要用集群,集群会增加复杂度和运维成本。
还有就是,任何技术都要在实际使用中不断踩坑、不断总结,才能真正掌握。希望我的这些踩坑经验能帮到大家,让大家少走弯路,用好Redis集群。
最后用一句话结尾:"纸上得来终觉浅,绝知此事要躬行。"技术是踩坑踩出来的,多实践,多总结,才能不断进步。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录