为什么 Redis 单线程能达到百万+QPS?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 性能测试报告查看了下阿里 Redis 的性能测试报告如下,能够达到数十万、百万级别的 QPS(暂时忽略阿里对 Redis 所做的优化),我们从 Redis 的设计和实现来分析一下 Redis 是怎么做的。

作者:在江湖中coding https://juejin.im/post/5e6097846fb9a07c9f3fe744


性能测试报告


查看了下阿里 Redis 的性能测试报告如下,能够达到数十万、百万级别的 QPS(暂时忽略阿里对 Redis 所做的优化),我们从 Redis 的设计和实现来分析一下 Redis 是怎么做的。

image.png

Redis的设计与实现


其实 Redis 主要是通过三个方面来满足这样高效吞吐量的性能需求


高效的数据结构


多路复用 IO 模型


事件机制


1、高效的数据结构

Redis 支持的几种高效的数据结构 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)


以上几种对外暴露的数据结构它们的底层编码方式都是做了不同的优化的,不细说了,不是本文重点。


2、多路复用 IO 模型

假设某一时刻与 Redis 服务器建立了 1 万个长连接,对于阻塞式 IO 的做法就是,对每一条连接都建立一个线程来处理,那么就需要 1万个线程,同时根据我们的经验对于 IO 密集型的操作我们一般设置,线程数 = 2 * CPU 数量 + 1,对于 CPU 密集型的操作一般设置线程 = CPU 数量 + 1。


当然各种书籍或者网上也有一个详细的计算公式可以算出更加合适准确的线程数量,但是得到的结果往往是一个比较小的值,像阻塞式 IO 这也动则创建成千上万的线程,系统是无法承载这样的负荷的更加弹不上高效的吞吐量和服务了。


而多路复用 IO 模型的做法是,用一个线程将这一万个建立成功的链接陆续的放入 event_poll,event_poll 会为这一万个长连接注册回调函数,当某一个长连接准备就绪后(建立建立成功、数据读取完成等),就会通过回调函数写入到 event_poll 的就绪队列 rdlist 中,这样这个单线程就可以通过读取 rdlist 获取到需要的数据。为什么 Redis 是单线程,点击这里看下这篇文章。


另外,大家可以关注微信公众号:Java技术栈,在后台回复:redis,可以获取我整理的 N 篇 Redis 教程,都是干货。


需要注意的是,除了异步 IO 外,其它的 I/O 模型其实都可以归类为阻塞式 I/O 模型,不同的是像阻塞式 I/O 模型在第一阶段读取数据的时候,如果此时数据未准备就绪需要阻塞,在第二阶段数据准备就绪后需要将数据从内核态复制到用户态这一步也是阻塞的。而多路复用 IO 模型在第一阶段是不阻塞的,只会在第二阶段阻塞。


通过这种方式,就可以用 1 个或者几个线程来处理大量的连接了,极大的提升了吐吞量


image.png


3、事件机制


Redis 客户端与 Redis 服务端建立连接,发送命令,Redis 服务器响应命令都是需要通过事件机制来做的,如下图

image.png



首先 redis 服务器运行,监听套接字的 AE_READABLE 事件处于监听的状态下,此时连接应答处理器工作


客户端与 Redis 服务器发起建立连接,监听套接字产生 AE_READABLE 事件,当 IO 多路复用程序监听到其准备就绪后,将该事件压入队列中,由文件事件分派器获取队列中的事件交于连接应答处理器工作处理,应答客户端建立连接成功,同时将客户端 socket 的 AE_READABLE 事件压入队列由文件事件分派器获取队列中的事件交命令请求处理器关联


客户端发送 set key value 请求,客户端 socket 的 AE_READABLE 事件,当 IO 多路复用程序监听到其准备就绪后,将该事件压入队列中,由文件事件分派器获取队列中的事件交于命令请求处理器关联处理


命令请求处理器关联处理完成后,需要响应客户端操作完成,此时将产生 socket 的 AE_WRITEABLE 事件压入队列,由文件事件分派器获取队列中的事件交于命令恢复处理器处理,返回操作结果,完成后将解除 AE_WRITEABLE 事件与命令恢复处理器的关联


reactor模式


大体上可以说 Redis 的工作模式是,reactor 模式配合一个队列,用一个 serverAccept 线程来处理建立请求的链接,并且通过 IO 多路复用模型,让内核来监听这些 socket,一旦某些 socket 的读写事件准备就绪后就对应的事件压入队列中,然后 worker 工作,由文件事件分派器从中获取事件交于对应的处理器去执行,当某个事件执行完成后文件事件分派器才会从队列中获取下一个事件进行处理。


