rdss原理?
能表達(dá)3中類型:字符串、整數(shù)和浮點(diǎn)數(shù)。根據(jù)場(chǎng)景相互間自動(dòng)轉(zhuǎn)型,并且根據(jù)需要選取底層的承載方式
value內(nèi)部以int、sds作為結(jié)構(gòu)存儲(chǔ)。int存放整型數(shù)據(jù),sds存放字節(jié)/字符串和浮點(diǎn)型數(shù)據(jù)
sds內(nèi)部結(jié)構(gòu):
用buf數(shù)組存儲(chǔ)字符串的內(nèi)容,但數(shù)組的長(zhǎng)度會(huì)大于所存儲(chǔ)內(nèi)容的長(zhǎng)度。會(huì)有一格專門存放”\0”(C標(biāo)準(zhǔn)庫(kù))作為結(jié)尾,還有預(yù)留多幾個(gè)空的(即free區(qū)域),當(dāng)append字符串的長(zhǎng)度小于free區(qū)域,則sds不會(huì)重新申請(qǐng)內(nèi)存,直接使用free區(qū)域
擴(kuò)容:當(dāng)對(duì)字符串的操作完成后預(yù)期的串長(zhǎng)度小于1M時(shí),擴(kuò)容后的buf數(shù)組大小=預(yù)期長(zhǎng)度*2+1;若大于1M,則buf總是會(huì)預(yù)留出1M的free空間
value對(duì)象通常具有兩個(gè)內(nèi)存部分:redisObject部分和redisObject的ptr指向的sds部分。創(chuàng)建value對(duì)象時(shí),通常需要為redisObject和sds申請(qǐng)兩次內(nèi)存。單對(duì)于短小的字符串,可以把兩者連續(xù)存放,所以可以一次性把兩者的內(nèi)存一起申請(qǐng)了
redis的list類型
list類型的value對(duì)象內(nèi)部以linkedlist或ziplist承載。當(dāng)list的元素個(gè)數(shù)和單個(gè)元素的長(zhǎng)度較小時(shí),redis會(huì)采用ziplist實(shí)現(xiàn)以減少內(nèi)存占用,否則采用linkedlist結(jié)構(gòu)
linkedlist內(nèi)部實(shí)現(xiàn)是雙向鏈表。在list中定義了頭尾元素指針和列表的長(zhǎng)度,是的pop/push操作、llen操作的復(fù)雜度為O(1)。由于是鏈表,lindex類的操作復(fù)雜度仍然是O(N)
ziplist的內(nèi)部結(jié)構(gòu)
所有內(nèi)容被放置在連續(xù)的內(nèi)存中。其中zlbytes表示ziplist的總長(zhǎng)度,zltail指向最末元素,zllen表示元素個(gè)數(shù),entry表示元素自身內(nèi)容,zlend作為ziplist定界符
rpush、rpop、llen,復(fù)雜度為O(1);lpush/pop操作由于涉及全列表元素的移動(dòng),復(fù)雜度為O(N)
redis的map類型
map又叫hash。map內(nèi)部的key和value不能再嵌套map了,只能是string類型:整形、浮點(diǎn)型和字符串
map主要由hashtable和ziplist兩種承載方式實(shí)現(xiàn),對(duì)于數(shù)據(jù)量較小的map,采用ziplist實(shí)現(xiàn)
hashtable內(nèi)部結(jié)構(gòu)
主要分為三層,自底向上分別是dictEntry、dictht、dict
dictEntry:管理一個(gè)key-value對(duì),同時(shí)保留同一個(gè)桶中相鄰元素的指針,一次維護(hù)哈希桶的內(nèi)部連
dictht:維護(hù)哈希表的所有桶鏈
dict:當(dāng)dictht需要擴(kuò)容/縮容時(shí),用于管理dictht的遷移
哈希表的核心結(jié)構(gòu)是dictht,它的table字段維護(hù)著hash桶,它是一個(gè)數(shù)組,每個(gè)元素指向桶的第一個(gè)元素(dictEntry)
set值的流程:先通過MurmurHash算法求出key的hash值,再對(duì)桶的個(gè)數(shù)取模,得到key對(duì)應(yīng)的桶,再進(jìn)入桶中,遍歷全部entry,判定是否已有相同的key,如果沒有,則將新key對(duì)應(yīng)的鍵值對(duì)插入到桶頭,并且更新dictht的used數(shù)量,used表示hash表中已經(jīng)存了多少元素。由于每次插入都要遍歷hash桶中的全部entry,所以當(dāng)桶中entry很多時(shí),性能會(huì)線性下降
擴(kuò)容:通過負(fù)載因子判定是否需要增加桶數(shù)。負(fù)載因子=哈希表中已有元素/哈希桶數(shù)的比值。有兩個(gè)閾值,小于1一定不擴(kuò)容;大于5一定擴(kuò)容。擴(kuò)容時(shí)新的桶數(shù)目是現(xiàn)有桶的2n倍
縮容:負(fù)載因子的閾值是0.1
擴(kuò)/縮容通過新建哈希表的方式實(shí)現(xiàn)。即擴(kuò)容時(shí),會(huì)并存兩個(gè)哈希表,一個(gè)是源表,一個(gè)是目標(biāo)表。通過將源表的桶逐步遷移到目標(biāo)表,以數(shù)據(jù)遷移的方式實(shí)現(xiàn)擴(kuò)容,遷移完成后目標(biāo)表覆蓋源表。遷移過程中,首先訪問源表,如果發(fā)現(xiàn)key對(duì)應(yīng)的源表桶已完成遷移,則重新訪問目標(biāo)表,否則在源表中操作
redis是單線程處理請(qǐng)求,遷移和訪問的請(qǐng)求在相同線程內(nèi)進(jìn)行,所以不會(huì)存在并發(fā)性問題
ziplist內(nèi)部結(jié)構(gòu)
和list的ziplist實(shí)現(xiàn)類似。不同的是,map對(duì)應(yīng)的ziplist的entry個(gè)數(shù)總是2的整數(shù)倍,奇數(shù)存放key,偶數(shù)存放value
ziplist實(shí)現(xiàn)下,由哈希遍歷變成了鏈表的順序遍歷,復(fù)雜度變成O(N)
redis的set類型
set以intset或hashtable來存儲(chǔ)。hashtable中的value永遠(yuǎn)為null,當(dāng)set中只包含整數(shù)型的元素時(shí),則采用intset
intset的內(nèi)部結(jié)構(gòu)
核心元素是一個(gè)字節(jié)數(shù)組,從小到大有序存放著set的元素
由于元素有序排列,所以set的獲取操作采用二分查找方式實(shí)現(xiàn),復(fù)雜度O(log(N))。進(jìn)行插入時(shí),首先通過二分查找得到本次插入的位置,再對(duì)元素進(jìn)行擴(kuò)容,再將預(yù)計(jì)插入位置之后的所有元素向右移動(dòng)一個(gè)位置,最后插入元素,插入復(fù)雜度為O(N)。刪除類似
redis的sorted-set類型
類似map是一個(gè)key-value對(duì),但是有序的。value是一個(gè)浮點(diǎn)數(shù),稱為score,內(nèi)部是按照score從小到大排序
內(nèi)部結(jié)構(gòu)以ziplist或skiplist+hashtable來實(shí)現(xiàn)
redis客戶端與服務(wù)器的交互模式
串行的請(qǐng)求/響應(yīng)模式
每一次請(qǐng)求的發(fā)送都依賴于上一次請(qǐng)求的相應(yīng)結(jié)果完全接收,同一個(gè)連接的每秒吞吐量低
redis對(duì)單個(gè)請(qǐng)求的處理時(shí)間通常比局域網(wǎng)的延遲小一個(gè)數(shù)量級(jí),所以串行模式下,單鏈接的大部分時(shí)間都處于網(wǎng)絡(luò)等待
雙工的請(qǐng)求/相應(yīng)模式(pipeline)
適用于批量的獨(dú)立寫入操作。即可將請(qǐng)求數(shù)據(jù)批量發(fā)送到服務(wù)器,再批量地從服務(wù)器連接的字節(jié)流中一次讀取每個(gè)響應(yīng)數(shù)據(jù),減少了網(wǎng)絡(luò)延遲,所以單連接吞吐量較串行會(huì)提高一個(gè)數(shù)量級(jí)
原子化的批量請(qǐng)求/響應(yīng)模式(事務(wù))
客戶端通過和redis服務(wù)器兩階段的交互做到批量命令原子執(zhí)行的事務(wù)效果:入隊(duì)操作(即服務(wù)器端先將客戶端發(fā)送過來的連接對(duì)象暫存在請(qǐng)求隊(duì)列中)和執(zhí)行階段(依次執(zhí)行請(qǐng)求隊(duì)列中的所有請(qǐng)求)
一個(gè)連接的請(qǐng)求在執(zhí)行批量請(qǐng)求的過程中,不會(huì)執(zhí)行其他客戶端的請(qǐng)求
redis的事務(wù)不是一致的,沒有回滾機(jī)制。如果中途失敗,則返回錯(cuò)誤信息,但已經(jīng)成功執(zhí)行的命令不會(huì)回滾
事務(wù)里面有可能會(huì)帶有讀操作作為條件,由于批量請(qǐng)求只會(huì)先入隊(duì)列,再批量一起執(zhí)行,所以一般讀操作不會(huì)跟批量寫請(qǐng)求一起執(zhí)行,這時(shí)候就有可能會(huì)導(dǎo)致批量寫之前和之后讀到的數(shù)據(jù)不一致,這種可以通過樂觀鎖的可串行化來解決,redis通過watch機(jī)制實(shí)現(xiàn)樂觀鎖。具體實(shí)現(xiàn)過程看下一題
發(fā)布/訂閱模式
發(fā)布端和訂閱者通過channel關(guān)聯(lián)
channel的訂閱關(guān)系,維護(hù)在reids實(shí)例級(jí)別,獨(dú)立于redisDB的key-value體系。所有的channel都由一個(gè)map維護(hù),鍵是channel的名字,value是它所有訂閱者client的指針鏈表
腳本化的批量執(zhí)行(腳本模式)
redis通過watch機(jī)制實(shí)現(xiàn)樂觀鎖流程
將本次事務(wù)涉及的所有key注冊(cè)為觀察模式
執(zhí)行只讀操作
根據(jù)只讀操作的結(jié)果組裝寫操作命令并發(fā)送到服務(wù)器端入隊(duì)
發(fā)送原子化的批量執(zhí)行命令EXEC試圖執(zhí)行連接的請(qǐng)求隊(duì)列中的命令
如果前面注冊(cè)為觀察模式的key中有一個(gè)貨多個(gè),在EXEC之前被修改過,則EXEC將直接失敗,拒絕執(zhí)行;否則順序執(zhí)行請(qǐng)求隊(duì)列中的所有請(qǐng)求
redis沒有原生的悲觀鎖或者快照實(shí)現(xiàn),但可通過樂觀鎖繞過。一旦兩次讀到的操作不一樣,watch機(jī)制觸發(fā),拒絕了后續(xù)的EXEC執(zhí)行
redis的網(wǎng)絡(luò)協(xié)議
redis協(xié)議位于TCP層之上,即客戶端和redis實(shí)例保持雙工的連接,交互的都是序列化后的協(xié)議數(shù)據(jù)
redis處理命令的主要邏輯
redis服務(wù)器對(duì)命令的處理都是單線程的,但是I/O層面卻面向多個(gè)客戶端并發(fā)地提供服務(wù),并發(fā)到內(nèi)部單線程的轉(zhuǎn)化通過多路復(fù)用框架來實(shí)現(xiàn)
首先從多路服用框架(epoll、evport、kqueue)中select出已經(jīng)ready的文件描述符(fileDescriptor)
ready的標(biāo)準(zhǔn)是已有數(shù)據(jù)到達(dá)內(nèi)核(kernel)、已準(zhǔn)備好寫入數(shù)據(jù)
對(duì)于上一步已經(jīng)ready的fd,redis會(huì)分別對(duì)每個(gè)fd上已ready的事件進(jìn)行處理,處理完相同fd上的所有事件后,再處理下一個(gè)ready的fd。有3中事件類型
acceptTcpHandler:連接請(qǐng)求事件
readQueryFromClient:客戶端的請(qǐng)求命令事件
sendReplyToClient:將暫存的執(zhí)行結(jié)果寫回客戶端
對(duì)來自客戶端的命令執(zhí)行結(jié)束后,接下來處理定時(shí)任務(wù)(TimeEvent)
aeApiPoll的等待時(shí)間取決于定時(shí)任務(wù)處理(TimeEvent)邏輯
本次主循環(huán)完畢,進(jìn)入下一次主循環(huán)的beforeSleep邏輯,后者負(fù)責(zé)處理數(shù)據(jù)過期、增量持久化的文件寫入等任務(wù)
redis的持久化機(jī)制
redis主要提供了兩種持久化機(jī)制:RDB和AOF;
RDB
默認(rèn)開啟,會(huì)按照配置的指定時(shí)間將內(nèi)存中的數(shù)據(jù)快照到磁盤中,創(chuàng)建一個(gè)dump.rdb文件,redis啟動(dòng)時(shí)再恢復(fù)到內(nèi)存中。
redis會(huì)單獨(dú)創(chuàng)建fork()一個(gè)子進(jìn)程,將當(dāng)前父進(jìn)程的數(shù)據(jù)庫(kù)數(shù)據(jù)復(fù)制到子進(jìn)程的內(nèi)存中,然后由子進(jìn)程寫入到臨時(shí)文件中,持久化的過程結(jié)束了,再用這個(gè)臨時(shí)文件替換上次的快照文件,然后子進(jìn)程退出,內(nèi)存釋放。
需要注意的是,每次快照持久化都會(huì)將主進(jìn)程的數(shù)據(jù)庫(kù)數(shù)據(jù)復(fù)制一遍,導(dǎo)致內(nèi)存開銷加倍,若此時(shí)內(nèi)存不足,則會(huì)阻塞服務(wù)器運(yùn)行,直到復(fù)制結(jié)束釋放內(nèi)存;都會(huì)將內(nèi)存數(shù)據(jù)完整寫入磁盤一次,所以如果數(shù)據(jù)量大的話,而且寫操作頻繁,必然會(huì)引起大量的磁盤I/O操作,嚴(yán)重影響性能,并且最后一次持久化后的數(shù)據(jù)可能會(huì)丟失;
AOF
以日志的形式記錄每個(gè)寫操作(讀操作不記錄),只需追加文件但不可以改寫文件,redis啟動(dòng)時(shí)會(huì)根據(jù)日志從頭到尾全部執(zhí)行一遍以完成數(shù)據(jù)的恢復(fù)工作。包括flushDB也會(huì)執(zhí)行。
主要有兩種方式觸發(fā):有寫操作就寫、每秒定時(shí)寫(也會(huì)丟數(shù)據(jù))。
因?yàn)锳OF采用追加的方式,所以文件會(huì)越來越大,針對(duì)這個(gè)問題,新增了重寫機(jī)制,就是當(dāng)日志文件大到一定程度的時(shí)候,會(huì)fork出一條新進(jìn)程來遍歷進(jìn)程內(nèi)存中的數(shù)據(jù),每條記錄對(duì)應(yīng)一條set語句,寫到臨時(shí)文件中,然后再替換到舊的日志文件(類似rdb的操作方式)。默認(rèn)觸發(fā)是當(dāng)aof文件大小是上次重寫后大小的一倍且文件大于64M時(shí)觸發(fā);
當(dāng)兩種方式同時(shí)開啟時(shí),數(shù)據(jù)恢復(fù)redis會(huì)優(yōu)先選擇AOF恢復(fù)。一般情況下,只要使用默認(rèn)開啟的RDB即可,因?yàn)橄鄬?duì)于AOF,RDB便于進(jìn)行數(shù)據(jù)庫(kù)備份,并且恢復(fù)數(shù)據(jù)集的速度也要快很多。
開啟持久化緩存機(jī)制,對(duì)性能會(huì)有一定的影響,特別是當(dāng)設(shè)置的內(nèi)存滿了的時(shí)候,更是下降到幾百reqs/s。所以如果只是用來做緩存的話,可以關(guān)掉持久化。
redis內(nèi)存分析的設(shè)計(jì)思路
主要有3種方式可以實(shí)現(xiàn)
keys命令:獲取到所有的key,再根據(jù)key獲取所有的內(nèi)容。缺點(diǎn)是如果key數(shù)量特別多,則會(huì)導(dǎo)致redis卡住影響業(yè)務(wù)
aof:通過aof文件獲取到所有數(shù)據(jù)。缺點(diǎn)是有一些redis實(shí)例寫入頻繁,不適合開啟aof,并且文件可能特別大,傳輸、解析效率差
rdb:使用bgsave獲取rdb文件,然后解析。缺點(diǎn)是bgsave在fork子進(jìn)程時(shí)有可能會(huì)卡住主進(jìn)程。當(dāng)對(duì)于其他兩種,在低峰期在從節(jié)點(diǎn)做bgsave獲取rdb文件,相對(duì)安全可靠。
設(shè)計(jì)思路:
在訪問低峰期時(shí)根據(jù)redis獲取rdb文件
解析rdb文件
根據(jù)相對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)及內(nèi)容,估算內(nèi)容消耗等
統(tǒng)計(jì)并生成報(bào)表
開源框架:https://github.com/xueqiu/rdr
redis內(nèi)存估算
基礎(chǔ)的數(shù)據(jù)類型:sds、dict、intset、zipmap、adlist、quicklist、skiplist
舉例:以key為hello,value為world,類型是string,它的內(nèi)存使用:
一個(gè)dictEntry的消耗(有2個(gè)指針,一個(gè)int64的內(nèi)存消耗),RedisDB就是一個(gè)大dict,每對(duì)kv都是其中的一個(gè)entry;
一個(gè)robj的消耗(有1指針,一個(gè)int,以及幾個(gè)使用位域的字段共消耗4字節(jié)),robj是為了在同一個(gè)dict內(nèi)能夠存儲(chǔ)不同類型的value,而使用的一個(gè)通用的數(shù)據(jù)結(jié)構(gòu),全名是RedisObject;
存儲(chǔ)key的sds消耗(存儲(chǔ)header以及字符串長(zhǎng)度+1的空間,header長(zhǎng)度根據(jù)字符串長(zhǎng)度不同也會(huì)有所不同),sds是Redis中存儲(chǔ)字符串使用的數(shù)據(jù)結(jié)構(gòu);
存儲(chǔ)過期時(shí)間消耗(也是存儲(chǔ)為一個(gè)dictEntry,時(shí)間戳為int64);
存儲(chǔ)value的sds消耗,根據(jù)數(shù)據(jù)結(jié)構(gòu)不同而不同;
前四項(xiàng)基本是存儲(chǔ)任何一個(gè)key都需要消耗的,最后一項(xiàng)根據(jù)value的數(shù)據(jù)結(jié)構(gòu)不同而不同;
redis集群(redis cluster)
redis3以后,節(jié)點(diǎn)之間提供了完整的sharding(分片)、replication(主備感知能力)、failover(故障轉(zhuǎn)移)的特性
配置一致性:每個(gè)節(jié)點(diǎn)(Node)內(nèi)部都保存了集群的配置信息,存儲(chǔ)在clusterState中,通過引入自增的epoch變量來使得集群配置在各個(gè)節(jié)點(diǎn)間保持一致
sharding數(shù)據(jù)分片
將所有數(shù)據(jù)劃分為16384個(gè)分片(slot),每個(gè)節(jié)點(diǎn)會(huì)對(duì)應(yīng)一部分slot,每個(gè)key都會(huì)根據(jù)分布算法映射到16384個(gè)slot中的一個(gè),分布算法為slotId=crc16(key)%16384
當(dāng)一個(gè)client訪問的key不在對(duì)應(yīng)節(jié)點(diǎn)的slots中,redis會(huì)返回給client一個(gè)moved命令,告知其正確的路由信息從而重新發(fā)起請(qǐng)求。client會(huì)根據(jù)每次請(qǐng)求來緩存本地的路由緩存信息,以便下次請(qǐng)求直接能夠路由到正確的節(jié)點(diǎn)
分片遷移:分片遷移的觸發(fā)和過程控制由外部系統(tǒng)完成,redis只提供遷移過程中需要的原語支持。主要包含兩種:一種是節(jié)點(diǎn)遷移狀態(tài)設(shè)置,即遷移錢標(biāo)記源、目標(biāo)節(jié)點(diǎn);另一種是key遷移的原子化命令
failover故障轉(zhuǎn)移
故障發(fā)現(xiàn):節(jié)點(diǎn)間兩兩通過TCP保持連接,周期性進(jìn)行PING、PONG交互,若對(duì)方的PONG相應(yīng)超時(shí)未收到,則將其置為PFAIL狀態(tài),并傳播給其他節(jié)點(diǎn)
故障確認(rèn):當(dāng)集群中有一半以上的節(jié)點(diǎn)對(duì)某一個(gè)PFAIL狀態(tài)進(jìn)行了確認(rèn),則將起改為FAIL狀態(tài),確認(rèn)其故障
slave選舉:當(dāng)有一個(gè)master掛掉了,則其slave重新競(jìng)選出一個(gè)新的master。主要根據(jù)各個(gè)slave最后一次同步master信息的時(shí)間,越新表示slave的數(shù)據(jù)越新,競(jìng)選的優(yōu)先級(jí)越高,就更有可能選中。競(jìng)選成功之后將消息傳播給其他節(jié)點(diǎn)。
集群不可用的情況:
集群中任意master掛掉,且當(dāng)前master沒有slave。
集群中超過半數(shù)以上master掛掉。
普通哈希算法和一致性哈希算法對(duì)比
普通哈希:也稱硬哈希,采用簡(jiǎn)單取模的方式,將機(jī)器進(jìn)行散列,這在cache環(huán)境不變的情況下能取得讓人滿意的結(jié)果,但是當(dāng)cache環(huán)境動(dòng)態(tài)變化時(shí),這種靜態(tài)取模的方式顯然就不滿足單調(diào)性的要求(當(dāng)增加或減少一臺(tái)機(jī)子時(shí),幾乎所有的存儲(chǔ)內(nèi)容都要被重新散列到別的緩沖區(qū)中)。
一致性哈希:將機(jī)器節(jié)點(diǎn)和key值都按照一樣的hash算法映射到一個(gè)0~2^32的圓環(huán)上。當(dāng)有一個(gè)寫入緩存的請(qǐng)求到來時(shí),計(jì)算Key值k對(duì)應(yīng)的哈希值Hash(k),如果該值正好對(duì)應(yīng)之前某個(gè)機(jī)器節(jié)點(diǎn)的Hash值,則直接寫入該機(jī)器節(jié)點(diǎn),如果沒有對(duì)應(yīng)的機(jī)器節(jié)點(diǎn),則順時(shí)針查找下一個(gè)節(jié)點(diǎn),進(jìn)行寫入,如果超過2^32還沒找到對(duì)應(yīng)節(jié)點(diǎn),則從0開始查找(因?yàn)槭黔h(huán)狀結(jié)構(gòu))。為了更可能的滿足平衡性,可以引入虛擬節(jié)點(diǎn),即一個(gè)實(shí)體節(jié)點(diǎn)映射到多個(gè)虛擬節(jié)點(diǎn)。
參考:http://blog.huanghao.me/?p=14
緩存雪崩,緩存穿透,緩存并發(fā),緩存預(yù)熱,緩存算法
緩存雪崩:可能是因?yàn)閿?shù)據(jù)未加載到緩存中,或者緩存同一時(shí)間大面積的失效,從而導(dǎo)致所有請(qǐng)求都去查數(shù)據(jù)庫(kù),導(dǎo)致數(shù)據(jù)庫(kù)CPU和內(nèi)存負(fù)載過高,甚至宕機(jī)。解決思路:
加鎖計(jì)數(shù)(即限制并發(fā)的數(shù)量,可以用semphore)或者起一定數(shù)量的隊(duì)列來避免緩存失效時(shí)大量請(qǐng)求并發(fā)到數(shù)據(jù)庫(kù)。但這種方式會(huì)降低吞吐量。
分析用戶行為,然后失效時(shí)間均勻分布。或者在失效時(shí)間的基礎(chǔ)上再加1~5分鐘的隨機(jī)數(shù)。
如果是某臺(tái)緩存服務(wù)器宕機(jī),則考慮做主備。
緩存穿透:指用戶查詢數(shù)據(jù),在數(shù)據(jù)庫(kù)沒有,自然在緩存中也不會(huì)有。這樣就導(dǎo)致用戶查詢的時(shí)候,在緩存中找不到,每次都要去數(shù)據(jù)庫(kù)中查詢。解決思路:
如果查詢數(shù)據(jù)庫(kù)也為空,直接設(shè)置一個(gè)默認(rèn)值存放到緩存,這樣第二次到緩沖中獲取就有值了,而不會(huì)繼續(xù)訪問數(shù)據(jù)庫(kù)。設(shè)置一個(gè)過期時(shí)間或者當(dāng)有值的時(shí)候?qū)⒕彺嬷械闹堤鎿Q掉即可。
可以給key設(shè)置一些格式規(guī)則,然后查詢之前先過濾掉不符合規(guī)則的Key。
緩存并發(fā):如果網(wǎng)站并發(fā)訪問高,一個(gè)緩存如果失效,可能出現(xiàn)多個(gè)進(jìn)程同時(shí)查詢DB,同時(shí)設(shè)置緩存的情況,如果并發(fā)確實(shí)很大,這也可能造成DB壓力過大,還有緩存頻繁更新的問題。解決思路:
對(duì)緩存查詢加鎖,如果KEY不存在,就加鎖,然后查DB入緩存,然后解鎖;其他進(jìn)程如果發(fā)現(xiàn)有鎖就等待,然后等解鎖后返回?cái)?shù)據(jù)或者進(jìn)入DB查詢。
緩存預(yù)熱:目的就是在系統(tǒng)上線前,將數(shù)據(jù)加載到緩存中。解決思路:
數(shù)據(jù)量不大的話,在系統(tǒng)啟動(dòng)的時(shí)候直接加載。
自己寫個(gè)簡(jiǎn)單的緩存預(yù)熱程序。
緩存算法:
FIFO算法:First in First out,先進(jìn)先出。原則:一個(gè)數(shù)據(jù)最先進(jìn)入緩存中,則應(yīng)該最早淘汰掉。也就是說,當(dāng)緩存滿的時(shí)候,應(yīng)當(dāng)把最先進(jìn)入緩存的數(shù)據(jù)給淘汰掉。
LFU算法:Least Frequently Used,最不經(jīng)常使用算法。
LRU算法:Least Recently Used,近期最少使用算法。
LRU和LFU的區(qū)別。LFU算法是根據(jù)在一段時(shí)間里數(shù)據(jù)項(xiàng)被使用的次數(shù)選擇出最少使用的數(shù)據(jù)項(xiàng),即根據(jù)使用次數(shù)的差異來決定。而LRU是根據(jù)使用時(shí)間的差異來決定的。
用redis實(shí)現(xiàn)分布式鎖
主要使用的命令:
setnx key val。當(dāng)且僅當(dāng)key不存在時(shí),set一個(gè)key為val的字符串,返回1;若key存在,則什么都不做,返回0。
expire key timeout。為key設(shè)置一個(gè)超時(shí)時(shí)間,單位為second,超過這個(gè)時(shí)間鎖會(huì)自動(dòng)釋放,避免死鎖。
delete key。刪除鎖
實(shí)現(xiàn)思想:
使用setnx加鎖,如果返回1,則說明加鎖成功,并設(shè)置超時(shí)時(shí)間,避免系統(tǒng)掛了,鎖沒法釋放。在finally中delete刪除鎖釋放。
如果需要設(shè)置超時(shí)等待時(shí)間,則可以加個(gè)while循環(huán),在獲取不到鎖的情況下,進(jìn)行循環(huán)獲取鎖,超時(shí)了則退出。