中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)站改版會(huì)影響排名嗎百度推廣代理開(kāi)戶

網(wǎng)站改版會(huì)影響排名嗎,百度推廣代理開(kāi)戶,做網(wǎng)站建設(shè)公司賺錢,鄭州手機(jī)網(wǎng)站建設(shè)公司排名前言:此篇文章系本人學(xué)習(xí)過(guò)程中記錄下來(lái)的筆記,里面難免會(huì)有不少欠缺的地方,誠(chéng)心期待大家多多給予指教。 基礎(chǔ)篇: Redis(一)Redis(二)Redis(三)Redis&#x…

?前言:此篇文章系本人學(xué)習(xí)過(guò)程中記錄下來(lái)的筆記,里面難免會(huì)有不少欠缺的地方,誠(chéng)心期待大家多多給予指教。


基礎(chǔ)篇:

  1. Redis(一)
  2. Redis(二)
  3. Redis(三)
  4. Redis(四)
  5. Redis(五)
  6. Redis(六)
  7. Redis(七)
  8. Redis(八)

進(jìn)階篇:

  1. Redis(九)

接上期內(nèi)容:上期完成了Redis基礎(chǔ)篇的學(xué)習(xí)。下面開(kāi)始學(xué)習(xí)Redis的進(jìn)階知識(shí),話不多說(shuō),直接發(fā)車。


一、前提說(shuō)明

進(jìn)階篇主要學(xué)習(xí)Redis面試中反復(fù)出現(xiàn)的經(jīng)典問(wèn)題以及與之緊密對(duì)應(yīng)的實(shí)際操作。這一階段的學(xué)習(xí)具有一定的挑戰(zhàn)性,需要你具備扎實(shí)的Redis基礎(chǔ)知識(shí)。如果你是零基礎(chǔ)的新手,或者目前基礎(chǔ)較為薄弱,建議你先移步基礎(chǔ)篇。待你在基礎(chǔ)篇中積累了足夠的知識(shí)和經(jīng)驗(yàn),再信心滿滿地回歸,向進(jìn)階篇發(fā)起沖擊。


二、經(jīng)典面試題

(一)、多線程 VS 單線程

1、Redis是多線程還是單線程

答:分版本討論。

redis版本為3.x ,redis是單線程。

redis版本4.x,嚴(yán)格意義來(lái)說(shuō)也不是單線程,而是負(fù)責(zé)處理客戶端請(qǐng)求的線程是單線程,但是開(kāi)始加了點(diǎn)多線程的東西(異步刪除)。

2020年5月版本的6.0.x后及2022年出的7.0版本后,告別了大家印象中的單線程,用一種全新的多線程來(lái)解決問(wèn)題

*總結(jié):分版本討論。Redis6.x之前都可以稱單線程,Redis6.x之后是多線程(這個(gè)多線程主要是用于處理網(wǎng)絡(luò) I/O 操作,而鍵值對(duì)的讀寫(xiě)操作仍然是單線程的)。


2、Redis的單線程指的什么?

答:Redis單線程指Redis的網(wǎng)絡(luò)IO和鍵值對(duì)讀寫(xiě)是由一個(gè)線程來(lái)完成的,Redis在處理客戶端的請(qǐng)求時(shí)包括獲取 (socket 讀)、解析、執(zhí)行、內(nèi)容返回 (socket 寫(xiě)) 等都由一個(gè)順序串行的主線程處理,這就是所謂的“單線程”。這也是Redis對(duì)外提供鍵值存儲(chǔ)服務(wù)的主要流程。

但Redis的其他功能,比如持久化RDB、AOF、異步刪除、集群數(shù)據(jù)同步等等,其實(shí)是由額外的線程執(zhí)行的。

*總結(jié):單線程指的是Redis命令工作線程,但是,整個(gè)Redis(6.x之后)來(lái)說(shuō),是多線程的。


3、Redis4.x之前為啥選擇單線程?

答:①、使用單線程模型是Redis的開(kāi)發(fā)和維護(hù)更簡(jiǎn)單,因?yàn)閱尉€程模型方便開(kāi)發(fā)和調(diào)試。

②、單線程避免了不必要的上下文切換和多線程競(jìng)爭(zhēng),這就省去了多線程切換帶來(lái)的時(shí)間和性能上的消耗,而且單線程不會(huì)導(dǎo)致死鎖問(wèn)題的發(fā)生。

③、即使使用單線程模型也并發(fā)的處理多客戶端的請(qǐng)求,主要使用的是IO多路復(fù)用和非阻塞IO。

④、對(duì)于Redis系統(tǒng)來(lái)說(shuō),主要的性能瓶頸是內(nèi)存或者網(wǎng)絡(luò)帶寬而并非 CPU。


4、既然單線程這么好,為啥又逐漸引入多線程?

答:單線程雖好,但也不是全能。正常情況下使用 del 指令可以很快的刪除數(shù)據(jù),而當(dāng)被刪除的 key 是一個(gè)非常大的對(duì)象時(shí),例如時(shí)包含了成千上萬(wàn)個(gè)元素的 hash 集合時(shí),那么 del 指令就會(huì)造成 Redis 主線程卡頓。這就是redis3.x單線程時(shí)代最經(jīng)典的故障,大key刪除的問(wèn)題。

于是在 Redis 4.x 中就新增了多線程的模塊,當(dāng)然此版本中的多線程主要是為了解決刪除數(shù)據(jù)效率比較低的問(wèn)題的,比如unlink key、flushdb async、flushall async命令。

*總結(jié):逐漸引入多線程是為了彌補(bǔ)單線程的短板,比如刪除bigKey問(wèn)題。


(二)、I/O多路復(fù)用

1、什么是I/O多路復(fù)用?

