掘金 后端 ( ) • 2024-03-13 21:50

theme: smartblue highlight: agate

背景介绍

在分布式架构的实践中,某一特定的Key数据往往会被针对性地分配至后端某台服务器上的独立Redis实例中,以实现高效的数据存储。然而,当这一Key遭遇突发性且高强度的请求操作时,往往会出现流量过于集中的情况。这种流量集中现象会导致单一Redis实例的处理能力受到严峻挑战,很可能使其CPU利用率急剧上升,逼近甚至达到满载状态。这种满载状态不仅会对整个系统的稳定性造成冲击,降低其可靠性,更可能直接威胁到系统的可用性,导致服务中断或性能下降。


热Key问题

当Redis服务面临超出其承受能力的压力时,服务器可能因过载而宕机,导致服务暂时中断,无法响应外部请求。在这种紧急情况下,原本应由Redis处理的大量数据请求将被迫直接转向数据库。鉴于Redis作为缓存层已无法有效支撑负载,数据库同样可能因瞬间激增的访问量而面临巨大的压力,极有可能在短时间内崩溃。这种现象正是我们所说的“热Key问题”。 在这里插入图片描述

  • 具体分析:
    • 流量集中会导致服务器(如CPU和网络IO)处理达到上限,影响Redis实例的整体性能。
    • 热Key的高并发请求不仅影响自身处理,还可能干扰同一Redis实例上其他Key的读写操作。
    • 扩容对于解决热Key问题效果有限,因为热Key请求过于集中,难以有效分散。
    • Redis请求失败可能导致查询操作直接转向数据库,给数据库带来巨大压力,甚至拖垮整个服务。

解决模型

接下来,我们将对热key可能引发的潜在问题进行深入分析,并致力于解决这些热key问题。我们将遵循一套科学有效的解决方案流程,以确保问题得到妥善处理。在这里插入图片描述

寻找热Key

根据个人的经验和分析,热key的寻找的方式主要总结为以下三个方面: 在这里插入图片描述

方案一:业务预测热Key

【基于业务系统的功能特点,可以在某些业务场景和功能下预先判断出热Key的出现】

例如,当网上商场策划一场商品秒杀活动时,由于需要快速响应大量用户的并发请求,秒杀商品的详细信息及数量往往会被预先缓存至Redis这类内存中,以显著提升访问速度,增强用户体验。然而,这种做法也带来了一个不容忽视的问题——热Key现象。

热Key:即在短时间内被大量用户频繁访问的缓存键,由于大量的请求集中在少数几个键上,可能导致缓存系统负载过高,甚至引发性能瓶颈。

问题:不可预测性

热Key的产生如果是由突发性事情所引发的,具有难以预测的特性。这些事件的突发性和不可预知性,使得我们难以准确预测热Key的出现及其影响。

方案二:手动埋点统计

为了进一步提升系统的可观测性和性能分析能力,我们可以对客户端工具进行封装处理。通过封装,我们可以在发送请求之前进行必要的数据收集与采集工作,确保能够捕获到每一次与Redis交互的详细信息。

SDK代码模式植入

Redis客户端工具的封装和数据收集上报机制的建立

在连接Redis服务器的过程中,我们通常依赖特定的软件开发工具包(SDK),比如Java环境中的Jedis和Redisson等客户端工具。这些工具为开发者提供了便捷的操作接口,简化了与Redis的交互过程。

问题:侵入性\成本高

  • 侵入性:客户端代码的修改可能会带来一定的侵入性,或者我们可能需要对现有的SDK工具进行二次开发以满足特定需求。

  • 成本高:无法很好地适应多语言架构。每一种语言的SDK都需要进行独立的开发,这无疑增加了后期开发和维护的成本。这种成本不仅体现在人力和时间的投入上,更在于长期维护的复杂性和难度。

解决:OpenTracing的JavaAgent处理

OpenTelemetry与Skywalking协同工作,实现对Redis操作和行为的细致监控在这里插入图片描述

  • OpenTelemetry以其标准化的API和灵活的数据模型,简化了监控数据的收集过程,确保数据的准确性和一致性。
  • Skywalking则提供了丰富的可视化界面和强大的分析能力,帮助我们直观地查看Redis的实时运行状态,并快速定位潜在问题。

方案三:Redis自带监控

scan命令和object freq命令

编写自己的脚本或使用第三方工具来遍历所有的 key,并使用 OBJECT FREQ 命令来获取每个 key 的访问频率。然后,你可以根据访问频率来识别热点 key。 在这里插入图片描述 具体来说,它首先使用scan命令遍历整个keyspace,然后对每个key调用object freq命令来获取其访问频率,从而识别出热点key。通过这一机制,我们可以轻松地监控和分析Redis中的热点key,进而优化系统的性能表现。下面便是shell脚本。

#!/bin/bash  
  
# Redis 服务器地址和端口  
REDIS_HOST="localhost"  
REDIS_PORT="6379"  
  
