<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 事件 [TOC=2,3] 事件是 Redis 服務器的核心,它處理兩項重要的任務: 1. 處理文件事件:在多個客戶端中實現多路復用,接受它們發來的命令請求,并將命令的執行結果返回給客戶端。 1. 時間事件:實現服務器常規操作(server cron job)。 本文以下內容就來介紹這兩種事件,以及它們背后的運作模式。 ### 文件事件 Redis 服務器通過在多個客戶端之間進行多路復用,從而實現高效的命令請求處理:多個客戶端通過套接字連接到 Redis 服務器中,但只有在套接字可以無阻塞地進行讀或者寫時,服務器才會和這些客戶端進行交互。 Redis 將這類因為對套接字進行多路復用而產生的事件稱為文件事件(file event),文件事件可以分為讀事件和寫事件兩類。 ### 讀事件 讀事件標志著客戶端命令請求的發送狀態。 當一個新的客戶端連接到服務器時,服務器會給為該客戶端綁定讀事件,直到客戶端斷開連接之后,這個讀事件才會被移除。 讀事件在整個網絡連接的生命期內,都會在等待和就緒兩種狀態之間切換: - 當客戶端只是連接到服務器,但并沒有向服務器發送命令時,該客戶端的讀事件就處于等待狀態。 - 當客戶端給服務器發送命令請求,并且請求已到達時(相應的套接字可以無阻塞地執行讀操作),該客戶端的讀事件處于就緒狀態。 作為例子,下圖展示了三個已連接到服務器、但并沒有發送命令的客戶端: ![digraph e { node [style = filled]; edge [style = "dotted, bold"]; rankdir = BT; server [label = "服務器", shape=circle, fillcolor = "#95BBE3"]; cx [label = "客戶端 X", fillcolor = "#A8E270"]; cy [label = "客戶端 Y", fillcolor = "#A8E270"]; cz [label = "客戶端 Z", fillcolor = "#A8E270"]; cx -> server [dir=none, style=dotted, label="等待命令請求"]; cy -> server [dir=none, style=dotted, label="等待命令請求"]; cz -> server [dir=none, style=dotted, label="等待命令請求"];}](https://box.kancloud.cn/2015-09-13_55f4effebf9d0.svg) 這三個客戶端的狀態如下表: | 客戶端 | 讀事件狀態 | 命令發送狀態 | |-----|-----|-----| | 客戶端 X | 等待 | 未發送 | | 客戶端 Y | 等待 | 未發送 | | 客戶端 Z | 等待 | 未發送 | 之后,當客戶端 X 向服務器發送命令請求,并且命令請求已到達時,客戶端 X 的讀事件狀態變為就緒: ![digraph e { node [style = filled]; edge [style = "dotted, bold"]; rankdir = BT; server [label = "服務器", shape=circle, fillcolor = "#95BBE3"]; cx [label = "客戶端 X", fillcolor = "#A8E270"]; cy [label = "客戶端 Y", fillcolor = "#A8E270"]; cz [label = "客戶端 Z", fillcolor = "#A8E270"]; cx -> server [style= "dashed, bold" , label="發送命令請求", color = "#B22222"]; cy -> server [dir=none, style=dotted, label="等待命令請求"]; cz -> server [dir=none, style=dotted, label="等待命令請求"];}](https://box.kancloud.cn/2015-09-13_55f4effec77aa.svg) 這時,三個客戶端的狀態如下表(只有客戶端 X 的狀態被更新了): | 客戶端 | 讀事件狀態 | 命令發送狀態 | |-----|-----|-----| | 客戶端 X | **就緒** | **已發送,并且已到達** | | 客戶端 Y | 等待 | 未發送 | | 客戶端 Z | 等待 | 未發送 | 當事件處理器被執行時,就緒的文件事件會被識別到,相應的命令請求會被發送到命令執行器,并對命令進行求值。 ### 寫事件 寫事件標志著客戶端對命令結果的接收狀態。 和客戶端自始至終都關聯著讀事件不同,服務器只會在有命令結果要傳回給客戶端時,才會為客戶端關聯寫事件,并且在命令結果傳送完畢之后,客戶端和寫事件的關聯就會被移除。 一個寫事件會在兩種狀態之間切換: - 當服務器有命令結果需要返回給客戶端,但客戶端還未能執行無阻塞寫,那么寫事件處于等待狀態。 - 當服務器有命令結果需要返回給客戶端,并且客戶端可以進行無阻塞寫,那么寫事件處于就緒狀態。 當客戶端向服務器發送命令請求,并且請求被接受并執行之后,服務器就需要將保存在緩存內的命令執行結果返回給客戶端,這時服務器就會為客戶端關聯寫事件。 作為例子,下圖展示了三個連接到服務器的客戶端,其中服務器正等待客戶端 X 變得可寫,從而將命令的執行結果返回給它: ![digraph e { node [style = filled]; edge [style = "dotted, bold"]; rankdir = BT; server [label = "服務器", shape=circle, fillcolor = "#95BBE3"]; cx [label = "客戶端 X", fillcolor = "#A8E270"]; cy [label = "客戶端 Y", fillcolor = "#A8E270"]; cz [label = "客戶端 Z", fillcolor = "#A8E270"]; cx -> server [dir=none, style=dotted, label="等待將命令結果返回\n等待命令請求"]; cy -> server [dir=none, style=dotted, label="等待命令請求"]; cz -> server [dir=none, style=dotted, label="等待命令請求"];}](https://box.kancloud.cn/2015-09-13_55f4effed0f0e.svg) 此時三個客戶端的事件狀態分別如下表: | 客戶端 | 讀事件狀態 | 寫事件狀態 | |-----|-----|-----| | 客戶端 X | 等待 | 等待 | | 客戶端 Y | 等待 | 無 | | 客戶端 Z | 等待 | 無 | 當客戶端 X 的套接字可以進行無阻塞寫操作時,寫事件就緒,服務器將保存在緩存內的命令執行結果返回給客戶端: ![digraph e { node [style = filled]; edge [style = "dotted, bold"]; rankdir = BT; server [label = "服務器", shape=circle, fillcolor = "#95BBE3"]; cx [label = "客戶端 X", fillcolor = "#A8E270"]; cy [label = "客戶端 Y", fillcolor = "#A8E270"]; cz [label = "客戶端 Z", fillcolor = "#A8E270"]; cx -> server [dir=back, style="dashed, bold", label="返回命令執行結果\n等待命令請求", color = "#B22222"]; cy -> server [dir=none, style=dotted, label="等待命令請求"]; cz -> server [dir=none, style=dotted, label="等待命令請求"];}](https://box.kancloud.cn/2015-09-13_55f4effed9356.svg) 此時三個客戶端的事件狀態分別如下表(只有客戶端 X 的狀態被更新了): | 客戶端 | 讀事件狀態 | 寫事件狀態 | |-----|-----|-----| | 客戶端 X | 等待 | **已就緒** | | 客戶端 Y | 等待 | 無 | | 客戶端 Z | 等待 | 無 | 當命令執行結果被傳送回客戶端之后,客戶端和寫事件之間的關聯會被解除(只剩下讀事件),至此,返回命令執行結果的動作執行完畢: ![digraph e { node [style = filled]; edge [style = "dotted, bold"]; rankdir = BT; server [label = "服務器", shape=circle, fillcolor = "#95BBE3"]; cx [label = "客戶端 X", fillcolor = "#A8E270"]; cy [label = "客戶端 Y", fillcolor = "#A8E270"]; cz [label = "客戶端 Z", fillcolor = "#A8E270"]; cx -> server [dir=none, style=dotted, label="等待命令請求"]; cy -> server [dir=none, style=dotted, label="等待命令請求"]; cz -> server [dir=none, style=dotted, label="等待命令請求"];}](https://box.kancloud.cn/2015-09-13_55f4effee45e9.svg) Note 同時關聯寫事件和讀事件 前面提到過,讀事件只有在客戶端斷開和服務器的連接時,才會被移除。 這也就是說,當客戶端關聯寫事件的時候,實際上它在同時關聯讀/寫兩種事件。 因為在同一次文件事件處理器的調用中,單個客戶端只能執行其中一種事件(要么讀,要么寫,但不能又讀又寫),當出現讀事件和寫事件同時就緒的情況時,事件處理器優先處理讀事件。 這也就是說,當服務器有命令結果要返回客戶端,而客戶端又有新命令請求進入時,服務器先處理新命令請求。 ### 時間事件 時間事件記錄著那些要在指定時間點運行的事件,多個時間事件以無序鏈表的形式保存在服務器狀態中。 每個時間事件主要由三個屬性組成: - `when` :以毫秒格式的 UNIX 時間戳為單位,記錄了應該在什么時間點執行事件處理函數。 - `timeProc` :事件處理函數。 - `next` 指向下一個時間事件,形成鏈表。 根據 `timeProc` 函數的返回值,可以將時間事件劃分為兩類: - 如果事件處理函數返回 `ae.h/AE_NOMORE` ,那么這個事件為單次執行事件:該事件會在指定的時間被處理一次,之后該事件就會被刪除,不再執行。 - 如果事件處理函數返回一個非 `AE_NOMORE` 的整數值,那么這個事件為循環執行事件:該事件會在指定的時間被處理,之后它會按照事件處理函數的返回值,更新事件的 `when` 屬性,讓這個事件在之后的某個時間點再次運行,并以這種方式一直更新并運行下去。 可以用偽代碼來表示這兩種事件的處理方式: ~~~ def handle_time_event(server, time_event): # 執行事件處理器,并獲取返回值 # 返回值可以是 AE_NOMORE ,或者一個表示毫秒數的非符整數值 retval = time_event.timeProc() if retval == AE_NOMORE: # 如果返回 AE_NOMORE ,那么將事件從鏈表中刪除,不再執行 server.time_event_linked_list.delete(time_event) else: # 否則,更新事件的 when 屬性 # 讓它在當前時間之后的 retval 毫秒之后再次運行 time_event.when = unix_ts_in_ms() + retval ~~~ 當時間事件處理器被執行時,它遍歷所有鏈表中的時間事件,檢查它們的到達事件(`when` 屬性),并執行其中的已到達事件: ~~~ def process_time_event(server): # 遍歷時間事件鏈表 for time_event in server.time_event_linked_list: # 檢查事件是否已經到達 if time_event.when <= unix_ts_in_ms(): # 處理已到達事件 handle_time_event(server, time_event) ~~~ Note 無序鏈表并不影響時間事件處理器的性能 在目前的版本中,正常模式下的 Redis 只帶有 `serverCron` 一個時間事件,而在 benchmark 模式下,Redis 也只使用兩個時間事件。 在這種情況下,程序幾乎是將無序鏈表退化成一個指針來使用,所以使用無序鏈表來保存時間事件,并不影響事件處理器的性能。 ### 時間事件應用實例:服務器常規操作 對于持續運行的服務器來說,服務器需要定期對自身的資源和狀態進行必要的檢查和整理,從而讓服務器維持在一個健康穩定的狀態,這類操作被統稱為常規操作(cron job)。 在 Redis 中,常規操作由 `redis.c/serverCron` 實現,它主要執行以下操作: - 更新服務器的各類統計信息,比如時間、內存占用、數據庫占用情況等。 - 清理數據庫中的過期鍵值對。 - 對不合理的數據庫進行大小調整。 - 關閉和清理連接失效的客戶端。 - 嘗試進行 AOF 或 RDB 持久化操作。 - 如果服務器是主節點的話,對附屬節點進行定期同步。 - 如果處于集群模式的話,對集群進行定期同步和連接測試。 Redis 將 `serverCron` 作為時間事件來運行,從而確保它每隔一段時間就會自動運行一次,又因為 `serverCron` 需要在 Redis 服務器運行期間一直定期運行,所以它是一個循環時間事件:`serverCron` 會一直定期執行,直到服務器關閉為止。 在 Redis 2.6 版本中,程序規定 `serverCron` 每秒運行 `10` 次,平均每 `100` 毫秒運行一次。從 Redis 2.8 開始,用戶可以通過修改 `hz` 選項來調整 `serverCron` 的每秒執行次數,具體信息請參考 `redis.conf` 文件中關于 `hz` 選項的說明。 ### 事件的執行與調度 既然 Redis 里面既有文件事件,又有時間事件,那么如何調度這兩種事件就成了一個關鍵問題。 簡單地說,Redis 里面的兩種事件呈合作關系,它們之間包含以下三種屬性: 1. 一種事件會等待另一種事件執行完畢之后,才開始執行,事件之間不會出現搶占。 1. 事件處理器先處理文件事件(處理命令請求),再執行時間事件(調用 `serverCron`) 1. 文件事件的等待時間(類 `poll` 函數的最大阻塞時間),由距離到達時間最短的時間事件決定。 這些屬性表明,實際處理時間事件的時間,通常會比時間事件所預定的時間要晚,至于延遲的時間有多長,取決于時間事件執行之前,執行文件事件所消耗的時間。 比如說,以下圖表就展示了,雖然時間事件 `TE 1` 預定在 `t1` 時間執行,但因為文件事件 `FE 1` 正在運行,所以 `TE 1` 的執行被延遲了: ~~~ t1 | V time -----------------+------------------->| | FE 1 | TE 1 | |<------>| TE 1 delay time ~~~ 另外,對于像 `serverCron` 這類循環執行的時間事件來說,如果事件處理器的返回值是 `t` ,那么 Redis 只保證: - 如果兩次執行時間事件處理器之間的時間間隔大于等于 `t` , 那么這個時間事件至少會被處理一次。 - 而并不是說, 每隔 `t` 時間, 就一定要執行一次事件 —— 這對于不使用搶占調度的 Redis 事件處理器來說,也是不可能做到的 舉個例子,雖然 `serverCron` (`sC`)設定的間隔為 `10` 毫秒,但它并不是像如下那樣每隔 `10` 毫秒就運行一次: ~~~ 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` 的運行方式更可能是這樣子的: ~~~ 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 ~~~ 根據情況,如果處理文件事件耗費了非常多的時間,`serverCron` 被推遲到一兩秒之后才能執行,也是有可能的。 整個事件處理器程序可以用以下偽代碼描述: ~~~ def process_event(): # 獲取執行時間最接近現在的一個時間事件 te = get_nearest_time_event(server.time_event_linked_list) # 檢查該事件的執行時間和現在時間之差 # 如果值 <= 0 ,那么說明至少有一個時間事件已到達 # 如果值 > 0 ,那么說明目前沒有任何時間事件到達 nearest_te_remaind_ms = te.when - now_in_ms() if nearest_te_remaind_ms <= 0: # 如果有時間事件已經到達 # 那么調用不阻塞的文件事件等待函數 poll(timeout=None) else: # 如果時間事件還沒到達 # 那么阻塞的最大時間不超過 te 的到達時間 poll(timeout=nearest_te_remaind_ms) # 處理已就緒文件事件 process_file_events() # 處理已到達時間事件 process_time_event() ~~~ 通過這段代碼,可以清晰地看出: - 到達時間最近的時間事件,決定了 `poll` 的最大阻塞時長。 - 文件事件先于時間事件處理。 將這個事件處理函數置于一個循環中,加上初始化和清理函數,這就構成了 Redis 服務器的主函數調用: ~~~ def redis_main(): # 初始化服務器 init_server() # 一直處理事件,直到服務器關閉為止 while server_is_not_shutdown(): process_event() # 清理服務器 clean_server() ~~~ ### 小結 - Redis 的事件分為時間事件和文件事件兩類。 - 文件事件分為讀事件和寫事件兩類:讀事件實現了命令請求的接收,寫事件實現了命令結果的返回。 - 時間事件分為單次執行事件和循環執行事件,服務器常規操作 `serverCron` 就是循環事件。 - 文件事件和時間事件之間是合作關系:一種事件會等待另一種事件完成之后再執行,不會出現搶占情況。 - 時間事件的實際執行時間通常會比預定時間晚一些。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看