答:①、I/O一般在操作系統(tǒng)層面指數(shù)據(jù)在內(nèi)核態(tài)和用戶態(tài)之間的讀寫(xiě)操作。

②、多路指多個(gè)客戶端連接(連接就是套接字描述符,即?socket?或者?channel)。

③、復(fù)用指復(fù)用一個(gè)或幾個(gè)線程

*總結(jié):Redis中的I/O多路復(fù)用指一個(gè)或一組線程處理多個(gè)TCP連接,使用單進(jìn)程就能夠?qū)崿F(xiàn)同時(shí)處理多個(gè)客戶端的連接,無(wú)需創(chuàng)建或者維護(hù)過(guò)多的進(jìn)程/線程。

Unix網(wǎng)絡(luò)編程中的I/O多路復(fù)用指的是一種同步的IO模型,實(shí)現(xiàn)一個(gè)線程多個(gè)文件句柄,一旦某個(gè)文件句柄就緒就能夠通知到對(duì)應(yīng)應(yīng)用程序進(jìn)行相應(yīng)的讀寫(xiě)操作,沒(méi)有文件句柄就緒時(shí)就會(huì)阻塞應(yīng)用程序,從而釋放CPU資源。

后續(xù)會(huì)深入研究。


2、主線程和IO線程如何協(xié)作完成請(qǐng)求?

答:

階段一:服務(wù)端和客戶端建立Socket連接,并分配處理線程

首先,主線程負(fù)責(zé)接收建立連接請(qǐng)求,當(dāng)有客戶端請(qǐng)求和實(shí)例建立Socket連接時(shí),主線程會(huì)創(chuàng)建和客戶端的連接,并把 Socket 放入全局等待隊(duì)列中。緊接著,主線程通過(guò)輪詢方法把Socket連接分配給IO線程。

階段二:IO線程讀取并解析請(qǐng)求
主線程一旦把 Socket 分配給IO線程,就會(huì)進(jìn)入阻塞狀態(tài),等待IO線程完成客戶端請(qǐng)求讀取和解析。因?yàn)橛卸鄠€(gè)IO線程在并行處理,所以,這個(gè)過(guò)程很快就可以完成。

階段三:主線程執(zhí)行請(qǐng)求操作
等到IO線程解析完請(qǐng)求,主線程還是會(huì)以單線程的方式執(zhí)行這些命令操作。

階段四:IO線程回寫(xiě) Socket 和主線程清空全局隊(duì)列
當(dāng)主線程執(zhí)行完請(qǐng)求操作后,會(huì)把需要返回的結(jié)果寫(xiě)入緩沖區(qū),然后,主線程會(huì)阻寨等待IO線程,把這些結(jié)果回寫(xiě)到 Socket 中,并返回給客戶端。和IO線程讀取和解析請(qǐng)求一樣,IO線程回寫(xiě)? Socket 時(shí),也是有多個(gè)線程在并發(fā)執(zhí)行,所以回寫(xiě) Socket 的速度也很快。等到IO線程回寫(xiě) Socket 完畢,主線程會(huì)清空全局隊(duì)列,等待客戶端的后續(xù)請(qǐng)求。

*總結(jié):Redis6.x之后將主線程的 IO 讀寫(xiě)任務(wù)拆分給一組獨(dú)立的線程去執(zhí)行,這樣就可以使多個(gè) socket 的讀寫(xiě)可以并行化了,采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò)IO的時(shí)間消耗),將最耗時(shí)的Socket的讀取、請(qǐng)求解析、寫(xiě)入單獨(dú)外包出去,剩下的命令執(zhí)行仍然由主線程串行執(zhí)行并和內(nèi)存的數(shù)據(jù)交互。


(三)、Redis為什么這么快?

答:①、基于內(nèi)存操作:Redis?的所有數(shù)據(jù)都存在內(nèi)存中,因此所有的運(yùn)算都是內(nèi)存級(jí)別的,所以他的性能比較高;

②、高效的數(shù)據(jù)結(jié)構(gòu):Redis?的數(shù)據(jù)結(jié)構(gòu)是專門設(shè)計(jì)的,而這些簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)的查找和操作的時(shí)間大部分復(fù)雜度都是?O(1),因此性能比較高;

③、I/O多路復(fù)用和非阻塞?I/O:Redis 使用?I/O多路復(fù)用功能來(lái)監(jiān)聽(tīng)多個(gè)?Socket 連接客戶端,這樣就可以使用一個(gè)線程連接來(lái)處理多個(gè)請(qǐng)求,減少線程切換帶來(lái)的開(kāi)銷,同時(shí)也避免了?I/O?阻塞操作;

④、上下文切換少:因?yàn)槭菃尉€程模型,因此就避免了不必要的上下文切換和多線程競(jìng)爭(zhēng),這就省去了多線程切換帶來(lái)的時(shí)間和性能上的消耗,而且單線程不會(huì)導(dǎo)致死鎖問(wèn)題的發(fā)生。


三、Redis多線程實(shí)操

(一)、開(kāi)啟多線程

在Redis7的版本中,多線程模式默認(rèn)是關(guān)閉的。

如果需要使用多線程功能,需要在redis.conf中完成兩個(gè)設(shè)置:

1、設(shè)置io-thread-do-reads配置項(xiàng)改為yes,表示啟動(dòng)多線程。

2、io-threads設(shè)置線程個(gè)數(shù)。關(guān)于線程數(shù)的設(shè)置,官方的建議是如果為 4 核的 CPU,建議線程數(shù)設(shè)置為 2 或 3,如果為 8 核 CPU 建議線程數(shù)設(shè)置為 6,線程數(shù)一定要小于機(jī)器核數(shù),線程數(shù)并不是越大越好。


四、深入理解I/O多路復(fù)用和epoll

(一)、出現(xiàn)的背景

