《玩转Redis》系列文章 by zxiaofan主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【15】篇,最新系列文章请前往 公众号“zxiaofan”(点我点我)查看,或 百度搜索“玩转Redis zxiaofan”(点我点我)即可。

本文关键字:玩转Redis、导出没有设置过期时间的key、删除没有设置过期时间的key

往期精选:《玩转Redis-生产环境如何导入、导出及删除大量数据》

大纲

  • 如何查询Redis中没有设置过期时间的key数量
  • 导出Redis中没有设置过期时间的key
  • 安全删除Redis中没有设置过期时间的key

  前段时间公司有新业务需要使用Redis,于是查看了生产一Redis集群的使用情况,用于评估是否能直接接入新业务。此Redis集群购买的阿里云集群社区版,8节点32G;

  不看不知道,一看吓一跳。Redis实例共计 450W key,其中 230W 设置了过期时间,也就是说足足有 220W key没有设置过期时间。 What !!! 接近 50% 的数据没有过期时间,完全不符合常理,严重浪费。

  不把这些钉子户揪出来,“誓不为猿”。

  文末已放 github 联接。

1、如何查询Redis中未设置过期时间的key数量

1.1、使用阿里云【云数据库Redis版管理控制台】查看

  如果Redis集群是云厂商提供的,厂商一般会提供管理控制台的。阿里云控制台搜索“云数据库Redis版”即可进入【云数据库Redis版管理控制台】。

1.1.1、 查看集群或节点的Key变化情况

  • 进入【性能监控】,默认选择【数据节点聚合指标】,此处是区间折线图;

    • 聚合指标可查看整个集群的数据情况;
    • 可通过调整“查询时间”扩大数据分析范围;
    • 亦可选择【数据节点】查看指定节点的数据情况;
  • 进入【CloudDBA】,进入【性能趋势】,可查看区间折线图
    • [Keys]一栏,可查看 key总量、设置过期时间key数量、已过期key数量 的变化信息;


1.1.2、查看全局节点的实时性能

  • 进入【CloudDBA】,进入【实时性能】,选择【全局节点实时性能】;
  • 默认勾选“自动刷新(5秒)”,即可查看各节点的实时性能;

1.1.3、云数据库Redis监控指标说明

监控指标 单位 说明
Keys Counts Key总数量,实例存储的一级Key总数
Expires Counts 实例中设置了过期时间的键值对数量,该指标展示的是采集数据时的瞬时值
ExpiredKeys Counts 历史累计淘汰的Key总数
EvictedKeys Counts 历史累计驱逐的Key总数
ExpiredKeysPerSecond Counts/s 每秒被淘汰的Key数量
EvictedKeysPerSecond Counts/s 每秒被驱逐的Key数量

参考地址:https://help.aliyun.com/document_detail/43887.html 。

1.2、使用Redis命令行查看

  执行 “info Keyspace” 指令即可,其中 keys 表示 集群中 key 的总量(大约 455W),expires 表示 设置了过期时间的key的总量(大约 240W)。

127.0.0.1:6379> info Keyspace
# Keyspace
db0:keys=4551001,expires=2405155,avg_ttl=219009799007

  使用命令行查看的缺点是 无法查阅数据一段时间的变化情况(当然运维可以自行搭建监控体系,普罗米修斯、ELK都是不错的选择),所以云厂商给我们提供了很大的方便。

2、导出Redis中没有设置过期时间的key

  一想着快 50% 的资源浪费了就无比痛心,So 必须导出这些钉子户分析罪魁祸首,然后干掉他。

  原本想着阿里云应该有类似的功能,结果在官网查看以及询问阿里云技术支持,发现阿里云并未提供此功能,只能自己捣鼓了。

  由于是生产环境,所以 【安全性、稳定性】 尤其重要,绝不能影响生产环境的正常使用。网上查询了相关资料,最终形成如下2种脚本解决方案:

  • 使用shell脚本导出数据;

    • 无长连接,相对较慢,大量连接对Redis有一定影响;
    • 数据量较大时注意合理配置休眠时间;
  • 使用python脚本导出数据。

