searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Redis常见问题经验总结

2023-05-24 10:50:12
28
0

一、概述

本文主要对Redis使用过程中一些常见问题经验总结,包括Redis CPU持续升高和内存飙升的常见原因分析及对应排查方法、解决方案参考。

二、CPU过高

1、原因分析

引起Redis的CPU过高的原因通常包括以下几种:

  • 同时执行的Redis操作命令太多
  • 对一个相当大的 key 进行操作(如del/add/smembers/hmembers等)
  • pipeline中的命令数量太多
  • 开启了持久化RDB或者AOF

2、排查方法

  • 通过监控信息,查看操作命令总量变化
  • 若操作命令总体变化不大,再看是否有大key操作
  • 若上面情况都没有,可以通过 redis-cli info | grep connected_clients 查看连接数量(根据经验 >2w连接数会对redis性能造成影响)
  • 如果负载未到 50%,可以考虑用 redis-cli monitor 命令抓取一定时间的操作,然后分析看看比较频繁的命令和数据量较大的key (注意:monitor会导致redis进程的 cpu 升高10%-20%, 本来已经到80%+的情况下尽量不要使用,且尽量在从节点运行)

3、解决方案

  1. 批量更新大量的用户信息(如白名单,粉丝信息等>10w的列表),使用 get, set, hset, zadd 等一条一条的执行操作,都会造成cpu升高。
    解决方法: 采用mget或者mset批量设置
  2. 不要直接操作大key (比如直接删除一个 1G 的key,很可能会卡死Redis)
    解决方法: 尽量不要把数据维护在一个key里面,请分成多个key或者将hot key操作分散到其他节点,删除时请先rename再分批scan删除
  3. 大量并发key操作时,需要加上分布式锁,或者先返回一个默认值,避免造成缓存穿透,从而Redis的CPU会暴涨
    解决方法: 避免缓存穿透,请加上分布式锁
  4. 开启了RDB或者AOF
    解决方法: 关闭master的RDB或者AOF
  5. 服务请求量太大,缓存设置不合理 (相同的数据存在不同的用户下,Redis命中率太低,查询太多的key等)
    解决方法: 短期: 确认对业务影响不大情况下限流 长期: 优化代码,批量刷新缓存,Redis key复用,对热点数据进行预热和定时刷新
  6. 连接数过高引起或业务增长引起
    解决方法: 若集群使用主从或者Sentinel模式,考虑迁移到Cluster并对Redis节点进行缩容,或根据业务分拆到其他Redis

三、内存过高

1、原因分析

引起Redis的内存过高的原因通常包括以下几种:

  • 大量的key设置了过期时间
  • 维护了大量的key,而这些key每个都占用较大的内存空间
  • 缓存雪崩
  • PUB/SUB,pipeline命令太多
  • Redis本身占内存太多,输入输出,连接等 (>4.0版本可以通过 MEMORY STATS 命令分析)
  • Redis默认移除算法, 对于过期后且未被访问的key会一直保留进行惰性删除, 故可能会存在占总量25%比例的已过期key

2、排查方法

  • 通过 redis-cli --bigkeys 命令对Redis中的key进行采样,寻找较大的key。用的是scan方式,不用担心会阻塞Redis很长时间不能处理其他的请求,尽量在从节点运行命令
  • 如果Redis CPU不高的话,可通过 redis-cli monitor 抓取一定时间的日志来进行分析, 能直观的看到key的操作,尽量在从节点运行命令
  • 使用第三方工具如 rdbtools 分析

3、解决方案

  1. 大量的key过期后重新加载到内存时长较长,造成缓存雪崩
    解决方法: 优化业务逻辑,改由一个任务异步刷新缓存
  2. 由于大key太多,造成输入缓存区过大,引起内存增加
    解决方法: 避免bigkey返回大量结果,使用 client-output-buffer-limit 设置合理的缓冲区大小上限,或是缓冲区连续写入时间和写入量上限
  3. PUB/SUB,pipeline 命令太多
    解决方法: 优化业务逻辑,合理设置操作命令,或者扩容Redis节点
  4. 使用 redis-cli monitor 后未关闭,引起内存持续增高
    解决办法:  使用 redis-cli client list | grep -v "omem=0"命令来查询输出缓冲区不为0的客户端连接,关闭monitor
  5. 惰性删除key
    解决办法:  调整redis的过期策略, 增大主动删除key的频率(可能引起CPU上升)

 

四、扫描脚本

# !/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import islice
import datetime
from redis import StrictRedis

NUM = 200
HOST = ""
PORT = 6379
PASSWORD = "****"

KEY_FILE = "keys.%s_%s_%s" % (
    HOST.replace(".", "_"), PORT,
    datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))


def scan_key():
    rd = StrictRedis(host=HOST, password=PASSWORD)
    total = 0
    with open(KEY_FILE, "a") as f:
        for keys in chunks(rd.scan_iter(match="*", count=NUM), n=NUM):
            pipe = rd.pipeline(transaction=False)
            total += len(keys)
            for key in keys:
                pipe.type(key)
            rets = pipe.execute()

            records = []
            for key, _type in zip(keys, rets):
                records.append("key=%s type=%s" % (key, _type))

            f.write("\n".join(records))
            if total % 100000 == 0:
                print "now had key=%s." % (total,)

    print "total key, len=%s." % (total,)


def chunks(seq, n=100, return_type=tuple):
    it = iter(seq)
    while True:
        chunk = return_type(islice(it, n))
        if not chunk:
            return
        yield chunk


if __name__ == "__main__":
    scan_key()
0条评论
0 / 1000
x****n
4文章数
1粉丝数
x****n
4 文章 | 1 粉丝
原创