在處理并發(fā)多客戶端連接的場(chǎng)景中,在多路復(fù)用技術(shù)出現(xiàn)之前,同步阻塞網(wǎng)絡(luò) I/O 模型是最簡(jiǎn)單且典型的方案。這種模式的核心特點(diǎn)是采用一個(gè)進(jìn)程來(lái)處理一個(gè)網(wǎng)絡(luò)連接,也就是對(duì)應(yīng)一個(gè)用戶請(qǐng)求。

優(yōu)點(diǎn):這種方式非常容易讓人理解,寫(xiě)起代碼來(lái)非常的自然,符合人的直線型思維

缺點(diǎn):性能差,每個(gè)用戶請(qǐng)求到來(lái)都得占用一個(gè)進(jìn)程來(lái)處理,來(lái)一個(gè)請(qǐng)求就要分配一個(gè)進(jìn)程跟進(jìn)處理。隨著客戶端數(shù)量的不斷增加,進(jìn)程數(shù)量也會(huì)相應(yīng)地急劇增長(zhǎng)。每個(gè)進(jìn)程都需要占用一定的系統(tǒng)資源,如內(nèi)存、CPU 時(shí)間片等,這會(huì)導(dǎo)致系統(tǒng)資源的大量消耗,甚至可能出現(xiàn)資源耗盡的情況。


(二)、Unix網(wǎng)絡(luò)編程中的五種IO模型

1、Blocking IO(阻塞式 IO)

在這種模型下,當(dāng)一個(gè)應(yīng)用程序執(zhí)行 IO 操作時(shí),例如讀取數(shù)據(jù),進(jìn)程會(huì)一直處于阻塞狀態(tài),直到數(shù)據(jù)準(zhǔn)備好并且被成功讀取到緩沖區(qū)中才會(huì)繼續(xù)執(zhí)行后續(xù)的代碼。也就是說(shuō),在等待數(shù)據(jù)的過(guò)程中,進(jìn)程無(wú)法進(jìn)行其他任何操作,只能等待。


2、Non-Blocking IO(非阻塞式 IO)

與阻塞式 IO 不同,當(dāng)應(yīng)用程序執(zhí)行 IO 操作時(shí),如果數(shù)據(jù)尚未準(zhǔn)備好,系統(tǒng)不會(huì)讓進(jìn)程進(jìn)入阻塞狀態(tài),而是立即返回一個(gè)錯(cuò)誤信息,告知當(dāng)前數(shù)據(jù)未準(zhǔn)備就緒。應(yīng)用程序可以在不被阻塞的情況下,繼續(xù)執(zhí)行其他任務(wù),并通過(guò)不斷輪詢的方式來(lái)檢查數(shù)據(jù)是否準(zhǔn)備好,從而決定是否再次嘗試進(jìn)行 IO 操作。


3、IO multiplexing(IO 多路復(fù)用)

該模型允許一個(gè)進(jìn)程同時(shí)監(jiān)視多個(gè)文件描述符(fd)的狀態(tài)變化。它通過(guò)使用特定的函數(shù)(如 select、poll、epoll 等)來(lái)監(jiān)聽(tīng)多個(gè)文件描述符,當(dāng)其中任何一個(gè)文件描述符上有數(shù)據(jù)可讀或可寫(xiě)時(shí),函數(shù)會(huì)返回相應(yīng)的信息,進(jìn)程就可以對(duì)這些就緒的文件描述符進(jìn)行對(duì)應(yīng)的 IO 操作。這種方式有效地提高了系統(tǒng)資源的利用率,避免了在多個(gè)文件描述符上進(jìn)行阻塞式 IO 操作時(shí)可能出現(xiàn)的資源浪費(fèi)。


4、signal driven IO(信號(hào)驅(qū)動(dòng)式 IO)(暫不涉及)

5、asynchronous IO(異步式 IO)(暫不涉及


(三)、四種狀態(tài)理解

1、同步(Synchronous)

同步是一種操作模式,在這種模式下,當(dāng)一個(gè)任務(wù)(比如函數(shù)調(diào)用、IO 操作等)被發(fā)起后,調(diào)用者(通常是進(jìn)程或線程)會(huì)一直等待該任務(wù)的完成期間不會(huì)執(zhí)行其他相關(guān)操作。只有當(dāng)任務(wù)執(zhí)行結(jié)束并返回結(jié)果后,調(diào)用者才會(huì)繼續(xù)執(zhí)行后續(xù)的代碼


2、異步(Asynchronous)

異步與同步相反,當(dāng)一個(gè)任務(wù)被發(fā)起后,調(diào)用者不會(huì)等待任務(wù)的完成,而是立即繼續(xù)執(zhí)行后續(xù)的代碼。任務(wù)在后臺(tái)由系統(tǒng)或其他線程進(jìn)行處理,當(dāng)任務(wù)完成時(shí),系統(tǒng)會(huì)以某種方式(如回調(diào)函數(shù)、事件通知等)告知調(diào)用者任務(wù)已完成。


3、阻塞(Blocking)

阻塞狀態(tài)主要用于描述進(jìn)程或線程在執(zhí)行某些操作時(shí)的行為。當(dāng)一個(gè)進(jìn)程或線程執(zhí)行一個(gè)阻塞操作(如阻塞式的 IO 操作)時(shí),它會(huì)暫停自身的執(zhí)行,進(jìn)入等待狀態(tài),直到操作完成(例如數(shù)據(jù)讀取完畢或?qū)懭氤晒?#xff09;才會(huì)繼續(xù)執(zhí)行后續(xù)代碼。


4、非阻塞(Non-Blocking)

非阻塞意味著進(jìn)程或線程在執(zhí)行某些操作時(shí),不會(huì)因?yàn)椴僮魑赐瓿啥粫和?/span>。如果操作不能立即完成,系統(tǒng)會(huì)立即返回一個(gè)狀態(tài)信息(如錯(cuò)誤碼或表示操作未完成的標(biāo)識(shí)),進(jìn)程或線程可以繼續(xù)執(zhí)行其他任務(wù)。


