大家好,我是Tom。Redis作为一项主流技术,应用场景非常多,很多大中小厂面试都被列为重点考察内容前几天,一位行星小伙伴在学习的时候,遇到了以下问题,他来了咨询汤姆。考虑到这些问题在工作中遇到的频率比较高,这里有一篇文章系统的解释一下问题描述:有个问题想问你:Tom大哥,在redisredis的时候,有一些问题请看一下:1.如果有是redis集群中的数据倾斜,数据分配不正确jun,如何解决?2、在处理hotKey的时候,为key创建多个副本,比如k-1,k-2……如何让这些副本均匀写入?如何访问?3、Redis使用哈希槽来维护集群。与一致性哈希类似,可以避免完全迁移。为什么不直接使用consistenthash呢?Tom哥的回复:分布式缓存作为性能加速器,在系统优化中起到了非常重要的作用。与本地缓存相比,虽然一次额外的网络传输占用时间不到1毫秒,但具有集中管理的优势,支持超大的存储容量。在分布式缓存领域,Redis目前被广泛使用。该框架纯内存存储,单线程命令执行,底层数据结构丰富,支持多维数据存储和搜索。当然,当数据量大的时候,就会出现各种问题,比如:数据倾斜,数据热点等等。什么是数据倾斜?单机的硬件配置是有上限的。一般我们会采用分布式架构,由多台机器组成集群。下图中的集群由三台Redis单机组成。客户端通过一定的路由策略将读写请求转发给特定的实例。由于业务数据的特殊性,根据指定的分片规则,不同实例上的数据分布可能不均匀,大量数据集中在一个或几个机器节点上进行计算,导致这些节点负载过重,同时其他节点处于空闲等待状态,最终导致整体效率低下。数据倾斜的原因有哪些?1、有一个大key,比如存储一个或多个String类型的bigKey数据,占用内存大。汤姆大哥以前查过这种问题。开发时为了省事,同事用JSON格式将多个业务数据合并为一个值,只关联一个key。导致这个key-valuepair的容量达到了几百M,频繁读写大key会消耗大量的内存资源,同时对网络传输造成很大的压力,进而导致请求变慢响应,触发雪崩效应,最后各种系统超时告警。解决方法:方法很简单。采用拆分策略,将一个bigKey拆分成多个smallkey,独立维护,成本会降低很多。当然,这次拆解也讲究一些原则。既要考虑业务场景,又要考虑访问场景,将密切相关的放在一起。比如:有一个内部依赖Redis的RPC接口。您之前访问过一次就可以获取所有数据。拆分会控制单值的大小和访问次数。毕竟调用次数的增加会放大整个界面。响应时间。浙江的政府部门都在提倡流程优化,最多跑一次也是这个道理。2、HashTag使用不当Redis使用单线程执行命令,从而保证了原子性。采用集群部署时,为了解决mset、lua脚本等多个key的批量操作,保证不同的key能够路由到同一个Redis实例,引入了HashTag机制。用法也很简单。使用{}大括号指定key只计算大括号内字符串的hash,这样不同key的key-value对插入到同一个hash槽中。例如:192.168.0.1:6380>CLUSTERKEYSLOTtesttag(integer)764192.168.0.1:6380>CLUSTERKEYSLOT{testtag}(integer)764192.168.0.1:6380>CLUSTERKEYSLOTmykey1{testtag}164.9.2.7CLUSTERKEYSLOT6380>{testtag}(integer)764检查业务代码是否引入了HashTag,将过多的key路由到一个实例。结合具体场景,考虑如何拆分。就像RocketMQ一样,很多时候只要能保证分区有序,就可以满足我们的业务需求。在实战中,需要找到这个平衡点,而不是为了解决问题而解决问题。3.slotslots分布不均如果采用RedisCluster的部署方式,集群中的数据库被划分为16384个槽位(slots),数据库中的每个key都属于这16384个槽位中的一个。一个节点可以处理的0个或最多16384个槽。可以手动将比较大的slot迁移到稍微闲置的机器上,保证存储和访问的统一性。什么是缓存热点?缓存热点是指大部分甚至所有的业务请求都命中了同一个缓存数据,这给缓存服务器带来了巨大的压力,甚至超过了单机的负载限制,导致服务器宕机。解决方法:1、要进行多份拷贝,我们可以在key后面加上序号,比如key#01,key#02。..key#10有多个副本,这些处理过的key位于多个缓存节点上。客户端每次访问,只需要在原有key的基础上拼接一个分片数上限的随机数,将请求路由到无法路由的实例节点即可。注意:缓存一般会设置一个过期时间。为了避免缓存集中失效,我们尽量让缓存的过期时间不一样。可以在预设的基础上添加一个随机数。至于数据路由的统一性,这是由Hash算法保证的。2、本地内存缓存将热点数据缓存在客户端本地内存中,并设置过期时间。对于每一个读请求,它会先检查数据是否存在于本地缓存中,如果存在则直接返回,如果不存在则去分布式缓存服务器。本地内存缓存完全“解放”了缓存服务器,不会给缓存服务器带来任何压力。缺点:实时感知最新的缓存数据有点麻烦,会造成数据不一致。我们可以设置一个比较短的过期时间,使用被动更新。当然,你也可以通过监听机制,在感知到数据发生变化时,及时更新本地缓存。为什么RedisCluster不使用一致性Hash?RedisCluster集群有16384个哈希槽。每个key通过CRC16校验后,根据16384取模来决定放置哪个slot。集群的每个节点负责一些哈希槽。例如当前集群有3个节点,那么node-1包含0到5460的hash槽,node-2包含5461到10922的hash槽,node-3包含5461到10922的hash槽。包含10922的hash槽到16383。一致性哈希算法由Karger等人提出。麻省理工学院1997年解决分布式缓存问题。一致性哈希算法本质上也是一种取模算法。与根据服务器数量取模不同,一致性哈希是对固定值2^32取模。公式=hash(key)%2^32。取模的结果必须是[0,2^32-1]区间内的整数,从映射在圆上的位置顺时针找到的第一个节点就是存放key的节点。一致性哈希算法大大缓解了扩容或缩容导致缓存失效的问题,只影响节点负责的那一小段key。如果集群中机器不多,单机负载水平通常很高,节点宕机带来的压力很容易引发雪崩效应。例如:一个Redis集群一共有4台机器。假设数据均匀分布,每台机器承担四分之一的流量。如果一台机器突然挂了,那么顺时针方向的下一台机器就会承担额外的流量。拿了四分之一的流量,最后又背负了一半的流量,这有点吓人。但是如果采用CRC16计算,结合slot和instance的绑定关系,不管是扩容还是缩容,只需要平滑迁移对应节点的key,新的slot映射关系就会广播和存储。缓存失效,灵活性高。另外,如果服务器节点配置有差异,我们可以自定义不同节点负责的槽位号,调整不同节点的负载能力,非常方便。
