<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis多線程原理詳解

          共 5433字,需瀏覽 11分鐘

           ·

          2020-12-04 15:49

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

          ? 作者?|??啊漢

          來源 |? urlify.cn/J7BzYn

          66套java從入門到精通實(shí)戰(zhàn)課程分享

          本篇文章為你解答以下問題:

          • 0:redis單線程的實(shí)現(xiàn)流程是怎樣的?

          • 1:redis哪些地方用到了多線程,哪些地方是單線程?

          • 2:redis多線程是怎么實(shí)現(xiàn)的?

          • 3:redis多線程是怎么做到無鎖的?

          ?

          0:redis單線程的實(shí)現(xiàn)流程是怎樣的?

          Redis一開始是單線程模型,在一個(gè)線程中要同時(shí)處理兩種事件:文件事件和時(shí)間事件

          文件事件主要是網(wǎng)絡(luò)I/O的讀寫,請求的接收和回復(fù)

          時(shí)間事件就是單次/多次執(zhí)行的定時(shí)器,如主從復(fù)制、定時(shí)刪除過期數(shù)據(jù)、字典rehash等

          redis所有核心功能都是跑在主線程中的,像aof文件落盤操作是在子線程中執(zhí)行的,那么在高并發(fā)情況下它是怎么做到高性能的呢?

          由于這兩種事件在同一個(gè)線程中執(zhí)行,就會(huì)出現(xiàn)互相影響的問題,如時(shí)間事件到了還在等待/執(zhí)行文件事件,或者文件事件已經(jīng)就緒卻在執(zhí)行時(shí)間事件,這就是單線程的缺點(diǎn),所以在實(shí)現(xiàn)上要將這些影響降到最低。那么redis是怎么實(shí)現(xiàn)的呢?

          ?

          定時(shí)執(zhí)行的時(shí)間事件保存在一個(gè)鏈表中,由于鏈表中任務(wù)沒有按照執(zhí)行時(shí)間排序,所以每次需要掃描單鏈表,找到最近需要執(zhí)行的任務(wù),時(shí)間復(fù)雜度是O(N),redis敢這么實(shí)現(xiàn)就是因?yàn)檫@個(gè)鏈表很短,大部分定時(shí)任務(wù)都是在serverCron方法中被調(diào)用。從現(xiàn)在開始到最近需要執(zhí)行的任務(wù)的開始時(shí)間,時(shí)長定位T,這段時(shí)間就是屬于文件事件的處理時(shí)間,以epoll為例,執(zhí)行epoll_wait最多等待的時(shí)長為T,如果有就緒任務(wù)epoll會(huì)返回所有就緒的網(wǎng)絡(luò)任務(wù),存在一個(gè)數(shù)組中,這時(shí)我們知道了所有就緒的socket和對應(yīng)的事件(讀、寫、錯(cuò)誤、掛斷),然后就可以接收數(shù)據(jù),解析,執(zhí)行對應(yīng)的命令函數(shù)。

          如果最近要執(zhí)行的定時(shí)任務(wù)時(shí)間已經(jīng)過了,那么epoll就不會(huì)阻塞,直接返回已經(jīng)就緒的網(wǎng)絡(luò)事件,即不等待。

          總之單線程,定時(shí)事件和網(wǎng)絡(luò)事件還是會(huì)互相影響的,正在處理定時(shí)事件網(wǎng)絡(luò)任務(wù)來了,正在處理網(wǎng)絡(luò)事件定時(shí)任務(wù)的時(shí)間到了。所以redis必須保證每個(gè)任務(wù)的處理時(shí)間不能太長。

          ?

          redis處理流程如下:

          1:服務(wù)啟動(dòng),開始網(wǎng)絡(luò)端口監(jiān)聽,等待客戶端請求

          2:客戶端想服務(wù)端發(fā)起連接請求,創(chuàng)建客戶端連接對象,完成連接

          3:將socket信息注冊到epoll,設(shè)置超時(shí)時(shí)間為時(shí)間事件的周期時(shí)長,等待客戶端發(fā)起請求

          4:客戶端發(fā)起操作數(shù)據(jù)庫請求(如GET)

          5:epoll收到客戶端的請求,可能多個(gè),按照順序處理請求

          6:接收請求參數(shù),接收完成后解析請求協(xié)議,得到請求命令

          7:執(zhí)行請求命令,即操作redis數(shù)據(jù)庫

          8:將結(jié)果返回給客戶端

          ?

          1:redis哪些地方用到了多線程,哪些地方是單線程?

          Redis多線程和單線程模型對比如下圖:

          ?

          從上圖中可以看出只有以下3個(gè)地方用的是多線程,其他地方都是單線程:

          1:接收請求參數(shù)

          2:解析請求參數(shù)

          3:請求響應(yīng),即將結(jié)果返回給client

          很明顯以上3點(diǎn)各個(gè)請求都是互相獨(dú)立互不影響的,很適合用多線程,特別是請求體/響應(yīng)體很大的時(shí)候,更能體現(xiàn)多線程的威力。而操作數(shù)據(jù)庫是請求之間共享的,如果使用多線程的話適合讀寫鎖。而操作數(shù)據(jù)庫本身是很快的(就是對map的增刪改查),單線程不一定就比多線程慢,當(dāng)然也有可能是作者偷懶,懶得實(shí)現(xiàn)罷了,但這次的多線程模型還是值得我們學(xué)習(xí)一下的。

          ?

          2:redis多線程是怎么實(shí)現(xiàn)的?

          先大致說一下多線程的流程:

          1:服務(wù)器啟動(dòng)時(shí)啟動(dòng)一定數(shù)量線程,服務(wù)啟動(dòng)的時(shí)候可以指定線程數(shù),每個(gè)線程對應(yīng)一個(gè)隊(duì)列(list *io_threads_list[128]),最多128個(gè)線程。

          2:服務(wù)器收到的每個(gè)請求都會(huì)放入全局讀隊(duì)列clients_pending_read,同時(shí)將隊(duì)列中的元素分發(fā)到每個(gè)線程對應(yīng)的隊(duì)列io_threads_list中,這些工作都是在主線程中執(zhí)行的。

          3:每個(gè)線程(包括主線程和子線程)接收請求參數(shù)并做解析,完事后在client中設(shè)置一個(gè)標(biāo)記CLIENT_PENDING_READ,標(biāo)識(shí)參數(shù)解析完成,可以操作數(shù)據(jù)庫了。(主線程和子線程都會(huì)執(zhí)行這個(gè)步驟)

          4:主線程遍歷隊(duì)列clients_pending_read,發(fā)現(xiàn)設(shè)有CLIENT_PENDING_READ標(biāo)記的,就操作數(shù)據(jù)庫

          5:操作完數(shù)據(jù)庫就是響應(yīng)client了,響應(yīng)是一組函數(shù)addReplyXXX,在client中設(shè)置標(biāo)記CLIENT_PENDING_WRITE,同時(shí)將client加入全局寫隊(duì)列clients_pending_write

          6:主線程將全局隊(duì)列clients_pending_write以輪訓(xùn)的方式將任務(wù)分發(fā)到每個(gè)線程對應(yīng)的隊(duì)列io_threads_list

          7:所有線程將遍歷自己的隊(duì)列io_threads_list,將結(jié)果發(fā)送給client

          ?

          3:redis多線程是怎么做到無鎖的?

          上面說了多線程的地方都是互相獨(dú)立互不影響的。但是每個(gè)線程的隊(duì)列就存在兩個(gè)兩個(gè)線程訪問的情況:主線程向隊(duì)列中寫數(shù)據(jù),子線程消費(fèi),redis的實(shí)現(xiàn)有點(diǎn)反直覺。按正常思路來說,主線程在往隊(duì)列中寫數(shù)據(jù)的時(shí)候加鎖;子線程復(fù)制隊(duì)列&并將隊(duì)列清空,這個(gè)兩個(gè)動(dòng)作是加鎖的,子線程消費(fèi)復(fù)制后的隊(duì)列,這個(gè)過程是不需要加鎖的,按理來說主線程和子線程的加鎖動(dòng)作都是非常快的。但是redis并沒有這么實(shí)現(xiàn),那么他是怎么實(shí)現(xiàn)的呢?

          ?

          redis多線程的模型是主線程負(fù)責(zé)搜集任務(wù),放入全局讀隊(duì)列clients_pending_read和全局寫隊(duì)列clients_pending_write,主線程在將隊(duì)列中的任務(wù)以輪訓(xùn)的方式分發(fā)到每個(gè)線程對應(yīng)的隊(duì)列(list *io_threads_list[128])

          1:一開始子線程的隊(duì)列都是空,主線程將全對隊(duì)列中的任務(wù)分發(fā)到每個(gè)線程的隊(duì)列,并設(shè)置一個(gè)隊(duì)列有數(shù)據(jù)的標(biāo)記(_Atomic unsigned long io_threads_pending[128]),io_threads_pending[1]=5表示第一個(gè)線程的隊(duì)列中有5個(gè)元素

          2:子線程死循環(huán)輪訓(xùn)檢查io_threads_pending[index] > 0,有數(shù)據(jù)就開始處理,處理完成之后將io_threads_pending[index] = 0,沒數(shù)據(jù)繼續(xù)檢查

          3:主線程將任務(wù)分發(fā)到子線程的隊(duì)列中,自己處理自己隊(duì)列中的任務(wù),處理完成后,等待所有子線程處理完所有任務(wù),繼續(xù)收集任務(wù)到全局隊(duì)列,在將任務(wù)分發(fā)給子線程,這樣就避免了主線程和子線程同時(shí)訪問隊(duì)列的情況,主線程向隊(duì)列寫的時(shí)候子線程還沒開始消費(fèi),子線程在消費(fèi)的時(shí)候主線程在等待子線程消費(fèi)完,子線程消費(fèi)完后主線程才會(huì)往隊(duì)列中繼續(xù)寫,就必須加鎖了。因?yàn)槿蝿?wù)是平均分配到每個(gè)隊(duì)列的,所以每個(gè)隊(duì)列的處理時(shí)間是接近的,等待的時(shí)間會(huì)很短。?

          ?

          4:源碼執(zhí)行流程

          為了方便你看源碼,這里加上一些代碼的執(zhí)行流程

          啟動(dòng)socket監(jiān)聽,注冊連接處理函數(shù),連接成功后創(chuàng)建連接對象connection,創(chuàng)建client對象,通過aeCreateFileEvent注冊client的讀事件

          main?->?initServer?->?acceptTcpHandler?->?anetTcpAccept?->?anetGenericAccept?->?accept(獲取到socket連接句柄)
          connCreateAcceptedSocket?->?connCreateSocket?->?創(chuàng)建一個(gè)connection對象
          acceptCommonHandler?->?createClient創(chuàng)建client連接對象?->?connSetReadHandler?->?aeCreateFileEvent?->?readQueryFromClient

          main?->?aeMain?->?aeProcessEvents?->?aeApiPoll(獲取可讀寫的socket)?->?readQueryFromClient(如果可讀)?->?processInputBuffer?->?processCommandAndResetClient(多線程下這個(gè)方法在當(dāng)前流程下不會(huì)執(zhí)行,而由主線程執(zhí)行)

          在多線程模式下,readQueryFromClient會(huì)將client信息加入server.clients_pending_read隊(duì)列,listAddNodeHead(server.clients_pending_read,c);

          ?

          主線程會(huì)將server.clients_pending_read中的數(shù)據(jù)分發(fā)到子線程的隊(duì)列(io_threads_list)中,子線程會(huì)調(diào)用readQueryFromClient就行參數(shù)解析,主線程分發(fā)完任務(wù)后,會(huì)執(zhí)行具體的操作數(shù)據(jù)庫的命令,這塊是單線程

          如果參數(shù)解析完成會(huì)在client->flags中加一個(gè)標(biāo)記CLIENT_PENDING_COMMAND,在主線程中先判斷client->flags & CLIENT_PENDING_COMMAND > 0,說明參數(shù)解析完成,才會(huì)調(diào)用processCommandAndResetClient,之前還擔(dān)心如果子線程還在做參數(shù)解析,主線程就開始執(zhí)行命令難道不會(huì)有問題嗎?現(xiàn)在一切都清楚了

          main?->?aeMain?->?aeProcessEvents?->?beforeSleep?->?handleClientsWithPendingReadsUsingThreads?->?processCommandAndResetClient?->?processCommand?->?call

          讀是多次讀:socket讀緩沖區(qū)有數(shù)據(jù),epoll就會(huì)一直觸發(fā)讀事件,所以讀可能是多次的

          寫是一次寫:往socket寫數(shù)據(jù)是在子線程中執(zhí)行的,直接循環(huán)直到數(shù)據(jù)寫完位置,就算某個(gè)線程阻塞了,也不會(huì)像單線程那樣導(dǎo)致所有任務(wù)都阻塞

          執(zhí)行完相關(guān)命令后,就是將結(jié)果返回給client,回復(fù)client是一組函數(shù),我們以addReply為例,說一下執(zhí)行流程,執(zhí)行addReply還是單線程的,將client信息插入全局隊(duì)列server.clients_pending_write。
          addReply?->?prepareClientToWrite?->?clientInstallWriteHandler?->?listAddNodeHead(server.clients_pending_write,c)

          在主線程中將server.clients_pending_write中的數(shù)據(jù)以輪訓(xùn)的方式分發(fā)到多個(gè)子線程中
          beforeSleep?->?handleClientsWithPendingWritesUsingThreads?->?將server.clients_pending_write中的數(shù)據(jù)以輪訓(xùn)的方式分發(fā)到多個(gè)線程的隊(duì)列中io_threads_list
          list?*io_threads_list[IO_THREADS_MAX_NUM];是數(shù)組雙向鏈表,一個(gè)線程對應(yīng)其中一個(gè)隊(duì)列

          子線程將client中的數(shù)據(jù)發(fā)給客戶端,所以是多線程
          server.c?->?main?->?initThreadedIO(啟動(dòng)一定數(shù)量的線程)?->?IOThreadMain(線程執(zhí)行的方法)?->?writeToClient?->?connWrite?->?connSocketWrite

          網(wǎng)絡(luò)操作對應(yīng)的一些方法,所有connection對象的type字段都是指向CT_Socket

          ConnectionType?CT_Socket?=?{
          ????.ae_handler?=?connSocketEventHandler,
          ????.close?=?connSocketClose,
          ????.write?=?connSocketWrite,
          ????.read?=?connSocketRead,
          ????.accept?=?connSocketAccept,
          ????.connect?=?connSocketConnect,
          ????.set_write_handler?=?connSocketSetWriteHandler,
          ????.set_read_handler?=?connSocketSetReadHandler,
          ????.get_last_error?=?connSocketGetLastError,
          ????.blocking_connect?=?connSocketBlockingConnect,
          ????.sync_write?=?connSocketSyncWrite,
          ????.sync_read?=?connSocketSyncRead,
          ????.sync_readline?=?connSocketSyncReadLine
          };








          粉絲福利:實(shí)戰(zhàn)springboot+CAS單點(diǎn)登錄系統(tǒng)視頻教程免費(fèi)領(lǐng)取

          ???

          ?長按上方微信二維碼?2 秒
          即可獲取資料



          感謝點(diǎn)贊支持下哈?

          瀏覽 77
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久国产视频福利 | 亚洲豆花视频 | 日本一级片在线 | 大香蕉伊人在线视频免费看 | 日韩精品淫秽视频 |