換個姿勢講解redis原理
“大爺你沒事吧”,
“沒事沒事,路有點滑,”
“大爺,你的鞋都被人追掉了”
“現在的小姑娘真是的,撩下就動手,多虧你小夥子,小夥子你是做什麼工作的”
“後端java開發”
“哦,有前途,這樣你救了我, 我今天就把我的不傳之謎 傳授於你,”
“?啥?”
說著大爺顫顫巍巍的從口袋裡掏出了一本筆記,赫然寫著

”小夥子 你知道 redis的速度麼?“
”單機讀可達10000次/S, 寫可達 5000次/s“
”恩 ,RDS巨擘mysql ,經過了這麼多年優化 才1000次/S,500次/S,那麼有麼有想過redis為何這麼吊麼,“
”redis 是操作記憶體,操作速度自然比mysql的磁碟操作要快太多了“
”哎,世人膚淺,都只看到了redis的記憶體操作主導了優勢,在我看來,這個原因也只是1/3(相比較RDS而言,最大優勢是記憶體,但是相對對於memcached等同類產品)“
”?另外2分是?“
”哼哼,“
老爺子舔了舔手指,慢慢翻開。
”資料結構!大家都知道redis提供了5種資料型別的支援,卻不知他們的其中奧妙,著實可惜,今日老夫就讓你見識見識,
redis最基礎的是SDS(simple dynamic string),動態字串,與java c的字串不同,他的定義是不單單是一個char 陣列構成,每個sds都會比它真實佔用的字元長度都長,通過一個空閒識別符號表示sds當前空閒字元有多少,如此設計,在一定長度範圍的內的字串都可以使用此sds,而且不會頻繁的進行記憶體分配,直到此sds不能容納分配的字串,如果遇到這種情況情況,才需要進行擴擴容,妙不可言;這是redis的最基礎的,所有的redis k-v 中的字串都是依託於sds,這是其一;
其二 dict,字典,類似於java中的hashmap:陣列,負載因子 hash演算法;
不同於它的就是,每個dict擁有兩個陣列,一個簡單的hash算“
“?兩個陣列?簡單的hash演算法”

”java hashmap的hash演算法是啥,對key進行hash,將hash值與當前的陣列長度進行計算,獲取當前key在陣列中的索引值“
”redis的很簡單,就是key和一個定值運算,簡單粗暴不是不傷手,保證了最快的執行速度,至於為啥兩個陣列,和hashmap一樣,也要擴容,初始的dic的陣列長度不能太大,隨著資料的增加,超過了負載因子,dic的陣列必須進行擴容,hashmap怎麼擴容?“

”新生一個更長的陣列,遍歷老陣列向新陣列的遷移“
”原理差不多,但是hashmap的擴張是一次性的,而redis的擴容是漸進式,不影響當前使用dic的正常使用,一點點劃拉,直到遷移完成,這是其二,
其三堪稱一個傑作,你知道mysql的索引的結構麼?“
“我知道B+樹,”
”B+樹,源於B樹,也是自動平衡的樹,隨機查詢效能舉世無雙,經過B+樹的改進,壓縮了整棵樹的高度,在搜尋效能又上一層樓,但是 二叉樹也有自己的弊端,資料插入的平衡維持:左旋右旋,旋的我腦殼疼,拖了效能,而我們即將說到的redis第三點,就是跳躍表,在隨機搜尋方面略低於B+樹,但是對於資料插入,跳躍表使用了歷史上最屌的演算法:拋硬幣,唯一能和這個演算法媲美的只有擲骰子,推牌九,鬥地主,扎金花等等“

“。。。這算哪門子演算法,尤其後面幾個,最多算民間藝術”
大爺,沒有理睬,繼續說道,
“在跳躍表是由N層連結串列組成,最底層是最完整的的資料,每次資料插入,率先進入到這個連結串列(有序的),插入完成後,通過拋硬幣的演算法,判斷是否將資料向上層跑,如果是1的話,就拋到上層,然後繼續拋硬碟,判斷是否繼續向上層拋,直到丟擲了0結束整個操作,每拋到一層的時候,如果當前層沒有資料,就構造一個連結串列,將資料放進去,然後使用指標指向來源地址,就這樣依次類推,形成了跳躍表,每次查詢,從最上層遍歷查詢,如果找到就返回結果,否則就在此層找到最接近查詢的值,將查詢操作移到另外一層,就是剛才說到來源地址,所在層,重複查詢,第一次聽到這跳躍表,簡直比左旋右旋還頭大,漸漸的領會到其要義:沒事多推牌九,鬥地主 扎金花

