热键问题什么是热键?hotkey是服务端的通病,指一段时间内某个key的访问量远远超过其他key,导致大量的访问流量落在某个redis实例上;或者带宽使用集中在特定键上(例如,每秒发送大量hgetall操作请求,用于包含2000个字段的哈希键);或者CPU使用时间集中在某个特定的key上(例如,对于一个包含10000个字段的key,每秒发送大量的zrange操作ask)。是否为热键由请求频率定义,没有固定的经验值。如果某个键被频繁访问导致系统稳定性下降,则可以将其定义为热键。可能出现的问题热点缓存会导致流量集中,redis缓存和数据库崩溃,造成系统雪崩。详情见《快速了解缓存穿透与缓存雪崩》。请求分布不均,热键节点访问压力较大,数据分片连接数可能枯竭甚至宕机。(即使采用扩容,也会造成很大的资源浪费。)发现方法由于热键的出现会极大的危害系统的稳定性,因此需要在上线前建立故障预案和监控告警机制以快速响应故障。优点:简单明了。缺点:但并不是所有的业务都能预知哪些键是热键。根据业务经验,估计哪些是热键。在客户端收集。在操作redis之前,添加统计频率的逻辑,然后将统计数据发送给一个聚合计算服务进行统计。优点:解决方法简单。缺点:不能支持多语言环境的大公司的SDK,或者难以对接多语言的SDK。另外,维护和升级SDK的成本会很高。在代理层收集。有些服务在请求redis之前会先请求代理服务。该场景可用于在代理层收集热键数据。收集机制类似于在客户端收集。优点:解决方案对用户完全透明;不存在SDK多语言异构和升级成本高的问题。(这个地方不明白的可以查看小辉之前的博文《通用能力抽象选择SDK组件还是API服务?》)缺点:不是所有的场景都会有代理层。Redis集群监控。如果一个实例的qps有偏差,说明可能存在hotkey。优点:不需要额外的开发。缺点:每次出现情况都需要人工排查,因为热键只是造成qps倾斜的一种可能。redis4.0版本后,有热点key发现功能。只需在执行redis-cli时添加--hotkeys选项即可。优点:不需要额外的开发。缺点:执行该参数时,如果key较多,执行时间会很长,导致查询的实时性差。Redis客户端使用TCP协议与服务器进行交互。通过脚本监听端口,解析网络包并分析。优点:对原有业务系统没有任何修改。缺点:开发成本高,维护困难,有丢包的可能。常用的处理方式如果所有的hotkey都缓存在本地,本地缓存会不会太大,影响应用本身的性能开销。可能需要保证本地缓存和redis数据的一致性。热键统计可以使用LFU数据结构结合上述发现方法统计出最热的topNkey,然后在客户端使用本地缓存来减少redis集群对热键的访问量,但是这种方法带来了两个一个问题:给hotkey加前缀或后缀,把hotkey的个数从1改为instance个数,利用sharding特性把n个key分散到不同的节点上,这样就可以使用的方式客户端负载均衡,随机选择一个key访问,将访问压力分散到不同的实例上。这种方案有一个明显的缺点,就是缓存的维护成本高:如果n为100,更新或删除key时需要操作100个key。采用读写分离,通过主从复制,增加从节点,实现读请求的负载均衡。该方案的明显缺点是使用了机器硬耐热密钥的数据,资源消耗较大;而读写分离架构的引入和节点数量的增加,都会增加系统的复杂度,降低稳定性。大钥匙问题什么是大钥匙?大key是指当redis的string类型占用内存过多或者非string类型的元素个数过多时。在生产环境中,综合运维和环境的情况,定义大key的参考值如下:string类型的key超过10KB,hash/set/等数据结构的元素个数zset/list大于5k,整体内存占用大于10MB不同系统性能情况不同,建议保守设置这个标准,以系统稳定性为第一考虑,可能会造成内存占用不均.比如在redis集群模式下,某个数据分片的内存占用远高于其他数据分片,数据分片的内存资源无法均衡。此外,还可能导致redis内存达到maxmemory参数定义的上限,导致重要的key被逐出,甚至导致内存溢出。响应时间增加,超时阻塞。由于redis是单线程架构,操作大key的时间比较长,可能会导致redis阻塞。过期时可能会阻塞。bigkey设置了过期时间,过期key会被删除。如果redis版本低于4.0,没有异步删除机制,会有阻塞redis的可能,慢查询找不到;同样,内存不足时的keyeviction或重命名大key也会阻塞redis服务。长时间阻塞主库可能会导致同步中断或主从切换。为什么我查不到慢查询。比如有请求进来,redis服务器正在扫描过期key,需要等待100毫秒。当客户端设置的超时时间小于100毫秒时,连接会超时关闭,并引发异常。这些现象无法从慢查询日志中查询到(因为慢查询只记录了逻辑处理过程,不包括等待时间)。网络拥塞。例如:一个大的key占用1MB,每秒访问1000次就会产生1000MB的流量,可能会导致本机或者局域网的带宽被占满,同时影响其他服务。发现方式利用工具定期扫描,建立监控和通知机制。优点:无阻塞服务缺点:信息少(只有每种最大的关键信息),内容不够准确(比如hash/list/set/zset都是以元素个数来衡量的,但是在事实上元素的数量并不多)意味着它占用了大量的内存)。redis-cli--bigkeys命令。它可用于查找实例的5种数据类型(字符串、哈希、列表、集合、zset)中的最大键。redis-rdb-tools工具。在redis实例上执行bgsave,然后分析dump出来的rdb文件。优点:获取更详细的信息缺点:需要离线操作,获取结果时间长越准确,越耗性能)。写一个python脚本,在集群低峰时,使用scan和memoryusage命令扫描redis,检查大键。优点:获取信息准确及时缺点:Python脚本需要注意不要影响正常的在线服务,设置监控熔断。常见的处理方式是bigkey不是hotkey。如果不是必须的信息,可以直接删除del或者unlink。如果是redis4.0之前的版本,建议key使用(scan/sscan/hscan/zscan),逐步删除大key(ltrim/zremrangebyscore/hdel/srem)。redis4.0以后直接用unlink代替del,会有后台线程异步删除bigkey。业务拆分,key的含义更细粒度,避免大key的出现。数据结构拆分。如果bigkey是一个大json,可以使用mset将这个key的内容分散到各个instance,减少bigkey对数据量的影响;如果是大列表,可以拆分成list_1、list_2、list_N;其他数据结构相同。(可以考虑增加一个单独的key来存放拆分次数或者大key的元数据信息)在redis没有开启异步删除机制的场景下,在设置过期时间的时候,需要避免出现如下现象:大量key同时过期,所以如果这种情况,最好给过期时间加上一个随机范围,缓解大量key同时过期导致客户端等待的现象超时。对于长文本,建议使用MongoDB等文档数据库。对于不需要高一致性的场景,尽量使用客户端缓存。(只是解决了redis的阻塞问题,对机器或者局域网的带宽问题并没有改善)大key的压缩。相当于使用CPU资源来减少网络io,其中比较常用的是google提出的snappy算法。对于hash等数据结构,需要关注业务是否可以引入定期清理无效字段的机制。
