起因
我们使用的一直是阿里云的redis, 我们并非高并发应用, 主要也就是拿来做分布式锁和少量的缓存, 基本不怎么需要维护, 昨天下午突然收到一封告警邮件,
线上redis内存使用100%, 瞬间神经绷紧感觉上控台确认.
这是一个16G的线上实例, 平时不到50%
排查修复
info信息
基本使用都是默认的db 0, 看到keys已经七千多万, 设置了过期的时间key只有173W..
查找bigkey, 想办法先释放一部分keys
先挂上bigkeys的命令
$ redis-cli -h xxx.redis.rds.aliyuncs.com -a xxx --bigkeys
发现几个出现频率较高的大string key, 和开发同事确认, 是最近添加的redis缓存, 缓存时间为10天, 通知先关闭开关, 避免影响已经运行的任务, 进redis暂时先删除几个比较大的keys看看, 释放了少量内存
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%, 到这里内存问题算是解决了.
用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
找开发同学确认下, 这里 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.*")
跑了大概九个小时... 清理完后还剩了三千多万key, 内存倒是没怎么释放 其余的问了开发同学基本不能动. 使用率现在维持在50%以下暂时就不动了
结语
- 关于redis内存逐出策略的问题, 阿里云默认给出的maxmemory刚好是等于你的内存配置大小的,也就是内存使用率100%了才会触发, 逐出策略默认是valatile-ttl只会逐出设置了过期时间的key, 相对于我们的情况, 大部分都是没设置过期时间基本就是杯水车薪. 如果改变策略为allkeys-xx 进行有效逐出,还是会影响到业务的正常运行
说到底还是需要规范使用者的习惯, 该设置过期时间的不能偷懒, 确实有大内存需求的独立分配资源.
- 其实有很多不错的现成的工具可以对redis进行诊断 可以参考下 https://scalegrid.io/blog/the-top-6-free-redis-memory-analysis-tools/
不过这里rdb只能分析dump.rdb文件, 其他几个工具试用了下对于keys数量级过大效率都略低下, 好在我们keys命名还算有一定规律所以自己写了个简单脚本去抽样统计, 比较喜欢的是redis-memory-for-key(只是用来统计具体key的内存占用情况),也可能我使用姿势不对吧 欢迎一起探讨