Redis是啥
Redis是一個開源的key-value存儲系統,由于擁有豐富的數據結構,又被其作者戲稱為數據結構服務器。它屬于NoSQL(Not Only SQL)數據庫中的鍵值(Key-Value)存儲數據庫,即它屬于與MySQL和Oracle等關系型數據庫不同的非關系型數據庫。它與memcached類似,但是優于memcached。
Redis和Memocache的區別
1.Memocache僅支持字符串類型,而redis支持豐富的數據類型
2.Redis 支持兩種持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
3.Redis Cluster 實現了分布式的支持,Memocache不支持
Redis的應用場景
1.用于持久化存儲:由于Redis擁有豐富的數據結構,所以可以存儲多種類型的數據。同時,它在存儲與獲取某些數據的效率方面也優于關系型數據庫。
2.用于數據緩存:Redis最適合所有數據in-momory的場景。
Redis的優點
1.性能好,讀寫速率快
2.擁有豐富的數據結構,可以存儲多種類型的數據(相對于memocached來說)
3.操作都是原子性的操作,不用擔心并發問題,還支持多個操作合并的原子操作
4.擁有其他豐富的特性,比如給key設置expire過期時間
Redis對大小寫不敏感
Redis為什么快
1、完全基于內存,絕大部分請求是純粹的內存操作,非常快速。數據存在內存中,類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度都是O(1);
2、數據結構簡單,對數據操作也簡單,Redis中的數據結構是專門進行設計的;
3、采用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的性能消耗;
4、使用多路I/O復用模型,非阻塞IO;
5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣,Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求;
Redis為什么是單線程
官方FAQ表示,因為Redis是基于內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成為瓶頸,那就順理成章地采用單線程的方案了(畢竟采用多線程會有很多麻煩!)
另,采用單線程發揮不出多核cpu的性能,不過可以通過在單機開多個redis來完善
數據類型
String
字符串是Redis的一種最基本的數據類型。Redis字符串是二進制安全的,這意味著一個Redis字符串能包含任意類型的數據。一個字符串類型的變量最多能存儲512M字節的內容。
底層實現
簡單動態字符串
常用的操作
- get(key):返回數據庫中名稱為key的string的value
- getset(key, value):給名稱為key的string賦予上一次的value
- mget(key1, key2,…, key N):返回庫中多個string(它們的名稱為key1,key2…)的value
- setnx(key, value):如果不存在名稱為key的string,則向庫中添加string,名稱為key,值為value
- setex(key, time, value):向庫中添加string(名稱為key,值為value)同時,設定過期時間time
- mset(key1, value1, key2, value2,…key N, value N):同時給多個string賦值,名稱為key i的string賦值value i
- msetnx(key1, value1, key2, value2,…key N, value N):如果所有名稱為key i的string都不存在,則向庫中添加string,名稱key i賦值為value i
- incr(key):名稱為key的string增1操作
- incrby(key, integer):名稱為key的string增加integer
- decr(key):名稱為key的string減1操作
- decrby(key, integer):名稱為key的string減少integer
- append(key, value):名稱為key的string的值附加value
- substr(key, start, end):返回名稱為key的string的value的子串
List
Redis中的List是簡單的字符串列表,可以按照插入的順序排序。我們可以添加一個元素到列表的左邊(頭部)或者是右邊(尾部)。對應的命令為LPUSH和RPUSH。
底層實現
鏈表/壓縮列表
常用的操作
- rpush(key, value):在名稱為key的list尾添加一個值為value的元素
- lpush(key, value):在名稱為key的list頭添加一個值為value的 元素
- llen(key):返回名稱為key的list的長度
- lrange(key, start, end):返回名稱為key的list中start至end之間的元素(下標從0開始,下同)
- ltrim(key, start, end):截取名稱為key的list,保留start至end之間的元素
- lindex(key, index):返回名稱為key的list中index位置的元素
- lset(key, index, value):給名稱為key的list中index位置的元素賦值為value
- lrem(key, count, value):刪除count個名稱為key的list中值為value的元素。count為0,刪除所有值為value的元素,count>0從頭至尾刪除count個值為value的元素,count<0從尾到頭刪除|count|個值為value的元素
- lpop(key):返回并刪除名稱為key的list中的首元素 rpop(key):返回并刪除名稱為key的list中的尾元素
- blpop(key1, key2,… key N, timeout):lpop命令的block版本。即當timeout為0時,若遇到名稱為key i的list不存在或該list為空,則命令結束。如果timeout>0,則遇到上述情況時,等待timeout秒,如果問題沒有解決,則對keyi+1開始的list執行pop操作
- brpop(key1, key2,… key N, timeout):rpop的block版本。參考上一命令。
- rpoplpush(srckey, dstkey):返回并刪除名稱為srckey的list的尾元素,并將該元素添加到名稱為dstkey的list的頭部
Hash
Hash是字符串字段和字符串值之間的映射,因此他們是展現對象的完美數據類型。一個帶有一些字段的hash僅僅需要一塊很小的空間存儲,因此我們可以存儲數以百萬計的對象在一個小小的redis實例當中。
底層實現
字典/壓縮列表
常用操作
- hset(key, field, value):向名稱為key的hash中添加元素field<—>value
- hget(key, field):返回名稱為key的hash中field對應的value
- hmget(key, field1, …,field N):返回名稱為key的hash中field i對應的value
- hmset(key, field1, value1,…,field N, value N):向名稱為key的hash中添加元素field i<—>value i
- hincrby(key, field, integer):將名稱為key的hash中field的value增加integer
- hexists(key, field):名稱為key的hash中是否存在鍵為field的域
- hdel(key, field):刪除名稱為key的hash中鍵為field的域
- hlen(key):返回名稱為key的hash中元素個數
- hkeys(key):返回名稱為key的hash中所有鍵
- hvals(key):返回名稱為key的hash中所有鍵對應的value
- hgetall(key):返回名稱為key的hash中所有的鍵(field)及其對應的value
Set(無序集合)
Redis集合(Set)是一個無序的字符串集合。我們可以在O(1)的時間復雜度(無論集合中有多少元素時間復雜度都是常量)完成添加、刪除或者是查看元素是否存在。Redis集合擁有令人滿意的不允許包含相同成員的屬性。多次添加相同的元素,最終在集合里面只會有一個元素。實際上說這些就是意味著在添加元素的時候無須檢測元素是否存在。一個關于Redis集合非常有趣的事情就是它支持一些服務端的命令從現有的集合出發去進行集合運算,因此我們可以在非常短的時間內合并(unions),求交集(intersections),找出不同的元素(difference of sets)。
底層實現
整數集合/字典
常用操作
- sadd(key, member):向名稱為key的set中添加元素member
- srem(key, member) :刪除名稱為key的set中的元素member
- spop(key) :隨機返回并刪除名稱為key的set中一個元素
- smove(srckey, dstkey, member) :將member元素從名稱為srckey的集合移到名稱為dstkey的集合
- scard(key) :返回名稱為key的set的基數
- sismember(key, member) :測試member是否是名稱為key的set的元素
- sinter(key1, key2,…key N) :求交集
- sinterstore(dstkey, key1, key2,…key N) :求交集并將交集保存到dstkey的集合
- sunion(key1, key2,…key N) :求并集
- sunionstore(dstkey, key1, key2,…key N) :求并集并將并集保存到dstkey的集合
- sdiff(key1, key2,…key N) :求差集
- sdiffstore(dstkey, key1, key2,…key N) :求差集并將差集保存到dstkey的集合
- smembers(key) :返回名稱為key的set的所有元素
- srandmember(key) :隨機返回名稱為key的set的一個元素
Sorted Set(有序集合)
Redis有序集合和普通集合非常類似,是一個沒有重復元素的字符串集合。不同之處在于有序集合的所有成員都關聯了一個評分,這個評分被用來按照從最低分到最高分的方式排序集合中的成員。集合的成員是唯一的,但是評分可以是重復的。使用有序集合我們可以用非常快的速度(O(logN))添加、刪除以及更新元素。因為元素是有序的,所以我們也可以很快地根據評分(score)或者次序(position)來獲取一個范圍的元素。訪問有序集合的中間元素也是非常快的,因此我們能夠使用有序集合作為一個沒有重復成員的智能列表。在有序集合中,我們可以很快捷地訪問一切我們所需要的東西:有序的元素、快速的存在性測試、快速訪問集合的中間元素。簡而言之,使用有序集合我們可以完成許多對性能有極端要求的任務,而這些任務是使用其他類型的數據庫很難完成的。
底層實現
跳表/壓縮鏈表
常用操作
- zadd(key, score, member):向名稱為key的zset中添加元素member,score用于排序。如果該元素已經存在,則根據score更新該元素的順序。
- zrem(key, member) :刪除名稱為key的zset中的元素member
- zincrby(key, increment, member) :如果在名稱為key的zset中已經存在元素member,則該元素的score增加increment;否則向集合中添加該元素,其score的值為increment
- zrank(key, member) :返回名稱為key的zset(元素已按score從小到大排序)中member元素的rank(即index,從0開始),若沒有member元素,返回“nil”
- zrevrank(key, member) :返回名稱為key的zset(元素已按score從大到小排序)中member元素的rank(即index,從0開始),若沒有member元素,返回“nil”
- zrange(key, start, end):返回名稱為key的zset(元素已按score從小到大排序)中的index從start到end的所有元素
- zrevrange(key, start, end):返回名稱為key的zset(元素已按score從大到小排序)中的index從start到end的所有元素
- zrangebyscore(key, min, max):返回名稱為key的zset中score >= min且score <= max的所有元素 zcard(key):返回名稱為key的zset的基數 zscore(key, element):返回名稱為key的zset中元素element的score zremrangebyrank(key, min, max):刪除名稱為key的zset中rank >= min且rank <= max的所有元素 zremrangebyscore(key, min, max) :刪除名稱為key的zset中score >= min且score <= max的所有元素
系統管理命令
- exists key:判斷一個key是否存在。存在返回1,否則返回0
- del key:刪除一個key。成功返回,失敗返回0
- type key:返回key的數據類型:string、list、set、zset、hash。key不存在返回none
- keys key-pattern:將所有能夠匹配key-pattern的key都列出來
- randomkey:隨機返回一個key,如果此時數據庫是空的,則返回一個為空的字符串
- clear:清除界面
- rename oldname newname:將key由原來的oldname改為newname,不管此時newname存不存在
- renamenx oldname newname:將key由原來的oldname改為newname.如果此時newname存在則更改失敗
- dbsize:返回當前數據庫中key的總數
expire key :限定key的生存時間,命令的一般形式如expire name 30,意思是值為name的
key只能存活30秒 - ttl key:返回key剩余存活時間
- flushdb:清除當前數據庫中所有的key
- flushall:清除數據庫中所有的key
- config get:讀取Redis此時的配置參數
- config set:設置Redis此時的配置參數
- auth:密碼認證
- info:可以查詢Redis幾乎所有的信息
使用場景:
底層數據結構
1.簡單動態字符串
2.鏈表
3.字典(Map)
4.跳表
5.整數集合
6.壓縮列表
7.對象
簡單動態字符串(Simple Dynamic String)
雖然Redis是使用C語言編寫的,但是Redis中的字符串類型并不是直接搬用C語言的字符串。
/字符串對象底層結構/ struct sds{ int len;//buf已占用的空間長度 int free;//buf中剩余空間長度 char buf[];//數據存儲空間 }
- 獲取字符串長度時間更快(SDS為O(1)/C語言字符串為O(n))
- 避免了緩沖區溢出問題:當我們在對SDS進行修改之前Redis會預先檢查所操作的SDS空間夠不夠。如果不夠,則會拓展對應SDS空間之后再進行拼接等操作
- 減少修改字符串時帶來的內存分配問題
- 二進制安全
- 兼容部分C語言中有關字符串的函數
鏈表
Redis中的list底層使用的是雙向鏈表
字典(Map)
在字典中,一個key和一個value關聯,并且字典中的每個key都是獨一無二的。
Redis字典使用的哈希表底層結構:
typedef struct dictht { //哈希表數組 dictEntry **table; //哈希表大小 unsigned long size; //哈希表大小掩碼,用于計算索引值 unsigned long sizemask; //該哈希表已有節點的數量 unsigned long used; }
哈希表節點:
typeof struct dictEntry{ //鍵 void *key; //值 union{ void *val; uint64_tu64; int64_ts64; } struct dictEntry *next; }
1.根據hash算法算出key的hash值然后分配存儲空間(由于哈希表中沒有記錄鏈表尾節點的位置,所以是在鏈表的head插入新的節點);
2.鏈地址法解決hash地址沖突
3.隨著哈希表中節點數量的增加,適當時候會進行rehash將哈希表的負載因子保持在一個合理的范圍
跳表(skiplist)
跳躍表(skiplist)是一種有序數據結構,它通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。跳躍表是一種隨機化的數據,跳躍表以有序的方式在層次化的鏈表中保存元素,效率和平衡樹媲美 ——查找、刪除、添加等操作都可以在對數期望時間下完成,并且比起平衡樹來說,跳躍表的實現要簡單直觀得多。Redis 只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另外一個是在集群節點中用作內部數據結構。
只有當有序集合中的元素個數大于128時才會使用跳表,否則將使用后面提到的壓縮列表
- 跳躍表是有序集合的底層實現之一
- 主要有zskiplist 和zskiplistNode兩個結構組成
- 每個跳躍表節點的層高都是1至32之間的隨機數
- 在同一個跳躍表中,多個節點可以包含相同的分值,但每個節點的對象必須是唯一的
- 節點按照分值的大小從大到小排序,如果分值相同,則按成員對象大小排序
zskiplist(鏈表)數據結構:
typedef struct zskiplist { //表頭節點和表尾節點 structz skiplistNode *header,*tail; //表中節點數量 unsigned long length; //表中層數最大的節點的層數 int level; }zskiplist;
zskiplistNode(節點)數據結構:
typedef struct zskiplistNode{ //層 struct zskiplistLevel{ //前進指針 struct zskiplistNode *forward; //跨度 unsigned int span; } level[]; //后退指針 struct zskiplistNode *backward; //分值 double score; //成員對象 robj *obj; }
整數集合(Intset)
整數集合是集合建的底層實現之一,當一個集合中只包含整數,且這個集合中的元素數量不多時,redis就會使用整數集合intset作為集合的底層實現。我們可以這樣理解整數集合,他其實就是一個特殊的集合,里面存儲的數據只能夠是整數,并且數據量不能過大。
整數集合升級不僅可以提高靈活性,還能節約內存。整數集合是集合鍵的底層實現之一。整數集合的底層實現為數組,這個數組以有序,無重復的范式保存集合元素,在有需要時,程序會根據新添加的元素類型改變這個數組的類型。同時,升級操作為整數集合帶來了操作上的靈活性,并且盡可能地節約了內存。但是整數集合只支持升級操作,不支持降級操作
壓縮列表
壓縮列表是列表鍵和哈希鍵的底層實現之一。當一個列表鍵只含少量列表項(一般是少于128)時并且每個列表項要么就是小整數,要么就是長度比較短的字符串,那么Redis就會使用壓縮列表來做列表鍵的底層實現。
關于壓縮列表的幾點總結:
- 壓縮列表是一種為了節約內存而開發的順序型數據結構
- 壓縮列表被用作列表鍵和哈希鍵的底層實現之一
- 壓縮列表可以包含多個節點,每個節點可以保存一個字節數組或者是整數值
- 添加新節點到壓縮列表,可能會引發連鎖更新操作
Redis持久化
Redis直接將數據存儲在內存當中,但是并不是所有的數據都一直存儲在內存中的(這是和memcached相比最大的一個區別)。Redis會緩存所有的key的信息,但是如果Redis發現內存的使用量超過了某一個閾值,就會觸發swap操作。Redis會計算出哪些key對應的value需要swap到磁盤,然后再將這些key對應的value持久化到磁盤中同時清除內存中存儲的對應的value。這種特性使得Redis可以保持超過其機器本身內存大小的數據。但是機器本身的內存必須可以有足夠的空間存儲所有的key,因為key是不會進行swap操作的。由于Redis將內存中的數據swap到磁盤中時,提供服務的主線程和進行swap操作的子線程會共享這部分內存。如果更新需要swap的數據,Redis將阻塞這個操作,直至子線程完成swap操作之后才可以進行修改
當從Redis中讀取數據的時候,如果讀取的key對應的value不在內存中,那么Redis就要從swap文件加載相應的數據,然后再返回給請求數據的一方。此時存在一個I/O線程池的問題。在默認情況下,Redis會出現阻塞,它要完成所有的swap文件的加載之后才會響應。這樣的策略在客戶端數量較少,進行批量操作的時候比較合適。但是如果將Redis應用在一個大型的網站中,這顯然是無法滿足高并發的需求的。所有Redis允許我們設置I/O線程池的大小,對需要從swap文件中加載對應數據的請求進行并發操作,減少阻塞時間。
Redis提供的兩種持久化方式
RDB快照:
- Redis支持將當前的快照存儲為一個數據文件的持久化機制,即當前提到的RDB快照。Redis借助了fork命令的copy on write機制(私有內存非共享內存)。在生成快照時,將當前進程fork出一個子進程。接著在子進程中迭代循環所有的數據同時將數據存儲到一個臨時文件當中。當數據全部處理完之后,就通過原子性rename系統調用將臨時文件重命名為RDB文件。值得注意的是,這樣的文件生成機制可以保證RDB文件不會壞掉,即Redis的RDB文件總是可用的。但是,RDB有明顯的不足-----一旦數據庫出現問題,那么我們的RDB文件中保存的數據并不是全新的,從上次RDB文件生成到Redis停機這段時間的數據全部丟掉了。在某些業務下,這是可以忍受的,我們也推薦這些業務使用RDB的方式進行持久化,因為開啟RDB的代價并不高。但是對于另外一些對數據安全性要求極高的應用,無法容忍數據丟失的應用,RDB就無能為力了,所以Redis引入了另一個重要的持久化機制:AOF日志。
我們可以通過Redis的save指令來配置快照生成的時機
save 900 1 #900秒內有一條key數據被修改就生成RDB文件
優點:
RDB 是一個非常緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件非常適合用于進行備份: 比如說,你可以在最近的 24 小時內,每小時備份一次 RDB 文件,并且在每個月的每一天,也備份一個 RDB 文件。 這樣的話,即使遇上問題,也可以隨時將數據集還原到不同的版本。
RDB 非常適用于災難恢復(disaster recovery):它只有一個文件,并且內容都非常緊湊,可以(在加密后)將它傳送到別的數據中心,或者亞馬遜 S3 中。
RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然后這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。
RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
缺點:
如果你需要盡量避免在服務器故障時丟失數據,那么 RDB 不適合你。 雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因為RDB 文件需要保存整個數據集的狀態, 所以它并不是一個輕松的操作。 因此你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。
每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,并由子進程來進行實際的持久化工作。 在數據集比較龐大時, fork() 可能會非常耗時,造成服務器在某某毫秒內停止處理客戶端; 如果數據集非常巨大,并且 CPU 時間非常緊張的話,那么這種停止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。
AOF日志:
AOF日志是追加寫入的日志文件。和一般數據庫的binlog不同的是,AOF文件是可讀性較強的純文本。其中保存的內容即Redis一條條的標準指令。但是并不是所有的Redis指令都會記錄在AOF文件中,只有會導致數據發生修改的指令才會追加到AOF文件中。隨著記錄的指令越來越多,文件會變得越來越大。此時Redis提供了一個叫做AOF rewrite的功能,可以重新生成一份新的并且更小的AOF文件。新的文件中針對同一條key只記錄了最新的一次數據修改的指令。AOF的文件生成機制和RDB快照類似。再寫入新文件的過程中,所有操作日志還是會寫到舊的文件當中,同時會記錄在內存緩沖區中。當rewrite操作完成后,會將所有緩沖區中的日志一次性寫入臨時文件并調用原子性的rename命令將新的AOF文件覆蓋舊的AOF文件。
優點:
- 使用 AOF 持久化會讓 Redis 變得非常耐久(much more durable):你可以設置不同的策略,比如無fsync ,每秒鐘一次 fsync,或者每次執行寫入命令時fsync 。 AOF 的默認策略為每秒鐘 fsync一次,在這種配置下,Redis 仍然可以保持良好的性能,并且就算發生故障停機,也最多只會丟失一秒鐘的數據fsync 會在后臺線程執行,所以主線程可以繼續努力地處理命令請求)。
- AOF 文件是一個只進行追加操作的日志文件(append only log), 因此對 AOF 文件的寫入不需要進行 seek , 即使日志因為某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。
- Redis 可以在 AOF 文件體積變得過大時,自動地在后臺對 AOF 進行重寫: 重寫后的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件里面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,并開始對新 AOF 文件進行追加操作。
- AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕松。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那么只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重啟 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。
缺點:
- 對于相同的數據集來說,AOF 文件的體積通常要大于 RDB 文件的體積。
- 根據所使用的 fsync 策略,AOF 的速度可能會慢于 RDB 。 在一般情況下, 每秒fsync的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的最大延遲時間(latency)。
- AOF 在過去曾經發生過這樣的 bug : 因為個別命令的原因,導致 AOF 文件在重新載入時,無法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引起過這樣的 bug 。) 測試套件里為這種情況添加了測試: 它們會自動生成隨機的、復雜的數據集, 并通過重新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中并不常見, 但是對比來說, RDB 幾乎是不可能出現這種 bug 的。
appendfsync:控制AOF文件寫入磁盤的時機
- appendfsync no:當設置appendfsync為no時,Redis不會主動調用fsync去將AOF日志內容同步到磁盤,這完全依賴于操作系統。對于大多數Linux系統來說,一般是每30秒進行一次fsync,將緩沖區中的數據同步到磁盤上;
- appendfsync everysec:當設置appendfsync為everysec的時候,Redis會默認每隔一秒進行一次fsync調用,將緩沖區中的數據寫到磁盤。但是當這一次的fsync調用時長超過1秒時。Redis會采取延遲fsync的策略,再等一秒鐘。也就是在兩秒后再進行fsync,這一次的fsync就不管會執行多長時間都會進行。這時候由于在fsync時文件描述符會被阻塞,所以當前的寫操作就會阻塞。所以結論就是,在絕大多數情況下,Redis會每隔一秒進行一次fsync。在最壞的情況下,兩秒鐘會進行一次fsync操作。這一操作在大多數數據庫系統中被稱為group commit,就是組合多次寫操作的數據,一次性將日志寫到磁盤。
- 當設置appendfsync為always時,每一次寫操作都會調用一次fsync,這時數據是最安全的,當然,由于每次都會執行fsync,所以其性能也會受到影響。
主從復制
為了保證單點故障下的數據可用性,Redis引入了Master節點和Slave節點。
- master節點可擁有多個slave節點
- 除了多個slave連到相同的master之外,slave也可以連接其他slave形成圖狀結構
- 主從復制不會阻塞master。也就是說,當一個或多個slave與master進行初次同步數據時,master可以繼續處理client發來的請求。相反slave在初次同步數據時則會阻塞,不能處理client的請求
- 主從復制可以用來提高系統的可伸縮性,我們可以用多個slave專門用于client的讀請求,比如sort操作可以使用slave來處理。也可以用來做簡單的數據冗余
- 可以在master禁用數據持久化,只需要注釋掉master配置文件中的所有save配置,然后只在slave上配置數據持久化
主從復制的過程
當設置好slave服務器后,slave會建立和master的連接,接著發送sync命令。無論是第一次同步建立的連接還是連接斷開后的重新連接,master都會啟動一個后臺進程,將數據庫快照保存到文件中,同時master主進程會開始收集新的寫命令并緩存起來。后臺進程完成寫文件后,master就發送文件給slave,slave將文件保存到磁盤上,然后加載到內存并恢復數據庫快照到磁盤上。接著master就會把緩存的命令轉發給slave,而且后續master收到的寫命令都會通過開始建立的連接發送給slave。從master到slave的同步數據的命令和從client發送的命令使用相同的協議格式。當master和slave的連接斷開時slave可以自動重新建立連接。如果master同時收到多個slave發來的同步連接命令,只會啟動一個進程來寫數據庫鏡像,然后發送給所有slave。
- 主服務器創建快照文件,發送給從服務器,并在發送期間使用緩沖區記錄執行的寫命令。快照文件發送完畢之后,開始向從服務器發送存儲在緩沖區中的寫命令;
- 從服務器丟棄所有舊數據,載入主服務器發來的快照文件,之后從服務器開始接受主服務器發來的寫命令;
- 主服務器每執行一次寫命令,就向從服務器發送相同的寫命令。
redis集群相關:
https://www.zhihu.com/question/21419897