”哦,原理還有這個玩意,好神奇哦,這些資料結構就能解決redis的速度問題?“
”非也非也“
”這是儲存方面巧妙的地方之一,更主要是單執行緒+多路 I/O 複用模型“
”單執行緒?單執行緒會更快。。。“

”如果說單執行緒更快的話 那簡直是騙人的,不過單執行緒的模式解決了資料儲存的頑疾:資料併發安全,任何執行多執行緒同時訪問資料庫都會存在這個問題,所以才有了mysql的mvcc和鎖, Memcached 的cas 樂觀鎖,來保證資料不會出現併發導致的資料問題,但是redis 使用單執行緒就不存在這個問題:1,單執行緒足夠簡單,無論在redis的實現還是作為呼叫方,都不需要為資料併發提心吊膽,不需要加鎖。 2.不會出現不必要的執行緒排程,你知道多執行緒,頻繁切換上下文,也會帶來很多效能消耗“
“額 。。。 什麼是切換上下文?”

”你記得上次有個帥哥講記憶體模型的時候( 兩程式設計師玩“鎖”,一人搶救無效身亡 ),有提到工作記憶體麼?執行緒每次執行需要把資料從主記憶體讀到工作記憶體,然而當執行緒被排程到阻塞的時候,這些工作記憶體的資料需要被快照到執行緒上下文中,其實就是一個記錄各個執行緒狀態的儲存結構,等到執行緒被喚醒的時候,再從上下文中讀取,稱之為上下文切換;減少上下文切換操作,也是使用單執行緒的奧妙;”
“。。。。”

“再說 多路 I/O 複用模型,這個也是java 的NIO體系使用的IO模型,也是linux諸多IO模型中的一種,說白了就是當一個請求來訪問redis後,redis去組織資料要返回給請求,這個時間段,redis的請求入口不是阻塞的,其他請求可以繼續向redis傳送請求,等到redis io流完成後,再向呼叫者返回資料,這樣一來,單執行緒也不怕會影響速度了
“哦 聽起來很厲害,不過我再想redis 真的是單執行緒就完全安全麼?”
“當然不是。。。一個有趣的場景,如果請求1 2 3 同時傳送修改redis的一個key值,這個是不可預期的,無法判斷那個才是正確的,如果你認定第一個修改的為正確的,就需要藉助redis其他的特性來完成,比如說watch,如果要保重多個操作原子性,就需要multi”
“算了,反正我也聽不懂,我先走了”

“少年留步,”
“大爺你鬆手,我聽著呢”

“下面我必須要叮囑你,redis持久化一定要慎重,AOF RDB,切不可濫用,redis叢集的資料庫同步,與mysql同步不同,別有洞天,最後老夫再傳授你“如何避免快取擊穿”的奧祕”
“?快取擊穿?是啥?”

“快取一般作為RDS的前置系統和伺服器直連,減輕rds的負擔,常理而言,如果伺服器查詢快取而不得的話,需要從rds中獲取然後更新到快取中,但是如果在“ 從rds中獲取然後更新到快取中 ”,這個階段,快取尚未更新成功,大量請求進來的話,rds勢必壓力暴增,甚至雪崩,或者歹人惡意攻擊,一直查詢rds和快取中未存在key,也會導致快取機制失效,rds壓力暴增,稱之為快取擊穿,
”前輩 那該咋兒辦?“
”快取永不失效,定時同步rds redis,不允許應用直接請求查詢rds,所有的查詢以快取中為準“
”那且不是資料不能實時展示了麼,“
”對的,不過你就認為伺服器臨時上了一個廁所,耽誤個幾分鐘,這樣會好受些“
”如果定時器掛了怎麼辦“
”伺服器掛了怎麼辦?其實道理一樣的,如何避免伺服器宕機,就如何避免定時器掛機“
”哦“

年輕人大概有些明白,或者有些恍惚,低頭間,只看到大爺的背影,漸行漸遠,
“大爺,未請教尊姓大名”
“黃見紅”
“前輩可留下祕籍,供晚輩瞻仰”
“拿去”
大爺從大手一輝
書落在了少年手中,還有淡淡的體溫,少年開啟,呢喃著
“redis設計與實現。。。”
“小子裡面有我二維碼,別忘了付款”
“。。。。”

慣例上圖:

本文章旨在為大家分析一項技術所涵蓋的體系,對於細節方面有些處理粗糙,如有不妥請多多指正
歡迎大家加微信騷擾:treenpool
請持續關注, http://treenpool.com 國內第一款專注於知識體系搭建工具