5、組合

在實(shí)際的編程場(chǎng)景中,這四種狀態(tài)常常會(huì)以不同的組合形式出現(xiàn)。例如:

  1. 同步阻塞:最常見(jiàn)的傳統(tǒng)操作模式,比如同步文件讀取操作,在讀取過(guò)程中進(jìn)程被阻塞,等待文件讀取完成。
  2. 同步非阻塞:雖然操作是同步的(需要等待操作結(jié)果),但操作本身是非阻塞的,調(diào)用者需要不斷輪詢檢查操作是否完成。
  3. 異步阻塞:這種情況相對(duì)較少見(jiàn),理論上異步操作不應(yīng)該阻塞調(diào)用者,但在某些特殊情況下,例如在異步操作完成前,調(diào)用者可能因?yàn)榈却惒讲僮鞯慕Y(jié)果而被阻塞(比如等待回調(diào)函數(shù)執(zhí)行完畢)。
  4. 異步非阻塞:這是一種理想的高效模式,任務(wù)在后臺(tái)異步執(zhí)行,調(diào)用者不會(huì)被阻塞,可以繼續(xù)執(zhí)行其他任務(wù),并且當(dāng)任務(wù)完成時(shí),系統(tǒng)會(huì)通過(guò)合適的方式通知調(diào)用者。

*總結(jié)同步、異步的討論對(duì)象是被調(diào)用者(服務(wù)提供者),重點(diǎn)在于獲得調(diào)用結(jié)果的消息通知方式上。阻塞、非阻塞的討論對(duì)象是調(diào)用者(服務(wù)請(qǐng)求者),重點(diǎn)在于等消息時(shí)候的行為,調(diào)用者是否能干其它事。


(四)、BIO、NIO實(shí)操

打算模擬一臺(tái)Redis服務(wù)器,兩臺(tái)兩臺(tái)客戶端來(lái)完成接下來(lái)兩種模式的實(shí)操。

1、BIO(阻塞式IO)

1.1、前提說(shuō)明

了解一個(gè)專業(yè)詞匯,recvfrom


1.2、模型圖

解釋:當(dāng)用戶進(jìn)程調(diào)用了recvfrom這個(gè)系統(tǒng)調(diào)用,kernel(內(nèi)核)就開(kāi)始了IO的第一個(gè)階段:準(zhǔn)備數(shù)據(jù)(對(duì)于網(wǎng)絡(luò)IO來(lái)說(shuō),很多時(shí)候數(shù)據(jù)在一開(kāi)始還沒(méi)有到達(dá)。比如,還沒(méi)有收到一個(gè)完整的UDP包。這個(gè)時(shí)候kernel就要等待足夠的數(shù)據(jù)到來(lái))。這個(gè)過(guò)程需要等待,也就是說(shuō)數(shù)據(jù)被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中是需要一個(gè)過(guò)程的。而在用戶進(jìn)程這邊,整個(gè)進(jìn)程會(huì)被阻塞(當(dāng)然,是進(jìn)程自己選擇的阻塞)。當(dāng)kernel一直等到數(shù)據(jù)準(zhǔn)備好了,它就會(huì)將數(shù)據(jù)從kernel中拷貝到用戶內(nèi)存,然后kernel返回結(jié)果,用戶進(jìn)程才解除block的狀態(tài),重新運(yùn)行起來(lái)。所以,BIO的特點(diǎn)就是在IO執(zhí)行的兩個(gè)階段都被block了。


1.3、代碼驗(yàn)證
1、單線程

RedisServer:

public class RedisServer {public static void main(String[] args) throws IOException {try (ServerSocket serverSocket = new ServerSocket(6379);) {while (true) {System.out.println("-----111 等待連接");Socket socket = serverSocket.accept();//阻塞1 ,等待客戶端連接System.out.println("-----222 成功連接");InputStream inputStream = socket.getInputStream();int length = -1;byte[] bytes = new byte[1024];System.out.println("-----333 等待讀取");while ((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客戶端發(fā)送數(shù)據(jù){System.out.println("-----444 成功讀取" + new String(bytes, 0, length));System.out.println("====================");System.out.println();}inputStream.close();socket.close();}}}}

RedisClient01和RedisClient02:

public class RedisClient01 {public static void main(String[] args) throws IOException {try (Socket socket = new Socket("127.0.0.1", 6379);OutputStream outputStream = socket.getOutputStream()) {while (true) {Scanner scanner = new Scanner(System.in);String string = scanner.next();if (string.equalsIgnoreCase("quit")) {break;}socket.getOutputStream().write(string.getBytes());System.out.println("------input quit keyword to finish......");}}}
}

先啟動(dòng)serve,在啟動(dòng)client,進(jìn)行測(cè)試:

結(jié)論:上面的模型存在很大的問(wèn)題,如果客戶端與服務(wù)端建立了連接,如果這個(gè)連接的客戶端遲遲不發(fā)數(shù)據(jù),主線程就會(huì)一直堵塞在read()方法上,這樣其他客戶端也不能進(jìn)行連接,也就是一次只能處理一個(gè)客戶端,對(duì)客戶端很不友好。

解決辦法:使用多線程模型解決主線程堵塞問(wèn)題。


2、多線程

RedisServerBIOMultiThread:

public class RedisServerBIOMultiThread {public static void main(String[] args) throws IOException {try (ServerSocket serverSocket = new ServerSocket(6379);) {while (true) {System.out.println("-----111 等待連接");Socket socket = serverSocket.accept();//阻塞1 ,等待客戶端連接System.out.println("-----222 成功連接");new Thread(() -> {try {InputStream inputStream = socket.getInputStream();int length = -1;byte[] bytes = new byte[1024];System.out.println("-----333 等待讀取");while ((length = inputStream.read(bytes)) != -1)//阻塞2 ,等待客戶端發(fā)送數(shù)據(jù){System.out.println("-----444 成功讀取" + new String(bytes, 0, length));System.out.println("====================");System.out.println();}inputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}, Thread.currentThread().getName()).start();System.out.println(Thread.currentThread().getName());}}}
}

RedisClient01和RedisClient02不改變。先啟動(dòng)serve,在啟動(dòng)client,進(jìn)行測(cè)試:

結(jié)論:多線程模型確實(shí)解決了單線程模型的問(wèn)題。但是每來(lái)一個(gè)客戶端,就要開(kāi)辟一個(gè)線程,如果來(lái)1萬(wàn)個(gè)客戶端,那就要開(kāi)辟1萬(wàn)個(gè)線程。在操作系統(tǒng)中用戶態(tài)不能直接開(kāi)辟線程,需要調(diào)用內(nèi)核來(lái)創(chuàng)建的一個(gè)線程。這其中還涉及到用戶狀態(tài)的切換(上下文的切換),十分耗資源。

解決辦法:

①、使用線程池。但是線程池在客戶端連接少的情況下可以使用,但是用戶量大的情況下,你不知道線程池要多大,太大了內(nèi)存可能不夠,也不可行。

②、使用NIO(非阻塞式IO)方式。因?yàn)閞ead()方法堵塞了,所有要開(kāi)辟多個(gè)線程,如果什么方法能使read()方法不堵塞,這樣就不用開(kāi)辟多個(gè)線程了,這就用到了另一個(gè)IO模型,NIO(非阻塞式IO)。


1.4、BIO優(yōu)劣勢(shì)總結(jié)

優(yōu)勢(shì):

