当前位置: 首页 > 后端技术 > PHP

如何开发和避免redis集群访问倾斜和数据倾斜

时间:2023-03-30 02:41:17 PHP

[TOC]概述Redis集群部署方式大多采用类Twemproxy方式部署。即通过Twemproxy对rediskey进行分片计算,将rediskey分片分配给多个redis实例中的一个。tewmproxy的结构图如下:由于Twemproxy背后的多个redis实例在内存配置和cpu配置上是一致的,一旦流量或数据量出现偏差,就可能导致某个redis实例达到性能瓶颈,从而导致整个集群达到性能瓶颈。热键的出现导致集群的流量倾斜。hotkey,即热key,是指在一段时间内,这个key的流量远高于其他rediskey,导致大部分流量通过代理分片。全部集中访问某个redis实例。热键通常存储不同业务的不同热点信息。例如新闻应用中的热点新闻内容;用户在活动系统中疯狂参与的活动的活动配置;商城秒杀系统中最亮眼性价比最高的商品信息;...解决方案1.在客户端使用本地缓存端使用本地缓存,从而减少redis集群对hotkey的访问量,但同时带来了两个问题:1、如果将可能成为热键的key缓存在本地,会不会本地缓存过大,影响应用本身所需的缓存开销。2、如何保证本地缓存和redis集群数据有效期的一致性。针对这两个问题,先不说了,先说第二种方案吧。2.利用sharding算法的特点,分散key。我们知道热键之所以是热键,是因为它只有一个键,落在一个实例上。所以我们可以给hotkey加一个前缀或者后缀,把一个hotkey的个数改成redis实例个数N的M倍数,这样访问一个rediskey就变成访问N*M个rediskey。N*M个rediskey通过分片的方式分布到不同的实例中,访问流量均匀分布到所有实例中。代码如下://redis实例数constM=16//redis实例数倍数(按需设计,2^n次,n一般为1到4的整数)constN=2funcmain(){//getredisinstancec,err:=redis.Dial("tcp","127.0.0.1:6379")iferr!=nil{fmt.Println("连接到redis错误",err)return}deferc.Close()hotKey:="hotKey:abc"//随机数randNum:=GenerateRangeNum(1,N*M)//获取分解热键的keytmpHotKey:=hotKey+"_"+strconv.Itoa(randNum)//热键过期时间expireTime:=50//一个时间随机值,用于平滑过期时间randExpireTime:=GenerateRangeNum(0,5)data,err:=redis.String(c.Do("GET",tmpHotKey))如果出错!=nil{data,err=redis.String(c.Do("GET",hotKey))iferr!=nil{data=GetDataFromDb()c.Do("SET","hotKey",data,expireTime)c.Do("SET",tmpHotKey,data,expireTime+randExpireTime)}else{c.Do("SET",tmpHotKey,data,expireTime+randExpireTime)}}}在这段代码中,传递一个大于等于1的less比M*N个随机数,得到一个tmpkey,程序会先访问tmpkey,如果没有数据再访问原来的hotkey,并将hotkey的内容写回tmpkey。值得注意的是,tmpkey的过期时间是hotkey的过期时间加上一个小的随机正整数,保证hotkey过期时,所有的tmpkey不会同时过期而造成缓存雪崩.这是一种通过斜率失效来避免雪崩的方法。同时可以使用原子锁写入数据,更加完善,减轻了db的压力。另外值得一提的是,默认情况下,我们在生成tmpkey的时候,会使用一个随机数作为hotkey的后缀,符合redis的命名空间,方便key的收集和管理。但是有一种极端的情况,就是热键的长度很长。此时,随机数不能作为后缀添加。原因是在Twemproxy的分片算法的计算过程中,越靠前的字符权重越大,测试后的字符权重越小。也就是说,对于键名,前面字符差异越大,计算出的分片值差异越大,越有可能分配给不同的实例(具体算法这里不讨论).因此,对于键名较长的热键,需要慎重处理随机数的插入,比如放在最后一个命令空格的前面(eg:从原来的space1:space2:space3_rand到space1:空间2:rand_space3)。大key导致集群数据量倾斜。bigkey,也就是一个数据量很大的key,因为它的数据量比其他key大很多,分片后,专门存放这个bigkey的实例的内存占用比其他实例大很多,导致,内存不足,拖累了整个集群的使用。bigkey通常体现在不同业务的不同数据上,例如:论坛中的大规模持续建站活动;聊天室系统中热门聊天室的消息列表;...解决方法是拆分bigkey,存储bigkey将数据(bigvalue)拆分为value1,value2...valueN,如果bigvalue是一个bigjson中,这个key的内容会通过mset的方式分散到各个instance中,减少bigkey对数据量的影响。影响。//保存msetkey1,vlaue1,key2,vlaue2...keyN,valueN//获取mgetkey1,key2...keyN如果bigvalue是一个biglist,可以拆分成list。=list_1、list_2、list3、listN等数据类型相同。它既是大键又是热键。在开发过程中,有些key不仅访问量大,数据量也大。这时候就需要考虑使用key的场景,存放在redis集群中是否合理,是否使用其他组件。更适合收纳;如果坚持使用redis存储,可以考虑迁移出集群,采用一主一备(或一主多备)架构存储。其他如何发现热键、大键1.预判在业务发展阶段,需要对可能成为hotkey或bigkey的数据进行预判并提前处理。这就需要对产品业务的理解,对运营节奏的把握,数据设计方面的经验。2.In-progress-monitoring和automaticprocessing监控是在应用端收集每个请求并上报给redis;不推荐,但运维资源匮乏的场景可以考虑。可以绕过运维进行开发);在代理层,收集并报告每个redis请求;(推荐,改动少,易维护);使用monitor命令统计redis实例的热键(不推荐,高并发情况下会有redis内存爆的隐患);在机器层面,Redis客户端使用TCP协议与服务器进行交互,通讯协议使用RESP。站在机器的角度,可以通过抓取本机所有Redis端口的TCP包来完成热键的统计(不推荐,公司每台机器上已经有很多基础组件了,就不要加了)混乱);自动处理通过监控后,程序可以获得bigkey和hotkey,在报警的同时,程序自动处理bigkey和hotkey。或告知程序员使用某些工具进行自定义处理(在程序中针对特定键执行上述解决方案)3.尽量不要事后诸葛,是血泪的教训,就不展开了在上面。感谢阅读,欢迎交流。