Redis单线程和多线程 - Go语言中文社区

Redis单线程和多线程


Redis单线程

Reids是单线程!
Reids是单线程!
Reids是单线程!

Redis架构模型:Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型即文件事件处理器

文件事件处理器(file event handler)

主要是包含 4 个部分:

  • 多个 socket(客户端连接)
  • IO 多路复用程序(支持多个客户端连接的关键)
  • 文件事件分派器(将 socket 关联到相应的事件处理器)
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

在这里插入图片描述

IO 多路复用程序(非堵塞)

堵塞:一个线程一次只能处理一个。

select和poll和epoll同一时间一个线程可以处理多个,所以不是堵塞。

含义:I/O 指的是网络I/O。
  多路指的是多个TCP 连接(Socket 或Channel)。
“复用”指的是复用同一个线程 。

作用:Redis 通过IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket)

优点:I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)

过程

  • I/O多路复用程序,负责监听多个套接字,按照处理顺序,将套接字存放在一个队列中。
  • 套接字队列以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。
  • 当上一个套接字产生的事件被处理完毕之后, I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。

select(同步非堵塞)

数据类型: fd_set就是一个long int类型的数组。因为每一位可以代表一个文件描述符。所以fd_set最多表示1024个文件描述符

调用过程:

select 是操作系统提供的系统调用函数,select 只能监听 1024 个文件描述符,通过它,我们可以把一个文件描述符的数组发给操作系统, 让操作系统去遍历,确定哪个文件描述符可以读写, 然后告诉我们去处理,不过,当 select 函数返回后,用户依然需要遍历刚刚提交给操作系统的 list。

只不过,操作系统会将准备就绪的文件描述符做上标识,用户层将不会再有无意义的系统调用开销。

问题

  • 最多只能管理 1024 个fd(文件描述符)
  • 很多fd的没有就绪或超时,而且短时间内也完不成的话, 还是会不停的去遍历问询,影响效率。

poll(同步非堵塞)

数据结构: pollfd 结构

**作用:**它和 select 的主要区别就是,去掉了 select 只能监听 1024 个文件描述符的限制。

epoll(异步非堵塞)

数据结构:链表加红黑树

链表存储:活跃连接,红黑树存储:socket节点

链表中为活跃的链接,红黑树中放的是所有事件。这样一来,当收到内核的数据时,只需遍历链表中的数据就行了,而注册read事件或者write事件的时候,向红黑树中记录。

调用过程:

  • epoll_create() 系统启动时,在 Linux 内核里创建 epoll 实例(申请一个红黑树 rbTree 和就绪链表 readyList),以便存放 socket 节点;

  • epoll_ctl() 每新建一个连接,都通过该函数操作 epoll 对象,在这个对象的红黑树里增、删、改对应的 socket 节点,绑定一个回调函数;

  • epoll_wait() 阻塞唤醒,只遍历一次,之后等待事件异步唤醒数据传入,并完成对应的 IO 操作。相应分三步:

    • 阻塞线程
    • 传入数据,内核查找红黑树中准备好的 socket,放入就绪链表 rdlist(过程是异步io,当数据准备好之后,系统自动返回,而不是一遍一遍select轮询查看数据是否准备好)
    • 就绪列表中的内容复制到 events(从内核态复制到用户态),准备循环处理这些已就绪的 socket 节点

改进:

  1. 内核中保存一份文件描述符集合,无需用户每次都重新传入,只需告诉内核修改的部分即可。

  2. 内核不再通过轮询的方式找到就绪的文件描述符,而是通过异步 IO 事件唤醒。

  3. 内核仅会将有 IO 事件的文件描述符返回给用户,用户也无需遍历整个文件描述符集合。

在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。

如果没有I/O事件产生,我们的程序就会阻塞在select处。有个问题,我们从select仅知道了有I/O事件发生了,但却不知是哪几个流,只能无差别轮询所有流,找出能读或写数据的流进行操作。

使用select,O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。

在这里插入图片描述

Redis 为什么不使用多线程?

原因有下面 3 个:

  1. Redis 的性能瓶颈不再 CPU ,主要在内存和网络io;
  2. 单线程编程容易并且更容易维护;
  3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。

Redis6.0之前的版本真的是单线程吗?

Redis在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。但如果严格来讲从Redis4.0之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等。

Redis6.0 之后为何引入了多线程?

Redis6.0 引入多线程主要是为了提高网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。

Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

• 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

• 使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

• 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

• 多线程任务可以分摊 Redis 同步 IO 读写负荷

Redis6.0默认是否开启了多线程?

Redis6.0的多线程默认是禁用的,只使用主线程。

Redis多线程参考:https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw
参考:https://blog.csdn.net/weixin_35973945/article/details/124153253
https://blog.csdn.net/lmx125254/article/details/118935332

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/mysnsds/article/details/126393563
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2023-01-02 20:40:40
  • 阅读 ( 184 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