  • 實(shí)現(xiàn)簡(jiǎn)單編程模型簡(jiǎn)單直觀,對(duì)于初學(xué)者和簡(jiǎn)單應(yīng)用場(chǎng)景,能快速完成系統(tǒng)搭建。
  • 可靠性高:阻塞機(jī)制使程序執(zhí)行流程清晰,降低了并發(fā)問(wèn)題出現(xiàn)的概率,保證了程序的可靠性。

劣勢(shì):

  • 性能瓶頸:高并發(fā)場(chǎng)景下,每個(gè)連接需獨(dú)立線程處理,隨著連接數(shù)增加,線程數(shù)量增多,會(huì)大量消耗系統(tǒng)資源(如 CPU、內(nèi)存),且線程創(chuàng)建、銷毀和上下文切換會(huì)帶來(lái)額外開(kāi)銷。
  • 可擴(kuò)展性差:受線程數(shù)量限制,難以處理大規(guī)模并發(fā)連接,當(dāng)并發(fā)數(shù)超過(guò)系統(tǒng)承受能力,會(huì)導(dǎo)致性能急劇下降甚至系統(tǒng)崩潰。

很明顯這種模式不適合Redis的定位。


2、NIO(非阻塞式IO)

2.1、前提說(shuō)明

在傳統(tǒng)的 BIO(Blocking I/O)模式中,服務(wù)器會(huì)為每個(gè)新接入的客戶端連接單獨(dú)分配一個(gè)線程來(lái)處理。隨著客戶端數(shù)量的增多,大量線程會(huì)被創(chuàng)建,這不僅會(huì)導(dǎo)致系統(tǒng)資源的過(guò)度消耗,還會(huì)因頻繁的線程上下文切換而降低性能。為有效解決這一問(wèn)題,可將多個(gè)客戶端的Socket連接統(tǒng)一存放在容器中進(jìn)行管理,以實(shí)現(xiàn)對(duì)資源的高效利用和連接的統(tǒng)一調(diào)度,為此NIO模型誕生。

在NIO模式中,一切都是非阻塞的

accept()方法非阻塞,如果沒(méi)有客戶端連接,就返回?zé)o連接標(biāo)識(shí)。read()方法是非阻塞,如果。

read()方法讀取不到數(shù)據(jù)就返回空閑中標(biāo)識(shí),如果讀取到數(shù)據(jù)時(shí),只阻塞read()方法讀數(shù)據(jù)的時(shí)間。

在NIO模式中,只有一個(gè)線程:當(dāng)一個(gè)客戶端與服務(wù)端進(jìn)行連接,這個(gè)socket就會(huì)加入到一個(gè)數(shù)組中,隔一段時(shí)間遍歷一次,看這個(gè)socket的read()方法能否讀到數(shù)據(jù),這樣一個(gè)線程就能處理多個(gè)客戶端的連接和讀取了。


2.2、模型圖

解釋:當(dāng)用戶進(jìn)程發(fā)出read操作時(shí),如果kernel(內(nèi)核)中的數(shù)據(jù)還沒(méi)有準(zhǔn)備好,那么它并不會(huì)block用戶進(jìn)程,而是立刻返回一個(gè)error。從用戶進(jìn)程角度講 ,它發(fā)起一個(gè)read操作后,并不需要等待,而是馬上就得到了一個(gè)結(jié)果。用戶進(jìn)程判斷結(jié)果是一個(gè)error時(shí),它就知道數(shù)據(jù)還沒(méi)有準(zhǔn)備好,于是它可以再次發(fā)送read操作。一旦kernel中的數(shù)據(jù)準(zhǔn)備好了,并且又再次收到了用戶進(jìn)程的system call,那么它馬上就將數(shù)據(jù)拷貝到了用戶內(nèi)存,然后返回。所以,NIO特點(diǎn)是用戶進(jìn)程需要不斷的主動(dòng)詢問(wèn)內(nèi)核數(shù)據(jù)準(zhǔn)備好了嗎?一句話,用輪詢替代阻塞!


2.3、代碼驗(yàn)證

新建RedisServerNIO:

public class RedisServerNIO {// 模擬Socket容器static ArrayList<SocketChannel> socketList = new ArrayList<>();//模擬數(shù)據(jù)報(bào)存儲(chǔ)空間static ByteBuffer byteBuffer = ByteBuffer.allocate(1024);public static void main(String[] args) throws IOException {try (ServerSocketChannel serverSocket = ServerSocketChannel.open()) {System.out.println("---------RedisServerNIO 啟動(dòng)等待中......");serverSocket.bind(new InetSocketAddress("127.0.0.1", 6379));serverSocket.configureBlocking(false);//設(shè)置為非阻塞模式while (true) {// 輪詢?nèi)萜鱢or (SocketChannel element : socketList) {int read = element.read(byteBuffer);if (read > 0) {System.out.println("-----讀取數(shù)據(jù): " + read);byteBuffer.flip();byte[] bytes = new byte[read];byteBuffer.get(bytes);System.out.println(new String(bytes));byteBuffer.clear();}}SocketChannel socketChannel = serverSocket.accept();if (socketChannel != null) {System.out.println("-----成功連接: ");socketChannel.configureBlocking(false);//設(shè)置為非阻塞模式socketList.add(socketChannel);System.out.println("-----socketList size: " + socketList.size());}}}}
}

?RedisClient01和RedisClient02不改變。先啟動(dòng)serve,在啟動(dòng)client,進(jìn)行測(cè)試:

?

結(jié)論:NIO成功的解決了BIO需要開(kāi)啟多線程的問(wèn)題。在NIO中雖然一個(gè)線程就能解決多個(gè)socket,但是還存在其他2個(gè)問(wèn)題。

Q1:這個(gè)模型在客戶端少的時(shí)候十分好用,但是客戶端如果很多,性能如何?

在有1萬(wàn)個(gè)客戶端連接的情況下,每次循環(huán)都要遍歷全部1萬(wàn)個(gè)Socket。即便只有10個(gè)Socket有數(shù)據(jù),但需要遍歷1萬(wàn)次,那9990次遍歷就是浪費(fèi)資源的系統(tǒng)調(diào)用。


Q2這個(gè)遍歷過(guò)程是在用戶態(tài)進(jìn)行的,用戶態(tài)判斷socket是否有數(shù)據(jù)還是調(diào)用內(nèi)核的read()方法實(shí)現(xiàn)的,這就涉及到用戶態(tài)和內(nèi)核態(tài)的切換,每遍歷一個(gè)就要切換一次,開(kāi)銷依舊很大。


2.4、NIO優(yōu)劣勢(shì)總結(jié)

優(yōu)勢(shì):