2.1、使用shell脚本导出数据

脚本部分参数可配置,可按需修改以下参数:

  • db_ip:Redis连接地址;
  • db_port:Redis连接端口,可作为参数传入;
  • password:Redis连接密码,可作为参数传入;
  • cursor:第一次 scan 迭代游标,默认0,首次使用不建议修改;
  • cnt:每次 scan 迭代的数量,默认1000,可根据生产情况调整优化。

需要注意的是:

  • 脚本默认每scan(ttl) 1000 个key即休眠0.1秒;
  • shell 脚本无法维护长连接,所以每次ttl都将创建连接,性能有一定影响;
  • 有想法使用lua脚本批量 ttl 提升性能,有兴趣的同学可以实现一下,欢迎分享反馈。

脚本执行:

  测试 Redis 单机版 共 30W key,按照默认的休眠频率(scan 1K 个key休眠0.1秒)总计耗时12分钟(可自行优化调整),扫描结果的 key 保存在 当前目录的 no_ttlkey.log 中。

[redis@xxx redis]$ time ./checknottl.sh 6378 password
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
scan_num: 0real 12m14.810s
user    4m14.343s
sys 5m41.646s

  脚本如下,将以下脚本内容保存为 checknottl.sh ,再执行 “chmod u+x *.sh
”授予执行权限;:

#!/bin/bash
# 查询没有设置过期时间的key;
# 脚本地址:https://github.com/zxiaofan/OpenSource_Study/tree/master/redis_scripts;
# 需在redis-cli 目录执行,或者修改脚本中的路径;
# checknottl.sh,默认每scan(ttl) 1000 个key即休眠0.1秒。
# 脚本参考网络,已做一定调整优化,更多Redis系列文章可前往:https://blog.csdn.net/u010887744/category_9356949.html;
# Note:
#           shell 脚本无法维护长连接,所以每次ttl都将创建连接,性能有一定影响。
#       有想法使用lua脚本批量 ttl 提升性能,有兴趣的同学可以实现一下,欢迎分享反馈。db_ip=127.0.0.1      # Redis ip
db_port=$1         # Redis 端口
password=$2     # Redis 密码
cursor=0             # 第一次游标
cnt=1000              # 每次迭代的数量
new_cursor=0         # 下一次游标
scan_num=0         # 已scan的key数量./redis-cli -h $db_ip -p $db_port -a $password scan $cursor count $cnt > scan_tmp_result
new_cursor=`sed -n '1p' scan_tmp_result`             # 获取下一次游标
sed -n '2,$p' scan_tmp_result > scan_result          # 获取 keyscat scan_result |while read line                     # 循环遍历所有 keys# 2>/dev/null,将标准错误丢弃,Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
dottl_result=`./redis-cli -h $db_ip -p $db_port -a $password ttl $line 2>/dev/null`      # 使用 ttl 指令获取 key 过期时间# $scan_num +=1;if [[ $ttl_result == -1 ]];then                  # 判断过期时间,-1 是不过期echo $line >> no_ttlkey.log                     # 追加到指定文件fi
doneecho 'scan_num: '$scan_num
while [ $cursor -ne $new_cursor ]    # 若 游标 不为 0 ,则证明没有迭代完所有的 key,继续执行
do./redis-cli -h $db_ip -p $db_port -a $password scan $new_cursor count $cnt 2>/dev/null> scan_tmp_resultnew_cursor=`sed -n '1p' scan_tmp_result`sed -n '2,$p' scan_tmp_result > scan_resultcat scan_result |while read linedottl_result=`./redis-cli -h $db_ip -p $db_port -a $password ttl $line 2>/dev/null`# $scan_num +=1;if [[ $ttl_result == -1 ]];thenecho $line >> no_ttlkey.logfi#if [ $scan_num % 1000 == 0 ];then#    sleep 0.5#fidonesleep 0.1
done
rm -f scan_tmp_result
rm -f scan_result

2.2、使用python脚本导出数据

  为什么要使用python,其实上面已经提到,shell无法维护长连接,在Redis数据量较大的情况下,大量的 ttl 将创建 海量连接,对性能以及Redis的稳定性都有一定影响。

