高性能IO模型:为什么单线程Redis能那么快?( 三 )
而且 , 采用多线程开发一般会引入同步原语来保护共享资源的并发访问 , 这也会降低系统代码的易调试性和可维护性 。 为了避免这些问题 , Redis直接采用了单线程模式 。
讲到这里 , 你应该已经明白了“Redis为什么用单线程” , 那么 , 接下来 , 我们就来看看 , 为什么单线程Redis能获得高性能 。
单线程Redis为什么那么快?
通常来说 , 单线程的处理能力要比多线程差很多 , 但是Redis却能使用单线程模型达到每秒数十万级别的处理能力 , 这是为什么呢?其实 , 这是Redis多方面设计选择的一个综合结果 。
一方面 , Redis的大部分操作在内存上完成 , 再加上它采用了高效的数据结构 , 例如哈希表和跳表 , 这是它实现高性能的一个重要原因 。 另一方面 , 就是Redis采用了多路复用机制 , 使其在网络IO操作中能并发处理大量的客户端请求 , 实现高吞吐率 。 接下来 , 我们就重点学习下多路复用机制 。
首先 , 我们要弄明白网络操作的基本IO模型和潜在的阻塞点 。 毕竟 , Redis采用单线程进行IO , 如果线程被阻塞了 , 就无法进行多路复用了 。
基本IO模型与阻塞点
你还记得我在第一节课介绍的具有网络框架的SimpleKV吗?
以Get请求为例 , SimpleKV为了处理一个Get请求 , 需要监听客户端请求(bind/listen) , 和客户端建立连接(accept) , 从socket中读取请求(recv) , 解析客户端发送请求(parse) , 根据请求类型读取键值数据(get) , 最后给客户端返回结果 , 即向socket中写回数据(send) 。
下图显示了这一过程 , 其中 , bind/listen、accept、recv、parse和send属于网络IO处理 , 而get属于键值数据操作 。 既然Redis是单线程 , 那么 , 最基本的一种实现是在一个线程中依次执行上面说的这些操作 。
文章图片
但是 , 在这里的网络IO操作中 , 有潜在的阻塞点 , 分别是accept()和recv() 。 当Redis监听到一个客户端有连接请求 , 但一直未能成功建立起连接时 , 会阻塞在accept()函数这里 , 导致其他客户端无法和Redis建立连接 。 类似的 , 当Redis通过recv()从一个客户端读取数据时 , 如果数据一直没有到达 , Redis也会一直阻塞在recv() 。
这就导致Redis整个线程阻塞 , 无法处理其他客户端请求 , 效率很低 。 不过 , 幸运的是 , socket网络模型本身支持非阻塞模式 。
非阻塞模式
Socket网络模型的非阻塞模式设置 , 主要体现在三个关键的函数调用上 , 如果想要使用socket非阻塞模式 , 就必须要了解这三个函数的调用返回类型和设置模式 。 接下来 , 我们就重点学习下它们 。
在socket模型中 , 不同操作调用后会返回不同的套接字类型 。 socket()方法会返回主动套接字 , 然后调用listen()方法 , 将主动套接字转化为监听套接字 , 此时 , 可以监听来自客户端的连接请求 。 最后 , 调用accept()方法接收到达的客户端连接 , 并返回已连接套接字 。
文章图片
针对监听套接字 , 我们可以设置非阻塞模式:当Redis调用accept()但一直未有连接请求到达时 , Redis线程可以返回处理其他操作 , 而不用一直等待 。 但是 , 你要注意的是 , 调用accept()时 , 已经存在监听套接字了 。
虽然Redis线程可以不用继续等待 , 但是总得有机制继续在监听套接字上等待后续连接请求 , 并在有请求时通知Redis 。
类似的 , 我们也可以针对已连接套接字设置非阻塞模式:Redis调用recv()后 , 如果已连接套接字上一直没有数据到达 , Redis线程同样可以返回处理其他操作 。 我们也需要有机制继续监听该已连接套接字 , 并在有数据达到时通知Redis 。
- 阳光房型材为什么用铝合金?
- dram|为什么工业计算机是移动监控的关键组件?
- |为什么你亚马逊广告的ACOS那么高?
- |单板计算机为什么对于嵌入式计算设计很重要?
- |中国最大光刻机企业已被美列入黑名单,为什么还不与华为联手研发?
- 华为|Mate50系列为什么会一机难求?
- iPhone|印度网友的迷惑提问:为什么中国厂商没有能力生产高质量产品?
- 乔布斯|感慨!为什么乔布斯终其一生都未与生父相认?
- 在之前的文章中|宇宙形成的第一个分子是什么,为什么不是氢分子
- 京东|嵌入式开发中为什么选择C语言?它有哪些特点?