目錄
效能瓶頸不在CPU
在Linux系統中可以使用vmstat指令來查看上下文切換的次數,以下是vmstat查看上下文切換次數的範例:
並行處理客戶端的請求(I/O多路復用)
可維護性
Redis6.0为何引入多线程?
首頁 資料庫 Redis Redis6.0到底為何引入多執行緒?

Redis6.0到底為何引入多執行緒?

Oct 19, 2020 pm 02:45 PM

下面由Redis教學欄位介紹Redis6.0到底為何介紹多執行緒? ,希望對需要的朋友有幫助!

Redis6.0到底為何引入多執行緒?

作者簡介:曾任職於阿里巴巴,每日優鮮等網路公司,擔任技術總監。 15年電商互聯網經驗。

一百天前Redis作者antirez在部落格上(antirez.com)發布了一條重磅消息,Redis6.0正式發布了。其中最引人注目的改動就是,Redis6.0引入了多執行緒。

本文主要分成兩部分。首先我們先聊聊Redis6.0之前為什麼要採用單執行緒模型。然後再詳細解釋Redis6.0的多執行緒。

Redis6.0到底為何引入多執行緒?

Redis6.0之前為什麼採用單執行緒模型


嚴格地說,從Redis 4.0之後並不是單線程。除了主執行緒外,還有一些後台執行緒處理一些較為緩慢的操作,例如無用連線的釋放、大 key 的刪除等等。

單執行緒模型,為何效能那麼高?


Redis作者從設計之初,進行了多方面的考慮。最後選擇使用單線程模型來處理命令。之所以選擇單線程模型,主要有以下幾個重要原因:

  1. Redis操作基於內存,絕大多數操作的性能瓶頸不在CPU

  2. 單執行緒模型,避免了執行緒間切換帶來的效能開銷

  3. 使用單執行緒模型也能並發的處理客戶端的請求(多路復用I/O)

  4. 使用單執行緒模型,可維護性更高,開發,除錯和維護的成本較低

上述第三個原因是Redis最終採用單線程模型的決定性因素,其他的兩個原因都是使用單線程模型額外帶來的好處,在這裡我們會按順序介紹上述的幾個原因。

效能瓶頸不在CPU


#下圖是Redis官網對單執行緒模型的說明。大概意思是:Redis的瓶頸並不在CPU,它的主要瓶頸在於記憶體和網路。在Linux環境中,Redis每秒甚至可以提交100萬次請求。

Redis6.0到底為何引入多執行緒?

為什麼說Redis的瓶頸不在CPU?

首先,Redis絕大部分的操作是基於記憶體的,而且是純kv(key-value)操作,所以指令執行速度非常快。我們可以大概理解成,redis中的資料儲存在一張大HashMap中,HashMap的優勢就是尋找和寫入的時間複雜度都是O(1)。 Redis內部採用這種結構儲存數據,就奠定了Redis高效能的基礎。根據Redis官網描述,在理想情況下Redis每秒可以提交一百萬次請求,每次請求提交所需的時間在奈秒的時間量級。既然每次的Redis操作都這麼快,單執行緒就可以完全搞定了,那還何必要用多執行緒呢!

執行緒上下文切換問題


另外,多執行緒場景下會發生執行緒上下文切換。線程是由CPU調度的,CPU的一個核在一個時間片內只能同時執行一個線程,在CPU由線程A切換到線程B的過程中會發生一系列的操作,主要過程包括保存線程A的執行現場,然後載入線程B的執行現場,這個過程就是「線程上下文切換」。其中涉及線程相關指令的保存和恢復。

頻繁的執行緒上下文切換可能會導致效能急劇下降,這會導致我們不僅沒有提升處理請求的速度,反而降低了效能,這也是 Redis 對於多執行緒技術持謹慎態度的原因之一。

在Linux系統中可以使用vmstat指令來查看上下文切換的次數,以下是vmstat查看上下文切換次數的範例:

Redis6.0到底為何引入多執行緒?vmstat 1 表示每秒統計一次, 其中cs列就是指上下文切換的數目. 一般情況下, 空閒系統的上下文切換每秒在1500以下。

並行處理客戶端的請求(I/O多路復用)


#如上所述:Redis的瓶頸並不在CPU,它的主要瓶頸在於記憶體和網路。所謂內存瓶頸很好理解,Redis做為緩存使用時很多場景需要緩存大量數據,所以需要大量內存空間,這可以通過集群分片去解決,例如Redis自身的無中心集群分片方案以及Codis這種基於代理的集群分片方案。    

#

對於網路瓶頸,Redis在網路I/O模型上採用了多路復用技術,來減少網路瓶頸帶來的影響。很多場景中使用單線程模型並不意味著程式不能並發的處理任務。 Redis 雖然使用單線程模型處理用戶的請求,但是它卻使用 I/O 多路復用技術「並行」處理來自客戶端的多個連接,同時等待多個連接發送的請求。使用 I/O多路復用技術能大幅減少系統的開銷,系統不再需要為每個連接創建專門的監聽線程,避免了由於大量的線程創建帶來的巨大性能開銷。