脚本部分参数可通过指令传入,可按需修改脚本:

  • -host:Redis连接地址,默认127.0.0.1;
  • -p:Redis连接端口;
  • -d:需要扫描的db,默认仅扫描0库;
  • -a:Redis连接密码;
  • -sn:sacn指定数量的key后变休眠1秒;

脚本执行:

  测试 Redis 单机版 共 30W key,按照默认的休眠频率(scan 1K 个key休眠0.5秒)总计耗时4分钟(可自行优化调整),扫描结果的 key 保存在 当前目录的 {port}_{db}_no_ttl_keys.txt 中。

  可以看到,虽然python版本的休眠时间更短,但是性能却提升了很多。同样你也可以使用其他可维护连接池的语言执行。

[redis@xxx redis]$ python checknottl.py -p 6378 -d 0 -a password
there are 300005 keys in db[0]
startTime of db[0] is :2020-12-06 10:40:09
[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]100.00%
done
endTime of db[0] is :2020-12-06 10:44:24
It takes 254.684270859 seconds :
no ttl keys number is : 300002
the file of keys with no ttl: ./6378_0_no_ttl_keys.txt

将以下脚本内容保存到 checknottl.py,安装python后再执行上面的命令即可。

# encoding: utf-8
"""
modify: zxiaofan
date: 2020-12-05
func: 查找Redis中没有设置ttl的 key
脚本参考网络(auther杨奇龙),已做一定调整优化,更多Redis系列文章可前往:https://blog.csdn.net/u010887744/category_9356949.html;
脚本地址:https://github.com/zxiaofan/OpenSource_Study/tree/master/redis_scripts;
Note:如果提示没有Redis模块“ImportError: No module named redis”,请执行“python -m pip install redis”安装Redis模块;默认每扫描1000个key即休眠0.5秒。
"""
import redis
import argparse
import time
import sysclass ShowProcess:"""显示处理进度的类调用该类相关函数即可实现处理进度的显示"""i = 0 # 当前的处理进度max_steps = 0 # 总共需要处理的次数max_arrow = 50 # 进度条的长度# 初始化函数,需要知道总共的处理次数def __init__(self, max_steps):self.max_steps = max_stepsself.i = 0# 显示函数,根据当前的处理进度i显示进度# 效果为[>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>]100.00%def show_process(self, i = None):if i is not None:self.i = ielse:self.i += 1num_arrow = int(self.i * self.max_arrow / self.max_steps) # 计算显示多少个'>'num_line = self.max_arrow - num_arrow # 计算显示多少个'-'percent = self.i * 100.0 / self.max_steps # 计算完成进度,格式为xx.xx%process_bar = '[' + '>' * num_arrow + ' ' * num_line + ']'\+ '%.2f' % percent + '%' + '\r' # 带输出的字符串,'\r'表示不换行回到最左边sys.stdout.write(process_bar) # 这两句打印字符到终端sys.stdout.flush()def close(self, words='done'):print ''print wordsself.i = 0def check_ttl(redis_conn, no_ttl_file, dbindex, scannum_thensleep):start_time = time.time()no_ttl_num = 0scan_num = 0keys_num = redis_conn.dbsize()print "there are {num} keys in db[{index}] ".format(num=keys_num, index=dbindex)# 打印扫描db开始时间print "startTime of db[{index}] is :{start_time}".format(index=dbindex, start_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))process_bar = ShowProcess(keys_num)with open(no_ttl_file, 'a') as f:for key in redis_conn.scan_iter(count=1000):process_bar.show_process()if redis_conn.ttl(key) == -1:no_ttl_num += 1if no_ttl_num < 1000:f.write(key+'\n')else:continuescan_num +=1;if(scan_num % scannum_thensleep == 0): # scan指定数量后即休眠time.sleep(0.5);process_bar.close()# 打印扫db描结束时间print "endTime of db[{index}] is :{end_time}".format(index=dbindex, end_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))print "It takes {sec} seconds :".format(sec=(time.time() - start_time))print "no ttl keys number is :", no_ttl_numprint "the file of keys with no ttl: %s" % no_ttl_filedef main():parser = argparse.ArgumentParser()parser.add_argument('-p', type=int, dest='port', action='store', help='port of redis ')parser.add_argument('-d', type=str, dest='db_list', action='store', default=0,help='ex : -d all or -d 1,2,3,4 ')parser.add_argument('-host', type=str, dest='host', action='store', default='127.0.0.1',help=' Redis host') # 20201205,支持连接远程Redisparser.add_argument('-a', type=str, dest='password', action='store', default= None,help=' Redis Password') # 20201205,支持传入Redis密码parser.add_argument('-sn', type=str, dest='scannum_thensleep', action='store', default=2000,help='After the number of scanning keys reaches [scannum_thensleep], sleep for 1 second ') # 20201205,支持sacn指定数量的key后变休眠1秒args = parser.parse_args()port = args.porthost = args.hostpassword = args.passwordscannum_thensleep = int(args.scannum_thensleep)if args.db_list == 'all':db_list = [i for i in xrange(0, 16)]else:db_list = [int(i) for i in args.db_list.split(',')]db_list = list(set(db_list)) # 20201205,去重,避免用户重复输入db序号;for index in db_list:try:pool = redis.ConnectionPool(host=host, port=port, db=index, password=password) # 20201205,支持传入Redis密码r = redis.StrictRedis(connection_pool=pool)except redis.exceptions.ConnectionError as e:print eelse:no_ttl_keys_file = "./{port}_{db}_no_ttl_keys.txt".format(port=port, db=index)check_ttl(r, no_ttl_keys_file, index, scannum_thensleep)if __name__ == '__main__':main()

