如何优雅地删除Redis大键
如何优雅地删除Redis大键
原文发布时间为:2017-11-12
参考
https://blog.csdn.net/liu1390910/article/details/79728569
觉得还挺有用 看了有收获就转了。
关于Redis大键(Key),我们从[空间复杂性]和访问它的[时间复杂度]两个方面来定义大键。
前者主要表示Redis键的占用内存大小;后者表示Redis集合数据类型(set/hash/list/sorted set)键,所含有的元素个数。以下两个示例:
1个大小200MB的String键(String Object最大512MB);内存空间角度占用较大
1个包含100000000(1kw)个字段的Hash键,对应访问模式(如hgetall)时间复杂度高
因为内存空间复杂性处理耗时都非常小,测试 del 200MB String键耗时约1毫秒,
而删除一个含有1kw个字段的Hash键,却会阻塞Redis进程数十秒。所以本文只从时间复杂度分析大的集合类键。删除这种大键的风险,以及怎么优雅地删除。
说明:Redis是单线程处理。
单个耗时过大命令,导致阻塞其他命令,容易引起应用程序雪崩或Redis集群发生故障切换。
在Redis集群中,应用程序尽量避免使用大键;直接影响容易导致集群的容量和请求出现”倾斜问题“。但在实际生产过程中,总会有业务使用不合理,出现这类大键;当DBA发现后推进业务优化改造,然后删除这个大键;如果直接删除它,DEL命令可能阻塞Redis进程数十秒,对应用程序和Redis集群可用性造成严重的影响。
生产环境中删除大Key,可能会导致Redis阻塞,出现故障切换和应用程序雪崩的故障。
从4.0版本开始,Redis会支持lazy delete free的方式,删除大键的过程不会阻塞正常请求。
127.0.0.1:6379> HLEN hlazykey (integer) 1000000 127.0.0.1:6379> del hlazykey (integer) 1 (1.36s) 127.0.0.1:6379> SLOWLOG get 1) 1) (integer) 0 2) (integer) 1501314385 3) (integer) 1360908 4) 1) "del" 2) "hlazykey" 5) "127.0.0.1:35595" 6) “" |
lazy free的使用分为2类:第一类是与DEL命令对应的主动删除,第二类是过期key删除、maxmemory key驱逐淘汰删除。
127.0.0.1:7000> LLEN mylist (integer) 2000000 127.0.0.1:7000> UNLINK mylist (integer) 1 127.0.0.1:7000> SLOWLOG get 1) 1) (integer) 1 2) (integer) 1505465188 3) (integer) 30 4) 1) "UNLINK" 2) "mylist" 5) "127.0.0.1:17015" 6) "" |
通过对FLUSHALL/FLUSHDB添加ASYNC异步清理选项,redis在清理整个实例或DB时,操作都是异步的。
127.0.0.1:7000> DBSIZE (integer) 1812295 127.0.0.1:7000> flushall //同步清理实例数据,180万个key耗时1020毫秒 OK (1.02s) 127.0.0.1:7000> DBSIZE (integer) 1812637 127.0.0.1:7000> flushall async //异步清理实例数据,180万个key耗时约9毫秒 OK 127.0.0.1:7000> SLOWLOG get 1) 1) (integer) 2996109 2) (integer) 1505465989 3) (integer) 9274 //指令运行耗时9.2毫秒 4) 1) "flushall" 2) "async" 5) "127.0.0.1:20110" 6) "" |
lazy free应用于被动删除中,目前有4种场景,每种场景对应一个配置参数; 默认都是关闭。
lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no slave-lazy-flush no |
注意:从测试来看lazy free回收内存效率还是比较高的; 但在生产环境请结合实际情况,开启被动删除的lazy free 观察redis内存使用情况。
针对设置有TTL的键,达到过期后,被redis清理删除时是否采用lazy free机制;
此场景建议开启,因TTL本身是自适应调整的速度。
5 lazy free实现的简单分析
antirez为实现lazy free功能,对很多底层结构和关键函数都做了修改;该小节只介绍lazy free的功能实现逻辑;代码主要在源文件lazyfree.c和bio.c中。
UNLINK命令
unlink命令入口函数unlinkCommand()和del调用相同函数delGenericCommand()进行删除KEY操作,使用lazy标识是否为lazyfree调用。如果是lazyfree,则调用dbAsyncDelete()函数。
但并非每次unlink命令就一定启用lazy free,redis会先判断释放KEY的代价(cost),当cost大于LAZYFREE_THRESHOLD才进行lazy free.
释放key代价计算函数lazyfreeGetFreeEffort(),集合类型键,且满足对应编码,cost就是集合键的元数个数,否则cost就是1.
举例:
1一个包含100元素的list key, 它的free cost就是100
2 一个512MB的string key, 它的free cost是1
所以可以看出,redis的lazy free的cost计算主要时间复杂度相关。
lazyfreeGetFreeEffort()函数代码
size_t lazyfreeGetFreeEffort(robj *obj) { if (obj->type == OBJ_LIST) { quicklist *ql = obj->ptr; return ql->len; } else if (obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) { dict *ht = obj->ptr; return dictSize(ht); } else if (obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){ zset *zs = obj->ptr; return zs->zsl->length; } else if (obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) { dict *ht = obj->ptr; return dictSize(ht); } else { return 1; /* Everything else is a single allocation. */ } } |
dbAsyncDelete()函数的部分代码
#define LAZYFREE_THRESHOLD 64 //根据FREE一个key的cost是否大于64,用于判断是否进行lazy free调用 int dbAsyncDelete(redisDb *db, robj *key) { /* Deleting an entry from the expires dict will not free the sds of * the key, because it is shared with the main dictionary. */ if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); //从expires中直接删除key dictEntry *de = dictUnlink(db->dict,key->ptr); //进行unlink处理,但不进行实际free操作 if (de) { robj *val = dictGetVal(de); size_t free_effort = lazyfreeGetFreeEffort(val); //评估free当前key的代价 /* If releasing the object is too much work, let's put it into the * lazy free list. */ if (free_effort > LAZYFREE_THRESHOLD) { //如果free当前key cost>64, 则把它放在lazy free的list, 使用bio子线程进行实际free操作,不通过主线程运行 atomicIncr(lazyfree_objects,1); //待处理的lazyfree对象个数加1,通过info命令可查看 bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL); dictSetVal(db->dict,de,NULL); } } } |
在bio中实际调用lazyfreeFreeObjectFromBioThread()函数释放key
void lazyfreeFreeObjectFromBioThread(robj *o) { decrRefCount(o); //更新对应引用,根据不同类型,调用不同的free函数 atomicDecr(lazyfree_objects,1); //完成key的free,更新待处理lazyfree的键个数 } |
flushall/flushdb async命令
当flushall/flushdb带上async,函数emptyDb()调用emptyDbAsync()来进行整个实例或DB的lazy free逻辑处理。
emptyDbAsync处理逻辑如下:
/* Empty a Redis DB asynchronously. What the function does actually is to * create a new empty set of hash tables and scheduling the old ones for * lazy freeing. */ void emptyDbAsync(redisDb *db) { dict *oldht1 = db->dict, *oldht2 = db->expires; //把db的两个hash tables暂存起来 db->dict = dictCreate(&dbDictType,NULL); //为db创建两个空的hash tables db->expires = dictCreate(&keyptrDictType,NULL); atomicIncr(lazyfree_objects,dictSize(oldht1)); //更新待处理lazyfree的键个数,加上db的key个数 bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,oldht2);//加入到bio list } |
在bio中实际调用lazyfreeFreeDatabaseFromBioThread函数释放db
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, dict *ht2) { size_t numkeys = dictSize(ht1); dictRelease(ht1); dictRelease(ht2); atomicDecr(lazyfree_objects,numkeys);//完成整个DB的free,更新待处理lazyfree的键个数 } |
被动删除键使用lazy free
被动删除4个场景,redis在每个场景调用时,都会判断对应的参数是否开启,如果参数开启,则调用以上对应的lazy free函数处理逻辑实现。
总结
因为Redis是单个主线程处理,antirez一直强调”Lazy Redis is better Redis”。而lazy free的本质就是把某些cost较高的(主要时间复制度,占用主线程cpu时间片)较高删除操作,从redis主线程剥离,让bio子线程来处理,极大地减少主线阻塞时间。从而减少删除导致性能和稳定性问题。
https://blog.csdn.net/zxl2016/article/details/78876016
这篇有些误导 类似3.4版本 支持lazy delete
如何优雅地删除Redis大键相关推荐
- php redis hash删除key,如何优雅的删除Redis的大key
关于Redis大键(Key),我们从[空间复杂性]和访问它的[时间复杂度]两个方面来定义大键.前者主要表示Redis键的占用内存大小:后者表示Redis集合数据类型(set/hash/list/sor ...
- Redis 大键值对 Big Key 懒惰删除机制
一.懒惰删除介绍 在删除元素数量很多的集合(set/hash/list/sortedSet)时,无论是使用DEL命令删除还是redis为了释放内存空间而进行的删除,在删除这些big key的时候,会导 ...
- Redis 过期键删除策略
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:八个开源的 Spring Boot 学习资源,你值得拥有个人原创+1博客:点击前往,查看更多 作者:罗曼蒂克 链 ...
- mysql isreg_`Innodb` MySQL中如何优雅的删除大表跑路
最近很想写写MySQL相关的内容,就从这个话题出发吧 有人说删MySQL表谁不会 不就是 drop table TABLENAME 如果在生产环境中,你对一张TB级别的大表,敲下这行命令 那么你的主管 ...
- mysql数据库truncate 夯住_MySQL如何优雅的删除大表实例详解
前言 删除表,大家下意识想到的命令可能是直接使用DROP TABLE "表名",这是初生牛犊的做法,因为当要删除的表达空间到几十G,甚至是几百G的表时候.这样一条命令下去,MySQ ...
- Redis 过期键删除策略、内存淘汰机制
文章目录 过期键删除策略 定时删除 惰性删除 定期删除 Redis的选择 内存淘汰机制 redis中缓存的数据是有过期时间的,当缓存数据失效时,redis会删除过期数据以节省内存,那redis是怎样怎 ...
- Redis过期键的删除策略
文章目录 立即删除 惰性删除 定时删除 Redis使用的策略 Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间.作为一个缓存数据库,这是非常实用的.如我们一 ...
- 深入理解Redis主键失效原理及实现机制
2019独角兽企业重金招聘Python工程师标准>>> 作为一种定期清理无效数据的重要机制,主键失效存在于大多数缓存系统中,Reids也不例外.在Redis提供的诸多命令中, EXP ...
- java如何保证redis设置过期时间的原子性_redis专题系列22 -- 如何优雅的基于redis实现分布式锁
几个概念 线程锁:主要用来给方法.代码块加锁.当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段.线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比 ...
- Redis——大Key问题
上一篇文章讲了Redis的热点key问题,本篇介绍Redis大Key问题的概念,大Key问题产生的原因.危害以及解决大Key问题的思路: 1. 什么是大key?多大算大key? 其实关于"大 ...
最新文章
- Linux 线程与进程,以及通信
- Python Django 事务管理
- Linux进程缓冲区大小,Linux IOCTL动态命令、动态|自定义缓冲区大小、IOCTL使用大缓冲区的问题...
- linux centos8下载,在Centos8上安装和使用curl
- CSS基础学习 18.CSS多列
- 阿里的Json解析包FastJson使用
- python金融数据分析电子版_python 金融大数据分析 pdf
- Ubuntu下备份系统的方法
- CDC相关知识点总结
- 标准商业计划书大纲模版
- 怎么设置电脑防火墙ping启用_如何通过windows防火墙启用和禁用ping命令
- 三十四个超级经典小故事
- WWF中Conditioned Activity Group的子Activity扩展CAG的WhenCondition属性代码解析
- 英语语法形容词的顺序
- 【数据库】某医院病房计算机管理中需要如下信息: 科室:科名、科地址、科电话、医生姓名 病房:病房号、床位号、所属科室名 医生:姓名、职称、所属科室名、年龄、工作证号 病人:病历号、姓名、性别、诊
- 蓝牙5.0跟蓝牙4.2的区别
- Xftp传输文件发生错误
- COOX基础培训之AM
- 自从我用了这些浏览器插件,工作效率至少提高了一倍,谷歌浏览器插件
- 十四. 四轮车驱动开发之五: 由浅至深理解6轴陀螺仪姿态解算算法<下>
热门文章
- 闲鱼平台API,item_search - 按关键字搜索闲鱼商品
- [Elasticsearch] 邻近匹配 (一) - 短语匹配以及slop参数
- 通用技术金工工艺高考必考必看的知识点含攻丝与套丝讲解
- [转]失业七个月,面试六十家公司的深圳体验(转贴)
- Bootloader的启动
- 【Python学习之七】类和对象
- 数据库应用系统的四个层次划分
- css backdrop-filter和border-radius的坑
- MTD/QTD/YTD 去年同期 同比增长——Power BI
- LeetCode-70.爬楼梯