Redis6.0到底為何引入多執行緒?

下面我們詳細解釋一下多路復用I/O模型。為了能更充分理解,我們先了解幾個基本概念。

Socket(套接字):Socket可以理解成,在兩個應用程式進行網路通訊時,分別在兩個應用程式中的通訊端點。通訊時,一個應用程式將資料寫入Socket,然後透過網路卡把資料發送到另外一個應用程式的Socket。我們平常所說的HTTP和TCP協定的遠端通信,底層都是基於Socket實現的。 5種網路IO模型也都要基於Socket實現網路通訊。

阻塞與非阻塞:所謂阻塞,就是發出一個請求不能立刻回傳回應,要等所有的邏輯全處理完才能回傳回應。非阻塞反之,發出一個請求立刻返回應答,不用等處理完所有邏輯。

核心空間與使用者空間:在Linux中,應用程式穩定性遠遠比不上作業系統程序,為了確保作業系統的穩定性,Linux區分了核心空間和使用者空間。可以這樣理解,內核空間運行作業系統程式和驅動程序,用戶空間運行應用程式。 Linux以這種方式隔離了作業系統程式和應用程序,避免了應用程式影響到作業系統本身的穩定性。這也是Linux系統超穩定的主因。所有的系統資源操作都在核心空間進行,例如讀寫磁碟文件,記憶體分配和回收,網路介面呼叫等。所以在一次網路IO讀取過程中,資料並不是直接從網路卡讀取到用戶空間中的應用程式緩衝區,而是先從網卡拷貝到核心空間緩衝區,然後再從核心拷貝到用戶空間中的應用程式緩衝區。對於網路IO寫入過程,過程則相反,先將資料從用戶空間中的應用程式緩衝區拷貝到核心緩衝區,再從核心緩衝區把資料透過網卡發送出去。

多路復用I/O模型,建立在多路事件分離函數select,poll,epoll之上。以Redis採用的epoll為例,在發起read請求前,先更新epoll的socket監控列表,然後等待epoll函數返回(此過程是阻塞的,所以說多路復用IO本質上也是阻塞IO模型)。當某個socket有資料到達時,epoll函數會傳回。此時用戶執行緒才正式發起read請求,讀取並處理資料。這個模式用一個專門的監視線程去檢查多個socket,如果某個socket有資料到達就交給工作執行緒處理。由於等待Socket資料到達過程非常耗時,所以這種方式解決了阻塞IO模型一個Socket連接就需要一個線程的問題,也不存在非阻塞IO模型忙輪詢帶來的CPU效能損耗的問題。多工IO模型的實際應用場景很多,大家耳熟能詳的Redis,Java NIO,以及Dubbo採用的通訊框架Netty都採用了這種模型。

Redis6.0到底為何引入多執行緒?

下圖是基於epoll函數Socket程式設計的詳細流程。

Redis6.0到底為何引入多執行緒?

可維護性


#我們知道,多執行緒可以充分利用多核心CPU,在高並發場景下,能夠減少因I/O等待而帶來的CPU損耗,帶來良好的效能表現。不過多執行緒卻是一把雙面刃,帶來好處的同時,還會帶來程式碼維護困難,線上問題難於定位與調試,死鎖等問題。多執行緒模型中程式碼的執行過程不再是串列的,多個執行緒同時存取的共享變數如果處理不當也會帶來詭異的問題。

Redis6.0到底為何引入多執行緒?

我們透過一個例子,看多執行緒場景下發生的詭異現象。看下面的程式碼:

class MemoryReordering {
  int num = 0;
  boolean flag = false;
  
  public void set() {
    num = 1;     //语句1
    flag = true; //语句2
  }
  
  public int cal() {
    if( flag == true) {    //语句3
      return num + num; //语句4
    }
   
    return -1;
  }
  
}
登入後複製

flag為true時,cal() 方法回傳值是多少?很多人會說:這還用問嗎!肯定回傳2

结果可能会让你大吃一惊!上面的这段代码,由于语句1和语句2没有数据依赖性,可能会发生指令重排序,有可能编译器会把flag=true放到num=1的前面。此时set和cal方法分别在不同线程中执行,没有先后关系。cal方法,只要flag为true,就会进入if的代码块执行相加的操作。可能的顺序是:

  • 语句1先于语句2执行,这时的执行顺序可能是:语句1->语句2->语句3->语句4。执行语句4前,num = 1,所以cal的返回值是2

  • 语句2先于语句1执行,这时的执行顺序可能是:语句2->语句3->语句4->语句1。执行语句4前,num = 0,所以cal的返回值是0

我们可以看到,在多线程环境下如果发生了指令重排序,会对结果造成严重影响。