3、安全删除Redis中没有设置过期时间的key

  利用前面提到的2种方式我们已经可以拿到没有设置过期时间的 key 了。接下来我们要做的就是数据分析,明确哪些 key 是钉子户,然后干掉它。

# deletedata.txt 数据示例:del key-0
del key-1
del key-2
del key-3
...

考虑到大key,更推荐使用 UNLINK :

  • DEL:阻塞式操作;
  • UNLINK:并不总是 阻塞,如果值很小,和 DEL 相同效果将直接删除,如果值较大,key将被放入列表中,被另一个线程释放。
# deletedata.txt 数据示例:UNLINK key-0
UNLINK key-1
UNLINK key-2
UNLINK key-3
...

由于 UNLINK 支持多参数(DEL 也支持),所以我们还可以做进一步优化:

# deletedata.txt 数据示例:UNLINK key-0 key-1 key-2 key-3 key-4 key-5
UNLINK key-6 key-7 key-8 key-9 key-10 key-11
...

最后我们再使用管道删除即可,如果数据量实在较大,建议分批删除呢。

# 使用管道删除cat deletedata.txt | redis-cli -c --pipe

  详细操作可以参考我先前的文章:
《玩转Redis-生产环境如何导入、导出及删除大量数据》

  最新脚本请前往Github查看:https://github.com/zxiaofan/OpenSource_Study/tree/master/redis_scripts。

参考文章:
http://blog.itpub.net/22664653/viewspace-2153419/;
https://www.cnblogs.com/klvchen/p/12071981.html。

【玩转Redis系列文章 近期精选 公众号@zxiaofan】

《玩转Redis-8种数据淘汰策略及近似LRU、LFU原理》

《玩转Redis-生产环境如何导入、导出及删除大量数据》

《玩转Redis-删除了两百万key,为什么内存依旧未释放?》

《玩转Redis-Redis中布隆过滤器的使用及原理》

《玩转Redis-HyperLogLog原理探索》


公众号搜索【zxiaofan】查阅最新系列文章。

Life is all about choices!

将来的你一定会感激现在拼命的自己!

【CSDN】【GitHub】【OSCHINA】【掘金】【语雀】【微信公众号(点击关注)】


