首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >产线问题分析与解决系列:2 Redis告急!内存飙升背后的真相与解决之道

产线问题分析与解决系列:2 Redis告急!内存飙升背后的真相与解决之道

作者头像
李福春
发布2025-07-01 19:56:34
发布2025-07-01 19:56:34
4140
举报

产生背景

场景:消息服务重构上线之后Redis的内存飙升

时间:2025年4月10日,Redis告警飙升后的会议室

角色:程序员(小李):负责消息中心重构后的的开发和优化。

运维(老张):负责系统的部署和监控。业务方

(李总):负责业务需求和上线进度,强调快速扩张。

老张(运维):

“李总,小李,今天早上新消息中心服务上线美国后,Redis的内存突然飙升,告警直接触发了。我查了一下,占比最多的key都来自消息服务。小李,这是怎么回事?”

李总(业务方):

“老张,这个问题很严重啊!美国市场是我们业务扩张的重点,Redis告警会影响整个系统的稳定性。小李,你这边有没有排查出原因?”

小李(程序员):

“李总,老张,我刚刚看了一下日志,发现新消息中心服务上线美国后,发送的消息量突然增加了好几倍。这些消息的key没有设置合理的过期时间,导致Redis内存迅速被占满。”

老张(运维):

“小李,你确定是key的过期时间问题吗?我看了监控,Redis的内存使用率在短时间内从30%飙升到了90%,这可不是小问题。如果消息量继续增加,Redis可能会直接崩溃。尤其是当前系统的所有服务共有的一个redis集群,一死全死”

李总(业务方):

“小李,老张,你们别互相推诿了!我们现在时间很紧,业务正在快速扩张,新消息中心服务上线美国必须尽快上线。小李,你能尽快解决这个问题吗?”

小李(程序员):

“李总,我理解你的担忧。我已经找到了临时解决方案:首先,我们可以暂时关闭美国的流量开关,走老的消息服务,减少消息发送量;其次,老张,你能帮忙删除Redis中占内存较大的key吗?这样可以快速缓解内存压力。”

老张(运维):

“好的,小李,我马上安排删除这些key。另外,我建议我们在生产环境启用Redis的内存监控和告警策略,防止类似问题再次发生。”

李总(业务方):

“老张,你先按照小李的方案操作,确保Redis稳定。小李,你这边有没有长期解决方案?我们不能每次都靠临时措施解决问题。”

小李(程序员):

“李总,我已经在后续版本中全量排查并修改了Redis.set值的过期时间,确保每个key都有合理的过期时间。另外,我会优化消息发送的逻辑,避免消息量突然激增。”

老张(运维):

“小李,我建议我们在测试环境模拟高并发场景,确保优化后的方案能够应对消息量的波动。另外,我们可以考虑引入Redis集群,分担单节点的压力。”

李总(业务方):

“好,老张,你先安排测试环境的模拟。小李,你这边抓紧时间优化代码,我们明天再开一次会,必须有一个明确的长期解决方案。记住,公司业务正在快速扩张,技术问题不能成为瓶颈!”

分析过程

  1.找DBA帮忙拿出占比较大的key就可以看出是哪个系统的问题

因为aws的redis分析能力有限,写了一段python脚本找出排名前50的key ;

代码语言:javascript
复制
import redis
from collections import defaultdict
def scan_keys(redis_client):
    cursor = 0
    while True:
        cursor, keys = redis_client.scan(cursor, count=1000)  # 每次扫描 1000 个 keys
        yield from keys  # 使用生成器返回 keys
        if cursor == 0:
            break  # 如果游标为 0,则表示扫描完成
