ITPub博客

首页 > 数据库 > 数据库开发技术 > 重新认识redis(六)

重新认识redis(六)

原创 数据库开发技术 作者:DBA修行之路 时间:2021-09-27 23:30:31 0 删除 编辑




点击上方“蓝字”,发现更多精彩。





重新认识redis(六)

“单"线程

一直以来,聊到redis,大部分人都只知道redis是单线程,但是你是否问过自己下面这些问题:

1.redis单线程具体指的是啥?

2.redis为什么用单线程?

3.单线程redis为什么那么快?


第一个问题,redis的单线程指的啥,redis单线程主要指的是redis对于需要客户端立即反馈的一些操作,例如键值对读操作,由一个主线程来执行完成,另外的一些可以异步进程的操作,例如持久化,异步删除等操作可以由后台子线程执行。所以,一般称呼redis为单线程高性能,实际上redis并不是单线程。

第二个问题,redis为什么使用单线程,的确,在一定范围内,使用多线程的系统可以通过增加系统中处理请求操作的资源实体来提升系统的吞吐率。但是,多线程访问共享资源时为了保证共享资源的正确性需要进行额外的安全保护机制,这会带来额外的开销,随着线程越多,开销也越大,这会降低系统的吞吐率。另外,并发访问控制实现复杂,维护性差,因此,redis舍弃了多线程。

第三个问题,单线程redis为什么那么快,一方面是因为其是内存数据库,所有操作在内存中完成,内存的访问速度本身就占很大的优势。另一方面,redis的数据结构设计非常合理。还有一个方面就是基于多路复用的redis IO模型。

IO多路复用机制

redis使用了linux中的IO多路复用机制,即一个线程处理多个IO流,也就是所谓的select/epoll机制,该机制允许在内核中同时存在多个监听套接字和已连接套接字,内核会持续监听这些套接字上的连接请求或数据请求,当请求到达后后交予redis线程进行处理。如下图基于多路复用的redis高性能IO模型,redis网络框架调用epoll机制,让内核监听FD,不同的FD表示不同的套接字,对应着不同的事件,select/epoll一旦检测到FD上有请求到达,就会触发相应的事件。这些事件会被放入一个事件队列,因此,redis不再需要一直轮询是否有请求发生,有效节约了cpu资源。然后redis调用对应的处理函数对事件进行处理。



操作大比拼

redis实例运行中,有键值对请求操作,持久化操作,数据同步操作等等,这些操作又涉及到对象交互,客户端和服务端交互,主实例与从实例交互等等。而交互的过程中可能会发生阻塞。所以,需要系统的了解下哪些操作可能会发生阻塞,哪些不会。

redis实例整体交互对象可以分为四类,客户端,磁盘,主从节点,切片集群实例。涉及的具体操作之间的关系如下:


客户端交互的操作有网络IO,键值对操作,数据库操作。其中,因为redis使用了Linux的IO多路复用机制,因此网络IO不会导致redis阻塞,但是复杂度高的增删改查操作是会造成redis的阻塞的,例如集合元素全量查询操作HGETALL,集合聚合统计操作等,以及大批量键值对删除的操作等等。还有对数据库的操作,例如FLUSHDB和FLUSHALL操作。

磁盘交互的操作有生成RDB文件,写aof日志以及aof日志重写操作,因为生成RDB文件和aof日志重写操作都是由后台子进程实现的,因此不会阻塞主线程,但是记录AOF日志时候,如果写回策略为同步写时,可能会导致主线程阻塞,因此实际生产中要在性能和数据丢失两者之间权衡来选择合适的写回策略。

主从节点交互的操作有生成RDB快照,RDB传输,加载RDB,以及数据库操作。生成RDB快照及RDB传输由主库子进程完成,不会阻塞主线程,但是从库接收RDB文件后,一方面要加载RDB文件到内存中,另一方面需要执行FLUSHDB命令清空当前数据库,这个会导致redis阻塞,另外,如果加载的RDB文件过大,加载的过程也就越慢,也就自然会对redis主线程造成阻塞。

