国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|數據庫技術|

服務器之家 - 數據庫 - Redis - Redis緩存IO模型的演進教程示例精講

Redis緩存IO模型的演進教程示例精講

2022-01-22 18:04假裝懂編程 Redis

這篇文章主要為大家介紹了Redis線程IO模型演進的教程示例精講,有需要朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪

前言

redis作為應用最廣泛的nosql數據庫之一,大大小小也經歷過很多次升級。在4.0版本之前,單線程+io多路復用使得redis的性能已經達到一個非常高的高度了。作者也說過,之所以設計成單線程是因為redis的瓶頸不在cpu上,而且單線程也不需要考慮多線程帶來的鎖開銷問題。

然而隨著時間的推移,單線程越來越不滿足一些應用場景了,比如針對大key刪除會造成主線程阻塞的問題,redis4.0出了一個異步線程。

針對單線程由于無法利用多核cpu的特性而導致無法滿足更高的并發,redis6.0也推出了多線程模式。所以說redis是單線程越來越不準確了。

事件模型

redis本身是個事件驅動程序,通過監聽文件事件和時間事件來完成相應的功能。其中文件事件其實就是對socket的抽象,把一個個socket事件抽象成文件事件,redis基于reactor模式開發了自己的網絡事件處理器。那么reactor模式是什么?

通信

思考一個問題,我們的服務器是如何收到我們的數據的?首先雙方先要建立tcp連接,連接建立以后,就可以收發數據了。發送方向socket的緩沖區發送數據,等待系統從緩沖區把數據取走,然后通過網卡把數據發出去,接收方的網卡在收到數據之后,會把數據copy到socket的緩沖區,然后等待應用程序來取,這是大概的發收數據流程。

Redis緩存IO模型的演進教程示例精講

copy數據的開銷

因為涉及到系統調用,整個過程可以發現一份數據需要先從用戶態拷貝到內核態的socket,然后又要從內核態的socket拷貝到用戶態的進程中去,這就是數據拷貝的開銷。

數據怎么知道發給哪個socket

內核維護的socket那么多,網卡過來的數據怎么知道投遞給哪個socket?

答案是端口,socket是一個四元組:

ip(client)+ port(client)+ip(server)+port(server)

注意千萬不要說一臺機器的理論最大并發是65535個,除了端口,還有ip,應該是端口數*ip數

這也是為什么一臺電腦可以同時打開多個軟件的原因。

socket的數據怎么通知程序來取

當數據已經從網卡copy到了對應的socket緩沖區中,怎么通知程序來取?假如socket數據還沒到達,這時程序在干嘛?這里其實涉及到cpu對進程的調度的問題。從cpu的角度來看,進程存在運行態、就緒態、阻塞態。

  • 就緒態:進程等待被執行,資源都已經準備好了,剩下的就等待cpu的調度了。
  • 運行態:正在運行的進程,cpu正在調度的進程。
  • 阻塞態:因為某些情況導致阻塞,不占有cpu,正在等待某些事件的完成。

當存在多個運行態的進程時,由于cpu的時間片技術,運行態的進程都會被cpu執行一段時間,看著好似同時運行一樣,這就是所謂的并發。當我們創建一個socket連接時,它大概會這樣:

?
1
2
3
4
sockfd = socket(af_inet, sock_stream, 0)
connect(sockfd, ....)
recv(sockfd, ...)
dosometing()

操作系統會為每個socket建立一個fd句柄,這個fd就指向我們創建的socket對象,這個對象包含緩沖區、進程的等待隊列...。對于一個創建socket的進程來說,如果數據沒到達,那么他會卡在recv處,這個進程會掛在socket對象的等待隊列中,對cpu來說,這個進程就是阻塞的,它其實不占有cpu,它在等待數據的到來。

Redis緩存IO模型的演進教程示例精講