# 初始化游标为 0  
CURSOR="0"  
  
# 热点 key 阈值(可以根据实际情况调整)  
HOTKEY_THRESHOLD="1000"  
  
# 存储热点 key 的文件  
HOTKEYS_FILE="hotkeys.txt"  
  
# 清空之前的热点 key 文件  
> "$HOTKEYS_FILE"  
  
# 循环遍历所有的 key  
while [ "$CURSOR" -ne "0" ]; do  
    # 使用 scan 命令获取下一批 key 和新的游标  
    RESPONSE=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" SCAN "$CURSOR")  
      
    # 提取新的游标和 key 列表  
    CURSOR=$(echo "$RESPONSE" | awk '{print $1}')  
    KEYS=$(echo "$RESPONSE" | awk '{for(i=2; i<=NF; i++) print $i}')  
      
    # 遍历 key 列表并获取频率  
    for KEY in $KEYS; do  
        FREQ=$(redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" OBJECT FREQ "$KEY")  
          
        # 检查频率是否超过阈值  
        if [ "$FREQ" -gt "$HOTKEY_THRESHOLD" ]; then  
            echo "Hot Key: $KEY, Freq: $FREQ" >> "$HOTKEYS_FILE"  
        fi  
    done  
done  
  
echo "Finished scanning. Hot keys written to $HOTKEYS_FILE"

问题:实时性差、耗时长

需要彻底遍历整个 keyspace,该过程的实时性相对较弱;扫描所需的时间与 keyspace 中 key 的数量成正比,若 key 数量巨大,则扫描过程可能极为漫长,从而导致效率低下。

解决热Key

在这里插入图片描述

方案一:增加实例

出现热 Key 的 Redis 实例,我们可采取水平扩容策略,通过增加副本数量来分散读请求的压力。具体而言,通过将读请求分发至不同的副本节点上,能够有效平衡负载,提高系统的吞吐量和响应速度。

这种方式不仅有助于缓解热 Key 带来的性能瓶颈,还能提升整个 Redis 集群的稳定性和可靠性。

方案二:本地缓存

当热 Key 出现时,我们可以选择将其加载至系统的 JVM 中,以便后续针对这些热 Key 的请求能够直接从 JVM 中快速获取,无需再经过 Redis 层。在实现这一机制时,我们可以利用多种本地缓存工具,例如 Ehcache、Google Guava 中的 Cache 工具,或者简单地使用 HashMap 作为本地缓存工具。 在这里插入图片描述 这些工具都能有效地提升热 Key 的访问速度,提高系统性能。通过合理选择和使用这些工具,我们可以更好地应对热 Key 带来的挑战,确保系统的稳定高效运行。

问题分析

  • 本地缓存过大:在实施热 Key 的本地缓存策略时,我们必须注意避免本地缓存过大,以免对系统性能造成负面影响。

  • 数据的一致性,我们需要设计有效的同步机制,确保本地缓存中的数据与 Redis 集群中的数据保持一致。这样,我们既能利用本地缓存提升热 Key 的访问速度,又能确保数据的准确性和可靠性。

方案三:流量分散

热 Key 的出现,源于大量针对同一 Key 的请求集中落在同一 Redis 实例上。若能设法将这些请求分散至不同实例,避免流量倾斜现象,热 Key 问题自然迎刃而解。

将针对某一热 Key 的访问请求均衡分配至不同实例呢?这将是解决热 Key 问题的关键所在。

解决方案

为了实现负载均衡并有效处理热 Key 问题,我们可以采用一种基于内存计数器和 hash(ip) 的分配轮询方案。通过这一方案,我们可以将针对同一热 Key 的请求均匀地分配到多个 Redis 实例上,确保每个实例都能承担适量的负载。 在这里插入图片描述 具体来说,我们可以维护一个内存计数器,用于记录每个 Redis 实例的访问次数。当接收到针对热 Key 的请求时,我们首先根据请求来源的 IP 地址进行哈希运算(hash(ip)),得到一个哈希值。 在这里插入图片描述 然后,根据这个哈希值和内存计数器的状态,决定将请求发送到哪个 Redis 实例。通过这种方式,我们可以确保不同来源的请求被均匀地分配到各个实例上,从而实现负载均衡。 在这里插入图片描述


最后:数据统计汇总

充分利用这些数据,我们可以设计一套定时上报机制。通过这一机制,我们可以将收集到的数据定期发送到统一的服务端进行聚合计算。这样做不仅可以减少数据传输的开销,还能确保数据的准确性和完整性。


最后总结

在构建和维护分布式系统时,我们必须高度重视热Key问题,并采取一系列有效的措施来预防和处理这种情况。例如,通过合理的负载均衡策略来分散请求流量,避免单个数据点过载;同时,加强监控和预警机制,一旦发现热Key现象,立即进行干预和调整。只有这样,我们才能确保系统在面临高强度请求时依然能够稳定运行,为用户提供优质的服务体验。