当然可以在第三行处,给flag加上关键字volatile来避免指令重排。即在flag处加上了内存栅栏,来阻隔flag(栅栏)前后的代码的重排序。当然多线程还会带来可见性问题,死锁问题以及共享资源安全等问题。

boolean volatile flag = false;
登入後複製

Redis6.0为何引入多线程?


Redis6.0引入的多线程部分,实际上只是用来处理网络数据的读写和协议解析,执行命令仍然是单一工作线程。

Redis6.0到底為何引入多執行緒?

从上图我们可以看到Redis在处理网络数据时,调用epoll的过程是阻塞的,也就是说这个过程会阻塞线程,如果并发量很高,达到几万的QPS,此处可能会成为瓶颈。一般我们遇到此类网络IO瓶颈的问题,可以增加线程数来解决。开启多线程除了可以减少由于网络I/O等待造成的影响,还可以充分利用CPU的多核优势。Redis6.0也不例外,在此处增加了多线程来处理网络数据,以此来提高Redis的吞吐量。当然相关的命令处理还是单线程运行,不存在多线程下并发访问带来的种种问题。

Redis6.0到底為何引入多執行緒?

性能对比


压测配置:

Redis Server: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge
Redis Benchmark Client: 阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 内存,主机型号 ecs.ic5.2xlarge
登入後複製

多线程版本Redis 6.0,单线程版本是 Redis 5.0.5。多线程版本需要新增以下配置:

io-threads 4 # 开启 4 个 IO 线程
io-threads-do-reads yes # 请求解析也是用 IO 线程
登入後複製

压测命令: redis-benchmark -h 192.168.0.49 -a foobared -t set,get -n 1000000 -r 100000000 --threads 4 -d ${datasize} -c 256

Redis6.0到底為何引入多執行緒?

图二

            图片来源于网络 Redis6.0到底為何引入多執行緒?

图三

                                                        图片来源于网络

从上面可以看到 GET/SET 命令在多线程版本中性能相比单线程几乎翻了一倍。另外,这些数据只是为了简单验证多线程 I/O 是否真正带来性能优化,并没有针对具体的场景进行压测,数据仅供参考。本次性能测试基于 unstble 分支,不排除后续发布的正式版本的性能会更好。

Redis6.0到底為何引入多執行緒?

最后


可见单线程有单线程的好处,多线程有多线程的优势,只有充分理解其中的本质原理,才能灵活运用于生产实践当中。

以上是Redis6.0到底為何引入多執行緒?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何在Redis群集中選擇一個碎片鍵? 如何在Redis群集中選擇一個碎片鍵? Mar 17, 2025 pm 06:55 PM

本文討論了在Redis群集中選擇碎片鍵,並強調了它們對性能,可伸縮性和數據分佈的影響。關鍵問題包括確保均勻數據分配,與訪問模式保持一致以及避免常見錯誤l

如何在Redis中實施身份驗證和授權? 如何在Redis中實施身份驗證和授權? Mar 17, 2025 pm 06:57 PM

本文討論了在REDIS中實施身份驗證和授權,重點是實現身份驗證,使用ACL以及確保REDIS的最佳實踐。它還涵蓋了管理用戶權限和工具以增強重新安全性。

如何將Redis用於工作隊列和背景處理? 如何將Redis用於工作隊列和背景處理? Mar 17, 2025 pm 06:51 PM

本文討論了使用REDIS進行工作隊列和背景處理,詳細的設置,作業定義和執行。它涵蓋了原子運營和工作優先級等最佳實踐,並解釋了REDIS如何提高處理效率。

如何在REDIS中實施緩存無效策略? 如何在REDIS中實施緩存無效策略? Mar 17, 2025 pm 06:46 PM

本文討論了在REDIS中實施和管理緩存無效的策略,包括基於時間的到期,事件驅動的方法和版本控制。它還涵蓋了緩存到期的最佳實踐和監視和自動的工具

如何監視REDIS群集的性能? 如何監視REDIS群集的性能? Mar 17, 2025 pm 06:56 PM

文章討論了使用Redis CLI,Redis Insight和Datadog和Prometheus等工具等工具進行監視REDIS群集的性能和健康。

如何將Redis用於酒吧/子消息傳遞? 如何將Redis用於酒吧/子消息傳遞? Mar 17, 2025 pm 06:48 PM

本文介紹瞭如何將Redis用於酒吧/子消息傳遞,涵蓋設置,最佳實踐,確保消息可靠性和監視性能。

如何在Web應用程序中使用REDI進行會話管理? 如何在Web應用程序中使用REDI進行會話管理? Mar 17, 2025 pm 06:47 PM

本文討論了在Web應用程序中使用REDIS進行會話管理,詳細介紹設置,諸如可伸縮性和性能以及安全措施之類的好處。

如何確保重新侵害常見漏洞? 如何確保重新侵害常見漏洞? Mar 17, 2025 pm 06:57 PM

文章討論了確保重新侵害漏洞,重點關注強密碼,網絡綁定,命令禁用,身份驗證,加密,更新和監視。

See all articles