可以类比在 netty 中,我们一般会设置 bossGroup 和 workerGroup 默认情况下 bossGroup 为 1,workerGroup = 2 * cpu 数量,这样可以由多个线程来处理读写就绪的事件,但是其中不能有比较耗时的操作如果有的话需要将其放入线程池中,不然会降低其吐吞量。在 Redis 中我们可以看做这二者的值都是 1。


为什么说存储的值不宜过大


比如一个 string key = a,存储了 500MB,首先读取事件压入队列中,文件事件分派器从中获取到后,交于命令请求处理器处理,此处就涉及到从磁盘中加载 500MB。


比如是普通的 SSD 硬盘,读取速度 200MB/S,那么需要 2.5S 的读取时间,在内存中读取数据比较快比如 DDR4 中 50G/秒,读取 500MB 需要 100 毫秒左右。


线程的库一般默认 10 毫秒就算慢查询了,大部分的指令执行时间都是微秒级别,此时其它 socket 所有的请求都将处于等待过程中,就会导致阻塞了 100 毫秒,同时又会占用较大的带宽导致吞吐量进一步下降。


相关实践学习
基于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
相关文章
|
7月前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
63 3
|
7月前
|
NoSQL Redis 缓存
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
【5月更文挑战第17天】Redis常被称为单线程,但实际上其在处理命令时采用单线程,但在6.0后IO变为多线程。持久化和数据同步等任务由额外线程处理,因此严格来说Redis是多线程的。面试时需理解Redis的IO模型,如epoll和Reactor模式,以及其内存操作带来的高性能。Redis使用epoll进行高效文件描述符管理,实现高性能的网络IO。在讨论Redis与Memcached的线程模型差异时,应强调Redis的单线程模型如何通过内存操作和高效IO实现高性能。
102 7
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?
|
7月前
|
NoSQL 网络协议 Linux
Redis单线程源码深入解析
Redis单线程源码深入解析
|
7月前
|
NoSQL 网络协议 关系型数据库
redis-学习笔记(redis 单线程模型)
redis-学习笔记(redis 单线程模型)
63 3
|
7月前
|
缓存 NoSQL Redis
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?--epoll调用和中断
【5月更文挑战第18天】`epoll`包含红黑树和就绪列表,用于高效管理文件描述符。关键系统调用有3个:`epoll_create()`创建epoll结构,`epoll_ctl()`添加/删除/修改文件描述符,`epoll_wait()`获取就绪文件描述符。`epoll_wait()`可设置超时时间(-1阻塞,0立即返回,正数等待指定时间)。当文件描述符满足条件(如数据到达)时,通过中断机制(如网卡或时钟中断)更新就绪列表,唤醒等待的进程。
80 6
|
6月前
|
NoSQL Redis
Redis的单线程和高性能
Redis 的单线程主要是指 Redis 的网络 I0 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。 但Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
32 0
|
7月前
|
NoSQL 关系型数据库 MySQL
Redis -- 单线程模型
Redis -- 单线程模型
72 1
|
7月前
|
缓存 NoSQL 中间件
【后端面经】【缓存】36|Redis 单线程:为什么 Redis 用单线程而 Memcached 用多线程?epoll、poll和select + Reactor模式
【5月更文挑战第19天】`epoll`、`poll`和`select`是Linux下多路复用IO的三种方式。`select`需要主动调用检查文件描述符,而`epoll`能实现回调,即使不调用`epoll_wait`也能处理就绪事件。`poll`与`select`类似,但支持更多文件描述符。面试时,重点讲解`epoll`的高效性和`Reactor`模式,该模式包括一个分发器和多个处理器,用于处理连接和读写事件。Redis采用单线程模型结合`epoll`的Reactor模式,确保高性能。在Redis 6.0后引入多线程,但基本原理保持不变。
71 2
QGS
|
7月前
|
NoSQL 关系型数据库 MySQL
手拉手Springboot+RocketMQ+Redis抢单实现10W级QPS
手拉手Springboot+RocketMQ+Redis抢单实现10W级QPS
QGS
111 3
|
7月前
|
存储 缓存 NoSQL
为什么Redis使用单线程 性能会优于多线程?
在计算机领域,性能一直都是一个关键的话题。无论是应用开发还是系统优化,我们都需要关注如何在有限的资源下,实现最大程度的性能提升。Redis,作为一款高性能的开源内存数据库,因其出色的单线程性能而备受瞩目。那么,为什么Redis使用单线程性能会优于多线程呢?
77 1