当Redis执行删除命令的时候,如果被删除的对象是列表、集合、散列类型,因为这些数据类型包含的元素存放在不同的内存块中,redis需要遍历所有元素来释放其对应的内存块空间,这个耗时操作可能导致redis阻塞,redis4提供的UNLINK命令可以实现非阻塞删除(Redis4引入了后台线程)。
UNLINK删除命令流程:
- 首先检查过期字典中是否包含该健,有则先从过期字典中删除
- 将该健在从过期字典中删除的返回键值对,这时并没有删除键值对对象
- 计算该键值对的值对象占用的字节数
- 若该值对象的字节数大于LAZYFREE_THRESHOLD(64字节),且该值对象只被当前一处引用则执行如下操作,实现非阻塞删除:
- 创建一个后台任务负责删除值对象(后台线程处理)
- 将该键值对的值对象引用设置为NULL,保证主线程访问不到
- 删除值对象,并释放其内存空间
非阻塞删除的场景下,后台线程负责删除值对象,主进程负责处理用户请求,后台线程后续删除值对象时并不需要进行线程同步操作(引用为null,主进程访问不到)。
后台线程的实现
- Redis有3类后台任务:文件关闭、磁盘同步和非阻塞删除:
- 磁盘同步策略若是每秒执行一次,则是后台线程负责
- AOF重写时,会用后台线程关闭临时文件
- 设置线程栈大小(避免有些线程栈太小无法处理任务)
- 创建后台线程,指定该线程负责的任务类型
- 线程创建以后抢占该任务的互斥量,再将该任务添加到对应的任务队列中
- 首先抢占该任务类型的互斥量
- 检查任务队列待处理任务是否为空,若为空,则阻塞当前线程
- 若队列中存在待处理的任务,获取一个任务,并释放互斥量
- 根据任务类型执行对应的处理逻辑,执行任务
- 重新抢占互斥量,并删除任务
- 处理任务,最后释放互斥量
- 线程创建以后抢占该任务的互斥量,再将该任务添加到对应的任务队列中