Redis常见问题经验总结

2023-05-24 10:50:12
28
0

一、概述

本文主要对Redis使用过程中一些常见问题经验总结,包括Redis CPU持续升高和内存飙升的常见原因分析及对应排查方法、解决方案参考。

二、CPU过高

1、原因分析

引起Redis的CPU过高的原因通常包括以下几种:

  • 同时执行的Redis操作命令太多
  • 对一个相当大的 key 进行操作(如del/add/smembers/hmembers等)
  • pipeline中的命令数量太多
  • 开启了持久化RDB或者AOF

2、排查方法

  • 通过监控信息,查看操作命令总量变化
  • 若操作命令总体变化不大,再看是否有大key操作
  • 若上面情况都没有,可以通过 redis-cli info | grep connected_clients 查看连接数量(根据经验 >2w连接数会对redis性能造成影响)
  • 如果负载未到 50%,可以考虑用 redis-cli monitor 命令抓取一定时间的操作,然后分析看看比较频繁的命令和数据量较大的key (注意:monitor会导致redis进程的 cpu 升高10%-20%, 本来已经到80%+的情况下尽量不要使用,且尽量在从节点运行)

3、解决方案

  1. 批量更新大量的用户信息(如白名单,粉丝信息等>10w的列表),使用 get, set, hset, zadd 等一条一条的执行操作,都会造成cpu升高。
    解决方法: 采用mget或者mset批量设置
  2. 不要直接操作大key (比如直接删除一个 1G 的key,很可能会卡死Redis)
    解决方法: 尽量不要把数据维护在一个key里面,请分成多个key或者将hot key操作分散到其他节点,删除时请先rename再分批scan删除
  3. 大量并发key操作时,需要加上分布式锁,或者先返回一个默认值,避免造成缓存穿透,从而Redis的CPU会暴涨
    解决方法: 避免缓存穿透,请加上分布式锁
  4. 开启了RDB或者AOF
    解决方法: 关闭master的RDB或者AOF
  5. 服务请求量太大,缓存设置不合理 (相同的数据存在不同的用户下,Redis命中率太低,查询太多的key等)
    解决方法: 短期: 确认对业务影响不大情况下限流 长期: 优化代码,批量刷新缓存,Redis key复用,对热点数据进行预热和定时刷新
  6. 连接数过高引起或业务增长引起
    解决方法: 若集群使用主从或者Sentinel模式,考虑迁移到Cluster并对Redis节点进行缩容,或根据业务分拆到其他Redis

三、内存过高

1、原因分析

引起Redis的内存过高的原因通常包括以下几种:

  • 大量的key设置了过期时间
  • 维护了大量的key,而这些key每个都占用较大的内存空间
  • 缓存雪崩
  • PUB/SUB,pipeline命令太多
  • Redis本身占内存太多,输入输出,连接等 (>4.0版本可以通过 MEMORY STATS 命令分析)
  • Redis默认移除算法, 对于过期后且未被访问的key会一直保留进行惰性删除, 故可能会存在占总量25%比例的已过期key

2、排查方法

  • 通过 redis-cli --bigkeys 命令对Redis中的key进行采样,寻找较大的key。用的是scan方式,不用担心会阻塞Redis很长时间不能处理其他的请求,尽量在从节点运行命令
  • 如果Redis CPU不高的话,可通过 redis-cli monitor 抓取一定时间的日志来进行分析, 能直观的看到key的操作,尽量在从节点运行命令
  • 使用第三方工具如 rdbtools 分析

3、解决方案

  1. 大量的key过期后重新加载到内存时长较长,造成缓存雪崩
    解决方法: 优化业务逻辑,改由一个任务异步刷新缓存
  2. 由于大key太多,造成输入缓存区过大,引起内存增加
    解决方法: 避免bigkey返回大量结果,使用 client-output-buffer-limit 设置合理的缓冲区大小上限,或是缓冲区连续写入时间和写入量上限
  3. PUB/SUB,pipeline 命令太多
    解决方法: 优化业务逻辑,合理设置操作命令,或者扩容Redis节点
  4. 使用 redis-cli monitor 后未关闭,引起内存持续增高
    解决办法:  使用 redis-cli client list | grep -v "omem=0"命令来查询输出缓冲区不为0的客户端连接,关闭monitor
  5. 惰性删除key
    解决办法:  调整redis的过期策略, 增大主动删除key的频率(可能引起CPU上升)

 

四、扫描脚本

# !/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import islice
import datetime
from redis import StrictRedis

NUM = 200
HOST = ""
PORT = 6379
PASSWORD = "****"

KEY_FILE = "keys.%s_%s_%s" % (
    HOST.replace(".", "_"), PORT,
    datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))


def scan_key():
    rd = StrictRedis(host=HOST, password=PASSWORD)
    total = 0
    with open(KEY_FILE, "a") as f:
        for keys in chunks(rd.scan_iter(match="*", count=NUM), n=NUM):
            pipe = rd.pipeline(transaction=False)
            total += len(keys)
            for key in keys:
                pipe.type(key)
            rets = pipe.execute()

            records = []
            for key, _type in zip(keys, rets):
                records.append("key=%s type=%s" % (key, _type))

            f.write("\n".join(records))
            if total % 100000 == 0:
                print "now had key=%s." % (total,)

    print "total key, len=%s." % (total,)


def chunks(seq, n=100, return_type=tuple):
    it = iter(seq)
    while True:
        chunk = return_type(islice(it, n))
        if not chunk:
            return
        yield chunk


if __name__ == "__main__":
    scan_key()
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
2
2