redis常見使用場景
1 緩存
緩存現(xiàn)在幾乎是所有中大型網(wǎng)站都在用的必殺技垢箕,合理的利用緩存不僅能夠提升網(wǎng)站訪問速度控嗜,還能大大降低數(shù)據(jù)庫的壓力庸推。Redis提供了鍵過期功能蘑辑,也提供了靈活的鍵淘汰策略洋机,所以,現(xiàn)在Redis用在緩存的場合非常多
2 排行榜
很多網(wǎng)站都有排行榜應(yīng)用的洋魂,如京東的月度銷量榜單绷旗、商品按時間的上新排行榜等。Redis提供的有序集合數(shù)據(jù)類構(gòu)能實現(xiàn)各種復(fù)雜的排行榜應(yīng)用副砍。
3 計數(shù)器
電商網(wǎng)站商品的瀏覽量衔肢、視頻網(wǎng)站視頻的播放數(shù)等都需要計數(shù)功能。為了保證數(shù)據(jù)實時性豁翎,每次瀏覽都得給+1角骤,并發(fā)量高時如果每次都請求數(shù)據(jù)庫操作無疑是種挑戰(zhàn)和壓力。Redis提供的incr命令來實現(xiàn)計數(shù)器功能心剥,內(nèi)存操作邦尊,性能非常好背桐,非常適用于這些計數(shù)場景
4 分布式會話
集群模式下,在應(yīng)用不多的情況下一般使用容器自帶的session復(fù)制功能就能滿足蝉揍,當應(yīng)用增多相對復(fù)雜的系統(tǒng)中链峭,一般都會搭建以Redis等內(nèi)存數(shù)據(jù)庫為中心的session服務(wù),session不再由容器管理又沾,而是由session服務(wù)及內(nèi)存數(shù)據(jù)庫管理弊仪。
5 分布式鎖
在很多互聯(lián)網(wǎng)公司中都使用了分布式技術(shù),分布式技術(shù)帶來的技術(shù)挑戰(zhàn)是對同一個資源的并發(fā)訪問杖刷,如全局ID撼短、減庫存、秒殺等場景挺勿,并發(fā)量不大的場景可以使用數(shù)據(jù)庫的悲觀鎖、樂觀鎖來實現(xiàn)喂柒,但在并發(fā)量高的場合中不瓶,利用數(shù)據(jù)庫鎖來控制資源的并發(fā)訪問是不太理想的,大大影響了數(shù)據(jù)庫的性能灾杰∥秘ぃ可以利用Redis的setnx功能來編寫分布式的鎖,如果設(shè)置返回1說明獲取鎖成功艳吠,否則獲取鎖失敗麦备,實際應(yīng)用中要考慮的細節(jié)要更多。
6 社交網(wǎng)絡(luò)
點贊昭娩、踩凛篙、關(guān)注/被關(guān)注、共同好友等是社交網(wǎng)站的基本功能栏渺,社交網(wǎng)站的訪問量通常來說比較大呛梆,而且傳統(tǒng)的關(guān)系數(shù)據(jù)庫類型不適合存儲這種類型的數(shù)據(jù),Redis提供的哈希磕诊、集合等數(shù)據(jù)結(jié)構(gòu)能很方便的的實現(xiàn)這些功能填物。
7 消息隊列
消息隊列是大型網(wǎng)站必用中間件,如ActiveMQ霎终、RabbitMQ滞磺、Kafka等流行的消息隊列中間件,主要用于業(yè)務(wù)解耦莱褒、流量削峰及異步處理實時性低的業(yè)務(wù)击困。Redis提供了發(fā)布/訂閱及阻塞隊列功能,能實現(xiàn)一個簡單的消息隊列系統(tǒng)广凸。另外沛励,這個不能和專業(yè)的消息中間件相比责语。
redis數(shù)據(jù)類型
字符串:可以存儲字符串、整數(shù)或者浮點數(shù)
常用命令:GET(獲取key對應(yīng)的值)目派、SET(設(shè)置key對應(yīng)的值)坤候、DEL(刪除key和值)
列表:是一個鏈表,鏈表上的每個節(jié)點都包含一個字符串
常用命令:LPUSH和RPUSH(將元素推入鏈表的左端和右端)企蹭、LPOP和RPOP(從鏈表的左端和右端彈出元素)白筹、LINDEX(獲取鏈表在指定位置上的一個元素)、LRANG(獲取鏈表在指定范圍的元素)
集合:可以存儲多個字符串谅摄,字符串都各不相同
常用命令:SADD(將元素添加到集合)徒河、SREM(將元素從集合中移除)、SISMEMBER(快速檢查一個元素是否存在于集合中)送漠、SMEMBERS(獲取集合包含的所有元素)顽照、SINTER(交集計算)、SUNION(并集計算)闽寡、SDIFF(差集計算)
哈希表:包含鍵值對的無序散列表代兵,和字符串一樣,散列存儲的值既可以是字符串爷狈,又可以是數(shù)字值植影,并且可以對數(shù)字值執(zhí)行自增、自減操作
常用命令:HSET(在散列表中設(shè)置鍵值對)涎永、HGET(在散列表中獲取指定鍵的值)思币、HGETALL(獲取散列表所有的鍵值對)、HDEL(從散列表中刪除鍵值對)
有序集合:字符串成員(member)和浮點數(shù)分值(score)之間的有序映射
常用命令:ZADD(將指定分值的成員添加到有序集合)羡微、ZRANGE(根據(jù)元素在有序集合中的位置谷饿,從有序集合中獲取多個元素)、ZRANGEBYSCORE(獲取有序集合在給定分值范圍內(nèi)的所有元素)妈倔、ZREM(從有序集合中移除成員)
redis數(shù)據(jù)結(jié)構(gòu)
1 簡單動態(tài)字符串
redis底層所有字符串都使用的是簡單動態(tài)字符串各墨,具有如下三個特性:可以高效執(zhí)行長度計算、可以高效執(zhí)行追加操作启涯、二進制安全(程序不對字符串里保存的數(shù)據(jù)做任何假設(shè)贬堵,數(shù)據(jù)可以是以\0結(jié)尾的C字符串、也可以是單純的字節(jié)數(shù)組结洼,或者其他格式的數(shù)據(jù))
struct sdshdr {
? ? ? ? int len;? // buf已使用長度????
? ? ? ? int free;? //buf剩余可用長度
? ? ? ? char buf[];? //實際保存字符串數(shù)據(jù)的地方
}
第一次創(chuàng)建時黎做,len等于buf長度,free等于0
執(zhí)行append操作時松忍,如果新字符串的總長度小于SDS_MAX_PREALLOC蒸殿,為字符串分配兩倍所需長度的空間、否則分配所需長度加上SDS_MAX_PREALLOC數(shù)量的空間
如果執(zhí)行APPEND操作的鍵很多,字符串體積又很多的話宏所,這種預(yù)分配的策略會浪費內(nèi)存酥艳,可以修改redis服務(wù)器,讓它定期釋放一些字符串鍵的預(yù)分配空間爬骤,從而更有效利用內(nèi)存
2 雙端鏈表
普通的雙向鏈表充石,無特殊的地方
3 字典
字典結(jié)構(gòu)體如下
typedef struct? dict {
.........
? ? ? ? dictht ht[2];? //哈希表 2個
? ? ? ? int rehashidx; // rehash標志,值為-1時表示rehash未進行
}dict;
哈希表結(jié)構(gòu)體如下
typedef? struct? dictht {
............
? ? ? ? dictEntry? **table; // 哈希桶
? ? ? ? unsigned long size; // 指針數(shù)組的大小
? ? ? ? unsigned long used; // 哈希表現(xiàn)有節(jié)點的數(shù)量
}dictht;
哈希表節(jié)點結(jié)構(gòu)體如下
typedef? struct? dictEntry {
? ? ? ? void *key; // 鍵
? ? ? ? union {
? ? ? ? ? ? ? ? void *val;
? ? ? ? ? ? ? ? uint64_t u64;
? ? ? ? ? ? ? ? int64_t s64;
????????} v;? // 值
? ? ? ? struct? dictEntry *next;// 后繼節(jié)點指針霞玄,采用鏈表法解決哈希碰撞
}dictEntry;
為了在字典的鍵值對不斷增多的情況下仍然保持良好的性能骤铃,字典需要對使用的哈希表進行rehash擴容操作,盡量將used和size的比率維持在1:1坷剧, 比率ratio(裝載因子)= used/size惰爬,rehash開始的時機:1 自然rehash:ratio >=1,且變量dict_can_resize為真? 2 強制rehash:ratio大于變量dict_force_resize_ratio(默認為5)
dict_can_resize什么時候為假惫企?redis 使用子進程對數(shù)據(jù)庫執(zhí)行后臺持久化任務(wù)時撕瞧,為了最大化的利用系統(tǒng)的copy on write機制,程序會暫時將dict_can_resize設(shè)置為假狞尔,避免執(zhí)行自然rehash丛版、減少程序?qū)?nèi)存的碰撞
rehash的過程:
? ? 1 創(chuàng)建一個比ht[0]更大的哈希表ht[1]
????2 將ht[0]中所有的鍵值對全部遷移到ht[1]
? ? 3 將原有的ht[0]清空,將ht[1]替換成新的ht[0]
漸進式rehash:在一個有很多鍵值對的字典里沪么,某個用戶在添加新的鍵值對時觸發(fā)了rehash過程,如果rehas將所有的鍵值對都遷移完畢后才將結(jié)果返回給用戶锌半,這樣用戶體驗是很不友好的禽车。
漸進式rehash時機:
? ? 1 被動rehash:每次執(zhí)行添加、查找刊殉、刪除操作時殉摔,將哈希表上第一個不為空的索引上的所有節(jié)點全部遷移到ht[1]
? ? 2 主動rehash:redis服務(wù)器常規(guī)任務(wù)執(zhí)行時
rehash過程中,所有的查找和刪除操作记焊,除了在ht[0]上進行逸月,還需要在ht[1]上進行,所有的添加操作遍膜,都只在ht[1]上進行
4 跳表
跳表從高層索引開始碗硬,向下層層查找,可以在對數(shù)時間復(fù)雜度下完成查找瓢颅、添加恩尾、刪除操作。跳表最底層鏈表是按數(shù)據(jù)從小到大排序的挽懦,因此跳表可以非常方便的進行范圍查找
跳表節(jié)點數(shù)據(jù)結(jié)構(gòu)
typedef? struct? zskiplistNode {
? ? ? ? robj *obj; // member對象
? ? ? ? double score;? // 分值
? ? ? ? struct? zskiplistNode *backward; // 后退指針
? ? ? ? struct? zskiplistLevel {? // 層
? ? ? ? ? ? ? ? struct? zskiplistNode * forward; // 前進指針
? ? ? ? ? ? ? ? unsigned? int? span;? // 層跨越的節(jié)點數(shù)量
????????}level[];
}zskiplistNode;
跳表索引動態(tài)更新:當我們不停的向跳表中添加數(shù)據(jù)翰意,如果不更新索引,就有可能出現(xiàn)兩個節(jié)點之間數(shù)據(jù)非常多的情況,極端情況下會退化成單鏈表冀偶。跳表通過隨機函數(shù)來維持平衡性醒第,當我們向跳表中插入數(shù)據(jù)時,可以選擇同時將這個數(shù)據(jù)插入到部分索引層进鸠,通過隨機函數(shù)來確定將這個節(jié)點插入到哪幾層索引中稠曼,如果隨機函數(shù)生成k,就將此節(jié)點插入第一層到第k層這k層索引中
5 整數(shù)集合
typedef? struct intset {
? ? ? ? uint32_t? ?encoding; // 元素使用的類型長度
? ? ? ? uint32_t? ?length; // 元素個數(shù)
? ? ? ? int8_t? ?contents[]; // 保存元素的數(shù)組
}intset;
contents數(shù)組有以下兩個特性:1 沒有重復(fù)元素 2 元素在數(shù)組中從小到大排列
6 壓縮列表
pre_entry_length可能占用1字節(jié)也可能占用5字節(jié)
1字節(jié):前一個entry的長度小于254字節(jié)
5字節(jié):前一個entry的長度大于等于254字節(jié)
encoding和length兩部分一起決定content部分的數(shù)據(jù)類型和長度
encoding長度兩個bit堤如,00蒲列、 01、 10 表示content部分保存這字符數(shù)組搀罢,11表示content部分保存著整數(shù)
redis數(shù)據(jù)類型的底層實現(xiàn)
1 字符串
字符串采用簡單動態(tài)字符串來實現(xiàn)
2 哈希表
鍵值對比較少時采用壓縮列表
鍵值對多時采用字典
3 列表
列表元素比較少時采用壓縮列表
列表元素多時采用雙端鏈表
4 集合
集合元素比較少時蝗岖,并且集合的元素全部可以表示為long long類型值時,采用整數(shù)集合
集合元素比較多或者集合元素中有不能被表示為long long類型值時榔至,采用字典抵赢,集合的元素保存到字典的鍵里邊,字典的值統(tǒng)一設(shè)置為null
5 有序集合
集合元素比較少時唧取,采用壓縮列表
每個有序集合的元素以兩個相鄰的ziplist節(jié)點表示铅鲤,第一個節(jié)點保存元素的member域,第二個節(jié)點保存元素的score域枫弟。多個元素之間按score值從小到大排序
集合元素多時邢享,采用字典加跳表
使用字典,將member作為鍵淡诗,score作為值骇塘,可以在O(1)復(fù)雜度內(nèi)檢查member是否在有序集合中、取出member對應(yīng)的分值
使用跳表韩容,可以高效的處理范圍性查找
為什么采用整數(shù)集合款违、壓縮列表
整數(shù)集合、壓縮列表采用了時間換空間的策略群凶,時間復(fù)雜度變高了插爹,但是內(nèi)存節(jié)省了
整數(shù)集合、字典占用內(nèi)存比較:字典需要存儲兩個hash table请梢、每個hash table需要存儲bulket和entry赠尾,每個entry中既有key、又有value毅弧、又有next指針萍虽,而整數(shù)集合中只需存儲key
壓縮列表、雙向鏈表形真、字典杉编、跳表占用內(nèi)存比較:壓縮列表pre_entry_length最多占用5字節(jié)超全、econding和length最多占用5字節(jié)。雙向鏈表64位系統(tǒng)pre和next指針各占8字節(jié)邓馒。字典上邊已分析嘶朱,需要更多的額外內(nèi)存。跳表需要雙向鏈表和額外的層光酣。這三個都比壓縮列表占用更多的內(nèi)存
為什么redis性能高
1 完全基于內(nèi)存疏遏,絕大部分請求是內(nèi)存操作
2?采用單線程,避免了不必要的上下文切換和競爭條件救军,也不存在多進程或者多線程導(dǎo)致的切換而消耗 CPU财异,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作唱遭,沒有因為可能出現(xiàn)死鎖而導(dǎo)致的性能消耗
3?使用多路I/O復(fù)用模型(epoll)戳寸,非阻塞IO
4 底層高效的數(shù)據(jù)結(jié)構(gòu)
redis事務(wù)
當客戶端處于非事務(wù)狀態(tài)下時,所有發(fā)送給服務(wù)器端的命令都會立即被服務(wù)器執(zhí)行拷泽。但是疫鹊,當客戶端進入事務(wù)狀態(tài)后(MULTI命令后進入事務(wù)狀態(tài)),服務(wù)器在收到來自客戶端的命令時司致,不會立即執(zhí)行命令拆吆,而是將這些命令全部放進一個事務(wù)隊列里,然后返回QUEUED脂矫,表示命令已入隊枣耀。
當執(zhí)行EXEC命令時,服務(wù)器根據(jù)客戶端所保存的事務(wù)隊列庭再,以先進先出的方式執(zhí)行事務(wù)隊列中的命令捞奕。
DISCARD命令用于取消一個事務(wù),清空客戶端的整個事務(wù)隊列佩微,將客戶端從事務(wù)狀態(tài)調(diào)整回非事務(wù)狀態(tài)缝彬。
WATCH命令只能在客戶端進入事務(wù)狀態(tài)前執(zhí)行萌焰,在事務(wù)狀態(tài)下執(zhí)行WATCH會引發(fā)一個錯誤哺眯。WATCH命令用于在事務(wù)開始之前監(jiān)聽任意數(shù)量的鍵,當調(diào)用EXEC命令執(zhí)行事務(wù)時扒俯,如果任意一個被監(jiān)視的鍵被其他客戶端修改了奶卓,整個事務(wù)不再執(zhí)行,返回失敗撼玄。
LUA腳本
redis事務(wù)可以確保事務(wù)執(zhí)行過程中夺姑,鍵不被其他客戶端并發(fā)修改(如果被其他客戶端修改,事務(wù)失斦泼汀)盏浙,但是并不能高效執(zhí)行CAS操作眉睹,CAS操作包括兩個步驟:讀取數(shù)據(jù)比較、設(shè)置數(shù)據(jù)废膘。redis 2.6版本通過內(nèi)嵌對lua環(huán)境的支持竹海,可以高效執(zhí)行CAS操作
REDIS數(shù)據(jù)持久化
redis運行時,數(shù)據(jù)維持在內(nèi)存中丐黄,為了讓這些數(shù)據(jù)在redis重啟之后仍然可用斋配,redis提供了rdb和aof兩種持久化模式
rdb
rdb程序?qū)斍皟?nèi)存中的數(shù)據(jù)庫數(shù)據(jù)庫快照保存到磁盤文件中,redis啟動時灌闺,rdb程序可以通過載入rdb文件來恢復(fù)數(shù)據(jù)庫艰争。rdbSave用于生成rdb文件到磁盤,rdbLoad用于將rdb文件中的數(shù)據(jù)重新載入內(nèi)存
SAVE命令會直接調(diào)用rdbSave函數(shù)桂对,阻塞redis主進程甩卓,直到保存完成為止。主進程阻塞期間接校,服務(wù)器不能處理客戶端的任何請求猛频。
BGSAVE命令會fork一個子進程,子進程調(diào)用rdbSave生成rdb文件
優(yōu)點:rdb對于文件備份和災(zāi)難恢復(fù)來說是個不錯的選擇蛛勉,如果數(shù)據(jù)集比較大鹿寻,采用rdb進行恢復(fù)的效率會更高
缺點:系統(tǒng)在定時持久化之前宕機,沒來及寫入磁盤的數(shù)據(jù)都將丟失
aof
aof以協(xié)議文本的方式诽凌,將所有對數(shù)據(jù)庫進行過寫入的命令及其參數(shù)記錄到aof文件
aof有三種保存模式:不保存毡熏、每一秒鐘保存一次、每執(zhí)行一個命令保存一次
優(yōu)點:數(shù)據(jù)安全性更高
缺點:對于相同數(shù)量的數(shù)據(jù)集侣诵,aof文件的大小比rdb文件大痢法,數(shù)據(jù)恢復(fù)時間比rdb長。運行效率比rdb低
二者選擇的標準杜顺,就是看系統(tǒng)是愿意犧牲一些性能财搁,換取更高的緩存一致性(aof),還是愿意寫操作頻繁的時候躬络,不啟用備份來換取更高的性能尖奔,待手動運行save的時候,再做備份(rdb)
redis緩存和數(shù)據(jù)庫雙寫一致性問題
只能保證最終一致性穷当,強一致性的數(shù)據(jù)不能放緩存
讀操作:先讀緩存提茁,緩存沒有的話,讀數(shù)據(jù)庫馁菜,然后將從數(shù)據(jù)庫中讀出的數(shù)據(jù)放入緩存
更新操作:
1 先更新數(shù)據(jù)庫茴扁,然后刪除緩存,待下次讀取時更新緩存(更新數(shù)據(jù)頻繁汪疮、讀取數(shù)據(jù)不頻繁場景)峭火,刪除緩存可能會失敗毁习,可以增加一個消息隊列進行補償
2 先更新數(shù)據(jù)庫,然后更新緩存卖丸,下次讀取時讀到最新的數(shù)據(jù)(更新跟讀取一樣頻繁的場景)蜓洪,更新緩存可能會失敗,可以增加一個消息隊列進行補償
redis過期策略和內(nèi)存淘汰機制
過期策略:定期刪除+惰性刪除
定期刪除:redis每隔100ms隨機抽取部分key進行檢查坯苹,如果有過期的key隆檀,將過期的key刪除
惰性刪除:在讀取某個key的時候,redis會檢查這個key是否設(shè)置了過期時間粹湃,是否超時恐仑,如果超時,將這個key刪除
內(nèi)存淘汰機制:如果定期刪除沒有刪除key为鳄,也沒有讀取key進行惰性刪除裳仆,那么內(nèi)存使用會越來越高。需要進行內(nèi)存淘汰孤钦。內(nèi)存淘汰可選配置:
noeviction:當內(nèi)存不足以容納新寫入數(shù)據(jù)時歧斟,新寫入操作會報錯。應(yīng)該沒人用吧偏形。
allkeys-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時静袖,在鍵空間中,移除最近最少使用的 Key俊扭。推薦使用队橙。
allkeys-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在鍵空間中萨惑,隨機移除某個 Key捐康。應(yīng)該也沒人用吧,你不刪最少使用 Key庸蔼,去隨機刪解总。
volatile-lru:當內(nèi)存不足以容納新寫入數(shù)據(jù)時,在設(shè)置了過期時間的鍵空間中姐仅,移除最近最少使用的 Key涌攻。這種情況一般是把 Redis 既當緩存振愿,又做持久化存儲的時候才用怠堪。不推薦腰池。
volatile-random:當內(nèi)存不足以容納新寫入數(shù)據(jù)時隙疚,在設(shè)置了過期時間的鍵空間中壤追,隨機移除某個 Key。依然不推薦供屉。
volatile-ttl:當內(nèi)存不足以容納新寫入數(shù)據(jù)時行冰,在設(shè)置了過期時間的鍵空間中溺蕉,有更早過期時間的 Key 優(yōu)先移除。不推薦悼做。
如果key沒有設(shè)置 expire 疯特,那么 volatile-lru,volatile-random 和 volatile-ttl 策略的行為肛走,和 noeviction(不刪除) 基本上一致
緩存穿透
黑客查詢一個一定不存在的數(shù)據(jù)漓雅,這個不存在的數(shù)據(jù)每次請求都到數(shù)據(jù)庫查詢,造成數(shù)據(jù)庫壓力
解決方法:
1 如果查詢的數(shù)據(jù)不存在朽色,仍然把這個空結(jié)果進行緩存邻吞,但設(shè)置一個比較短的過期時間,最長不超過5分鐘
2 采用布隆過濾器葫男,在控制層先進行校驗抱冷,不存在直接丟棄
緩存雪崩
同一時間梢褐,緩存大面積失效旺遮,大量的查詢都請求到數(shù)據(jù)庫,造成緩存雪崩
解決方法:
1 不同的key盈咳,緩存失效時間加個隨機值耿眉,設(shè)置不同的過期時間
2 緩存失效后,通過鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存線程的數(shù)量
3 二級緩存鱼响,兩個緩存設(shè)置不同的過期時間跷敬。比如緩存A設(shè)置為短期,緩存B設(shè)置為長期
緩存預(yù)熱
緩存預(yù)熱就是系統(tǒng)上線后热押,提前將相關(guān)的緩存數(shù)據(jù)直接加載到緩存系統(tǒng)西傀。避免在用戶請求時先查詢數(shù)據(jù)庫
線上如何獲取redis所有的keys
keys命令可以獲取redis所有的key
smembers命令可以獲取集合所有的元素
在一個大的線上數(shù)據(jù)系統(tǒng)中,使用keys桶癣、smembers可能會造成阻塞
可以用下邊四個scan命令來代替
SCAN命令用于迭代當前數(shù)據(jù)庫中的數(shù)據(jù)庫鍵拥褂。
SSCAN命令用于迭代集合鍵中的元素。
HSCAN命令用于迭代哈希鍵中的鍵值對牙寞。
ZSCAN命令用于迭代有序集合中的元素(包括元素成員和元素分值)
SCAN cursor [MATCH pattern] [COUNT count]