当前位置: 首页 > 科技观察

Redis数据变多了,是加内存还是加实例?_0

时间:2023-03-13 06:12:03 科技观察

曾经遇到过这样一个需求:用Redis保存5000万个键值对,每个键值对大概512B。为了快速部署和对外提供服务,我们使用云主机运行Redis实例。那么,如何选择云主机的内存容量呢?我粗略算了一下,这些键值对占用的内存空间大概是25GB(5000万*512B)。所以,当时我想到的第一个方案就是:选择一台32GB内存的云主机来部署Redis。因为32GB的内存可以保存所有的数据,还有7GB可以保证系统的正常运行。同时我也使用RDB来持久化数据,保证Redis实例出现故障后可以从RDB中恢复数据。但是在使用的过程中,我发现Redis的响应有时会很慢。后来我们使用INFO命令查看了Redis的latest_fork_usec指标值(表示最新一次fork的耗时),结果发现该指标值极高,几乎在秒级。这跟Redis的持久化机制有关系。当使用RDB进行持久化时,Redis会fork子进程来完成。fork操作所花费的时间与Redis中的数据量正相关,fork在执行过程中会阻塞主线程。数据量越大,主线程被fork操作阻塞的时间就越长。因此,在使用RDB持久化25GB数据时,数据量较大,创建fork时后台运行的子进程阻塞了主线程,导致Redis的响应变慢。看来第一个方案显然是行不通的,只好另寻他法。这时候,我们注意到了Redis分片集群。虽然设置切片集群比较麻烦,但是可以保存大量的数据,对Redis主线程的阻塞影响较小。分片集群,也叫分片集群,是指启动多个Redis实例组成一个集群,然后将接收到的数据按照一定的规则分成多份,每一份由一个实例保存。回到我们之前的场景,如果我们把25GB的数据分成5份(当然没必要平分),用5个实例来保存,每个实例只需要保存5GB的数据。如下图所示:那么,在分片集群中,当一个实例为5GB数据生成一个RDB时,数据量要小很多,而且fork子进程一般不会长时间阻塞主线程时间。使用多实例保存数据分片后,我们不仅可以保存25GB的数据,还可以避免fork子进程阻塞主线程导致响应突然变慢。在Redis的实际应用中,随着用户或业务规模的扩大,通常难免要保存大量的数据。切片集群是一个很好的解决方案。这节课,我们一起来了解一下。如何保存更多数据?刚才的案例,为了保存大量的数据,我们使用了两种方式:大内存云主机和分片集群。这两种方式其实对应Redis应对数据量增加的两种方案:scaleup和scaleout。纵向扩展:升级单个Redis实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的CPU。如下图所示,原实例内存8GB,硬盘50GB。纵向扩展后,内存增加到24GB,磁盘增加到150GB。横向扩展:横向增加当前Redis实例的个数,如下图,其中一个8GB内存,50GB磁盘的实例,现在使用三个相同配置的实例。那么,这两种方式各有什么优缺点呢?首先,垂直扩展的优点是简单易行。然而,该方案也面临着两个潜在的问题。第一个问题是,在使用RDB持久化数据时,如果数据量增加,需要的内存也会增加,fork子进程时主线程可能会阻塞(比如刚才例子中的情况)。但是,如果你不需要持久化存储Redis数据,那么垂直伸缩会是一个不错的选择。然而,这时候,你不得不面对第二个问题:垂直扩展会受到硬件和成本的限制。这很容易理解。毕竟内存从32GB扩容到64GB还是很容易的。但是,如果要扩展到1TB,就会面临硬件容量和成本的限制。向外扩展是比向上扩展更具扩展性的解决方案。这是因为,如果你想保存更多的数据,采用这种方案,只需要增加Redis实例的数量,而不用担心单个实例的硬件和成本限制。当面对百万级或者千万级的用户规模时,scale-out的Redis分片集群会是一个非常好的选择。但是,当只使用单个实例时,数据存储在哪里以及客户端访问它的地方是非常清楚的。但是分片集群不可避免的涉及到多个实例的分布式管理。为了使用分片集群,我们需要解决两大问题:数据分片后,如何在多个实例间分配?客户端如何确定它要访问的数据在哪个实例上?接下来,我们将一一讨论解决。数据切片与实例对应的分布关系在一个切片集群中,数据需要分布在不同的实例上。那么,数据与实例如何对应呢?这和我接下来要讲的RedisCluster方案有关。不过,我们首先要了解分片集群和RedisCluster的联系和区别。事实上,分片集群是一种通用的存储大量数据的机制,而这种机制可以有不同的实现。在Redis3.0之前,官方并没有提供分片集群的具体解决方案。从3.0开始,官方提供了RedisCluster的方案来实现分片集群。RedisCluster方案中规定了数据和实例的相应规则。具体来说,RedisCluster方案是使用HashSlots(哈希槽,我就直接叫Slots)来处理数据和实例的映射关系。RedisCluster方案中,一个切片集群共有16384个哈希槽。这些哈希槽类似于数据分区,每个键值对根据其键映射到一个哈希槽。具体的映射过程分为两步:首先根据键值对的key,按照CRC16算法计算出一个16位的值;然后,用这个16位的值对16384取模,得到0~16383范围内的模,每个模代表一个对应编号的哈希槽。关于CRC16算法,感兴趣的话!可以自行Google查询那么,这些哈希槽是如何映射到具体的Redis实例的呢?当我们部署RedisCluster方案时,我们可以使用clustercreate命令来创建一个集群。这时,Redis会自动将这些槽平均分配到集群实例中。例如集群中有N个实例,那么每个实例上的槽数就是16384/N。当然,我们也可以使用clustermeet命令手动建立实例之间的连接,形成一个集群,然后使用clusteraddslots命令指定每个实例上的hashslots数量。客户端如何定位数据?在定位键值对数据时,可以通过计算得到它所在的哈希槽,这个计算可以在客户端发送请求的时候进行。但是,要进一步定位实例,还需要知道哈希槽分布在哪个实例上。一般来说,客户端与集群实例建立连接后,实例会将哈希槽分配信息发送给客户端。但是,在刚刚创建集群时,每个实例只知道自己分配了哪些哈希槽,而不知道其他实例拥有的哈希槽信息。那么,为什么客户端在访问任何一个实例时都能获取到所有的哈希槽信息呢?这是因为Redis实例会将自己的hashslot信息发送给与其连接的其他实例,以完成hashslot分配信息的扩散。当实例相互连接时,每个实例都有一个所有哈希槽的映射关系。客户端收到哈希槽信息后,会在本地缓存哈希槽信息。客户端请求一个键值对时,首先计算出该键对应的哈??希槽,然后向对应的实例发送请求。总结上述切片集群在存储大数据方面的优势,以及基于哈希槽的数据分布机制和客户端定位键值对的方法在处理数据量膨胀时,虽然垂直扩展增加内存的方法简单明了,但是会导致数据库内存过大,导致性能变慢。Redis分片集群提供了水平扩展的模式,即使用多个实例,为每个实例配置一定数量的哈希槽。数据可以通过key的哈希值映射到哈希槽中,然后通过哈希槽进行分散。保存到不同的实例。这样做的好处是扩展性好,不管有多少数据,分片集群都能搞定。