def main(redis_host, redis_port, redis_db, redis_password, use_ssl):
    # 连接 Redis,支持 SSL 连接
    redis_client = redis.StrictRedis(
        host=redis_host,
        port=redis_port,
        db=redis_db,
        password=redis_password,
        ssl=use_ssl,  # 根据配置是否使用 SSL
        decode_responses=True  # 自动解码字节为字符串
    )
    # 要统计的 key 前缀和数量
    prefix_count = defaultdict(int)
    # 使用 SCAN 统计前缀
    for key in scan_keys(redis_client):
        prefix = key[:6]  # 取前 6 个字符作为前缀
        prefix_count[prefix] += 1  # 计数
    # 输出统计结果并取前50个
    sorted_prefix_count = sorted(prefix_count.items(), key=lambda item: item[1], reverse=True)[:50]
    print("前缀统计结果(前 50 个):")
    for prefix, count in sorted_prefix_count:
        print(f"前缀: {prefix}, 数量: {count}")
if __name__ == "__main__":
    # 输入 Redis 配置信息
#     redis_host = input("请输入 Redis 的 IP 地址: ")
#     redis_port = int(input("请输入 Redis 的端口: "))  # 端口一般是 6379
#     redis_db = int(input("请输入 Redis 的数据库索引 (0-15): "))  # 默认数据库索引是 0
#     redis_password = input("请输入 Redis 的密码 (如果没有,请直接回车): ") or None
    redis_host = '10.100.1.182'
    redis_port = 63100  # 端口一般是 6379
    redis_db = 3  # 默认数据库索引是 0
    redis_password ='xxxxxxxx'
#     use_ssl = False
    use_ssl = True
    main(redis_host, redis_port, redis_db, redis_password,use_ssl)

结论: messag前缀的key占比最多,167W条;

  2.分析这些key,因为设置过期时间太长,且业务发送量大,导致Redis内存飙升

发送消息的时候做幂等控制,过期时间太长了。60个小时变为30分钟;

这个需要更新程序。

 3. 清理掉这些已知的不影响业务的密集key 。

代码语言:javascript
复制
import redis
import time
def delete_keys_with_prefix(redis_client, prefix, batch_size=1000):
    """
    批量删除指定前缀的Redis key
    :param redis_client: Redis客户端实例
    :param prefix: 要删除的key的前缀
    :param batch_size: 每次删除的key数量,默认1000
    """
    cursor = 0
    total_deleted = 0
    while True:
        # 使用SCAN命令获取一批key
        cursor, keys = redis_client.scan(cursor=cursor, match=f"{prefix}*", count=batch_size)

        if not keys:
            print("没有找到更多符合条件的key,删除完成。")
            break
        # 删除这批key
        deleted_count = redis_client.delete(*keys)
        total_deleted += deleted_count
        print(f"已删除 {deleted_count} 个key,总共删除 {total_deleted} 个key。")
        # 如果SCAN返回的cursor为0,表示遍历完成
        if cursor == 0:
            print("所有符合条件的key已删除完毕。")
            break
        # 为了避免对Redis造成过大压力,可以适当休眠
        time.sleep(0.1)
if __name__ == "__main__":
    # Redis连接配置
    redis_host = "localhost"  # Redis主机地址
    redis_port = 6379         # Redis端口
    redis_db = 0              # Redis数据库编号
    redis_password = "your_password_here"  # Redis密码
    redis_ssl = False         # 是否启用SSL认证
    # 创建Redis客户端
    redis_client = redis.Redis(
        host=redis_host,
        port=redis_port,
        db=redis_db,
        password=redis_password,
        ssl=redis_ssl,        # 是否启用SSL认证
        decode_responses=True
    )
    # 要删除的key的前缀
    prefix = "messag"
    # 批量删除
    delete_keys_with_prefix(redis_client, prefix)

解决之后的效果:

解决方案

临时方案:

          1、切换开关,发消息通道切回旧msg发送

          2、DBA帮忙删除占内存较大的key

最终方案:

          后续版本全量排查修改Redis.set值的过期时间

经验总结 

系统重构上线,要考虑到各个中间件性能问题,不光是Redis,还有MySQL、MQ性能问题,从系统自身业务量出发设计合理技术方案,以及兜底方案,尽量对业务零影响。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-05-10,如有侵权请联系 cloudcommunity@tencent.com 删除
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档