一文搞懂 Redis高性能之IO多路复用

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 相信大家在面试过程中经常会被问到:“单线程的Redis为啥这么快?”哈哈,反正我在面试时候经常会问候选人这个问题,这个问题其实是对redis内部机制的一个考察,可以牵扯出好多涉及底层深入原理的一些列问题。

微信图片_20220607203036.jpg


相信大家在面试过程中经常会被问到:“单线程的Redis为啥这么快?


哈哈,反正我在面试时候经常会问候选人这个问题,这个问题其实是对redis内部机制的一个考察,可以牵扯出好多涉及底层深入原理的一些列问题。


回到问题本身,基本的回答就两点:


  • 完全基于内存


  • IO多路复用


1、关于第1点比较好理解。Redis 绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,查找和操作的时间复杂度都是O(1)。


2、关于第2点IO多路复用,有些同学看到概念后感觉一头雾水,到底什么是IO多路复用?本文从IO并发性能提升来整体思考,来逐步剖析IO多路复用的原理。


一、如何快速理解IO多路复用?


那得从IO并发性能提升来考虑:


  • 多进程


  • 多线程


  • 基于单进程的IO多路复用(select/poll/epoll)

 

多进程


对于并发情况,假如一个进程不行,那搞多个进程不就可以同时处理多个客户端连接了么?


多进程这种方式的确可以解决了服务器在同一时间能处理多个客户端连接请求的问题,但是仍存在一些缺点:


  • fork()等系统调用会使得进程上下文进行切换,效率较低


  • 进程创建的数量随着连接请求的增加而增加。比如10w个请求,就要fork 10w个进程,开销太大


  • 进程与进程之间的地址空间是私有、独立的,使得进程之间的数据共享变得困难

 

多线程


线程是运行在进程上下文的逻辑流,一个进程可以包含多个线程,多个线程运行在同一进程上下文中,因此可共享这个进程地址空间的所有内容,解决了进程与进程之间通信难的问题。


同时,由于一个线程的上下文要比一个进程的上下文小得多,所以线程的上下文切换,要比进程的上下文切换效率高得多。

 

IO多路复用


简单理解就是:一个服务端进程可以同时处理多个套接字描述符。


  • 多路多个客户端连接(连接就是套接字描述符)


  • 复用使用单进程就能够实现同时处理多个客户端的连接


以上是通过增加进程和线程的数量来并发处理多个套接字,免不了上下文切换的开销,而IO多路复用只需要一个进程就能够处理多个套接字,从而解决了上下文切换的问题。


其发展可以分select->poll→epoll三个阶段来描述。


二、如何简单理解select/poll/epoll呢?


按照以往惯例,还是联系一下我们日常中的现实场景,这样更助于大家理解。

举栗说明:


领导分配员工开发任务,有些员工还没完成。如果领导要每个员工的工作都要验收check,那在未完成的员工那里,只能阻塞等待,等待他完成之后,再去check下一位员工的任务,造成性能问题。


那如何解决这个问题呢?


select


举栗说明:


领导找个Team Leader(后文简称TL),负责代自己check每位员工的开发任务。


TL 的做法是:遍历问各个员工“完成了么?”,完成的待CR check无误后合并到Git分支,对于其他未完成的,休息一会儿后再去遍历....


这样存在什么问题呢?


  • 这个TL存在能力短板问题,最多只能管理1024个员工


  • 很多员工的任务没有完成,而且短时间内也完不成的话,TL还是会不停的去遍历问询,影响效率。


select函数:

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);


select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。


select具有良好的跨平台支持,其缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024。

 

poll


举栗说明:


换一个能力更强的New Team Leader(后文简称NTL),可以管理更多的员工,这个NTL可以理解为poll。


poll函数:


intpoll(structpollfd*fds, nfds_t nfds,int timeout);
typedef struct pollfd{ 
int fd; // 需要被检测或选择的文件描述符 
short events; // 对文件描述符fd上感兴趣的事件 
short revents; // 文件描述符fd上当前实际发生的事件
} pollfd_t;


poll改变了文件描述符集合的描述方式,使用了pollfd结构而不是select的fd_set结构,使得poll支持的文件描述符集合限制远大于select的1024。

 

epoll


举栗说明:


在上一步poll方式的NTL基础上,改进一下NTL的办事方法:遍历一次所有员工,如果任务没有完成,告诉员工待完成之后,其应该做xx操作(制定一些列的流程规范)。这样NTL只需要定期check指定的关键节点就好了。这就是epoll。


Linux中提供的epoll相关函数如下:

intepoll_create(int size);
intepoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
intepoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);


epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

 

小结


  • select就是轮询,在Linux上限制个数一般为1024个


  • poll解决了select的个数限制,但是依然是轮询


  • epoll解决了个数的限制,同时解决了轮询的方式


三、IO多路复用在Redis中的应用


Redis 服务器是一个事件驱动程序, 服务器处理的事件分为时间事件和文件事件两类。


  • 文件事件Redis主进程中,主要处理客户端的连接请求与相应。


  • 时间事件fork出的子进程中,处理如AOF持久化任务等。


由于Redis的文件事件是单进程,单线程模型,但是确保持着优秀的吞吐量,IO多路复用起到了主要作用。


文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答、写入、读取、关闭等操作时,就会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。


IO多路复用程序负责监听多个套接字并向文件事件分派器传送那些产生了事件的套接字。文件事件分派器接收IO多路复用程序传来的套接字,并根据套接字产生的事件的类型,调用相应的事件处理器。示例如图所示:

微信图片_20220607203039.jpg


文件处理器的四个组成部分


Redis的IO多路复用程序的所有功能都是通过包装常见的select、poll、evport和kqueue这些IO多路复用函数库来实现的,每个IO多路复用函数库在Redis源码中都有对应的一个单独的文件。


Redis为每个IO多路复用函数库都实现了相同的API,所以IO多路复用程序的底层实现是可以互换的。如图:

微信图片_20220607203042.jpg


多个IO复用库实现可选



Redis把所有连接与读写事件、还有我们没提到的时间事件一起集中管理,并对底层IO多路复用机制进行了封装,最终实现了单进程能够处理多个连接以及读写事件。这就是IO多路复用在redis中的应用。


四、总结


Redis 6.0 之后的版本开始选择性使用多线程模型。


Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;


而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。


凡事不能有绝对,寻找到适中的平衡点最重要!

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
89 0
|
1月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
80 1
Linux C/C++之IO多路复用(aio)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
74 6
|
3月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
71 0
|
1月前
|
设计模式 NoSQL 网络协议
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
大数据-48 Redis 通信协议原理RESP 事件处理机制原理 文件事件 时间事件 Reactor多路复用
37 2
|
1月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
23 0
Linux C/C++之IO多路复用(poll,epoll)
|
1月前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
45 2
|
1月前
|
监控 网络协议 Java
IO 多路复用? 什么是 IO 多路复用? 简单示例(日常生活)来解释 IO 多路复用 一看就懂! 大白话,可爱式(傻瓜式)教学! 保你懂!
本文通过日常生活中的简单示例解释了IO多路复用的概念,即一个线程通过监控多个socket来处理多个客户端请求,提高了效率,同时介绍了Linux系统中的select、poll和epoll三种IO多路复用的API。
110 2
|
2月前
|
存储 缓存 NoSQL
Redis的高性能之谜
Redis的高性能之谜
41 5
|
2月前
|
消息中间件 NoSQL Java
面试官:谈谈你对IO多路复用的理解?
面试官:谈谈你对IO多路复用的理解?
46 0
面试官:谈谈你对IO多路复用的理解?