  • 資源利用率相比于BIO高由于線程不會(huì)因 I/O 操作而長(zhǎng)時(shí)間阻塞,系統(tǒng)可以使用更少的線程來(lái)處理更多的連接,減少了線程創(chuàng)建和上下文切換的開(kāi)銷,從而提高了資源的利用率。
  • 實(shí)時(shí)性好:線程可以在發(fā)起讀或?qū)懖僮骱罅⒓捶祷?#xff0c;不用等待數(shù)據(jù)傳輸完成。

劣勢(shì):

  • 可靠性問(wèn)題在高并發(fā)場(chǎng)景下,可能慧出現(xiàn)空輪詢問(wèn)題,需要進(jìn)行額外的處理和優(yōu)化。
  • 編程復(fù)雜度高:NIO 的編程模型相對(duì)復(fù)雜,要處理各種復(fù)雜的事件和狀態(tài)變化,增加了開(kāi)發(fā)和調(diào)試的難度。

很明顯這種模式也不是Redis的最優(yōu)選擇。


(五)、IO多路復(fù)用

1、定義

I/O 多路復(fù)用是一種讓單個(gè)線程能夠同時(shí)監(jiān)視多個(gè)文件描述符(如套接字)的 I/O 狀態(tài)變化的機(jī)制。當(dāng)其中任何一個(gè)或多個(gè)文件描述符就緒(可讀、可寫(xiě)或發(fā)生異常)時(shí),程序能夠及時(shí)感知并進(jìn)行相應(yīng)的 I/O 操作。


2、模型圖

解釋:IO multiplexing就是我們說(shuō)的select,poll,epoll,有些技術(shù)書(shū)籍也稱這種IO方式為event driven IO事件驅(qū)動(dòng)IO。就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫(xiě)就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作。可以基于一個(gè)阻塞對(duì)象并同時(shí)在多個(gè)描述符上等待就緒,而不是使用多個(gè)線程(每個(gè)文件描述符一個(gè)線程,每次new一個(gè)線程),這樣可以大大節(jié)省系統(tǒng)資源。所以,I/O 多路復(fù)用的特點(diǎn)是通過(guò)一種機(jī)制使得一個(gè)進(jìn)程能同時(shí)等待多個(gè)文件描述符而這些文件描述符(套接字描述符)其中的任意一個(gè)進(jìn)入讀就緒狀態(tài),select,poll,epoll等函數(shù)就可以返回。


3、具體實(shí)現(xiàn)

前提說(shuō)明:由于這三個(gè)函數(shù)都是C語(yǔ)言的內(nèi)容,不做深入研究,大概了解原理即可。

3.1、select
①、定義

select(2) - Linux manual page可以在Linux上通過(guò)man手冊(cè)參看,也可以通過(guò)Linux官網(wǎng)查看,select(2) - Linux manual page


②、核心思想

通過(guò)設(shè)置一個(gè)文件描述符集合(實(shí)際上是一個(gè)bitmap),調(diào)用 select 函數(shù)時(shí)將該集合從用戶空間復(fù)制到內(nèi)核空間,內(nèi)核檢查集合中的文件描述符狀態(tài),當(dāng)有文件描述符就緒時(shí),select 返回,程序再遍歷集合來(lái)確定哪些文件描述符就緒。類似于我們自己寫(xiě)的這段Java代碼。


③、執(zhí)行流程
  1. select是一個(gè)阻塞函數(shù),當(dāng)沒(méi)有數(shù)據(jù)時(shí),會(huì)一直阻塞在select那一行。
  2. 當(dāng)有數(shù)據(jù)時(shí)會(huì)將rset中對(duì)應(yīng)的那一位置為1。
  3. select函數(shù)返回,不再阻塞。
  4. 遍歷文件描述符數(shù)組,判斷哪個(gè)fd被置位了。
  5. 讀取數(shù)據(jù),然后處理。

④、優(yōu)缺點(diǎn)

優(yōu):

