1.Redis特性
1)速度快:數據存放在內存上、基于C語言實現、單線程架構預防多線程競爭問題;
2)基于鍵值對的數據結構,支持5中數據結構:字符串(string)、列表(list)、哈希(hash)、集合(set)、有序集合,同時在字符串基礎上演變出位圖(Bitmaps)和HyperLogLog;
3)豐富的功能:鍵過期、發布訂閱、Lua腳本、事務、流水線;
4)持久化:RDB和AOF兩種策略將內存數據保存到硬盤;
5)主從復制
2.基礎指令
-
keys *
:查看所有鍵,需要遍歷數據庫O(n); -
dbsize
:查看數據庫鍵總數,直接獲取內置的鍵總數變量O(1); -
exists key
:查看鍵是否存在,存在返回1,否則0; -
del key [key ..]
:刪除鍵,返回刪除成功的個數; -
expire key seconds
:對鍵添加過期時間,超期自動刪除鍵; -
ttl key
:返回鍵的剩余過期時間v,若v大于0,則代表鍵的過期時間,若v等于-1,則代表沒有設置過期時間,若等于-2,則表示鍵不存在; -
type key
:返回鍵的數據結構類型,若鍵不存在返回none; -
object encoding
:查詢內部編碼;
3.單線程架構
Redis使用單線程架構和I/O多路復用模型來實現高性能的內存數據庫。為什么單線程還能這么快?
a)純內存訪問;
b)非阻塞I/O,使用epoll作為I/O多路復用技術的實現,再加上Redis自身的時間處理模型將epoll中的連接、讀寫、關閉都轉換為事件;
c)單線程避免了線程切換和競態產生的消耗;
4.數據結構的運用及其實現
請參考:"Redis5種數據結構的運用及實現"(http://www.lxweimin.com/p/7d207d057d46 )
5.對象
? ? ? ?基于上述數據結構構建了一個對象系統,字符串對象、列表對象、哈希對象、集合對象和有序集合對象。使用基于引用計數的內存回收機制(不使用自動釋放)和對象共享機制(多庫鍵共享對象)。
? ? ? ?Redis中每個對象都由一個redisObject結構表示,有三個屬性和保存數據有關,分別是:type屬性、encoding屬性和ptr屬性,一個與內存回收有關的屬性refcount,一個lru屬性記錄對象最后一次被命令程序訪問的時間;
- 類型(type):字符串對象、列表對象、哈希對象、集合對象和有序集合對象的其中一個,鍵總是字符串對象;
- 編碼(encoding):對象的ptr指針指向對象的底層實現數據結構,由encoding屬性決定,主要包括int、embstr、raw、ht、linkedlist、ziplist、intset、skiplist;
- 引用計數(refcount):由于C不具備自動內存回收功能,Redis自己構建了一個引用計數技術來實現內存回收機制,創建對象時,引用計數被初始化為1,當對象被一個新程序使用時,計數值加以,不再使用時減一,當計數值變為0時,對象所占用的內存會被釋放;同時引用計數屬性還有對象共享的作用,Redis在初始化時創建了一萬個字符串對象,包含了從0到9999的所有整數,當需要使用這些對象時,服務器會直接使用這些共享對象,并且引用計數加一,而不是新創建對象;
字符串對象
編碼可以是int、embstr、raw:
- int:如果一個字符串對象保存的是可由long表示的整數值,會將該整數值保存在ptr屬性里面,并將編碼設置為int。若修改后不再是整數值而是一個字符串,則轉為raw;
- raw:如果字符串對象保存的是一個長度>32字節的字符串值,使用SDS保存該字符串,并將編碼設置為raw;
- embstr(只讀):如果字符串對象保存的是一個長度<=32字節的字符串值,使用embstr編碼方式來保存;embstr專門用于保存短字符串的優化編碼方式,raw和embstr都使用redisObject結構和sdshdr結構來表示字符串對象,但raw會調用兩次內存分配函數來分別創建redisObject結構和sdshdr結構,而embstr則通過調用一次內存分配函數來分配一塊連續的空間,因此在創建和釋放時embstr都只需調用一次內存分配/釋放,且embstr保存在連續的內存中,更好的利用了緩存。embstr字符串對象無法修改,沒有任何修改程序,對他的修改實際上是轉成raw后進行修改;
列表對象
編碼可以是ziplist和linkedlist,當列表對象保存的元素個數不小于512個或者有元素的長度大于64字節則使用linkedlist(上限可改):
- ziplist:使用壓縮列表作為底層實現,每個壓縮列表節點節點保存一個列表元素;
- linkedlist:使用雙端鏈表作為底層實現,每個雙端鏈表節點保存了一個字符串對象;
哈希對象
編碼可以是ziplist、hashtable,當鍵值字符串長度不小于64字節或者鍵值對數量不小于512個則使用hashtable編碼。
- ziplist編碼的哈希對象使用壓縮列表作為底層實現,當有新的鍵值加入時,先在表尾推入鍵,然后推入值,鍵值總是緊挨在一起;
- hashtable編碼的哈希對象使用字典作為底層實現,字典的鍵值均為字符串對象;
集合對象
編碼可以是intset、hashtable,當集合對象保存的都是整數值,且集合元素不超過512個時,使用intset編碼:
- intset編碼的集合對象使用整數集合作為底層實現,所有元素都被保存在整數集合里面;
- hashtable編碼的集合對象使用字典作為底層實現,每個鍵都是一個字符串對象,代表一個集合元素,而值則全部被設置為NULL;
有序集合對象
編碼可以是ziplist、skiplist,若元素數量小于128個并且所有元素長度都小于64字節,則使用ziplist編碼:
- ziplist編碼的有序集合對象使用壓縮列表作為底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素成員,第二節點保存元素分值,各個元素按分值大小從小到大排序;
- skiplist編碼的有序集合對象使用zset作為底層實現,一個zset同時包含一個字典和一個跳躍表,跳躍表按分值從小到大保存了所有集合元素,每個跳躍表節點保存一個集合元素;字典則為有序集合創建了一個成員到分值的映射,鍵值對保存的是集合元素的成員(鍵)和分值(值),通過字典可以O(1)的查找給定成員的分值。跳躍表和字典會通過指針共享相同元素的成員和分值,也就不會因此浪費內存;
6.數據庫
? ? ? ?Redis服務器將所有數據庫都保存在服務器狀態redis.h/redisServer結構的db數組中,初始化服務器時,根據結構中的dbnum屬性確定創建多少個數據庫(默認16)。每個Redis客戶端都有自己的目標數據庫(默認0),由客戶端狀態redisClient結構的db屬性記錄客戶端當前的目標數據庫,可通過SELECT命令來切換目標數據庫。
鍵空間
? ? ? ?Redis中每個數據庫由一個redis.h/redisDb結構表示,其中的dict字典保存了數據庫中的所有鍵值對,稱這個字典為鍵空間,鍵空間的鍵也是數據庫的鍵,是一個字符串對象,鍵空間的值即數據庫的值,每個值可以是字符串對象、列表對象、哈希表對象、集合對象、有序集合對象。
讀寫鍵空間的維護操作
- 讀取一個鍵后(讀寫都要讀),會根據鍵是否存在來更新服務器鍵空間的命中次數(keyspace_hits)和不命中次數(keyspace_misses);
- 讀取一個鍵后,會更新鍵的LRU(最后一次使用)時間,用于計算鍵的閑置時間;
- 若服務器在讀取一個鍵時發現鍵已過期,則先刪除這個過期鍵;
- 若服務器使用WATCH命令監視了某個鍵,那么對被監視鍵進行修改后,會將這個鍵標記為臟,從而讓事務程序注意到這個鍵已經被修改;
- 服務器每修改一個鍵,會將臟鍵計數器值加1,該計數器會觸發服務器的持久化以及復制操作;
- 若服務器開啟了數據庫通知功能,那么對鍵進行修改了之后,服務器按配置發送相應的數據庫通知;
鍵過期
? ? ? ?通過EXPIRE
命令,客戶端可以以秒或毫秒精度為數據庫中的某個鍵設置過期時間,過期時間是一個UNIX時間戳;TTL
命令接受一個帶生存時間或過期時間的鍵,返回這個鍵的剩余生存時間。
? ? ? ?在redisDb結構的expires字典保存了數據庫中所有鍵的過期時間,稱為過期字典。過期字典的鍵是一個指針,指向鍵空間中的某個鍵對象;過期字典的值是一個Long類型的整數,保存了鍵所指向的數據庫鍵的過期時間(毫米精度的UNIX時間戳)。通過直接查詢字典的過期時間與當前時間對比來判斷鍵是否過期。
過期鍵刪除策略
? ? ? ?Redis服務器實際使用惰性刪除和定期刪除策略,通過配合使用兩種刪除策略,很好地在使用CPU和避免內存浪費之間取得了平衡;
- 定時刪除:在設置鍵的過期時間的同時,創建一個定時器,讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作;
- 惰性刪除:放任鍵過期不管,每次從鍵空間獲取鍵時,都檢查取得的鍵是否過期,如果過期則刪除該鍵,沒有則返回該鍵;
- 定期刪除:每隔一段時間,程序對數據庫進行一次檢查,刪除里面的過期鍵;
7.RDB持久化
? ? ? ?RDB持久化功能所生成的RDB文件是一個經過壓縮的二進制文件,通過該文件可以還原生成RDB文件時的數據庫狀態(將非空數據庫以及它們的鍵值對統稱為數據庫狀態)。
RDB文件的創建與載入
? ? ? ?RDB文件的載入工作在服務器啟動時自動執行的,創建RDB文件有兩個命令可以用于生成RDB文件:
-
SAVE
:阻塞Redis服務器進程,直到RDB文件創建完畢為止,在服務器進程阻塞期間,服務器不能處理任何命令請求; -
BGSAVE
:與SAVE
的直接阻塞服務器進程不同,BGSAVE命令會派生出一個子進程來創建RDB文件,Redis服務器仍然可以繼續處理客戶端的命令請求。但是對于SAVE、BGSAVE命令會被拒絕,防止同時調用rdbsave函數產生競爭條件;對于BGREWRITEAOF,如果BGSAVE正在執行,則BGREWRITEAOF會被延遲到前者執行完成,如果BGREWRITEAOF正在執行,BGSAVE會被拒絕;
? ? ? ?由于AOF文件的更新頻率通常比RDB文件的更新頻率高,所以:
- 如果服務器開啟了AOF持久化功能,那么服務器會使用AOF文件來還原數據庫狀態;
- 只有AOF持久化功能處于關閉狀態時,服務器才會使用RDB文件來還原數據庫狀態,由
rdb.c/rdbLoad
函數完成;
自動間隔性保存
? ? ? ?BGSAVE可以在不阻塞服務器進程的情況下執行,所以Redis允許用戶通過設置服務器配置的save選項,讓服務器每隔一段時間自動執行一次BGSAVE命令。用戶可以通過save
選項設置多個保存條件,但只要其中任意一個條件被滿足,服務器就會執行BGSAVE命令。
save 900 1 //服務器在900秒之內,對數據庫進行了至少1次修改
save 300 10 //服務器在300秒之內,對數據庫進行了至少10次修改
save 60 10000 //服務器在60秒之內,對數據庫進行了至少10000次修改
? ? ? ?保存條件保存在服務器狀態redisServer結構中的saveparams屬性中,saveparams
是1個數組,數組中的每個元素都是一個savepara
結構,代表1個保存條件;
struct savaparam {
//秒數
time_t seconds;
//修改數
int changes;
}
dirty計數器和lastsave屬性
- dirty計數器:記錄距離上一次成功執行SAVE或BGSAVE命令之后,服務器對數據庫狀態進行了多少次修改;
- lastsave屬性:一個UNIX時間戳,記錄了服務器上一次成功執行SAVE或BGSAVE命令的時間;
? ? ? ?Redis服務器周期性操作函數serverCron
,默認每個100毫秒執行一次對保存條件的檢查,會遍歷檢查saveparams數組內的所有保存條件,只要有任意一個條件被滿足,那么服務器就會執行BGSAVE命令。
RDB文件結構
REDIS | db_version | databases | EOF | check_sum |
---|
注:全大寫單詞表示常量,全小寫表示變量和數據
- REDIS:RDB文件的最開頭部分,長度5字節,保存著"REDIS"這5個字符,方便在載入時快速檢查文件是否RDB文件;
- db_version:長度4字節,值是一個字符串表示的整數,記錄了RDB文件的版本號;
- databases:包含0個或任意多個數據庫,以及各個數據庫中的鍵值對數據。若服務器的數據庫狀態為空,則對應這部分也為空;若數據庫狀態非空,則根據數據庫所保存鍵值對的數量、類型和內容不同,對應長度也不同;
- EOF:長度1字節,標志著RDB文件正文內容的結束;
- check_sum:長度8字節,無符號整數,保存著一個校驗和,由前面4部分的內容進行計算得出;
? ? ? ?每個非空數據庫在RDB文件中都可以保存為SELECTDB
、db_number
、key_value_pairs
;
SELECTDB:長度1字節的常量,表示接下來要讀入的將是一個數據庫號碼;
db_number:保存著1個數據庫號碼,根據號碼的不同,長度可以是1,2,5字節,用于切換數據庫,使對應數據庫的鍵值對能存入正確;
-
key_value_pairs:保存了數據庫中的所有鍵值對數據,若鍵值對有過期時間,則過期時間也會和鍵值對保存在一起。鍵值對由[EXPIRETIME_MS、ms]、TYPE、key、value組成:
- EXPIRETIME_MS:(若無過期時間則不存在此屬性) 常量,長度1字節,告知程序接下來讀入的將是以毫秒為單位的過期時間;
- ms:(若無過期時間則不存在此屬性) 8字節的帶符號整數,記錄一個以毫秒為單位的UNIX時間戳,代表鍵值對的過期時間;
- TYPE:記錄了value的類型,長度為1字節;
- key:字符串對象,以REDIS_RDB_TYPE_STRING類型編碼,長度隨內容變化;
- value:根據TYPE類型的不同和保存內容的不同,結構和長度也會不同;
value的編碼
-
字符串對象(REDIS_RDB_TYPE_STRING)
? ? ? ?字符串對象的編碼可以是REDIS_ENCODING_INT
和REDIS_ENCODING_RAW
。- 若為
REDIS_ENCODING_INT
,說明對象保存的是長度超過32位的整數,編碼對象按[EBCODING,integer]
形式保存,ENCODING可以是ENCODING_RDB_ENC_INT8/16/32
,分別代表使用8,16,32位來保存整數值 - 若為
REDIS_ENCODING_RAW
編碼,代表對象所保存的是1個字符串值,根據字符串長度不同,有壓縮(字符串長度>20字節)和不壓縮(字符串長度<=20字節,直接原樣保存)兩種方法保存;- 對于沒有被壓縮的字符串,RDB程序會以[len string]結構來保存該字符串,string部分保存了字符串值本身,len保存了字符串值的長度;
- 對于壓縮后的字符串,RDB程序會以一下結構保存字符串的長度。REDIS_RDB_ENC_LZF常量標志著字符串已經被LZF算法壓縮過,會根據壓縮后長度compressed_len、原長度origin_len、壓縮后字符串compressed_string對字符串進行解壓;
- 若為
REDIS_RDB_ENC_LZF | compressed_len | origin_len | compressed_string |
---|
2.列表對象
? ? ? ?Type的值為REDIS_RDB_TYPE_LIST,那么value保存的就是一個REDIS_ENCODING_LINKEDLIST編碼的列表對象,結構如下,list_length記錄了列表的長度,item代表列表的項,每個列表項都是一個字符串對象。
list_length | item1 | item2 | … | itemN |
---|
3.集合對象
? ? ? ?Type的值為REDIS_RDB_TYPE_SET,那么value保存的就是一個REDIS_ENCODING_HT編碼的集合對象,結構如下,set_size記錄了集合的大小,elem代理集合元素,每一個集合都是一個字符串對象。
set_size | elem1 | elem2 | … | elemN |
---|
4.哈希表對象
? ? ? ?Type的值為REDIS_RDB_TYPE_HASH,那么value保存的是一個REDIS_ENCODING_HT編碼的集合對象,結構如下,hash_size記錄了哈希表的大小,key_value_pair代表哈希表中的鍵值對,鍵和值均為字符串對象。
hash_size | key_value_pair1 | key_value_pair2 | … | key_value_pairN |
---|
5.有序集合對象
? ? ? ?Type的值為REDIS_RED_TYPE_ZSET,那么value保存的是REDIS_ENCODING_SKIPLIST編碼的有序集合對象,結構如下,sorted_set_size記錄了有序集合的大小,element代表有序集合中的元素,每個元素分為成員和分值,成員是一個字符串對象,分值則是一個double類型的浮點數。
sorted_set_size | element1 | element2 | … | elementN |
---|
6.整數集合(INTSET編碼)
? ? ? ?Type的值為REDIS_RDB_TYPE_SET_INTSET,那么value保存的是一個整數集合對象,保存方法是將整數集合轉換為字符串對象,讀取時再由字符串對象轉回整數集合。
7.ZIPLIST編碼的列表、哈希表或者有序集合
? ? ? ?Type的值為REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST或者REDIS_RDB_TYPE_ZSET_ZIPLIST,那么value保存的是一個壓縮列表對象,保存方法是將壓縮列表轉換一個字符串對象后保存到RDB文件,讀取時再由字符串轉換回原來的壓縮列表對象,再根據TYPE的值設置壓縮列表對象的類型。
8.AOF持久化
? ? ? ?RDB持久化通過保存數據庫中的鍵值對來記錄數據庫狀態,AOF持久化通過保存Redis服務器執行的寫命令來記錄服務器狀態。服務器在啟動時,可以通過載入和執行AOF文件中保存的命令來還原服務器關閉之前的數據庫狀態。
1)AOF持久化功能的實現
- 命令追加:當AOF持久化功能打開時,服務器在執行完一個寫命令之后,會以協議格式將被執行的寫命令追加到服務器狀態的aof_buf緩沖區的末尾。
- AOF文件的寫入和同步:Redis的服務器進程就是一個事件循環,循環中的文件事件負責接收客戶端的命令請求并向客戶端發送命令回復,時間事件則負責執行定時任務。
? ? ? ?當處理文件事件時執行了寫命令,會使其被追加到aof_buf緩沖區里面,所以服務器每次結束一個事件循環之前,都會調用flushAppendOnlyFile函數考慮是否需要將aof_buf緩沖區中的內容寫入到AOF文件里面。
? ? ? ?由服務器配置的appendfsync選項的值決定是否將緩沖區中的內容同步到AOF文件中。
appendfsync | flushAppendOnlyFile函數行為 |
---|---|
always | 將aof_buf緩沖區中的所有內容寫入并同步到AOF文件 |
everysec(默認) | 將aof_buf緩沖區中的所有內容寫入并同步到AOF文件,若上次同步距離現在超過一秒,再次同步,該同步操作由一個線程專門負責執行 |
no | 將aof_buf緩沖區中的所有內容寫入并同步到AOF文件,但不對其進行同步,何時同步由操作系統決定 |
2)AOF文件的載入和數據還原
? ? ? ?AOF文件里面包含了重建數據庫狀態所需的所有寫命令,所以服務器只要讀入并重新執行一遍AOF文件里面保存的寫命令,就可以還原服務器關閉之前的數據庫狀態。詳細步驟如下:
- 創建一個不帶網絡連接的偽客戶端,因為Redis的命令只能在客戶端上下文中執行;
- 從AOF文件中分析并讀取出一條寫命令;
- 使用偽客戶端執行被讀出的寫命令;
- 重復步驟2,3,直到AOF文件中的所有寫命令都被處理完畢;
3)AOF重寫
? ? ? ?隨著服務器運行,AOF文件記錄的內容越來越多、體積越來越大。為了解決AOF文件體積膨脹問題,Redis提供了AOF文件重寫功能,使新的AOF代替舊AOF,兩者保存的數據庫狀態相同,但新AOF不包含任何浪費空間的冗余命令。
AOF文件重寫的實現:重寫功能并不需要讀取和分析舊AOF文件,僅需對數據庫從讀取鍵值,并通過一條寫命令來表示,這樣就可以將多次寫入的數據通過一條寫入命令表示,節省了AOF文件的空間。但是當重寫程序處理集合、列表、哈希表等時,會先檢查元素數量,若超過REDIS_AOF_REWRITE_ITEMS_PER_CMD
(默認64)時,會用多條命令來記錄該集合,每條命令設置的元素數量也為64個。
AOF后臺重寫
? ? ? ?由于Redis服務器使用單個線程來處理命令請求,所以直接由服務器直接調用aof_rewrite
會使服務器在重寫期間無法執行客戶端請求。
? ? ? ?因此Redis將重寫程序放到子進程里執行,這樣服務器進程可以繼續處理請求命令;且子進程帶有服務器進程的數據副本,使用子進程而不是線程,可以避免使用鎖的情況下,保證數據安全性。但子進程重寫期間,服務器進程新處理的命令會對現有數據庫狀態進行修改,使得數據庫狀態與重寫后的AOF文件所保存的數據庫狀態不一致。因此在子進程重寫期間,服務器進程需要執行以下三個工作:
- 執行客戶端發來的命令;
- 將執行后的寫命令追加到AOF緩沖區;
- 將執行后的寫命令追加到AOF重寫緩沖區;
? ? ? ?這樣就保證了AOF緩沖區的內容會定期被寫入和同步到AOF文件;從創建子進程開始,服務器執行的所有寫命令都會被記錄到AOF重寫緩沖區。當子進程完成AOF重寫之后,會向服務器進程發生信號,服務器進程接到信號執行信號處理函數:
- 將AOF重寫緩沖區的內容寫入到新AOF,此時AOF和服務器保存的數據庫狀態一致;
- 對新AOF文件進行改名,原子地覆蓋現有的AOF文件,完成新舊AOF的替換;
9.事件
1)文件事件
? ? ? ?Redis服務器通過套接字與客戶端/其他服務器進行連接,文件事件就是服務器對套接字操作的抽象。文件事件處理器主要由套接字、I/O多路復用程序、文件事件分派器以及事件處理器構成。
- 文件事件處理器使用I/O多路復用來同時監聽多個套接字,并根據套接字目前執行的任務來為套接字關聯不同的事件處理器;
- 當被監聽的套接字準備好執行連接應答、讀取、寫入、關閉等操作時,與操作相對應的文件事件就會產生來處理這些事件;
? ? ? ?盡管多個文件事件可能會并發地出現,但I/O多路復用程序總是會將所有產生事件的套接字放到一個隊列里面,然后通過這個隊列,以有序、同步、每次一個套接字的方式向文件事件分派器傳送套接字。
文件事件的處理器
- 連接應答處理器:用于對連接服務器監聽套接字的客戶端進行應答,當Redis服務器進行初始化時,會將此連接應答處理器和服務器監聽套接字的AE_READABLE事件關聯起來;
- 命令請求處理器:負責從套接字中讀入客戶端發生的命令請求內容,當客戶端通過連接應答處理器成功連接到服務器后,會將客戶端套接字的AR_READABLE事件和命令請求處理器關聯起來;
- 命令回復處理器:負責將服務器執行命令后得到的命令回復通過套接字返回給客戶端,當服務器有命令回復需要傳送給客戶端的時候,服務器會將客戶端套接字的AE_WRITABLE時間和命令回復處理器關聯;
2)時間事件
? ? ? ?Redis的時間事件分為定時事件、周期性事件,一個時間事件主要由以下三個屬性組成:
- id:服務器為時間事件創建的全局唯一ID;
- when:毫秒精度的UNIX時間戳,記錄了時間事件的到達時間;
- timeProc:時間事件處理器,當時間事件到達時,服務器會調用相應的處理器來處理時間;
? ? ? ?一個時間事件是定時還是周期主要取決于時間事件處理器的返回值,服務器將所有時間事件都放在一個無序鏈表(不按when屬性排序)中,每當時間事件執行器運行時,遍歷整個鏈表,查找所有已到達的時間事件,并調用相應的事件處理器。
- 返回
ae.h/AE_NOMORE
,則為定時事件,在達到一次之后就會被刪除,之后不再到達; - 返回非
AE_NOMORE
的整數,則為周期事件,當一個時間事件到達之后,服務器會根據事件處理器返回的值,對時間事件的when屬性進行更新;
3)事件的調度和執行
? ? ? ?由于服務器同時存在文件事件和時間事件兩種事件類型,所有服務器必須對這兩種事件進行調度,決定何時處理何種事件。
- aeApiPoll函數的最大阻塞事件由到達時間最接近當前時間的時間事件決定,這樣既可避免服務器對時間事件進行頻繁的輪詢,也可確保aeApiPoll不會阻塞過長時間;
- 由于文件事件時隨機出現的,如果等待并處理完一次文件事件之后,仍未有任何時間事件到達,則服務器再次等待并處理文件事件;
- 對文件事件和時間事件的處理都是同步、有序、原子地執行的,服務器不會中途中斷事件處理,也不會對事件進行搶占。盡可能的減少程序的阻塞時間,并在有需要時主動讓出執行權,降低造成事件饑餓的可能性;