[TOC]
相信大家在面試過程中經常會被問到:“**單線程的Redis為啥這么快?**”
哈哈,反正我在面試時候經常會問候選人這個問題,這個問題其實是對redis內部機制的一個考察,可以牽扯出好多涉及底層深入原理的一些列問題。
**回到問題本身,基本的回答就兩點:**
* 完全基于內存
* IO多路復用
1、關于第1點比較好理解。Redis 絕大部分請求是純粹的內存操作,非常快速。數據存在內存中,類似于HashMap,查找和操作的時間復雜度都是O(1)。
2、關于第2點IO多路復用,有些同學看到概念后感覺一頭霧水,到底什么是IO多路復用?
本文從IO并發性能提升來整體思考,來逐步剖析IO多路復用的原理。
## 一、如何快速理解IO多路復用?
* 多進程
* 多線程
* 基于單進程的IO多路復用(select/poll/epoll)
### 1、多進程
對于并發情況,假如一個進程不行,那搞多個進程不就可以同時處理多個客戶端連接了么?
多進程這種方式的確可以解決了服務器在同一時間能處理多個客戶端連接請求的問題,但是仍存在一些缺點:
* fork()等系統調用會使得進程上下文進行切換,效率較低
* 進程創建的數量隨著連接請求的增加而增加。比如10w個請求,就要fork 10w個進程,開銷太大
* 進程與進程之間的地址空間是私有、獨立的,使得進程之間的數據共享變得困難
### 2、多線程
線程是運行在進程上下文的邏輯流,一個進程可以包含多個線程,多個線程運行在同一進程上下文中,因此可共享這個進程地址空間的所有內容,解決了進程與進程之間通信難的問題。
同時,由于一個線程的上下文要比一個進程的上下文小得多,所以線程的上下文切換,要比進程的上下文切換效率高得多。
### 3、IO多路復用
簡單理解就是:一個服務端進程可以同時處理多個套接字描述符。
* **多路**:多個客戶端連接(連接就是套接字描述符)
* **復用**:使用單進程就能夠實現同時處理多個客戶端的連接
以上是通過增加進程和線程的數量來并發處理多個套接字,免不了上下文切換的開銷,而IO多路復用只需要一個進程就能夠處理多個套接字,從而解決了上下文切換的問題。
其發展可以分**select->poll→epoll**三個階段來描述。
## 二、如何簡單理解select/poll/epoll呢?
按照以往慣例,還是聯系一下我們日常中的現實場景,這樣更助于大家理解。
> **舉栗說明:**
>
> 領導分配員工開發任務,有些員工還沒完成。如果領導要每個員工的工作都要驗收check,那在未完成的員工那里,只能阻塞等待,等待他完成之后,再去check下一位員工的任務,造成性能問題。
**那如何解決這個問題呢?**
### 1、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。
### 2、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。
### 3、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利用率。
### 4、小結
* **select**就是輪詢,在Linux上限制個數一般為1024個
* **poll**解決了select的個數限制,但是依然是輪詢
* **epoll**解決了個數的限制,同時解決了輪詢的方式
## 三、IO多路復用在Redis中的應用
Redis 服務器是一個事件驅動程序, 服務器處理的事件分為時間事件和文件事件兩類。
* **文件事件**:Redis主進程中,主要處理客戶端的連接請求與相應。
* **時間事件**:fork出的子進程中,處理如AOF持久化任務等。
由于Redis的文件事件是單進程,單線程模型,但是確保持著優秀的吞吐量,IO多路復用起到了主要作用。
文件事件是對套接字操作的抽象,每當一個套接字準備好執行連接應答、寫入、讀取、關閉等操作時,就會產生一個文件事件。因為一個服務器通常會連接多個套接字,所以多個文件事件有可能會并發地出現。
IO多路復用程序負責監聽多個套接字并向文件事件分派器傳送那些產生了事件的套接字。文件事件分派器接收IO多路復用程序傳來的套接字,并根據套接字產生的事件的類型,調用相應的事件處理器。示例如圖所示:

文件處理器的四個組成部分
Redis的IO多路復用程序的所有功能都是通過包裝常見的select、poll、evport和kqueue這些IO多路復用函數庫來實現的,每個IO多路復用函數庫在Redis源碼中都有對應的一個單獨的文件。
Redis為每個IO多路復用函數庫都實現了相同的API,所以IO多路復用程序的底層實現是可以互換的。如圖:

多個IO復用庫實現可選
Redis把所有連接與讀寫事件、還有我們沒提到的時間事件一起集中管理,并對底層IO多路復用機制進行了封裝,最終實現了單進程能夠處理多個連接以及讀寫事件。這就是IO多路復用在redis中的應用。
- 一.JVM
- 1.1 java代碼是怎么運行的
- 1.2 JVM的內存區域
- 1.3 JVM運行時內存
- 1.4 JVM內存分配策略
- 1.5 JVM類加載機制與對象的生命周期
- 1.6 常用的垃圾回收算法
- 1.7 JVM垃圾收集器
- 1.8 CMS垃圾收集器
- 1.9 G1垃圾收集器
- 2.面試相關文章
- 2.1 可能是把Java內存區域講得最清楚的一篇文章
- 2.0 GC調優參數
- 2.1GC排查系列
- 2.2 內存泄漏和內存溢出
- 2.2.3 深入理解JVM-hotspot虛擬機對象探秘
- 1.10 并發的可達性分析相關問題
- 二.Java集合架構
- 1.ArrayList深入源碼分析
- 2.Vector深入源碼分析
- 3.LinkedList深入源碼分析
- 4.HashMap深入源碼分析
- 5.ConcurrentHashMap深入源碼分析
- 6.HashSet,LinkedHashSet 和 LinkedHashMap
- 7.容器中的設計模式
- 8.集合架構之面試指南
- 9.TreeSet和TreeMap
- 三.Java基礎
- 1.基礎概念
- 1.1 Java程序初始化的順序是怎么樣的
- 1.2 Java和C++的區別
- 1.3 反射
- 1.4 注解
- 1.5 泛型
- 1.6 字節與字符的區別以及訪問修飾符
- 1.7 深拷貝與淺拷貝
- 1.8 字符串常量池
- 2.面向對象
- 3.關鍵字
- 4.基本數據類型與運算
- 5.字符串與數組
- 6.異常處理
- 7.Object 通用方法
- 8.Java8
- 8.1 Java 8 Tutorial
- 8.2 Java 8 數據流(Stream)
- 8.3 Java 8 并發教程:線程和執行器
- 8.4 Java 8 并發教程:同步和鎖
- 8.5 Java 8 并發教程:原子變量和 ConcurrentMap
- 8.6 Java 8 API 示例:字符串、數值、算術和文件
- 8.7 在 Java 8 中避免 Null 檢查
- 8.8 使用 Intellij IDEA 解決 Java 8 的數據流問題
- 四.Java 并發編程
- 1.線程的實現/創建
- 2.線程生命周期/狀態轉換
- 3.線程池
- 4.線程中的協作、中斷
- 5.Java鎖
- 5.1 樂觀鎖、悲觀鎖和自旋鎖
- 5.2 Synchronized
- 5.3 ReentrantLock
- 5.4 公平鎖和非公平鎖
- 5.3.1 說說ReentrantLock的實現原理,以及ReentrantLock的核心源碼是如何實現的?
- 5.5 鎖優化和升級
- 6.多線程的上下文切換
- 7.死鎖的產生和解決
- 8.J.U.C(java.util.concurrent)
- 0.簡化版(快速復習用)
- 9.鎖優化
- 10.Java 內存模型(JMM)
- 11.ThreadLocal詳解
- 12 CAS
- 13.AQS
- 0.ArrayBlockingQueue和LinkedBlockingQueue的實現原理
- 1.DelayQueue的實現原理
- 14.Thread.join()實現原理
- 15.PriorityQueue 的特性和原理
- 16.CyclicBarrier的實際使用場景
- 五.Java I/O NIO
- 1.I/O模型簡述
- 2.Java NIO之緩沖區
- 3.JAVA NIO之文件通道
- 4.Java NIO之套接字通道
- 5.Java NIO之選擇器
- 6.基于 Java NIO 實現簡單的 HTTP 服務器
- 7.BIO-NIO-AIO
- 8.netty(一)
- 9.NIO面試題
- 六.Java設計模式
- 1.單例模式
- 2.策略模式
- 3.模板方法
- 4.適配器模式
- 5.簡單工廠
- 6.門面模式
- 7.代理模式
- 七.數據結構和算法
- 1.什么是紅黑樹
- 2.二叉樹
- 2.1 二叉樹的前序、中序、后序遍歷
- 3.排序算法匯總
- 4.java實現鏈表及鏈表的重用操作
- 4.1算法題-鏈表反轉
- 5.圖的概述
- 6.常見的幾道字符串算法題
- 7.幾道常見的鏈表算法題
- 8.leetcode常見算法題1
- 9.LRU緩存策略
- 10.二進制及位運算
- 10.1.二進制和十進制轉換
- 10.2.位運算
- 11.常見鏈表算法題
- 12.算法好文推薦
- 13.跳表
- 八.Spring 全家桶
- 1.Spring IOC
- 2.Spring AOP
- 3.Spring 事務管理
- 4.SpringMVC 運行流程和手動實現
- 0.Spring 核心技術
- 5.spring如何解決循環依賴問題
- 6.springboot自動裝配原理
- 7.Spring中的循環依賴解決機制中,為什么要三級緩存,用二級緩存不夠嗎
- 8.beanFactory和factoryBean有什么區別
- 九.數據庫
- 1.mybatis
- 1.1 MyBatis-# 與 $ 區別以及 sql 預編譯
- Mybatis系列1-Configuration
- Mybatis系列2-SQL執行過程
- Mybatis系列3-之SqlSession
- Mybatis系列4-之Executor
- Mybatis系列5-StatementHandler
- Mybatis系列6-MappedStatement
- Mybatis系列7-參數設置揭秘(ParameterHandler)
- Mybatis系列8-緩存機制
- 2.淺談聚簇索引和非聚簇索引的區別
- 3.mysql 證明為什么用limit時,offset很大會影響性能
- 4.MySQL中的索引
- 5.數據庫索引2
- 6.面試題收集
- 7.MySQL行鎖、表鎖、間隙鎖詳解
- 8.數據庫MVCC詳解
- 9.一條SQL查詢語句是如何執行的
- 10.MySQL 的 crash-safe 原理解析
- 11.MySQL 性能優化神器 Explain 使用分析
- 12.mysql中,一條update語句執行的過程是怎么樣的?期間用到了mysql的哪些log,分別有什么作用
- 十.Redis
- 0.快速復習回顧Redis
- 1.通俗易懂的Redis數據結構基礎教程
- 2.分布式鎖(一)
- 3.分布式鎖(二)
- 4.延時隊列
- 5.位圖Bitmaps
- 6.Bitmaps(位圖)的使用
- 7.Scan
- 8.redis緩存雪崩、緩存擊穿、緩存穿透
- 9.Redis為什么是單線程、及高并發快的3大原因詳解
- 10.布隆過濾器你值得擁有的開發利器
- 11.Redis哨兵、復制、集群的設計原理與區別
- 12.redis的IO多路復用
- 13.相關redis面試題
- 14.redis集群
- 十一.中間件
- 1.RabbitMQ
- 1.1 RabbitMQ實戰,hello world
- 1.2 RabbitMQ 實戰,工作隊列
- 1.3 RabbitMQ 實戰, 發布訂閱
- 1.4 RabbitMQ 實戰,路由
- 1.5 RabbitMQ 實戰,主題
- 1.6 Spring AMQP 的 AMQP 抽象
- 1.7 Spring AMQP 實戰 – 整合 RabbitMQ 發送郵件
- 1.8 RabbitMQ 的消息持久化與 Spring AMQP 的實現剖析
- 1.9 RabbitMQ必備核心知識
- 2.RocketMQ 的幾個簡單問題與答案
- 2.Kafka
- 2.1 kafka 基礎概念和術語
- 2.2 Kafka的重平衡(Rebalance)
- 2.3.kafka日志機制
- 2.4 kafka是pull還是push的方式傳遞消息的?
- 2.5 Kafka的數據處理流程
- 2.6 Kafka的腦裂預防和處理機制
- 2.7 Kafka中partition副本的Leader選舉機制
- 2.8 如果Leader掛了的時候,follower沒來得及同步,是否會出現數據不一致
- 2.9 kafka的partition副本是否會出現腦裂情況
- 十二.Zookeeper
- 0.什么是Zookeeper(漫畫)
- 1.使用docker安裝Zookeeper偽集群
- 3.ZooKeeper-Plus
- 4.zk實現分布式鎖
- 5.ZooKeeper之Watcher機制
- 6.Zookeeper之選舉及數據一致性
- 十三.計算機網絡
- 1.進制轉換:二進制、八進制、十六進制、十進制之間的轉換
- 2.位運算
- 3.計算機網絡面試題匯總1
- 十四.Docker
- 100.面試題收集合集
- 1.美團面試常見問題總結
- 2.b站部分面試題
- 3.比心面試題
- 4.騰訊面試題
- 5.哈羅部分面試
- 6.筆記
- 十五.Storm
- 1.Storm和流處理簡介
- 2.Storm 核心概念詳解
- 3.Storm 單機版本環境搭建
- 4.Storm 集群環境搭建
- 5.Storm 編程模型詳解
- 6.Storm 項目三種打包方式對比分析
- 7.Storm 集成 Redis 詳解
- 8.Storm 集成 HDFS 和 HBase
- 9.Storm 集成 Kafka
- 十六.Elasticsearch
- 1.初識ElasticSearch
- 2.文檔基本CRUD、集群健康檢查
- 3.shard&replica
- 4.document核心元數據解析及ES的并發控制
- 5.document的批量操作及數據路由原理
- 6.倒排索引
- 十七.分布式相關
- 1.分布式事務解決方案一網打盡
- 2.關于xxx怎么保證高可用的問題
- 3.一致性hash原理與實現
- 4.微服務注冊中心 Nacos 比 Eureka的優勢
- 5.Raft 協議算法
- 6.為什么微服務架構中需要網關
- 0.CAP與BASE理論
- 十八.Dubbo
- 1.快速掌握Dubbo常規應用
- 2.Dubbo應用進階
- 3.Dubbo調用模塊詳解
- 4.Dubbo調用模塊源碼分析
- 6.Dubbo協議模塊