  • 是最早的 I/O 多路復(fù)用實(shí)現(xiàn),幾乎在所有操作系統(tǒng)上都有支持,具有良好的跨平臺(tái)性。
  • 用戶態(tài)和內(nèi)核態(tài)不用頻繁切換,節(jié)省系統(tǒng)資源。

缺:

  • 能監(jiān)視的文件描述符數(shù)量有限(通常受限于 FD_SETSIZE),最大1024。
  • rset每次循環(huán)都必須重新置位為0,不可重復(fù)使用。
  • 每次調(diào)用select都需要進(jìn)行用戶空間和內(nèi)核空間的文件描述符集合復(fù)制,開(kāi)銷較大。
  • 需要遍歷整個(gè)文件描述符集合來(lái)查找就緒的文件描述符,時(shí)間復(fù)雜度為 O (n)。

⑤、小總結(jié)

select方式,雖然做到了一個(gè)線程處理多個(gè)客戶端連接(文件描述符),同時(shí)又減少了系統(tǒng)調(diào)用的開(kāi)銷(多個(gè)文件描述符只有一次 select 的系統(tǒng)調(diào)用 + N次就緒狀態(tài)的文件描述符的 read 系統(tǒng)調(diào)用),但也有一定的優(yōu)化空間。


3.2、poll
①、定義

?可以在Linux上通過(guò)man手冊(cè)參看,也可以通過(guò)Linux官網(wǎng)查看,poll(2) - Linux manual page


②、核心思想

優(yōu)化掉了select的前兩個(gè)缺點(diǎn)。


③、執(zhí)行流程
  1. 將fd數(shù)組從用戶態(tài)拷貝到內(nèi)核態(tài)。
  2. poll為阻塞方法,執(zhí)行pol方法,如果有數(shù)據(jù)會(huì)將fd對(duì)應(yīng)的revents置為pollin。
  3. poll方法返回。
  4. 循環(huán)遍歷,查找那個(gè)fd被置位為pollin了。
  5. 將revents重置為0,便于復(fù)用。
  6. 遍歷fd,找到置位的fd進(jìn)行讀取和處理。

④、優(yōu)缺點(diǎn)

優(yōu):

  • poll使用pollfd數(shù)組來(lái)代替select中的bitmap,解決了select文件描述符數(shù)量限制的問(wèn)題。
  • 當(dāng)pollfds數(shù)組中有事件發(fā)生,相應(yīng)的revents置位為1,遍歷的時(shí)候又置位回零,實(shí)現(xiàn)了pollfd數(shù)組的重用。

缺:

  • pollfds數(shù)組拷貝到了內(nèi)核態(tài),仍然有開(kāi)銷。
  • 每次調(diào)用 poll 后仍需要遍歷整個(gè)數(shù)組來(lái)查找就緒的文件描述符,時(shí)間復(fù)雜度為 O (n)。

⑤、小總結(jié)

相比于select,雖做了優(yōu)化,但是原理基本一致。核心問(wèn)題并沒(méi)有解決掉。


3.3、epoll
①、定義

可以在Linux上通過(guò)man手冊(cè)參看,也可以通過(guò)Linux官網(wǎng)查看,epoll(7) - Linux manual page


②、核心思想

使用紅黑樹(shù)來(lái)管理注冊(cè)的文件描述符,用鏈表來(lái)存儲(chǔ)就緒的文件描述符。通過(guò) epoll_create 創(chuàng)建一個(gè) epoll 實(shí)例,epoll_ctl 用于添加、修改或刪除文件描述符的監(jiān)視事件,epoll_wait 等待就緒的文件描述符,徹底優(yōu)化掉select、poll沒(méi)解決掉的問(wèn)題。


③、執(zhí)行流程
  1. 首先epoll是非阻塞的,當(dāng)有數(shù)據(jù)的時(shí)候,會(huì)把相應(yīng)的文件描述符“置位”,但是epool沒(méi)有revent標(biāo)志位,所以并不是真正的置位,這時(shí)候會(huì)把有數(shù)據(jù)的文件描述符放到隊(duì)首。
  2. epoll會(huì)返回有數(shù)據(jù)的文件描述符的個(gè)數(shù)。
  3. 根據(jù)返回的個(gè)數(shù),讀取前N個(gè)文件描述符即可。(重點(diǎn):不是全部遍歷)
  4. 讀取、處理。

④、優(yōu)缺點(diǎn)

優(yōu):

