分布式架構(gòu)Redis中有哪些數(shù)據(jù)結(jié)構(gòu)及底層實(shí)現(xiàn)原理
引言
面完了負(fù)載均衡,正向代理,反向代理,終于松了一口氣,然后話題轉(zhuǎn)向了緩存Redis,為什么是這個(gè)順序呢?
回想了一下系統(tǒng)架構(gòu),我大概知道原因了。
Redis 處于服務(wù)最上層。面試官是按照這個(gè)順序從上到下考察我對整個(gè)系統(tǒng)設(shè)計(jì)能力,圍著整個(gè)系統(tǒng)自頂向下的結(jié)構(gòu)考察基礎(chǔ)。不糾結(jié)這么多,反正先問后問,Redis一定是你必須掌握的。
1、面試官:我看你提到,項(xiàng)目中使用了Reids作為緩存,為什么是Reids而不是其他,Redis有什么優(yōu)勢嗎?
問題分析: Redis的設(shè)計(jì)理念已經(jīng)成了很多一線互聯(lián)網(wǎng)公司自主研發(fā)分布式緩存框架的標(biāo)桿,因?yàn)橄啾葌鹘y(tǒng)的 Memcache ,Redis 豐富的數(shù)據(jù)結(jié)構(gòu)實(shí)在太香。
答:
- 首先 Redis 支持豐富的數(shù)據(jù)結(jié)構(gòu),新版本數(shù)據(jù)結(jié)構(gòu)從最初的5種變成9種。
- 其次 Redis 是讀寫單進(jìn)程單線程,不用考慮并發(fā)讀寫的復(fù)雜場景,速度也快。
- Reids 功能完備,支持?jǐn)?shù)據(jù)持久化,支持主從復(fù)制和集群。
- 還有Lua腳本,事務(wù),發(fā)布訂閱模型,Reids 都支持。
在高并發(fā)請求時(shí),為何我們頻繁提到緩存技術(shù)?最直接的原因是,磁盤IO及網(wǎng)絡(luò)開銷是直接請求內(nèi)存IO千百上千倍,做個(gè)簡單計(jì)算,如果我們需要某個(gè)數(shù)據(jù),該數(shù)據(jù)從數(shù)據(jù)庫磁盤讀出來需要0.0045S,經(jīng)過網(wǎng)絡(luò)請求傳輸需要0.0005S,那么每個(gè)請求完成最少需要0.005S,該數(shù)據(jù)服務(wù)器每秒最多只能響應(yīng)200個(gè)請求,而如果該數(shù)據(jù)存于本機(jī)內(nèi)存里,讀出來只需要100us,那么每秒能夠響應(yīng)10000個(gè)請求。通過將數(shù)據(jù)存儲(chǔ)到離CPU更近的未位置,減少數(shù)據(jù)傳輸時(shí)間,提高處理效率,這就是緩存的意義。
給您列舉一個(gè)我利用Reids把項(xiàng)目QPS提到幾十萬級(jí)別的案例:
一個(gè)風(fēng)控系統(tǒng)在日常24H中 Redis集群 QPS 曲線圖,從業(yè)務(wù)低峰期幾千或晚高峰最高30W,一個(gè) Redis 集群都可輕松應(yīng)對,30WQPS 在大型系統(tǒng)中流量并不算高,且不是核心系統(tǒng),如果在多幾倍幾十倍多流量,一個(gè)結(jié)構(gòu)優(yōu)良的Redis 集群都可輕松應(yīng)對,這充分說明了我們?yōu)槭裁匆褂镁彺?,緩存可以把系統(tǒng)響應(yīng)能力提高N個(gè)數(shù)量級(jí),遠(yuǎn)高于傳統(tǒng)基于硬盤的關(guān)系型數(shù)據(jù)庫
面試官心想:看來是做足了功課。
2、面試官:剛剛你提到Redis是單線程,為什么單線程模型的 Redis 性能不減。
問題分析:成功挖坑,提到單線程肯定會(huì)問我為什么要這樣設(shè)計(jì)。
答:
單線程不代表一定就慢,單線程有一個(gè)最大好處就是節(jié)省線程切換的開銷,更不用考慮并發(fā)讀寫帶來的復(fù)雜操作場景,這就大大節(jié)省了線程間切換的時(shí)間了。
單線程模型避免了多線程的頻繁上下文切換,這也避免了多線程可能產(chǎn)生的競爭問題。
Reids 是基于內(nèi)存的讀寫操作,內(nèi)存肯定比傳統(tǒng)磁盤IO數(shù)據(jù)庫快。
Reids 核心是基于非阻塞的IO多路復(fù)用機(jī)制。
3、面試官:那你剛剛說的Redis數(shù)據(jù)結(jié)構(gòu)都有哪幾種,如何選擇使用哪種?
問題分析: 常用的5種,重點(diǎn)學(xué)會(huì)這5種數(shù)據(jù)結(jié)構(gòu)的使用足夠了。
答:比較常用的有5種
字符串 String: 字符串是 Redis 中最為基礎(chǔ)的數(shù)據(jù)存儲(chǔ)類型,數(shù)據(jù)結(jié)構(gòu)簡單,可存儲(chǔ)文本,Json,圖片數(shù)據(jù)等任何二進(jìn)制文件。如姓名,訂單號(hào)等,對于一些特殊的數(shù)據(jù)結(jié)構(gòu),比如List、Set等,建議采用相應(yīng)的下面介紹的List和Set數(shù)據(jù)結(jié)構(gòu)進(jìn)行存儲(chǔ),這樣不僅可以節(jié)省存儲(chǔ)空間還可以提高操作效率。
列表 List: 類似 Java 中的 List ,按照插入順序排序的字符串鏈表,在插入時(shí),如果該鍵并不存在,Redis將為該鍵創(chuàng)建一個(gè)新的鏈表。與此相反,如果鏈表中所有的元素均被移除,那么該鍵也將會(huì)被從數(shù)據(jù)庫中刪除。
集合 Set: 類似 Java 中的set,但它是一個(gè)無序集合,用于存儲(chǔ)無序(存入和取出的順序不一定相同)元素,值不能重復(fù)??梢允褂肦edis的Set數(shù)據(jù)類型跟蹤一些唯一性數(shù)據(jù),比如訪問系統(tǒng)的唯一IP地址,唯一用戶ID等信息,再比如在微博應(yīng)用中,每個(gè)人的好友存在一個(gè)集合(set)中,這樣求兩個(gè)人的共同好友的操作,可能就只需要用求交集命令即可。
有序集合 Sorted Set: 類似 Java 中的 TreeSet,支持從小到大排序的 set,適用于排行榜結(jié)構(gòu)的數(shù)據(jù)存儲(chǔ)。
Hash: 類型相當(dāng)于Java中的HashMap,所以該類型非常適合于存儲(chǔ)值對象的信息,比如用戶基本信息對象含有昵稱、性別和Age等屬性,可以使用Hash來存儲(chǔ)User對象,Key可以為用戶的唯一ID屬性。
除此之外,新版本的Redis還提供了位圖,地理坐標(biāo),流幾種結(jié)構(gòu)。
深入分析
曾經(jīng)有面試官問我,你看過Reids源碼嗎,我說沒有看過,他說有精力可以研究一下,Redis那幾種常用的數(shù)據(jù)結(jié)構(gòu)底層實(shí)現(xiàn)原理還是值得學(xué)習(xí)的。
1、簡單動(dòng)態(tài)字符串結(jié)構(gòu),Redis字符串的實(shí)現(xiàn)方式
簡單動(dòng)態(tài)字符串(simple dynamic string)簡稱SDS。Redis使用C語言編寫,但是傳統(tǒng)的C字符串使用長度為 N+1 的字符串?dāng)?shù)組來表示長度為N的字符串,所以為了獲取一個(gè)長度為C字符串的長度,必須遍歷整個(gè)字符串。和C字符串不同,動(dòng)態(tài)字符串的數(shù)據(jù)結(jié)構(gòu)中,有專門用于保存字符串長度的變量,我們可以通過獲取len屬性的值,直接知道字符串長度,從一定程度上提高了讀取效率。
Redis源碼中,動(dòng)態(tài)字符串的定義:
/* * 保存字符串對象的結(jié)構(gòu) */ struct sdshdr { // buf 中已占用空間的長度 int len; // buf 中剩余可用空間的長度 int free; // 數(shù)據(jù)空間 char buf[]; };
len
變量,用于記錄buf 中已經(jīng)使用的空間長度。
free
變量,用于記錄buf 中還空余的空間,初次分配空間,一般沒有空余,在對字符串修改的時(shí)候,會(huì)有剩余空間出現(xiàn),這樣做是為了杜絕C語言中緩沖區(qū)溢出的可能性,當(dāng)我們需要對一個(gè)SDS進(jìn)行修改的時(shí)候,Redis 會(huì)在執(zhí)行拼接操作之前,預(yù)先檢查給定SDS空間是否足夠,如果不夠,會(huì)先拓展SDS的空間,然后再執(zhí)行拼接操作。
buf
字符數(shù)組,用于記錄我們的字符串(記錄Redis)。
2、鏈表數(shù)據(jù)結(jié)構(gòu),List 底層結(jié)構(gòu)
鏈表還是常規(guī)的普通雙端鏈表,可以支持反向查找和遍歷,更方便操作,通過增刪節(jié)點(diǎn)來靈活地調(diào)整鏈表的長度,雙端鏈表在Redis內(nèi)部也是被多次使用:
- 事務(wù)模塊使用雙端鏈表依序保存輸入的命令。
- 服務(wù)器模塊使用雙端鏈表來保存多個(gè)客戶端。
- 訂閱/發(fā)送模塊使用雙端鏈表來保存訂閱模式的多個(gè)客戶端。
- 事件模塊使用雙端鏈表來保存時(shí)間事件(time event)。
3、跳躍表,sorted set底層結(jié)構(gòu)
Redis sorted set的內(nèi)部使用HashMap和跳躍表(SkipList)來保證數(shù)據(jù)的存儲(chǔ)和有序,(如果你還不了解紅黑樹,需要先額外補(bǔ)補(bǔ)功課),HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據(jù)是HashMap里存的score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率,并且在實(shí)現(xiàn)上比較簡單。
那為什么Redis的作者使用 SkipList 結(jié)構(gòu)而不是紅黑樹?
紅黑樹:紅黑樹的查找效率很高,但是在進(jìn)行重新平衡時(shí),會(huì)涉及到大量節(jié)點(diǎn)的變化,因此實(shí)現(xiàn)和操作起來都比較復(fù)雜。
跳躍表:通過簡單的多層索引結(jié)構(gòu),實(shí)現(xiàn)簡單,且能達(dá)到近似于紅黑樹的查找效率,插入節(jié)點(diǎn)(多層插入)不需要像紅黑樹那樣有額外操作。而且跳躍表還能實(shí)現(xiàn)范圍查找及輸出,而紅黑樹只支持單個(gè)元素查找,對于范圍查找效率低。
關(guān)于緩存的一些算法
(偷偷告訴你,這幾個(gè)關(guān)于Reids的算法很大概率也會(huì)被問到,需要多少知道幾種)
常用緩存數(shù)據(jù)淘汰策略
緩存是非常寶貴的資源,不能把所有數(shù)據(jù)都放入緩存,只能把最重要的或者要求查詢速度最快的數(shù)據(jù)緩存起來,比如微博熱門話題排行榜功能,通常使用緩存查詢,而不是數(shù)據(jù)庫。
FIFO(First In First Out): 先進(jìn)先出算法,即先放入緩存的先被移除。
LRU(Least Recently Used): 最近最少使用算法,使用時(shí)間距離現(xiàn)在最久的那個(gè)被移除。
LFU(Least Frequently Used): 最不常用算法,一定時(shí)間段內(nèi)使用次數(shù)(頻率)最少的那個(gè)被移除。
緩存數(shù)據(jù)更新策略
- 定時(shí)任務(wù)從數(shù)據(jù)庫直接更新緩存:適用于對時(shí)間不敏感的數(shù)據(jù)。
- 查詢時(shí)寫緩存,即查詢優(yōu)先查詢緩存,若緩存未命中,查詢數(shù)據(jù)庫,將返回結(jié)果寫入緩存,數(shù)據(jù)更新時(shí)先 delete緩存,再更新緩存。
- MQ 消息異步更新緩存,后文中會(huì)針對MQ的應(yīng)用做單獨(dú)講解。
總結(jié)
這一節(jié)重點(diǎn)講解分布式緩存 Redis ,本地緩存不一定每個(gè)項(xiàng)目都會(huì)使用,但是 Redis 數(shù)據(jù)設(shè)計(jì)合理,保證超高命中率,集群足夠穩(wěn)定,那完全可以替代一級(jí)本地緩存。所以 Redis 非常值得你花更多時(shí)間學(xué)習(xí)。分布式緩存是面試必問。
Redis 是建設(shè)高性能網(wǎng)站后臺(tái)不可缺少的工具,無論你是面試業(yè)務(wù)開發(fā)工程師還是架構(gòu)師,都需要熟練掌握。
關(guān)于Redis,推薦閱讀黃建宏的《Redis 設(shè)計(jì)與實(shí)現(xiàn)》,能夠掌握Redis的5種數(shù)據(jù)結(jié)構(gòu),Redis 的持久化方式 RDB 和 AOF,兩者有什么優(yōu)點(diǎn)和缺點(diǎn),如何選型,以及了解高可用 Redis 集群的建設(shè)方案。
以上就是分布式架構(gòu)Redis中有哪些數(shù)據(jù)結(jié)構(gòu)及底層實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于分布式架構(gòu)Redis底層數(shù)據(jù)結(jié)構(gòu)及原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
手把手教你使用redis實(shí)現(xiàn)排行榜功能
使用Redis中有序集合的特性來實(shí)現(xiàn)排行榜是又好又快的選擇,一般排行榜都是有實(shí)效性的,比如“用戶積分榜”,下面這篇文章主要給大家介紹了關(guān)于使用redis實(shí)現(xiàn)排行榜功能的相關(guān)資料,需要的朋友可以參考下2023-04-04Redis 數(shù)據(jù)遷移的項(xiàng)目實(shí)踐
本文主要介紹了Redis 數(shù)據(jù)遷移的項(xiàng)目實(shí)踐,通過Redis-shake的sync(同步)模式,可以將Redis的數(shù)據(jù)實(shí)時(shí)遷移至另一套R(shí)edis環(huán)境,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09Redis筆記點(diǎn)贊排行榜的實(shí)現(xiàn)示例
探店筆記類似點(diǎn)評(píng)網(wǎng)站的評(píng)價(jià),本文主要介紹了Redis筆記點(diǎn)贊排行榜的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01Redis分布式鎖的正確實(shí)現(xiàn)方法總結(jié)
在本篇文章里小編給大家整理的是關(guān)于Redis分布式鎖的正確實(shí)現(xiàn)方式介紹,有興趣的朋友們可以學(xué)習(xí)下。2020-02-02python腳本實(shí)現(xiàn)Redis未授權(quán)批量提權(quán)
這篇文章主要給大家介紹了關(guān)于利用python腳本實(shí)現(xiàn)redis未授權(quán)批量提權(quán)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09