當數據到來時,網卡會告訴cpu,cpu執行中斷程序,把網卡的數據copy到對應的socket的緩沖區中,然后喚醒等待隊列中的進程,把這個進程重新放回運行隊列中,當這個進程被cpu運行的時候,它就可以執行最后的讀取操作了。這種模式有兩個問題:

recv只能接收一個fd,如果要recv多個fd怎么辦?

通過while循環效率稍低。

進程除了讀取數據,還要處理接下里的邏輯,在數據沒到達時,進程處于阻塞態,即使用了while循環來監聽多個fd,其它的socket是不是因為其中一個recv阻塞,而導致整個進程的阻塞。

針對上述問題,于是reactor模式和io多路復用技術出現了。

reactor

reactor是一種高性能處理io的模式,reactor模式下主程序只負責監聽文件描述符上是否有事件發生,這一點很重要,主程序并不處理文件描述符的讀寫。那么文件描述符的可讀可寫誰來做?答案是其他的工作程序,當某個socket發生可讀可寫的事件后,主程序會通知工作程序,真正從socket里面讀取數據和寫入數據的是工作程序。這種模式的好處就是就是主程序可以扛并發,不阻塞,主程序非常的輕便。事件可以通過隊列的方式等待被工作程序執行。通過reactor模式,我們只需要把事件和事件對應的handler(callback func),注冊到reactor中就行了,比如:

?
1
2
3
4
type reactor interface{
   registerhandler(writecallback func(), "writeevent");
   registerhandler(readcallback func(), "readevent");
}

當一個客戶端向redis發起set key value的命令,這時候會向socket緩沖區寫入這樣的命令請求,當reactor監聽到對應的socket緩沖區有數據了,那么此時的socket是可讀的,reactor就會觸發讀事件,通過事先注入的readcallback回調函數來完成命令的解析、命令的執行。當socket的緩沖區有足夠的空間可以被寫,那么對應的reactor就會產生可寫事件,此時就會執行事先注入的writecallback回調函數。當發起的set key value執行完畢后,此時工作程序會向socket緩沖區中寫入ok,最后客戶端會從socket緩沖區中取走寫入的ok。在redis中不管是readcallback,還是writecallback,它們都是一個線程完成的,如果它們同時到達那么也得排隊,這就是redis6.0之前的默認模式,也是最廣為流傳的單線程redis。

整個流程下來可以發現reactor主程序非常快,因為它不需要執行真正的讀寫,剩下的都是工作程序干的事:io的讀寫、命令的解析、命令的執行、結果的返回..,這一點很重要。

io多路復用器

通過上面我們知道reactor它是一個抽象的理論,是一個模式,如何實現它?如何監聽socket事件的到來?。最簡單的辦法就是輪詢,我們既然不知道socket事件什么時候到達,那么我們就一直來問內核,假設現在有1w個socket連接,那么我們就得循環問內核1w次,這個開銷明顯很大。

用戶態到內核態的切換,涉及到上下文的切換(context),cpu需要保護現場,在進入內核前需要保存寄存器的狀態,在內核返回后還需要從寄存器里恢復狀態,這是個不小的開銷。

由于傳統的輪詢方法開銷過大,于是io多路復用復用器出現了,io多路復用器有select、poll、evport、kqueue、epoll。redis在i/o多路復用程序的實現源碼中用#include宏定義了相應的規則,程序會在編譯時自動選擇系統中性能最高的i/o多路復用函數庫來作為redis的i/o多路復用程序的底層實現:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// include the best multiplexing layer supported by this system. the following should be ordered by performances, descending.
# ifdef have_evport
# include "ae_evport.c"
# else
    # ifdef have_epoll
    # include "ae_epoll.c"
    # else
        # ifdef have_kqueue
        # include "ae_kqueue.c"
        # else
        # include "ae_select.c"
        # endif
    # endif
# endif