玩转Redis-干掉钉子户-没有设置过期时间的key相关推荐

  1. redis 存储数据不设置过期时间 会自动过期吗_Redis-数据淘汰策略持久化方式(RDB/AOF)Redis与Memcached区别...

    Redis与Memcached区别: 两者都是非关系型数据库.主要有以下不同: 数据类型: Memcached仅支持字符串类型. redis支持:String,List,set,zset,hash 可 ...

  2. 利用redis保存验证码并设置过期时间

    package com.atguigu.jedis;import redis.clients.jedis.Jedis;import java.util.Random;public class Phon ...

  3. redis hash结构如何设置过期时间

    Redis中有个设置时间过期的功能,即通过setex或者expire实现,目前redis没有提供hsetex()这样的方法,redis中过期时间只针对顶级key类型,对于hash类型是不支持的,这个时 ...

  4. @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...

    大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...

  5. c# redis hashid如何设置过期时间_Redis中Key过期策略amp;淘汰机制

    1. Redis中设置Key过期时间 我们有两种方式设置过期时间 1.1 设置多久后过期 设置一个 key 10s 过期,可以这样 127.0.0.1:6379> SET key value E ...

  6. java redis设置过期时间_Redis的一些核心原理

    点关注,不迷路:持续更新Java相关技术及资讯!!! 一.Redis的单线程和高性能 Redis 单线程为什么还能这么快? 因为它所有的数据都在内存中,所有的运算都是内存级别的运算(纳秒),而且单线程 ...

  7. redis如何设置定时过期_redis补充6之Redis 设置过期时间

    一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间. Redis 中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间.作为一个缓存数据库,这是非常实用的.如我 ...

  8. redis如何设置定时过期_redis设置过期时间

    redis是在内存中进行缓存的,我们在设置redis缓存时,可以设置下过期时间.那么在设置时间到期后redis是如何进行数据删除的. redis清理过期数据. 定期清理 + 惰性清理 定期删除:red ...

  9. stringredistemplate设置过期时间_Redis的过期删除策略和内存淘汰机制

    Redis的key可以设置过期时间,那是否意味着时间一到就会马上被删除呢? Redis的数据存储大小是有限的,假如内存不足Redis有什么应对策略呢? 本篇文章将介绍一下Redis的过期策略和内存淘汰 ...

最新文章

  1. 从AVL树的定义出发,一步步推导出旋转的方案。
  2. 多行列表右边距为零的实现方法
  3. 从JVM看类的加载过程与对象实例化过程
  4. 2 Powershell与Cmd以及Unix/Linux Shell
  5. Django——2 路由分配设置 re_path正则匹配 include总路由 url传参 name使用 模板渲染render方法 模板渲染方法...
  6. 上课点名app_【APP种草】网瘾少年的自我救赎之最强锁机软件
  7. C++STL之fill()函数使用方法
  8. dataframe 上下拼接_pandas DataFrame 的横向纵向拼接组合
  9. Springboot集成通用Mapper与Pagehelper,实现mybatis+Druid的多数据源配置
  10. wireshark抓包教程详解
  11. C#中路径表示\ 和 /
  12. 51单片机——八段数码管
  13. filecoin lotus 转账fil流程和gas计算
  14. 如何登陆FTP服务器下载文件
  15. 苹果电脑怎么设置和修改开机密码?
  16. python_matplotlib改变横坐标和纵坐标上的刻度(ticks)
  17. jquery Callbacks 回调对象的读书笔记-源码分析
  18. 苹果iPhone 8或被阻击!
  19. javascript trim方法
  20. iOS中触摸事件传递和响应原理

热门文章

  1. div布局系列 - 两端对齐的方法
  2. 米家小相机最新固件_能拍4K的米家小相机只要699了,你还要啥自行车?!
  3. 杰理之linein LAC底噪【篇】
  4. WIN7,让光驱走开
  5. soul显示服务器异常,soul聊天状态异常 消息发送失败
  6. HP EliteDesk 800 G4 DM 35W (Japan) Mini Tower黑苹果efi引导文件
  7. 18岁误入网站_是市场驱动的技术领先现代医学误入歧途
  8. 什么是常识?一个人独立生活所具备的能力
  9. 梁继璋:给儿子的一封信
  10. 腾讯 2020年新增 20 亿行代码,C++蝉联腾讯最受欢迎的编程语言