99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Redis 事件

2018-08-02 11:57 更新

事件

事件是 Redis 服務(wù)器的核心,它處理兩項(xiàng)重要的任務(wù):

  1. 處理文件事件:在多個(gè)客戶端中實(shí)現(xiàn)多路復(fù)用,接受它們發(fā)來(lái)的命令請(qǐng)求,并將命令的執(zhí)行結(jié)果返回給客戶端。
  2. 時(shí)間事件:實(shí)現(xiàn)服務(wù)器常規(guī)操作(server cron job)。

本文以下內(nèi)容就來(lái)介紹這兩種事件,以及它們背后的運(yùn)作模式。

文件事件

Redis 服務(wù)器通過(guò)在多個(gè)客戶端之間進(jìn)行多路復(fù)用,從而實(shí)現(xiàn)高效的命令請(qǐng)求處理:多個(gè)客戶端通過(guò)套接字連接到 Redis 服務(wù)器中,但只有在套接字可以無(wú)阻塞地進(jìn)行讀或者寫(xiě)時(shí),服務(wù)器才會(huì)和這些客戶端進(jìn)行交互。

Redis 將這類(lèi)因?yàn)閷?duì)套接字進(jìn)行多路復(fù)用而產(chǎn)生的事件稱(chēng)為文件事件(file event),文件事件可以分為讀事件和寫(xiě)事件兩類(lèi)。

讀事件

讀事件標(biāo)志著客戶端命令請(qǐng)求的發(fā)送狀態(tài)。

當(dāng)一個(gè)新的客戶端連接到服務(wù)器時(shí),服務(wù)器會(huì)給為該客戶端綁定讀事件,直到客戶端斷開(kāi)連接之后,這個(gè)讀事件才會(huì)被移除。

讀事件在整個(gè)網(wǎng)絡(luò)連接的生命期內(nèi),都會(huì)在等待和就緒兩種狀態(tài)之間切換:

  • 當(dāng)客戶端只是連接到服務(wù)器,但并沒(méi)有向服務(wù)器發(fā)送命令時(shí),該客戶端的讀事件就處于等待狀態(tài)。
  • 當(dāng)客戶端給服務(wù)器發(fā)送命令請(qǐng)求,并且請(qǐng)求已到達(dá)時(shí)(相應(yīng)的套接字可以無(wú)阻塞地執(zhí)行讀操作),該客戶端的讀事件處于就緒狀態(tài)。

作為例子,下圖展示了三個(gè)已連接到服務(wù)器、但并沒(méi)有發(fā)送命令的客戶端:

digraph e {    node [style = filled];    edge [style = 

server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cy -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cz -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"];}" />

這三個(gè)客戶端的狀態(tài)如下表:

客戶端 讀事件狀態(tài) 命令發(fā)送狀態(tài)
客戶端 X 等待 未發(fā)送
客戶端 Y 等待 未發(fā)送
客戶端 Z 等待 未發(fā)送

之后,當(dāng)客戶端 X 向服務(wù)器發(fā)送命令請(qǐng)求,并且命令請(qǐng)求已到達(dá)時(shí),客戶端 X 的讀事件狀態(tài)變?yōu)榫途w:

digraph e {    node [style = filled];    edge [style = 

server [style= "dashed, bold" , label="發(fā)送命令請(qǐng)求", color = "#B22222"]; cy -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cz -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"];}" />

這時(shí),三個(gè)客戶端的狀態(tài)如下表(只有客戶端 X 的狀態(tài)被更新了):

客戶端 讀事件狀態(tài) 命令發(fā)送狀態(tài)
客戶端 X 就緒 已發(fā)送,并且已到達(dá)
客戶端 Y 等待 未發(fā)送
客戶端 Z 等待 未發(fā)送

當(dāng)事件處理器被執(zhí)行時(shí),就緒的文件事件會(huì)被識(shí)別到,相應(yīng)的命令請(qǐng)求會(huì)被發(fā)送到命令執(zhí)行器,并對(duì)命令進(jìn)行求值。

寫(xiě)事件

寫(xiě)事件標(biāo)志著客戶端對(duì)命令結(jié)果的接收狀態(tài)。

和客戶端自始至終都關(guān)聯(lián)著讀事件不同,服務(wù)器只會(huì)在有命令結(jié)果要傳回給客戶端時(shí),才會(huì)為客戶端關(guān)聯(lián)寫(xiě)事件,并且在命令結(jié)果傳送完畢之后,客戶端和寫(xiě)事件的關(guān)聯(lián)就會(huì)被移除。

一個(gè)寫(xiě)事件會(huì)在兩種狀態(tài)之間切換:

  • 當(dāng)服務(wù)器有命令結(jié)果需要返回給客戶端,但客戶端還未能執(zhí)行無(wú)阻塞寫(xiě),那么寫(xiě)事件處于等待狀態(tài)。
  • 當(dāng)服務(wù)器有命令結(jié)果需要返回給客戶端,并且客戶端可以進(jìn)行無(wú)阻塞寫(xiě),那么寫(xiě)事件處于就緒狀態(tài)。

當(dāng)客戶端向服務(wù)器發(fā)送命令請(qǐng)求,并且請(qǐng)求被接受并執(zhí)行之后,服務(wù)器就需要將保存在緩存內(nèi)的命令執(zhí)行結(jié)果返回給客戶端,這時(shí)服務(wù)器就會(huì)為客戶端關(guān)聯(lián)寫(xiě)事件。

作為例子,下圖展示了三個(gè)連接到服務(wù)器的客戶端,其中服務(wù)器正等待客戶端 X 變得可寫(xiě),從而將命令的執(zhí)行結(jié)果返回給它:

digraph e {    node [style = filled];    edge [style = 

server [dir=none, style=dotted, label="等待將命令結(jié)果返回\n等待命令請(qǐng)求"]; cy -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cz -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"];}" />

此時(shí)三個(gè)客戶端的事件狀態(tài)分別如下表:

客戶端 讀事件狀態(tài) 寫(xiě)事件狀態(tài)
客戶端 X 等待 等待
客戶端 Y 等待 無(wú)
客戶端 Z 等待 無(wú)

當(dāng)客戶端 X 的套接字可以進(jìn)行無(wú)阻塞寫(xiě)操作時(shí),寫(xiě)事件就緒,服務(wù)器將保存在緩存內(nèi)的命令執(zhí)行結(jié)果返回給客戶端:

digraph e {    node [style = filled];    edge [style =

server [dir=back, style="dashed, bold", label="返回命令執(zhí)行結(jié)果\n等待命令請(qǐng)求", color = "#B22222"]; cy -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cz -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"];}" />

此時(shí)三個(gè)客戶端的事件狀態(tài)分別如下表(只有客戶端 X 的狀態(tài)被更新了):

客戶端 讀事件狀態(tài) 寫(xiě)事件狀態(tài)
客戶端 X 等待 已就緒
客戶端 Y 等待 無(wú)
客戶端 Z 等待 無(wú)

當(dāng)命令執(zhí)行結(jié)果被傳送回客戶端之后,客戶端和寫(xiě)事件之間的關(guān)聯(lián)會(huì)被解除(只剩下讀事件),至此,返回命令執(zhí)行結(jié)果的動(dòng)作執(zhí)行完畢:

digraph e {    node [style = filled];    edge [style =

server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cy -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"]; cz -> server [dir=none, style=dotted, label="等待命令請(qǐng)求"];}" />

Note

同時(shí)關(guān)聯(lián)寫(xiě)事件和讀事件

前面提到過(guò),讀事件只有在客戶端斷開(kāi)和服務(wù)器的連接時(shí),才會(huì)被移除。

這也就是說(shuō),當(dāng)客戶端關(guān)聯(lián)寫(xiě)事件的時(shí)候,實(shí)際上它在同時(shí)關(guān)聯(lián)讀/寫(xiě)兩種事件。

因?yàn)樵谕淮挝募录幚砥鞯恼{(diào)用中,單個(gè)客戶端只能執(zhí)行其中一種事件(要么讀,要么寫(xiě),但不能又讀又寫(xiě)),當(dāng)出現(xiàn)讀事件和寫(xiě)事件同時(shí)就緒的情況時(shí),事件處理器優(yōu)先處理讀事件。

這也就是說(shuō),當(dāng)服務(wù)器有命令結(jié)果要返回客戶端,而客戶端又有新命令請(qǐng)求進(jìn)入時(shí),服務(wù)器先處理新命令請(qǐng)求。

時(shí)間事件

時(shí)間事件記錄著那些要在指定時(shí)間點(diǎn)運(yùn)行的事件,多個(gè)時(shí)間事件以無(wú)序鏈表的形式保存在服務(wù)器狀態(tài)中。

每個(gè)時(shí)間事件主要由三個(gè)屬性組成:

  • when :以毫秒格式的 UNIX 時(shí)間戳為單位,記錄了應(yīng)該在什么時(shí)間點(diǎn)執(zhí)行事件處理函數(shù)。
  • timeProc :事件處理函數(shù)。
  • next 指向下一個(gè)時(shí)間事件,形成鏈表。

根據(jù) timeProc 函數(shù)的返回值,可以將時(shí)間事件劃分為兩類(lèi):

  • 如果事件處理函數(shù)返回 ae.h/AE_NOMORE ,那么這個(gè)事件為單次執(zhí)行事件:該事件會(huì)在指定的時(shí)間被處理一次,之后該事件就會(huì)被刪除,不再執(zhí)行。
  • 如果事件處理函數(shù)返回一個(gè)非 AE_NOMORE 的整數(shù)值,那么這個(gè)事件為循環(huán)執(zhí)行事件:該事件會(huì)在指定的時(shí)間被處理,之后它會(huì)按照事件處理函數(shù)的返回值,更新事件的 when 屬性,讓這個(gè)事件在之后的某個(gè)時(shí)間點(diǎn)再次運(yùn)行,并以這種方式一直更新并運(yùn)行下去。

可以用偽代碼來(lái)表示這兩種事件的處理方式:

def handle_time_event(server, time_event):

    # 執(zhí)行事件處理器,并獲取返回值
    # 返回值可以是 AE_NOMORE ,或者一個(gè)表示毫秒數(shù)的非符整數(shù)值
    retval = time_event.timeProc()

    if retval == AE_NOMORE:

        # 如果返回 AE_NOMORE ,那么將事件從鏈表中刪除,不再執(zhí)行
        server.time_event_linked_list.delete(time_event)

    else:

        # 否則,更新事件的 when 屬性
        # 讓它在當(dāng)前時(shí)間之后的 retval 毫秒之后再次運(yùn)行
        time_event.when = unix_ts_in_ms() + retval

當(dāng)時(shí)間事件處理器被執(zhí)行時(shí),它遍歷所有鏈表中的時(shí)間事件,檢查它們的到達(dá)事件(when 屬性),并執(zhí)行其中的已到達(dá)事件:

def process_time_event(server):

    # 遍歷時(shí)間事件鏈表
    for time_event in server.time_event_linked_list:

        # 檢查事件是否已經(jīng)到達(dá)
        if time_event.when <= unix_ts_in_ms():

            # 處理已到達(dá)事件
            handle_time_event(server, time_event)

Note

無(wú)序鏈表并不影響時(shí)間事件處理器的性能

在目前的版本中,正常模式下的 Redis 只帶有 serverCron 一個(gè)時(shí)間事件,而在 benchmark 模式下,Redis 也只使用兩個(gè)時(shí)間事件。

在這種情況下,程序幾乎是將無(wú)序鏈表退化成一個(gè)指針來(lái)使用,所以使用無(wú)序鏈表來(lái)保存時(shí)間事件,并不影響事件處理器的性能。

時(shí)間事件應(yīng)用實(shí)例:服務(wù)器常規(guī)操作

對(duì)于持續(xù)運(yùn)行的服務(wù)器來(lái)說(shuō),服務(wù)器需要定期對(duì)自身的資源和狀態(tài)進(jìn)行必要的檢查和整理,從而讓服務(wù)器維持在一個(gè)健康穩(wěn)定的狀態(tài),這類(lèi)操作被統(tǒng)稱(chēng)為常規(guī)操作(cron job)。

在 Redis 中,常規(guī)操作由 redis.c/serverCron 實(shí)現(xiàn),它主要執(zhí)行以下操作:

  • 更新服務(wù)器的各類(lèi)統(tǒng)計(jì)信息,比如時(shí)間、內(nèi)存占用、數(shù)據(jù)庫(kù)占用情況等。
  • 清理數(shù)據(jù)庫(kù)中的過(guò)期鍵值對(duì)。
  • 對(duì)不合理的數(shù)據(jù)庫(kù)進(jìn)行大小調(diào)整。
  • 關(guān)閉和清理連接失效的客戶端。
  • 嘗試進(jìn)行 AOF 或 RDB 持久化操作。
  • 如果服務(wù)器是主節(jié)點(diǎn)的話,對(duì)附屬節(jié)點(diǎn)進(jìn)行定期同步。
  • 如果處于集群模式的話,對(duì)集群進(jìn)行定期同步和連接測(cè)試。

Redis 將 serverCron 作為時(shí)間事件來(lái)運(yùn)行,從而確保它每隔一段時(shí)間就會(huì)自動(dòng)運(yùn)行一次,又因?yàn)?serverCron 需要在 Redis 服務(wù)器運(yùn)行期間一直定期運(yùn)行,所以它是一個(gè)循環(huán)時(shí)間事件:serverCron 會(huì)一直定期執(zhí)行,直到服務(wù)器關(guān)閉為止。

在 Redis 2.6 版本中,程序規(guī)定 serverCron 每秒運(yùn)行 10 次,平均每 100 毫秒運(yùn)行一次。從 Redis 2.8 開(kāi)始,用戶可以通過(guò)修改 hz 選項(xiàng)來(lái)調(diào)整 serverCron 的每秒執(zhí)行次數(shù),具體信息請(qǐng)參考 redis.conf 文件中關(guān)于 hz 選項(xiàng)的說(shuō)明。

事件的執(zhí)行與調(diào)度

既然 Redis 里面既有文件事件,又有時(shí)間事件,那么如何調(diào)度這兩種事件就成了一個(gè)關(guān)鍵問(wèn)題。

簡(jiǎn)單地說(shuō),Redis 里面的兩種事件呈合作關(guān)系,它們之間包含以下三種屬性:

  1. 一種事件會(huì)等待另一種事件執(zhí)行完畢之后,才開(kāi)始執(zhí)行,事件之間不會(huì)出現(xiàn)搶占。
  2. 事件處理器先處理文件事件(處理命令請(qǐng)求),再執(zhí)行時(shí)間事件(調(diào)用 serverCron
  3. 文件事件的等待時(shí)間(類(lèi) poll 函數(shù)的最大阻塞時(shí)間),由距離到達(dá)時(shí)間最短的時(shí)間事件決定。

這些屬性表明,實(shí)際處理時(shí)間事件的時(shí)間,通常會(huì)比時(shí)間事件所預(yù)定的時(shí)間要晚,至于延遲的時(shí)間有多長(zhǎng),取決于時(shí)間事件執(zhí)行之前,執(zhí)行文件事件所消耗的時(shí)間。

比如說(shuō),以下圖表就展示了,雖然時(shí)間事件 TE 1 預(yù)定在 t1 時(shí)間執(zhí)行,但因?yàn)槲募录?FE 1 正在運(yùn)行,所以 TE 1 的執(zhí)行被延遲了:

                      t1
                      |
                      V
time -----------------+------------------->|

     |       FE 1              |   TE 1    |

                      |<------>|
                        TE 1
                        delay
                        time

另外,對(duì)于像 serverCron 這類(lèi)循環(huán)執(zhí)行的時(shí)間事件來(lái)說(shuō),如果事件處理器的返回值是 t ,那么 Redis 只保證:

  • 如果兩次執(zhí)行時(shí)間事件處理器之間的時(shí)間間隔大于等于 t , 那么這個(gè)時(shí)間事件至少會(huì)被處理一次。
  • 而并不是說(shuō), 每隔 t 時(shí)間, 就一定要執(zhí)行一次事件 —— 這對(duì)于不使用搶占調(diào)度的 Redis 事件處理器來(lái)說(shuō),也是不可能做到的

舉個(gè)例子,雖然 serverCronsC)設(shè)定的間隔為 10 毫秒,但它并不是像如下那樣每隔 10 毫秒就運(yùn)行一次:

time ----------------------------------------------------->|

     |<---- 10 ms ---->|<---- 10 ms ---->|<---- 10 ms ---->|

     | FE 1 | FE 2     | sC 1 | FE 3     |  sC 2 |  FE 4   |

     ^                 ^      ^          ^       ^
     |                 |      |          |       |
   file event      time event |      time event  |
   handler         handler    |      handler     |
   run             run        |      run         |
                          file event          file event
                          handler             handler
                          run                 run

在實(shí)際中,serverCron 的運(yùn)行方式更可能是這樣子的:

time ----------------------------------------------------------------------->|

     |<---- 10 ms ---->|<---- 10 ms ---->|<---- 10 ms ---->|<---- 10 ms ---->|

     | FE 1         | FE 2     | sC 1 | FE 3 | FE 4 |   FE 5  |    sC 2  |

     |<-------- 15 ms -------->|      |<------- 12 ms ------->|
            >= 10 ms                          >= 10 ms
     ^                         ^      ^                       ^
     |                         |      |                       |
  file event              time event  |                  time event
  handler                 handler     |                  handler
  run                     run         |                  run
                                 file event
                                 handler
                                 run

根據(jù)情況,如果處理文件事件耗費(fèi)了非常多的時(shí)間,serverCron 被推遲到一兩秒之后才能執(zhí)行,也是有可能的。

整個(gè)事件處理器程序可以用以下偽代碼描述:

def process_event():

    # 獲取執(zhí)行時(shí)間最接近現(xiàn)在的一個(gè)時(shí)間事件
    te = get_nearest_time_event(server.time_event_linked_list)

    # 檢查該事件的執(zhí)行時(shí)間和現(xiàn)在時(shí)間之差
    # 如果值 <= 0 ,那么說(shuō)明至少有一個(gè)時(shí)間事件已到達(dá)
    # 如果值 > 0 ,那么說(shuō)明目前沒(méi)有任何時(shí)間事件到達(dá)
    nearest_te_remaind_ms = te.when - now_in_ms()

    if nearest_te_remaind_ms <= 0:

        # 如果有時(shí)間事件已經(jīng)到達(dá)
        # 那么調(diào)用不阻塞的文件事件等待函數(shù)
        poll(timeout=None)

    else:

        # 如果時(shí)間事件還沒(méi)到達(dá)
        # 那么阻塞的最大時(shí)間不超過(guò) te 的到達(dá)時(shí)間
        poll(timeout=nearest_te_remaind_ms)

    # 處理已就緒文件事件
    process_file_events()

    # 處理已到達(dá)時(shí)間事件
    process_time_event()

通過(guò)這段代碼,可以清晰地看出:

  • 到達(dá)時(shí)間最近的時(shí)間事件,決定了 poll 的最大阻塞時(shí)長(zhǎng)。
  • 文件事件先于時(shí)間事件處理。

將這個(gè)事件處理函數(shù)置于一個(gè)循環(huán)中,加上初始化和清理函數(shù),這就構(gòu)成了 Redis 服務(wù)器的主函數(shù)調(diào)用:

def redis_main():

    # 初始化服務(wù)器
    init_server()

    # 一直處理事件,直到服務(wù)器關(guān)閉為止
    while server_is_not_shutdown():
        process_event()

    # 清理服務(wù)器
    clean_server()

小結(jié)

  • Redis 的事件分為時(shí)間事件和文件事件兩類(lèi)。
  • 文件事件分為讀事件和寫(xiě)事件兩類(lèi):讀事件實(shí)現(xiàn)了命令請(qǐng)求的接收,寫(xiě)事件實(shí)現(xiàn)了命令結(jié)果的返回。
  • 時(shí)間事件分為單次執(zhí)行事件和循環(huán)執(zhí)行事件,服務(wù)器常規(guī)操作 serverCron 就是循環(huán)事件。
  • 文件事件和時(shí)間事件之間是合作關(guān)系:一種事件會(huì)等待另一種事件完成之后再執(zhí)行,不會(huì)出現(xiàn)搶占情況。
  • 時(shí)間事件的實(shí)際執(zhí)行時(shí)間通常會(huì)比預(yù)定時(shí)間晚一些。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)