我們這里主要介紹兩種非常經典的復用器select和epoll,select是io多路復用器的初代,select是如何解決不停地從用戶態到內核態的輪詢問題的?

select

既然每次輪詢很麻煩,那么select就把一批socket的fds集合一次性交給內核,然后內核自己遍歷fds,然后判斷每個fd的可讀可寫狀態,當某個fd的狀態滿足時,由用戶自己判斷去獲取。

?
1
2
3
4
5
6
7
8
9
fds = []int{fd1,fd2,...}
for {
 select (fds)
 for i:= 0; i < len(fds); i++{
  if isready(fds[i]) {
      read()
     }
  }
}

select的缺點:當一個進程監聽多個socket的時候,通過select會把內核中所有的socket的等待隊列都加上本進程(多對一),這樣當其中一個socket有數據的時候,它就會把告訴cpu,同時把這個進程從阻塞態喚醒,等待被cpu的調度,同時會把進程從所有的socket的等待隊列中移除,當cpu運行這個進程的時候,進程因為本身傳進去了一批fds集合,我們并不知道哪個fd來數據了,所以只能都遍歷一次,這樣對于沒有數據到來的fd來說,就白白浪費了。由于每次select要遍歷socket集合,那么這個socket集合的數量過大就會影響整體效率,這原因也是select為什么支持最大1024個并發的。

epoll

如果有一種方法使得不用遍歷所有的socket,當某個socket的消息到來時,只需要觸發對應的socket fd,而不用盲目的輪詢,那效率是不是會更高。epoll的出現就是為了解決這個問題:

?
1
2
3
4
5
6
7
8
epfd = epoll_create()
epoll_ctl(epfd, fd1, fd2...)
for {
  epoll_wait()
  for fd := range fds {
    dosomething()
  }
}
  • 首先通過epoll_create創建一個epoll對象,它會返回一個fd句柄,和socket的句柄一樣,也是管理在fds集合下。
  • 通過epoll_ctl,把需要監聽的socket fd和epoll對象綁定。
  • 通過epoll_wait來獲取有數據的socket fd,當沒有一個socket有數據的時候,那么此處會阻塞,有數據的話,那么就會返回有數據的fds集合。

epoll是怎么做到的?

首先內核的socket不在和用戶的進程綁定了,而是和epoll綁定,這樣當socket的數據到來時,中斷程序就會給epoll的一個就緒對列添加對應socket fd,這個隊列里都是有數據的socket,然后和epoll關聯的進程也會被喚醒,當cpu運行進程的時候,就可以直接從epoll的就緒隊列中獲取有事件的socket,執行接下來的讀。整個流程下來,可以發現用戶程序不用無腦遍歷,內核也不用遍歷,通過中斷做到"誰有數據處理誰"的高效表現。

Redis緩存IO模型的演進教程示例精講

單線程到多線程的演進

單線程

結合reactor的思想加上高性能epoll io模式,redis開發出一套高性能的網絡io架構:單線程的io多路復用,io多路復用器負責接受網絡io事件,事件最終以隊列的方式排隊等待被處理,這是最原始的單線程模型,為什么使用單線程?因為單線程的redis已經可以達到10w qps的負載(如果做一些復雜的集合操作,會降低),滿足絕大部分應用場景了,同時單線程不用考慮多線程帶來的鎖的問題,如果還沒達到你的要求,那么你也可以配置分片模式,讓不同的節點處理不同的sharding key,這樣你的redis server的負載能力就能隨著節點的增長而進一步線性增長。

異步線程

在單線程模式下有這樣一個問題,當執行刪除某個很大的集合或者hash的時候會很耗時(不是連續內存),那么單線程的表現就是其他還在排隊的命令就得等待。當等待的命令越來越多,那么不好的事情就會發生。于是redis4.0針對大key刪除的情況,出了個異步線程。用unlink代替del去執行刪除,這樣當我們unlink的時候,redis會檢測當刪除的key是否需要放到異步線程去執行(比如集合的數量超過64個...),如果value足夠大,那么就會放到異步線程里去處理,不會影響主線程。同樣的還有flushall、flushdb都支持異步模式。此外redis還支持某些場景下是否需要異步線程來處理的模式(默認是關閉的):