切片集群交互的操作由哈希槽扩散和数据迁移,一般来说,哈希槽的信息量不大,数据迁移也是渐进式的,对redis主线程的阻塞风险不大,但是如果在redis cluster中迁移bigkey的话们就会造成redis主线程的阻塞。



阻塞点

针对上面的四大对象及十类操作,可以得出以下几个可能阻塞的点:

1.集合全量查询和聚合操作

2.bigkey删除

3.aof日志同步写

4.从库清空数据库

5.从库加载RDB文件

如果redis主线程因为执行这些操作而发生阻塞,从而长时间无法服务其他请求,这在生产上是致命的,因此,redis提供了异步线程机制,即redis会启动一些子线程,然后将一些任务交给这些子线程,让其在后台执行。但是,并不是所有的操作都能异步执行,如果客户端立即需要请求的具体结果,则无法进行异步执行,例如读请求,客户端发送请求后就需要准确的数据返回,而删除请求,只需要先反馈客户端一个结果就行,然后便可以在后台由至子线程进行删除操作完成。同理,清空数据库,aof日志同步写及从库加载RDB文件也类似。总结来说,就是除了集合全量查询和聚合操作,从库加载RDB文件,其他可能阻塞的点都可以通过异步子线程机制来避免。



异步机制

redis异步子线程机制中,redis主线程启动后,会使用操作系统提供的pthread_create函数创建3个子线程,分别负责aof日志写操作,键值对删除操作,文件关闭操作的异步执行。


在aof日志写操作中,如果配置中配置成every选项或其他非同步选项时,主线程会将aof写日志操作封装成一个任务,放入到任务队列中,然后后台子线程读取任务后自动执行相应操作。

在键值对删除操作中,从redis4.0开始,redis支持异步的键值对和数据库清空操作,键值对删除对应的命令是unlink命令,建议当集合类型中有大量元素需要删除时可以使用该命令,清空数据库对应的命令是flushdb async和flushall async命令。这种删除亦称作惰性删除。


基本流程如下:



自问自答

Q:
删除操作阻塞主线程?

redis中,主从集群一般是采用读写分离的模式,当主库发生故障后,客户端依旧可以将读请求发送给从库,对于已同步的数据,读请求可以正常响应,但是部分数据可能未来得及同步,如果这部分数据是新写数据,读请求是会失败的,如果这部分数据是更新的数据,则读请求会返回旧值。对于写请求,一般是无法响应的。

Q:
什么时候可以异步执行?

redis中操作的命令是否可以异步执行主要取决于请求对客户端返回结果的的关心程度,例如,如果进行某个键值对值的查询,如果命令没有返回结果,客户端就会一直等待,直到返回结果,因此不能异步,如果进行某个键值对的写入操作,如果客户端不关心是否写成功或者可以先返回值为ok,则此时就可以异步操作。

Q:
子进程vs子线程?

子线程和子进程都独立于父进程,共享父进程的资源,但是启动一个线程需要的资源消耗小于进程,系统在运行时会为每个进程分配不同的内存区域,但是不会为线程分配内存。这一块,后续深入学习操作系统层面再细化吧。


结尾小结

最近学习的内容是redis的单线程机制以及redis常见操作,并对这些常见操作中可能的阻塞点及redis提供的解决方法进行学习总结,记录笔记。就先到这吧,最近感觉要学的东西太多了,好多知识点看了几遍还是感觉理解不够,等吃透了再汇总吧。




END



扫码关注我
每天进步一点点,未来才可期,我们共同的目标是星辰大海。

赞一下你最美!



原文作者:DBA修行之路;如有侵权,请联系管理员删除。

上一篇: 重新认识redis(七)
下一篇: 重新认识redis(五)
请登录后发表评论 登录
全部评论

注册时间:2021-05-25

  • 博文量
    64
  • 访问量
    23946