前言

之前我们介绍了Redis懒惰删除的特性,它是使用异步线程对已经删除的节点进行延后内存回收。但是还不够深入,所以本节我们要对异步线程逻辑处理的细节进行分析,看看Antirez是如何实现异步线程处理的。异步线程在Redis内部有一个特别的名称,它就是BIO,全称是Background IO,意思是在背后默默干活的IO线程。不过内存回收本身并不是什么IO操作,只是CPU的计算消耗可能会比较大而已。


懒惰删除的最初实现不是异步线程

Antirez实现懒惰删除时,它并不是一开始就想到了异步线程。最初的尝试是使用类似于字典渐进式搬迁那样来实现渐进式删除回收,在主线程里。比如对于一个非常大的字典来说,懒惰删除是采用类似于scan操作的方法,通过遍历第一维数组来逐步删除回收第二维链表的内容,等到所有链表都回收完了,再一次性回收第一维数组。这样也可以达到删除大对象时不阻塞主线程的效果。

但是说起来容易做起来却很难,渐进式回收需要仔细控制回收频率,它不能回收的太猛,这会导致CPU资源占用过多,也不能回收的蜗牛慢,内存回收不及时可能导致内存持续增长。Antirez需要采用合适的自适应算法来控制回收频率。他首先想到的是检测内存增长的趋势是增长(+1)还是下降(-1)来渐进式调整回收频率系数,这样的自适应算法实现也很简单。但是测试后发现在服务繁忙的时候,QPS会下降到正常情况下65%的水平,这点非常致命。

所以Antirez才使用了如今使用的方案——异步线程,这套方案就简单多了,释放内存不用为每种数据结构适配一套渐进式释放策略,也不用搞个自适应算法来仔细控制回收频率。将对象从全局字典中摘掉,然后往队列里一扔,主线程就去干别的去了。异步线程从队列里取出对象来,直接走正常的同步释放逻辑就可以了。

不过使用异步线程也是有代价的,主线程和异步线程之间在内存回收器(jemalloc)的使用上存在竞争。这点竞争消耗是可以忽略不计的,因为Redis的主线程在内存的分配与回收上花的时间相对整体运算时间而言是极少的。

异步线程方案其实也相当复杂

刚才上面说异步线程方案很简单,为什么这里又说它很复杂呢?因为有一点我们没有想到,这点非常可怕,严重阻碍了异步线程方案的改造。那就是Redis的内部对象有共享机制。

比如集合的并集操作sunionstore用来将多个集合合并成一个新集合

我们看到新的集合包含了旧集合的所有元素。但是这里有一个我们没看到的trick。那就是底层的字符串对象被共享了。

为什么对象共享是懒惰删除的巨大障碍呢?因为懒惰删除相当于彻底砍掉某个树枝,将它扔到异步删除队列里去。注意这里必须是彻底删除,而不能藕断丝连。如果底层对象是共享的,那就做不到彻底删除。

所以antirez为了支持懒惰删除,将对象共享机制彻底抛弃,它将这种对象结构称为「share-nothing」,也就是无共享设计。但是甩掉对象共享谈何容易!这种对象共享机制散落在源代码的各个角落,牵一发而动全身,改起来犹如在布满地雷的道路上小心翼翼地行走。

不过antirez还是决心改了,他将这种改动描述为「绝望而疯狂」,可见改动之大之深之险,前后花了好几周的时间才改完。不过效果也是很明显的,对象的删除操作再也不会导致主线程卡顿了。

异步删除的实现

主线程需要将删除任务传递给异步线程,它是通过一个普通的双向链表来传递的。因为链表需要支持多线程并发操作,所以它需要有锁来保护。

执行懒惰删除时,redis将删除操作的相关参数封装成一个bio_job结构,然后追加到链表尾部。异步线程通过遍历链表摘取job元素来挨个执行异步任务。

我们注意到这个job结构有三个参数,为什么删除对象需要三个参数呢?我们继续看代码

可以看到通过组合这三个参数可以实现不同结构的释放逻辑。接下来我们继续追踪普通对象的异步删除lazyfreeFreeObjectFromBioThread是如何进行的,请仔细阅读代码注释



这些代码散落在多个不同的文件,我将它们凑到了一块便于读者阅读。从代码中我们可以看到释放一个对象要深度调用一系列函数,每种对象都有它独特的内存回收逻辑。

队列安全

前面提到任务队列是一个不安全的双向链表,需要使用锁来保护它。当主线程将任务追加到队列之前它需要加锁,追加完毕后,再释放锁,还需要唤醒异步线程,如果它在休眠的话。

异步线程需要对任务队列进行轮训处理,依次从链表表头摘取元素逐个处理。摘取元素的时候也需要加锁,摘出来之后再解锁。如果一个元素的没有,它需要等待,直到主线程来唤醒它继续工作。

研究完这些加锁解锁的代码后,我开始有点当心主线程的性能。我们都知道加锁解锁是一个相对比较耗时的操作,尤其是悲观锁最为耗时。如果删除很频繁,主线程岂不是要频繁加锁解锁。所以这里肯定还有优化空间,Java的ConcurrentLinkQueue就没有使用这样粗粒度的悲观锁,它优先使用cas来控制并发。