  • select、poll具備的優(yōu)點(diǎn),epoll都有。
  • epoll_wait只返回就緒的文件描述符,無(wú)需遍歷所有注冊(cè)的文件描述符,時(shí)間復(fù)雜度為 O (1)。
  • epoll僅注冊(cè)時(shí)進(jìn)行一次用戶空間到內(nèi)核空間的復(fù)制,而select、poll都是兩次。

缺:Linux 系統(tǒng)特有的,不具備跨平臺(tái)性。


⑤、小總結(jié)

epoll在 Linux 系統(tǒng)的高并發(fā)場(chǎng)景下表現(xiàn)出色,是一種非常先進(jìn)的 I/O 多路復(fù)用實(shí)現(xiàn)。這也是Redis為啥這么快的根本原因所在。


五、epoll在Redis中的體現(xiàn)

Redis利用epoll來(lái)實(shí)現(xiàn)IO多路復(fù)用,將連接信息和事件放到隊(duì)列中,一次放到文件事件分派器,事件分派器將事件分發(fā)給事件處理器。

Redis 是跑在單線程中的,所有的操作都是按照順序線性執(zhí)行的,但是由于讀寫(xiě)操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會(huì)導(dǎo)致某一文件的 I/O 阻塞導(dǎo)致整個(gè)進(jìn)程無(wú)法對(duì)其它客戶提供服務(wù),而 I/O 多路復(fù)用就是為了解決這個(gè)問(wèn)題而出現(xiàn)。

此外Redis 服務(wù)采用 Reactor 的方式來(lái)實(shí)現(xiàn)文件事件處理器(每一個(gè)網(wǎng)絡(luò)連接其實(shí)都對(duì)應(yīng)一個(gè)文件描述符)。

Redis基于Reactor模式開(kāi)發(fā)了網(wǎng)絡(luò)事件處理器,這個(gè)處理器被稱為文件事件處理器。它的組成結(jié)構(gòu)為4部分:多個(gè)套接字、IO多路復(fù)用程序、文件事件分派器、事件處理器。因?yàn)槲募录峙善麝?duì)列的消費(fèi)是單線程的,所以Redis才叫單線程模型

參考《Redis?設(shè)計(jì)與實(shí)現(xiàn)》。


六、總結(jié)

在對(duì) Redis 的進(jìn)階知識(shí)學(xué)習(xí)時(shí),通過(guò)將一些經(jīng)典面試題拆分、細(xì)化、理解的方法,開(kāi)展了系統(tǒng)性的研究與學(xué)習(xí),使我對(duì) Redis 的理解有了新的深度,為以后找工作又打了夯實(shí)的基礎(chǔ)。


ps:努力到底,讓持續(xù)學(xué)習(xí)成為貫穿一生的堅(jiān)守。學(xué)習(xí)筆記持續(xù)更新中。。。。

http://www.risenshineclean.com/news/6005.html

相關(guān)文章:

  • 企業(yè)建設(shè)網(wǎng)站的主要目的有哪些搜索引擎優(yōu)化營(yíng)銷
  • java購(gòu)物網(wǎng)站掃碼支付怎么做宜興百度推廣
  • 不同網(wǎng)站建設(shè)特點(diǎn)石家莊seo推廣公司
  • 蕪湖網(wǎng)站建設(shè)公司官網(wǎng)seo是什么意思
  • 網(wǎng)站扁平化盤搜搜
  • 規(guī)劃電子商務(wù)網(wǎng)站流程bt磁力搜索
  • 沒(méi)有排名的網(wǎng)站怎么做營(yíng)銷的四種方式
  • 做國(guó)際黃金看什么網(wǎng)站寶雞網(wǎng)站seo
  • 口碑好的丹陽(yáng)網(wǎng)站建設(shè)企業(yè)培訓(xùn)考試
  • 2017網(wǎng)站建設(shè)百度網(wǎng)站app下載
  • 連云港網(wǎng)站建設(shè)價(jià)格我要推廣
  • 新媒體與網(wǎng)站建設(shè)小紅書(shū)外鏈管家
  • 薊縣網(wǎng)站制作培訓(xùn)網(wǎng)站源碼
  • 網(wǎng)絡(luò)營(yíng)銷企業(yè)網(wǎng)站推廣廣告信息發(fā)布平臺(tái)
  • vs做網(wǎng)站通過(guò)e瀏覽器南寧百度seo軟件
  • 網(wǎng)站空間大小 論壇青島seo優(yōu)化
  • 靜態(tài)網(wǎng)站制作價(jià)格seo是什么公司
  • 東莞網(wǎng)站推廣培訓(xùn)市場(chǎng)營(yíng)銷計(jì)劃
  • 淮南市潘集區(qū)信息建設(shè)網(wǎng)站種子搜索神器 bt 下載
  • 西安網(wǎng)站建設(shè)制作專業(yè)公司活動(dòng)推廣軟文范例
  • 在國(guó)外做外國(guó)的成人網(wǎng)站合法嗎寧波seo外包優(yōu)化
  • 好的平面網(wǎng)站模板制作網(wǎng)站的app
  • 門戶網(wǎng)站開(kāi)發(fā)模板論文收錄網(wǎng)站
  • 學(xué)校網(wǎng)站建設(shè)開(kāi)發(fā)方案書(shū)平臺(tái)外宣推廣技巧
  • vue 做雙語(yǔ)版網(wǎng)站電商代運(yùn)營(yíng)十大公司排名
  • 學(xué)php動(dòng)態(tài)網(wǎng)站開(kāi)發(fā)好就業(yè)搜索引擎營(yíng)銷策略有哪些
  • 谷德設(shè)計(jì)網(wǎng)入口貴陽(yáng)網(wǎng)站優(yōu)化公司
  • 網(wǎng)站開(kāi)發(fā)dreamweaver站長(zhǎng)工具ip地址查詢
  • 教做湘菜的視頻網(wǎng)站可靠的網(wǎng)站優(yōu)化
  • 怎么做淘寶客的跳轉(zhuǎn)網(wǎng)站谷歌網(wǎng)站收錄提交入口