国产chinesehdxxxx野外,国产av无码专区亚洲av琪琪,播放男人添女人下边视频,成人国产精品一区二区免费看,chinese丰满人妻videos

Redis 事件

2018-08-02 11:57 更新

事件

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

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

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

文件事件

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

Redis 將這類因為對套接字進(jìn)行多路復(fù)用而產(chǎn)生的事件稱為文件事件(file event),文件事件可以分為讀事件和寫事件兩類。

讀事件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

寫事件

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

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

一個寫事件會在兩種狀態(tài)之間切換:

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

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

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

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

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

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

客戶端 讀事件狀態(tài) 寫事件狀態(tài)
客戶端 X 等待 等待
客戶端 Y 等待
客戶端 Z 等待

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

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

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

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

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

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

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

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

Note

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

前面提到過,讀事件只有在客戶端斷開和服務(wù)器的連接時,才會被移除。

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

因為在同一次文件事件處理器的調(diào)用中,單個客戶端只能執(zhí)行其中一種事件(要么讀,要么寫,但不能又讀又寫),當(dāng)出現(xiàn)讀事件和寫事件同時就緒的情況時,事件處理器優(yōu)先處理讀事件。

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

時間事件

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

每個時間事件主要由三個屬性組成:

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

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

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

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

def handle_time_event(server, time_event):

    # 執(zhí)行事件處理器,并獲取返回值
    # 返回值可以是 AE_NOMORE ,或者一個表示毫秒數(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)前時間之后的 retval 毫秒之后再次運(yùn)行
        time_event.when = unix_ts_in_ms() + retval

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

def process_time_event(server):

    # 遍歷時間事件鏈表
    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

無序鏈表并不影響時間事件處理器的性能

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

     |       FE 1              |   TE 1    |

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

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

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

舉個例子,雖然 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

在實際中,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)了非常多的時間,serverCron 被推遲到一兩秒之后才能執(zhí)行,也是有可能的。

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

def process_event():

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

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

    if nearest_te_remaind_ms <= 0:

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

    else:

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

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

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

通過這段代碼,可以清晰地看出:

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

將這個事件處理函數(shù)置于一個循環(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 的事件分為時間事件和文件事件兩類。
  • 文件事件分為讀事件和寫事件兩類:讀事件實現(xiàn)了命令請求的接收,寫事件實現(xiàn)了命令結(jié)果的返回。
  • 時間事件分為單次執(zhí)行事件和循環(huán)執(zhí)行事件,服務(wù)器常規(guī)操作 serverCron 就是循環(huán)事件。
  • 文件事件和時間事件之間是合作關(guān)系:一種事件會等待另一種事件完成之后再執(zhí)行,不會出現(xiàn)搶占情況。
  • 時間事件的實際執(zhí)行時間通常會比預(yù)定時間晚一些。


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號