?
1
2
3
4
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no

lazyfree-lazy-eviction:針對redis有設置內存達到maxmemory的淘汰策略時,這時候會啟動異步刪除,此場景異步刪除的缺點就是如果刪除不及時,內存不能得到及時釋放。

lazyfree-lazy-expire:對于有ttl的key,在被redis清理的時候,不執行同步刪除,加入異步線程來刪除。

replica-lazy-flush:在slave節點加入進來的時候,會執行flush清空自己的數據,如果flush耗時較久,那么復制緩沖區堆積的數據就越多,后面slave同步數據較相對慢,開啟replica-lazy-flush后,slave的flush可以交由異步現成來處理,從而提高同步的速度。

lazyfree-lazy-server-del:這個選項是針對一些指令,比如rename一個字段的時候執行rename key newkey, 如果這時newkey是b存在的,對于rename來說它就要刪除這個newkey原來的老值,如果這個老值很大,那么就會造成阻塞,當開啟了這個選項時也會交給異步線程來操作,這樣就不會阻塞主線程了。

多線程

redis單線程+異步線程+分片已經能滿足了絕大部分應用,然后沒有最好只有更好,redis在6.0還是推出了多線程模式。默認情況下,多線程模式是關閉的。

?
1
2
# io-threads 4 # work線程數
# io-threads-do-reads no # 是否開啟

多線程的作用點?

通過上文我們知道當我們從一個socket中讀取數據的時候,需要從內核copy到用戶空間,當我們往socket中寫數據的時候,需要從用戶空間copy到內核。redis本身的計算還是很快的,慢的地方那么主要就是socket io相關操作了。當我們的qps非常大的時候,單線程的redis無法發揮多核cpu的好處,那么通過開啟多個線程利用多核cpu來分擔io操作是個不錯的選擇。

so for instance if you have a four cores boxes, try to use 2 or 3 i/o threads, if you have a 8 cores, try to use 6 threads.

開啟的話,官方建議對于一個4核的機器來說,開2-3個io線程,如果有8核,那么開6個io線程即可。

多線程的原理

需要注意的是redis的多線程僅僅只是處理socket io讀寫是多個線程,真正去運行指令還是一個線程去執行的。

  1. redis server通過eventloop來監聽客戶端的請求,當一個請求到來時,主線程并不會立馬解析執行,而是把它放到全局讀隊列clients_pending_read中,并給每個client打上client_pending_read標識。
  2. 然后主線程通過rr(round-robin)策略把所有任務分配給i/o線程和主線程自己。
  3. 每個線程(包括主線程和子線程)根據分配到的任務,通過client的client_pending_read標識只做請求參數的讀取和解析(這里并不執行命令)。
  4. 主線程會忙輪詢等待所有的io線程執行完,每個io線程都會維護一個本地的隊列io_threads_list和本地的原子計數器io_threads_pending,線程之間的任務是隔離的,不會重疊,當io線程完成任務之后,io_threads_pending[index] = 0,當所有的io_threads_pending都是0的時候,就是任務執行完畢之時。
  5. 當所有read執行完畢之后,主線程通過遍歷clients_pending_read隊列,來執行真正的exec動作。
  6. 在完成命令的讀取、解析、執行之后,就要把結果響應給客戶端了。主線程會把需要響應的client加入到全局的clients_pending_write隊列中。
  7. 主線程遍歷clients_pending_write隊列,再通過rr(round-robin)策略把所有任務分給i/o線程和主線程,讓它們將數據回寫給客戶端。

