介紹
Redis是一個開源的高性能的key-value存儲系統(tǒng)。具有以下特點:
1、Redis支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時候可以再次加載進(jìn)行使用。
2、Redis不僅僅支持簡單的key-value類型的數(shù)據(jù),同時還提供list,set,sorted set,hash等數(shù)據(jù)結(jié)構(gòu)的存儲。
3、Redis支持?jǐn)?shù)據(jù)的備份,即master-slave模式的數(shù)據(jù)備份。
Redis優(yōu)勢:
1、性能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
2、豐富的數(shù)據(jù)類型 – Redis支持二進(jìn)制案例的 String, List, Hash, Set 及 Sorted Set 數(shù)據(jù)類型操作。
3、原子 – Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執(zhí)行。
4、豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過期等等特性
數(shù)據(jù)類型
string、hash、list、set、sorted set
內(nèi)存管理
首先 Redis 內(nèi)部使用一個 redisObject 對象來表示所有的 key 和 value,redisObject 最主要的信息如上圖所示:type 代表一個 value 對象具體是何種數(shù)據(jù)類型,encoding 是不同數(shù)據(jù)類型在 redis 內(nèi)部的存儲方式,比如:type=string 代表 value 存儲的是一個普通字符串,那么對應(yīng)的 encoding 可以是 raw 或者是 int,如果是 int 則代表實際 redis 內(nèi)部是按數(shù)值型類存儲和表示這個字符串的,當(dāng)然前提是這個字符串本身可以用數(shù)值表示,比如:”123″ “456”這樣的字符串。
內(nèi)存調(diào)優(yōu)
1、首先最重要的一點是不要開啟 Redis 的 VM 選項,即虛擬內(nèi)存功能,這個本來是作為 Redis 存儲超出物理內(nèi)存數(shù)據(jù)的一種數(shù)據(jù)在內(nèi)存與磁盤換入換出的一個持久化策略,但是其內(nèi)存管理成本也非常的高,并且我們后續(xù)會分析此種持久化策略并不成熟,所以要關(guān)閉 VM 功能,請檢查你的 redis.conf 文件中 vm-enabled 為 no。
2、其次最好設(shè)置下 redis.conf 中的 maxmemory 選項,該選項是告訴 Redis 當(dāng)使用了多少物理內(nèi)存后就開始拒絕后續(xù)的寫入請求,該參數(shù)能很好的保護(hù)好你的 Redis 不會因為使用了過多的物理內(nèi)存而導(dǎo)致 swap,最終嚴(yán)重影響性能甚至崩潰。
3、Redis 為不同數(shù)據(jù)類型分別提供了一組參數(shù)來控制內(nèi)存使用,我們在前面詳細(xì)分析過 Redis Hash 是 value 內(nèi)部為一個 HashMap,如果該 Map 的成員數(shù)比較少,則會采用類似一維線性的緊湊格式來存儲該 Map,即省去了大量指針的內(nèi)存開銷,這個參數(shù)控制對應(yīng)在 redis.conf 配置文件中下面2項:
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
hash-max-zipmap-entries
含義是當(dāng) value 這個 Map 內(nèi)部不超過多少個成員時會采用線性緊湊格式存儲,默認(rèn)是64,即 value 內(nèi)部有64個以下的成員就是使用線性緊湊存儲,超過該值自動轉(zhuǎn)成真正的 HashMap。
hash-max-zipmap-value 含義是當(dāng) value 這個 Map 內(nèi)部的每個成員值長度不超過多少字節(jié)就會采用線性緊湊存儲來節(jié)省空間。
以上2個條件任意一個條件超過設(shè)置值都會轉(zhuǎn)換成真正的 HashMap,也就不會再節(jié)省內(nèi)存了,那么這個值是不是設(shè)置的越大越好呢,答案當(dāng)然是否定的,HashMap 的優(yōu)勢就是查找和操作的時間復(fù)雜度都是 O(1) 的,而放棄 Hash 采用一維存儲則是 O(n) 的時間復(fù)雜度,如果成員數(shù)量很少,則影響不大,否則會嚴(yán)重影響性能,所以要權(quán)衡好這個值的設(shè)置,總體上還是最根本的時間成本和空間成本上的權(quán)衡。
內(nèi)存淘汰策略
Redis提供了下面幾種淘汰策略供用戶選擇,其中默認(rèn)的策略為noeviction策略:
noeviction:當(dāng)內(nèi)存使用達(dá)到閾值的時候,所有引起申請內(nèi)存的命令會報錯。
allkeys-lru:在主鍵空間中,優(yōu)先移除最近未使用的key。
volatile-lru:在設(shè)置了過期時間的鍵空間中,優(yōu)先移除最近未使用的key。
allkeys-random:在主鍵空間中,隨機移除某個key。
volatile-random:在設(shè)置了過期時間的鍵空間中,隨機移除某個key。
volatile-ttl:在設(shè)置了過期時間的鍵空間中,具有更早過期時間的key優(yōu)先移除
持久化
redis是一個支持持久化的內(nèi)存數(shù)據(jù)庫,也就是說redis需要經(jīng)常將內(nèi)存中的數(shù)據(jù)同步到磁盤來保證持久化。
redis支持兩種持久化方式,一種是 RDB(快照)也是默認(rèn)方式,另一種是Append-only file(aof)的方式。
RDB
快照是默認(rèn)的持久化方式。這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb。可以通過配置設(shè)置自動做快照持久化的方式。我們可以配置redis在n秒內(nèi)如果超過m個key被修改就自動做快照。也可以命令行的方式讓redis進(jìn)行snapshotting。
快照生成過程大致如下:
redis調(diào)用fork,現(xiàn)在有了子進(jìn)程和父進(jìn)程;
父進(jìn)程繼續(xù)處理client請求,子進(jìn)程負(fù)責(zé)將內(nèi)存內(nèi)容寫入到臨時文件。由于os的寫時復(fù)制機制(copy on write)父子進(jìn)程會共享相同的物理頁面,當(dāng)父進(jìn)程處理寫請求時os會為父進(jìn)程要修改的頁面創(chuàng)建副本,而不是寫共享的頁面。所以子進(jìn)程的地址空間內(nèi)的數(shù)據(jù)是fork時刻整個數(shù)據(jù)庫的一個快照;
當(dāng)子進(jìn)程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進(jìn)程退出。
同時snapshotting也有不足的,因為兩次快照操作之間是有時間間隔的,一旦數(shù)據(jù)庫出現(xiàn)問題,那么快照文件中保存的數(shù)據(jù)并不是全新的,從上次快照文件生成到Redis停機這段時間的數(shù)據(jù)全部丟掉了。如果業(yè)務(wù)對數(shù)據(jù)準(zhǔn)確性要求極高的話,就得采用aof持久化機制了。
AOF
比快照方式有更好的持久化性,是由于在使用aof持久化方式時,redis會將每一個收到的寫命令都通過write函數(shù)追加到文件中(默認(rèn)是 appendonly.aof)。當(dāng)redis重啟時會通過重新執(zhí)行文件中保存的寫命令來在內(nèi)存中重建整個數(shù)據(jù)庫的內(nèi)容。當(dāng)然由于os會在內(nèi)核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。不過我們可以通過配置文件告訴redis我們想要通過fsync函數(shù)強制os寫入到磁盤的時機。有三種方式如下(默認(rèn)是:每秒fsync一次):
1、appendonly yes //啟用aof持久化方式
2、# appendfsync always //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
3、appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
4、# appendfsync no //完全依賴os,性能最好,持久化沒保證
aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調(diào)用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多余的。因為要恢復(fù)數(shù)據(jù)庫的狀態(tài)其實文件中保存一條set test 100就夠了。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內(nèi)存中的數(shù)據(jù) 以命令的方式保存到臨時文件中,最后替換原來的文件。
bgrewriteaof命令執(zhí)行過程如下:
redis調(diào)用fork ,現(xiàn)在有父子兩個進(jìn)程;
子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫快照,往臨時文件中寫入重建數(shù)據(jù)庫狀態(tài)的命令;
父進(jìn)程繼續(xù)處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進(jìn)程重寫失敗的話并不會出問題;
當(dāng)子進(jìn)程把快照內(nèi)容寫入以命令方式寫到臨時文件中后,子進(jìn)程發(fā)信號通知父進(jìn)程。然后父進(jìn)程把緩存的寫命令也寫入到臨時文件;
現(xiàn)在父進(jìn)程可以使用臨時文件替換老的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。
這兩種持久化方式有各自的特點,快照相對性能影響不大,但一旦崩潰,數(shù)據(jù)量丟失較大,而aof數(shù)據(jù)安全性較高,但性能影響較大,這就得根據(jù)業(yè)務(wù)特點自行選擇了。
線程體系
Redis可以說是基于單線程模型的,因為對于客戶端的所有讀寫請求的處理,都由一個主線程串行地處理,因此多個客戶端同時對一個鍵進(jìn)行寫操作不會有并發(fā)問題,但是除了客戶端讀寫請求之外還有一些比較耗時的操作,如持久化RDB文件,持久化AOF文件等等,這些操作不能放在主線程里面處理,因此Redis會在適當(dāng)?shù)臅r候fork子進(jìn)程來異步的處理這種任務(wù)。除了這些,Redis還有一組異步任務(wù)處理線程,用于處理不需要主線程同步處理的工作,總體上Redis的線程體系結(jié)構(gòu)大致如下圖:
主從同步
Redis的主從復(fù)制功能非常強大,一個master可以擁有多個slave,而一個slave又可以擁有多個slave,如此下去,形成了強大的多級服務(wù)器集群架構(gòu)。下面是關(guān)于redis主從復(fù)制的一些特點:
1.master可以有多個slave
2.除了多個slave連到相同的master外,slave也可以連接其他slave形成圖狀結(jié)構(gòu)
3.主從復(fù)制不會阻塞master。也就是說當(dāng)一個或多個slave與master進(jìn)行初次同步數(shù)據(jù)時,master可以繼續(xù)處理client發(fā)來的請求。相反slave在初次同步數(shù)據(jù)時則會阻塞不能處理client的請求。
4.主從復(fù)制可以用來提高系統(tǒng)的可伸縮性,我們可以用多個slave 專門用于client的讀請求,比如sort操作可以使用slave來處理。也可以用來做簡單的數(shù)據(jù)冗余
5.可以在master禁用數(shù)據(jù)持久化,只需要注釋掉master 配置文件中的所有save配置,然后只在slave上配置數(shù)據(jù)持久化。
下面介紹下主從復(fù)制的過程 當(dāng)設(shè)置好slave服務(wù)器后,slave會建立和master的連接,然后發(fā)送sync命令。無論是第一次同步建立的連接還是連接斷開后的重新連 接,master都會啟動一個后臺進(jìn)程,將數(shù)據(jù)庫快照保存到文件中,同時master主進(jìn)程會開始收集新的寫命令并緩存起來。后臺進(jìn)程完成寫文件 后,master就發(fā)送文件給slave,slave將文件保存到磁盤上,然后加載到內(nèi)存恢復(fù)數(shù)據(jù)庫快照到slave上。接著master就會把緩存的命 令轉(zhuǎn)發(fā)給slave。而且后續(xù)master收到的寫命令都會通過開始建立的連接發(fā)送給slave。從master到slave的同步數(shù)據(jù)的命令和從 client發(fā)送的命令使用相同的協(xié)議格式。當(dāng)master和slave的連接斷開時slave可以自動重新建立連接。如果master同時收到多個 slave發(fā)來的同步連接命令,只會使用啟動一個進(jìn)程來寫數(shù)據(jù)庫鏡像,然后發(fā)送給所有slave。
redis的主從復(fù)制策略是通過其持久化的rdb文件來實現(xiàn)的,其過程是先dump出rdb文件,將rdb文件全量傳輸給slave,然后再將dump后的操作實時同步到slave中。
1、Slave端在配置文件中添加了slaveof指令,于是Slave啟動時讀取配置文件,初始狀態(tài)為REDIS_REPL_CONNECT;
2、Slave端在定時任務(wù)serverCron(Redis內(nèi)部的定時器觸發(fā)事件)中連接Master,發(fā)送sync命令,然后阻塞等待master發(fā)送回其內(nèi)存快照文件(最新版的Redis已經(jīng)不需要讓Slave阻塞);
3、Master端收到sync命令簡單判斷是否有正在進(jìn)行的內(nèi)存快照子進(jìn)程,沒有則立即開始內(nèi)存快照,有則等待其結(jié)束,當(dāng)快照完成后會將該文件發(fā)送給Slave端;
4、Slave端接收Master發(fā)來的內(nèi)存快照文件,保存到本地,待接收完成后,清空內(nèi)存表,重新讀取Master發(fā)來的內(nèi)存快照文件,重建整個內(nèi)存表數(shù)據(jù)結(jié)構(gòu),并最終狀態(tài)置位為 REDIS_REPL_CONNECTED狀態(tài),Slave狀態(tài)機流轉(zhuǎn)完成;
5、Master端在發(fā)送快照文件過程中,接收的任何會改變數(shù)據(jù)集的命令都會暫時先保存在Slave網(wǎng)絡(luò)連接的發(fā)送緩存隊列里(list數(shù)據(jù)結(jié)構(gòu)),待快照完成后,依次發(fā)給Slave,之后收到的命令相同處理,并將狀態(tài)置位為 REDIS_REPL_ONLINE。
從以上的復(fù)制過程中可以發(fā)現(xiàn),Slave從庫在連接Master主庫時,Master會進(jìn)行內(nèi)存快照,然后把整個快照文件發(fā)給Slave,也就是沒有象MySQL那樣有復(fù)制位置的概念,即無增量復(fù)制,如果一個master連接多個slave,就會比較影響master性能了。
事務(wù)實現(xiàn)原理
Redis事務(wù)通常會使用MULTI,EXEC,WATCH等命令來完成,redis實現(xiàn)事務(wù)實現(xiàn)的機制與常見的關(guān)系型數(shù)據(jù)庫有很大的卻別,比如redis的事務(wù)不支持回滾,事務(wù)執(zhí)行時會阻塞其它客戶端的請求執(zhí)行。
Redis事務(wù)從開始到結(jié)束通常會通過三個階段:1、事務(wù)開始,2、命令入隊,3、命令執(zhí)行。
redis > MULTI
OK
redis > SET username huating
QUEUED
redis > SET password 161616
QUEUED
redis > GET username
redis > EXEC
1
) ok
2
)
huating
3
)
huating
與事務(wù)相關(guān)的狀態(tài)flag
#define REDIS_MULTI (1<<3)
#define REDIS_DIRTY_EXEC (1<<12)
#define REDIS_DIRTY_CAS (1<<5)
客戶端redisClient中有一個名叫flags的成員,標(biāo)識當(dāng)前客戶端的狀態(tài)。
在聲明事務(wù)之前,我們可以通過watch命令對一個或多個key進(jìn)行監(jiān)視。如果在事務(wù)執(zhí)行之前這些被監(jiān)視的key被其他命令修改,Redis將redisClient->flags設(shè)置為REDIS_DIRTY_CAS標(biāo)識。
使用multi命令可以標(biāo)識著一個事務(wù)的開始,此時redisClient進(jìn)入事務(wù)狀態(tài),其flags字段被設(shè)置為REDIS_MULTI標(biāo)識。
當(dāng)客戶端進(jìn)入事務(wù)狀態(tài)后,Redis服務(wù)器等待接收一個或多個命令,并把它們放入命令隊列中等待執(zhí)行。如果某條命令在入隊過程中發(fā)生錯誤,Redis會將redisClient的flags字段置為REDIS_DIRTY_EXEC標(biāo)識。
最后我們通過exec命令執(zhí)行事務(wù),該命令將會檢查redisClient的flags標(biāo)識,如果該標(biāo)識為REDIS_DIRTY_CAS或REDIS_DIRTY_EXEC,則事務(wù)執(zhí)行失敗,否則Redis一次性執(zhí)行事務(wù)中的多個命令,并將所有命令的結(jié)果集合到回復(fù)隊列,再作為 exec 命令的結(jié)果返回給客戶端。
緩存時效機制
1、延遲時效機制:也叫消極失效機制,延遲失效機制即當(dāng)客戶端請求操作某個key的時候,Redis會對客戶端請求操作的key進(jìn)行有效期檢查,如果key過期才進(jìn)行相應(yīng)的處理
2、主動時效機制:也叫積極失效機制,即服務(wù)端定時的去檢查失效的緩存,如果失效則進(jìn)行相應(yīng)的操作。