【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

起因

我们使用的一直是阿里云的redis, 我们并非高并发应用, 主要也就是拿来做分布式锁和少量的缓存, 基本不怎么需要维护, 昨天下午突然收到一封告警邮件,
【Redis】线上7000w+ keys && 16G内存100%的排查修复经历
线上redis内存使用100%, 瞬间神经绷紧感觉上控台确认.
【Redis】线上7000w+ keys && 16G内存100%的排查修复经历
这是一个16G的线上实例, 平时不到50%

排查修复

info信息

【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

基本使用都是默认的db 0, 看到keys已经七千多万, 设置了过期的时间key只有173W..

查找bigkey, 想办法先释放一部分keys

先挂上bigkeys的命令

$ redis-cli -h  xxx.redis.rds.aliyuncs.com -a xxx --bigkeys

【Redis】线上7000w+ keys && 16G内存100%的排查修复经历
发现几个出现频率较高的大string key, 和开发同事确认, 是最近添加的redis缓存, 缓存时间为10天, 通知先关闭开关, 避免影响已经运行的任务, 进redis暂时先删除几个比较大的keys看看, 释放了少量内存
【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

BoundValueOperations<String, String> contactListCache = kvLockTemplate.boundValueOps("athena.cache.contactlist" + user.getId());

现在key太多, 如果直接keys效率非常低下, 好在redis原生提供了SCAN, 可以迭代遍历, 写个简单的python脚本用scan每次扫描100W,把相关的keys给删掉.

import redis

def clean_excess(host='xxx.redis.rds.aliyuncs.com', port=6379, db=0,
                     password='xxx', pattern=None):
    _redis = redis.StrictRedis(host=host, port=port, db=db, password=password)
    i = None
    while i != 0:
        if i is None: i = 0
        print('>> scan index', i)
        _scan = _redis.scan(i, match=pattern, count="1000000")
        i, l = _scan
        if l:
            for _i in l:
                print("-- delete key {}".format(_i))
                _redis.delete(_i)
if __name__ == '__main__':
    clean_excess(pattern="athena.cache.contactlist*")

运行完成后内存使用率降到了 45%, 到这里内存问题算是解决了.
【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

用awk快速抽样统计下keys的比例

好在我们都是keyPrefix + 数字id这样的格式, 这里抽样100W看下比例,

redis-cli -h  xxx.redis.rds.aliyuncs.com -a xxx scan 0 count 1000000 | awk -F '[0-9]' '{s=NF>0?$1:$0;print s}' | sort  | uniq -c | sort -n

【Redis】线上7000w+ keys && 16G内存100%的排查修复经历
找开发同学确认下, 这里 ip.try.counter 原本是某个版本用来锁定用户ip尝试登陆的..居然没设置过期时间, 其余的key多多少少都带了一些神奇的逻辑, 就基本没法动了....内心是崩溃的 那么先把这些清理掉

import redis

def clean_excess(host='xxx.redis.rds.aliyuncs.com', port=6379, db=0,
                     password='xxx', pattern=None):
    _redis = redis.StrictRedis(host=host, port=port, db=db, password=password)
    i = None
    while i != 0:
        if i is None: i = 0
        print('>> scan index', i)
        _scan = _redis.scan(i, match=pattern, count="1000000")
        i, l = _scan
        if l:
            for _i in l:
                print("-- delete key {}".format(_i))
                _redis.delete(_i)
if __name__ == '__main__':
    clean_excess(pattern="ip.try.counter.*")

【Redis】线上7000w+ keys && 16G内存100%的排查修复经历
【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

跑了大概九个小时... 清理完后还剩了三千多万key, 内存倒是没怎么释放 其余的问了开发同学基本不能动. 使用率现在维持在50%以下暂时就不动了

结语

  • 关于redis内存逐出策略的问题, 阿里云默认给出的maxmemory刚好是等于你的内存配置大小的,也就是内存使用率100%了才会触发, 逐出策略默认是valatile-ttl只会逐出设置了过期时间的key, 相对于我们的情况, 大部分都是没设置过期时间基本就是杯水车薪. 如果改变策略为allkeys-xx 进行有效逐出,还是会影响到业务的正常运行
    【Redis】线上7000w+ keys && 16G内存100%的排查修复经历

说到底还是需要规范使用者的习惯, 该设置过期时间的不能偷懒, 确实有大内存需求的独立分配资源.

  • 其实有很多不错的现成的工具可以对redis进行诊断 可以参考下 https://scalegrid.io/blog/the-top-6-free-redis-memory-analysis-tools/
    不过这里rdb只能分析dump.rdb文件, 其他几个工具试用了下对于keys数量级过大效率都略低下, 好在我们keys命名还算有一定规律所以自己写了个简单脚本去抽样统计, 比较喜欢的是redis-memory-for-key(只是用来统计具体key的内存占用情况),也可能我使用姿势不对吧 欢迎一起探讨
上一篇:Prism for WPF初探(构建简单的模块化开发框架)


下一篇:全新 DOCKER PALS 计划上线,带给您不一样的参会体验!