多線程模式下,每個io線程負責處理自己的隊列,不會互相干擾,io線程要么同時在讀,要么同時在寫,不會同時讀或寫。主線程也只會在所有的子線程的任務處理完畢之后,才會嘗試再次分配任務。同時最終的命令執行還是由主線程自己來完成,整個過程不涉及到鎖。

以上就是redis線程io模型的演進教程示例精講的詳細內容,更多關于redis線程io模型的演進的資料請關注服務器之家其它相關文章!

原文鏈接:https://blog.csdn.net/shanwu1/article/details/121109743

延伸 · 閱讀

精彩推薦
  • Redisredis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    redis中如何使用lua腳本讓你的靈活性提高5個逼格詳解

    這篇文章主要給大家介紹了關于redis中如何使用lua腳本讓你的靈活性提高5個逼格的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具...

    一線碼農5812019-11-18
  • RedisRedis如何實現數據庫讀寫分離詳解

    Redis如何實現數據庫讀寫分離詳解

    Redis的主從架構,能幫助我們實現讀多,寫少的情況,下面這篇文章主要給大家介紹了關于Redis如何實現數據庫讀寫分離的相關資料,文中通過示例代碼介紹...

    羅兵漂流記6092019-11-11
  • RedisRedis 事務知識點相關總結

    Redis 事務知識點相關總結

    這篇文章主要介紹了Redis 事務相關總結,幫助大家更好的理解和學習使用Redis,感興趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • Redisredis 交集、并集、差集的具體使用

    redis 交集、并集、差集的具體使用

    這篇文章主要介紹了redis 交集、并集、差集的具體使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友...

    xiaojin21cen10152021-07-27
  • Redisredis實現排行榜功能

    redis實現排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用來實現排行榜功能,本文就來簡單的介紹一下如何使用,具有一定的參考價值,感興趣的小伙伴們...

    乘月歸5022021-08-05
  • RedisRedis全量復制與部分復制示例詳解

    Redis全量復制與部分復制示例詳解

    這篇文章主要給大家介紹了關于Redis全量復制與部分復制的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Redis爬蟲具有一定的參考學習...

    豆子先生5052019-11-27
  • RedisRedis的配置、啟動、操作和關閉方法

    Redis的配置、啟動、操作和關閉方法

    今天小編就為大家分享一篇Redis的配置、啟動、操作和關閉方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    大道化簡5312019-11-14
  • Redis詳解Redis復制原理

    詳解Redis復制原理

    與大多數db一樣,Redis也提供了復制機制,以滿足故障恢復和負載均衡等需求。復制也是Redis高可用的基礎,哨兵和集群都是建立在復制基礎上實現高可用的...

    李留廣10222021-08-09
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 精久久久 | 欧美色图一区 | 午夜在线视频播放 | 亚洲免费在线视频 | 99久久久国产精品 | 欧美高清视频在线观看 | 黄色中文字幕 | 国产电影一区二区三区 | 免费在线观看黄色av | 国产精品亚洲视频 | 天天摸天天做天天爽 | 99国内精品视频 | 狠狠综合久久av一区二区老牛 | 亚洲欧美综合精品久久成人 | av在线免费观看网址 | 国产欧美高清在线观看 | 神马久久久久久久 | 精品96久久久久久中文字幕无 | 国产一区亚洲 | 日韩一区二区在线播放 | 国产精品原创巨作av | 欧美一区二区三区在线看 | 欧美国产综合 | 免费av一区 | 美日韩一区| 蜜桃传媒一区二区 | 99精品视频在线免费观看 | 日韩视频精品在线 | 久久综合图片 | 三级在线观看 | 国产99久久久精品视频 | 午夜激情视频在线 | 日韩在线短视频 | 精品久久久久久久久久久久 | 亚洲av毛片一区二二区三三区 | 久热中文 | 一级黄色大片 | 日韩国产在线观看 | 九九九久久国产免费 | 在线观看免费成人av | 精品香蕉视频 |