思考

Redis还有其它地方用到了对象共享机制么?

Java的ConcurrentLinkQueue具体是如何实现的?

欢迎工作一到五年的Java工程师朋友们加入Java爬坑之路:860113481

群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

转载于:https://blog.51cto.com/13732225/2156807

如履薄冰 —— Redis懒惰删除的巨大牺牲相关推荐

  1. 如履薄冰:Redis 懒惰删除的巨大牺牲

    作者 | 老钱 责编 | 胡巍巍 大家都知道 Redis 是单线程的,但是 Redis 4.0 增加了懒惰删除功能,懒惰删除需要使用异步线程对已删除的节点进行内存回收,这意味着 Redis 底层其实并 ...

  2. 【Redis】懒惰删除

    一直以来我们认为 Redis 是单线程的,单线程为 Redis 带来了代码的简洁性和丰富多样的数据结构.不过Redis内部实际上并不是只有一个主线程,它还有几个异步线程专门用来处理一些耗时的操作. R ...

  3. Redis之懒惰删除

    Redis内部有一个异步线程叫做BIO(background io),懒惰删除的内存回收就是使用这个线程异步去做的. 非异步回收 redis最开始实现懒惰删除时,并不是在异步线程里做,而是在异步线程里 ...

  4. 关于 Redis 的 懒惰删除 (十三)

    平波缓进 -- 懒惰删除 一直以来我们认为 Redis 是单线程的,单线程为 Redis 带来了代码的简洁性和丰富多样的数据结构.不过Redis内部实际上并不是只有一个主线程,它还有几个异步线程专门用 ...

  5. Redis 大键值对 Big Key 懒惰删除机制

    一.懒惰删除介绍 在删除元素数量很多的集合(set/hash/list/sortedSet)时,无论是使用DEL命令删除还是redis为了释放内存空间而进行的删除,在删除这些big key的时候,会导 ...

  6. Redis 惰性删除

    文章目录 前言 lazy free flush 异步队列 AOF Sync 更多异步删除点 前言 Redis 虽然是单线程的,但是其内部实际上并不是只有一个主线程,它还有几个异步线程专门用来处理一些耗 ...

  7. 为什么集群要奇数_面试系列 redis数据删除amp;集群

    redis数据删除/内存淘汰 如果我们设置一批key只能存活1小时,那么1小时之后,redis是怎么对这批数据进行删除的? 答案:定期删数+惰性删除 (1)定期删除 指的是redis默认是每隔100m ...

  8. redis批量删除key

    批量删除Key Redis 中有删除单个 Key 的指令 DEL,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux 的 xargs 指令来完成这个动作 1 2 3 redis-cli ...

  9. Redis淘汰删除策略

    Redis淘汰删除策略 Redis淘汰删除策略6种淘汰Key策略3种删除过期键策略定时删除惰性删除定期删除其他模块的淘汰处理RDB 快照持久化创建载入AOF 只追加持久化写入重写主从复模式下对过期键的 ...

最新文章

  1. Sudo 漏洞隐患不断,macOS 也受牵连!
  2. 用户信号量及其PV操作处理实际问题
  3. 字符串中统计单词个数
  4. IBM公司扩展云平台 计划推出SmartCloud
  5. gis 大屏_gis大屏可视化应用技术方案_gis大屏可视化应用技术_gis大屏可视化应用 - 帆软...
  6. 域名恶意指向的问题解决
  7. 創建oracle用戶及表空間,window,linux下創建oracle用戶及表空間 對比 易於學習
  8. 32位Windows系统未分页内存限制导致的VPS的容量问题
  9. Windows 7系统快捷键汇总
  10. 用c语言运行Linux命令,使用execv(C语言)从linux命令提示符运行命令
  11. leetcode971. Flip Binary Tree To Match Preorder Traversal
  12. HTTP请求解析过程 (简单概括)
  13. ubuntu安装Arial.ttf字体
  14. 7.1 php7.0 微擎_php7.1以上微擎-人人商城小程序授权登录问题
  15. vue仿微博评论回复_js模拟回帖/微博评论功能案例
  16. uni-app弹窗多选样式分享
  17. 苹果手机免越狱群控电脑端控制手机
  18. 搭建springboot+mybatis+freemarker项目
  19. 从play store下载apk
  20. 【开发随记】【提效】工作习惯那些事系列之一——To-Do List

热门文章

  1. php 截图插件,react中有实现截图插件吗
  2. 浙江大学远程教育计算机应用基础,浙江大学远程教育计算机应用基础.pdf
  3. 巴特沃斯滤波器 python_巴特沃斯、切比雪夫、贝塞尔滤波器的区别
  4. python嵌套列表操作方法_python中多层嵌套列表的拆分方法
  5. stm32双向可控硅调压程序_双向可控硅的工作原理
  6. Notepad++ 配置 Markdown
  7. 19软件班专业英语学期总结
  8. 安卓学习笔记01:安装集成开发环境Android Studio
  9. 大数据学习笔记38:Hive - 内置函数(1)
  10. 《天天数学》连载21:一月二十一日