天天看點

java redis設定過期時間_Redis基礎知識總結(面試必備)

java redis設定過期時間_Redis基礎知識總結(面試必備)

Redis元件因其開源免費、出色的讀寫性能,備受廣大網際網路公司的熱愛,許多公司用Redis作為緩存來抗住C端的請求,若沒有Redis,許多公司的業務将不堪一擊。Redis可以作為單機緩存(業務程序重新開機後,内容不會丢失,代替共享記憶體的使用),或者通過叢集的方式作為服務後端的緩存、提供可無限擴充的讀寫能力(隻要伺服器資源足夠),也有公司将Redis叢集作為高可靠的KV存儲(開啟持久化選項)。在面試過程中,Redis的相關知識也是問的最多的,可以說是必問必會的知識點。以前在使用Redis的過程中做的學習筆記,現整理做一下分享。

1. Redis對叢集的支援情況

  • redis 3.0之前版本不支援叢集,若要實作叢集,則需要借助中間件來實作存值和取值的對應節點。
  • redis3.0之後的版本(含3.0),搭建叢集,叢集中機器兩兩連接配接,每台機器都可以做中轉,事先給每台機器配置設定槽點(總共16384個),對于存值和取值,根據key來映射對應槽點和機器節點,若key不屬于目前節點則跳轉到對應其他節點,相當于每台機器節點都是一個全局路由。

2. Redis叢集投票機制

Redis叢集通過Gossip通信,當某個節點發現另外一個節點失聯的時候,對外廣播PFail資訊,當某個節點收到叢集絕大多數節點都判定那個節點為PFail的時候,強制将那個節點設定為Fail、并廣播出去讓其他節點接受、并立即對該節點進行主從切換。cluster-node-timeout ,當某個節點持續timeout時間失聯,才認為該節點故障,需要主從切換,進而避免頻繁的主從切換(資料的重新複制)。

3. Redis的淘汰政策

  • volatile_lru:從設定過期的key中,挑選最近最少使用的key。
  • volatile_ttl:從設定過期的key中,挑選即将過期的key。
  • volatile_random:從設定過期的key中,随機挑選一個key。
  • allkeys_lru:從資料集中,挑選最近最少使用的key。
  • allkeys_random:從資料集中,随機挑選一個key。
  • no_enviction:禁止淘汰資料(還是會根據引用計數進行記憶體釋放),當記憶體使用超過設定的門檻值,若再有記憶體申請操作則會失敗。

4. Redis解決并發問題的思路

  • 采用單程序單線程模式,沒有鎖的概念
  • 通過隊列将并發通路串行化
  • 通過setnx指令,若不存在則設定對應的key
  • 多路複用,保證高并發,定時任務,小頂堆,将最近最快要執行的任務放在堆頂,距離要執行的時間為多路複用的timeout,因為redis知道這段時間是沒有任務要執行的,可以安心sleep,QPS可以達到10w/s。

5. Redis記憶體回收

  • 對于不再引用或者過期的key,采用惰性和定時任務檢測的方式,使用者有請求過來、同時判斷此key是否過期,若過期則删除并傳回空,定時任務随機選取一些key(可配置),判斷是否過期,對于Redis單程序單線程的架構來說,這樣可以節省CPU和記憶體。定時任務每個資料庫随機抽檢20個key,發現過期則删除,若超過檢查數目的25%的key過期,則循環執行,直到不足25%或者超過執行時間。
  • 當設定了最大使用記憶體後、并且記憶體使用已經超過設定門檻值,則采用上述淘汰政策進行記憶體回收。

6. Redis的資料類型

  • string:動态的字元串,可以修改,當字元串長度小于1MB的時候,擴充都是加倍現有空間,超過1MB的話,擴充一次最多增加1MB,最大512MB。
  • list:類似于java的linklist,插入和删除很快,O(1),但是查找很慢,O(n), 當彈出list的最後一個元素後,該list自動被删除,記憶體被回收。ziplist和雙向連結清單結合來實作quicklist,ziplist裡面是一塊連續的記憶體,是經過壓縮的,因為對于int等基礎類型,額外的兩個指針太耗費空間,存儲方式類似于LevelDb裡面的Block,最後通過雙向連結清單将ziplist連起來,這樣既滿足快速插入、删除的特性,空間浪費也不多。
  • hash:相當于java的HashMap,無序字典,内部實作是用數組+連結清單,第一維的數組出現碰撞,則存到連結清單裡面去。Rehash的時候,為了不阻塞服務,采用的是漸進式的Rehash,保留2個Hash,逐漸在指令執行或者定時任務中,将資料從老的Hash遷移到新的Hash。
  • set:相當于java的HashSet,無序的鍵值對,不過其值都是NULL,鍵不可以重複。資料量較少且是整數的時候用有序數組,較大的時候采用散清單。
  • zset:最具特色的資料結構,也是面試官問的最多的知識點。通過散清單+跳表實作,一方面是一個set,保證鍵的唯一性,另一方面,可以給每個value賦一個score,可以根據score排序、區段查找。應用場景:value為粉絲ID,score是關注時間,可以按照關注時間順序給出粉絲ID。value是學生ID,score是其分數,可以按照分數排序。

7. Redis的過期設定

Redis所有的資料結構都可以設定過期時間,比如hash,需要注意,隻能對hash整體設定過期時間,無法對其中的單個field設定過期時間。若一個對象已經設定了過期時間,但是待會你又修改了他,那麼他的過期時間就會消失,也就是需要重新設定過期時間。

8. Redis分布式鎖的一些讨論

redis是單程序單線程,本身沒有鎖的概念,主要是應用分布在多台機器,用戶端不同順序通路引起的互斥問題,例如,A讀取字段field1,然後修改設定,B也同時讀取字段field1(在A修改生效前),然後也修改設定(在A修改生效後),這樣B的修改就将A的修改覆寫掉了,下面是遞進的幾種改進思路。

  • setnx key value; del key,若程式異常,del沒有執行的話,則其他程式永遠得不到鎖了。
  • setnx key value; expire key 逾時時間(機關秒);del key,加一個逾時指令,似乎能解決問題,但是也存在同樣的問題,即在執行expire之前程式就可能core dump了。
  • set key value ex 逾時時間(機關秒) nx; del key,這個基本沒有問題了,但是業務執行時間不能超過逾時時間,否則可能也會有問題。例如,程式A獲得了鎖,并執行,過了設定的逾時時間後,鎖自動釋放,程式B獲得鎖,這個時候程式A執行完畢,将鎖删除了。這樣程式C就能獲得鎖,B和C可能會産生沖突。
  • 如果産生鎖的時候随機生成一個數,删除的時候先check一下随機數是否相同,隻有相同了才能删除鎖(即是當時生産鎖的責任人)。Redis本身沒有這個機制(檢測和删除不是原子的),不過可以通過lua腳本來實作。
  • 若是Redis叢集,主從切換期間,程式A先在主機申請獲得了一把鎖,這時候發生主從切換,從機還沒有感覺到那把鎖(即還沒有将對應key同步過來),然後程式B又在新的主機(原來的從機)申請獲得了一把同樣的鎖,這樣業務就會出現問題。出現這個情況,本質上是因為Redis叢集是異步複制(先給用戶端回包,再主->從replication),不像Paxos等強一緻性保證。

Redis提供的分布式鎖RedLock:

  • 分布式鎖通常采用租約的形式,即設定的一個逾時時間,避免某個獲得鎖的用戶端挂死導緻鎖一直得不到釋放。
  • 租約鎖通常是不安全的,例如,程式A獲得鎖,嘗試修改某些資訊,在這期間可能因為網絡、java等的GC機制導緻程式暫停,很可能就過了租約期了即使在前面加多次check租約是否到期的邏輯、也可能沒有用。這個時候程式B獲得鎖,并發的修改的某些受控資訊,這就會引起混亂。
  • 給資源加上fencing token,遞增的數字token,這個時候,配合的,存儲服務需要check 一下token的值,隻能遞增的修改,不能回頭,比如token 34修改過了,token 33就不能修改了。
  • RedLock沒有提供fenciing token 機制, 随機數不能解決這個問題,單機提供一個計數器也不行,若要多機的話,需要一個consensus算法生成fencing token。
  • 通常,分布式一緻性算法,是不會去依賴時間的,性能可能很差,但是絕不會出錯,比如paxos。
  • RedLock 做了很多時間假設,假設網絡延遲等時間、程式因為GC等暫停的時間比鎖的租約時間小,redis某節點擁有的key的時間也小于鎖租約時間。
  • 作者的一些建議:1)如果用鎖是為了效率(避免做多次做很重的活),可以直接用單例redis(conditional set-if-not-exists to obtain a lock, atomic delete-if-value-matches to release a lock),不用太多擔心;2)如果用鎖是為了正确,請不要用RedLock, 不靠譜,應該用zk等。

9. Redis中使用lua腳本的好處及其限制

  • lua腳本中的指令一起執行,是原子的,不用擔心被打斷,有效解決多個用戶端的讀+修改+回寫引起的沖突問題。
  • 可以定制自己的指令,并常駐記憶體。
  • 有效降少客戶單、伺服器之間的互動。
  • lua腳本不要執行太長時間,預設是不超過5秒。
  • redis+lua适合單機,不适合叢集,主要是沒有實作排程,一個腳本中的所有key要在同一個槽點執行。
  • 為了安全考慮,屏蔽了很多指令。
  • 阿裡雲redis叢集支援lua,不過對腳本做了如下限制:1)所有key應該由keys數組傳遞,2)所有key必須在一個slot上,3)調用必須帶key。

10. hyperloglog

場景舉例,往集合裡面添加100w條記錄,計算其中不重複的資料個數,若用set去重,會占用很多空間,使用hyperloglog隻用12KB,但是會損失一些精準度,誤差在0.81%左右。

11. Redis中的布隆過濾元件

布隆過濾器可以作為一個插件加載到redis server: redis-server --loadmodule /path/to/redisbloom.so 一個大型的位數組和若幹無偏Hash函數,多個Hash函數保證元素的hash值算的均勻一點。

  • add操作:經過若幹Hash函數,計算出若幹hash值,和位數組大小取模後得到數組的索引,然後将對應位置設定為1.
  • exists操作:和add一樣,先經過若幹Hash函數,計算出若幹hash值,和位數組大小取模後得到一些數組的索引,判斷這些索引對應的位置的值,隻要有一個為0,則說明不存在。如果都為1的話,則說明存在。”存在“的判斷可能會有誤判,因為可能是其他内容設定的值,”不存在“的判斷沒有誤判。

12. Redis限流子產品

redis-cell,rust語言編寫的基于令牌桶算法的限流子產品,舉例: cl.throttle "svr:itf" 15 30 60 1   其中, "svr:itf":限流對象 15:capacity, 桶的容量 30:操作次數 60:60s,和上面一個值表示,在60s内最多有30次操作 1:可選參數,預設值是1,代表一個操作。

針對某個限流微服務接口,預先設定好通路頻率,每次請求過來,執行類似的如上指令,若傳回0代表可以調用,若傳回1則表示不可以調用。若一段時間後,接口頻率增大了,可以重新使用此指令設定一次(調整桶的容量和頻次)。

13. GeoHash算法

地理位置距離排序算法GeoHash算法。 經度:(-180, 180],以本初子午線(英國格林尼治天文台)東正西負。 緯度:(-90, 90],以赤道為界,北正南負。

将地球想象成一個平面,用正方形方格分隔它,最終将二維坐标映射成一個整數,整數越長,精度越精确。通過zset結構存儲,value是位址,score是上面映射的52位整數,通過zset的score排序,就可以得到附近的人的坐标(整數還原成二維坐标)

  • 添加經緯度資訊:geoadd key longitude latitude member [longitude latitude member  ...]
  • 計算兩者距離:geodist key member1 member2 [unit]
  • 擷取集合中元素的地理資訊:geopos key member [member ...]
  • 擷取某個地點的geohash編碼:geohash key member[member ...]
  • 查找指定元素附近的其他元素:1)georadiusbymember key member radius m|km|ft|mi [WITHCOORD][WITHDIST][COUNT count][ASC|DESC][STORE key][STOREDIST key], 2) georadius key longitude latitude radius m|km|ft|mi [WITHCOORD][WITHDIST][COUNT count][ASC|DESC][STORE key][STOREDIST key]

geoXXX 操作的是一個zset結構,假設key為company,則可以通過del company 來删除整個集合,geo沒有提供删除單個元素的接口,不過可以通過zrem key member來删除某個值。需要注意geo結構的大小(zset),若用redis叢集,結果過大會給叢集遷移造成停頓,可以按照國家/城市/區做劃分,前面再加一個地點路由,減少key的數量。

14. Redis的scan

通過遊标、分步驟執行,不阻塞線程,不影響其他指令的執行,通過limit限制每次吐出的資料,可多可少,傳回結果可能是重複的,需要用戶端去重,伺服器不儲存遊标狀态,唯一的遊标狀态是傳回的遊标整數,單次傳回空不代表周遊結束,周遊是否結束要看傳回的遊标整數是否為0。在周遊過程中,修改後的值能否周遊到,這是不确定的。

scan x match y count limit , x~遊标,從0開始,本次scan會傳回一個遊标,下次scan用那個傳回的遊标,直到為0才算結束,y~key的正則比對,limit~每次傳回的limit(實際是周遊的槽位數目),隻是一個參考。

key的的存儲結構:一維數組+二維連結清單,一維數組也就是槽位,limit指的是這個,之是以傳回的數量有多有少,是因為并不是所有槽位上面都挂有連結清單。

擴容是按照一維數組來的,為了避免擴容、縮容的過程中重複周遊元素,采用高位加法(從左到右開始加并進位)。

zscan對zset掃描,hscan對hash掃描,sscan對set掃描。

15. 大key問題

大key對redis是一個挑戰,包括redis叢集(叢集會遷移節點),記憶體申請、擴容、删除等場景造成卡頓。

scan->type指令得到各個資料類似->用各個資料結構的size/len計算大小,對每種類型,保留topN,通過這種方式找到大key。

官方腳本:redis-cli -h 127.0.0.1 -p 6379 -bigkeys -i 0.1 ,  -i 0.1 代表每執行100條指令休息0.1秒。

16. Redis通信協定

直覺的文本協定,實作簡單,解析性能好,單元結束後統一加上\r\n。

  • 單行字元串,以+符号開始 ,+hello world\r\n
  • 多行字元串,以$符号開始,後跟字元串長度 $11\r\nhello world\r\n
  • 整數值,以:符号開始,後跟整數的字元串形式 :1024\r\n
  • 錯誤消息,以-符号開始, -WRONGTYPE Operation against a key holding the wrong kind of value
  • 數組,以*開始,後跟數組的長度,*3\r\n:1\r\n:2\r\n:3\r\n
  • NULL用多行字元串表示,不過長度要寫成-1, $-1\r\n
  • 空行用多行字元串表示,長度填0,$0\r\n

17. Redis的持久化

  • 快照(rdb檔案),全量備份,記憶體資料的二進制序列化,非常緊湊,通過COW(Copy on Write)機制,fork一個子程序專門用于寫磁盤,父程序繼續接受請求進行處理,一開始父子程序共享資料段,當父程序有寫操作的時候,會在寫之前單獨拷貝一份内容給子程序(以頁為機關),這樣雖然會導緻記憶體增大,但是可以保證資料在某一時刻的一緻性,即“快照”,通過save/bgsave 産生快照。
  • AOF日志,連續的增量備份,記憶體修改記錄的指令文本,需要定期重寫,隻存儲修改指令,指令參數校驗沒有問題後,寫AOF日志、然後執行指令,長時間的運作會導緻AOF日志過大,系統重新開機後的回放也很耗時。通過bgrewriteaof指令開啟子程序進行日志瘦身,将可以合并的寫指令進行合并。fsync,控制刷盤時間,通常1s一次,在性能和安全之間做權衡。
  • 混合持久化,快照(rdb檔案)和AOF同時使用,此時AOF不是增量備份檔案,而是快照落盤開始到落盤結束這段時間的增量内容。

通常主節點不持久化,由從節點進行持久化,适當增加從節點的個數,保證資料安全。

18. pipeline與事務

為提高執行效率,通過pipeline一次執行多個指令,且整體當做一個事務,滿足隔離性但是不滿足原子性,比如3個指令,第二個失敗了,第三個會繼續執行。pipeline不是服務端的特性,是用戶端的特性,用戶端通過改變讀寫順序帶來了性能的巨大提升(前提是pipeline中的多條指令沒有前後資料依賴,通常也不會有的)。

java redis設定過期時間_Redis基礎知識總結(面試必備)

19. Redis記憶體占用

32bit位編譯,記憶體上限是4GB,可以使指針等記憶體減少一半,可以結合多執行個體來突破4GB的限制。

小對象壓縮存儲ziplist見下圖,若是hash,key和value作為兩個entry相鄰在一起,若是zset,value和score作為兩個entry相鄰在一起。

java redis設定過期時間_Redis基礎知識總結(面試必備)

intset:緊湊型set,用于整數且數量較小的set集合,若有字元串,則立即更新為hashtable結構。不同的資料結構,當數量或者元素大小較小的時候,用緊湊型存儲,當超過規定後,開始用标準存儲,一般元素個數是512,元素大小是64位元組。

下面是各個控制參數的意義:

  • hash-max-zipmap-entries 512 : hash的元素個數超過512就必須使用标準結構存儲。
  • hash-max-zipmap-value 64:hash的任意元素的key/value的長度超過64就必須使用标準結構存儲。
  • list-max-ziplist-entries 512 : list的元素個數超過512就必須使用标準結構存儲。
  • list-max-ziplist-value 64:list的任意元素的長度超過64就必須使用标準結構存儲。
  • zset-max-ziplist-entries 128 : zset的元素個數超過128就必須使用标準結構存儲。
  • zset-max-ziplist-value 64:zset的任意元素的長度超過64就必須使用标準結構存儲。
  • set-max-intset-entries 512 : set的元素個數超過512就必須使用标準結構存儲。

20. Redis記憶體配置設定與回收

Redis沒有管記憶體配置設定,直接采用第三方記憶體配置設定算法,libc、jemalloc,tcmalloc。

删除key後并不會讓記憶體立即釋放,因為作業系統回收是以頁為機關的,隻要這一頁還有1個key在使用,就無法回收。Redis會重用已經删除的key的記憶體的。

21. Redis主從同步

CAP原理:C-一緻性,A-可用性,P-分區容忍性, 當網絡發生故障(網絡分區),分布的節點無法互相通路:

  • 若保證一緻性,則無法對外提供修改(若能修改,則必然不一緻了)。
  • 若保證可用性(可修改),則無法保證一緻性。

Redis是異步同步,不滿足一緻性,滿足可用性,但是保證最終一緻性,支援主從同步和從從同步,同步方式如下,主從複制是Redis分布式的基礎,Redis的高可用離開主從複制則無從談起,叢集模式都依賴主從複制。

  • 增量同步,同步的是修改狀态的指令流,指令流有一個固定的buffer,循環使用,繞一圈則覆寫,主機将指令流同步給從機,從機執行指令流,并告知主機的指令偏移。
  • 快照同步,先在主庫上面執行bgsave将目前記憶體的資料全部快照到磁盤,拷貝到從機,執行全量加載,加載完畢後通知主機進行增量同步,若快照過大,在快照恢複期間,主機的指令流buffer滿了,從機即使加載了快照仍然無法從主機同步增量,這就需要重新弄一變快照,極端情況可能會死循環,是以要合理設定buffer大小。
  • 無盤複制:主機通過套接字将快照内容發給從機,生成快照是一個周遊的過程,一邊周遊,一邊将内容序列化後發給從機,從機接受後先儲存到本地檔案,後面再一次性load。

Redis的複制預設是異步,wait指令可以将異步變成同步,確定系統強一緻,不過這會一定程度上影響主機的執行效率。wait param1 param2, param1~從機的數量,param2~時間,最多等待param2毫秒,為0則表示無限等待直到N個從庫同步完成。

22. Redis Sentinel 模式

類似一個zk叢集,多機,專門負責Redis的主從切換,用戶端先請求Sentinel,Sentinel負責配置設定主機位址給用戶端,後續用戶端直接和給定的主機位址通信。若Redis主機挂掉,Sentinel會識别出來,并從多個從機中選擇一個作為主機,原先挂掉的主機恢複後成為從機,和新的主機進行同步,用戶端原先的舊的連結會斷掉、重新連接配接、或者抛出readonly異常(舊的主機恢複後變成從機,隻讀,不能修改)。min-slaves-to-write 1 主節點至少有一個從節點在正常同步,否則停止對外提供寫操作。min-slaves-max-lag 10 如果10s内沒有收到從節點的回報,則說明從節點同步異常。

Sentinel切換主從,用戶端如何感覺:

  1. redis-py 建立連結的時候,會和庫内主位址比較,若不一樣,則會斷掉所有連結、重新建立連結。
  2. 若是原先主庫異常恢複後降為從機,原先連結的寫操作會抛ReadOnly錯誤,檢測到此錯誤後會斷掉舊連結、建立新連結。
  3. 接續第二點,若隻有讀操作,連結沒有切換,這樣也沒有危險,讓他繼續好了(若沒有來得及同步,則可能會有一點不一緻)。

23. Codis,分而治之

go語言開發,對外提供的協定和Redis一樣,外界無感覺,代理中間件,Codis後面挂多個Redis執行個體,形成一個Redis叢集,可以線上擴容。

通過zk、etcd持久化槽位資訊,Codis proxy 通過監聽槽位變化并同步槽位資訊,進而實作多個Codis proxy共享相同的槽位資訊。

Codis記憶體維護槽位和Redis執行個體的關系,例如1024個槽位,根據key計算相應的槽位(crc32 hash然後取1024餘數),然後根據槽位得到Redis執行個體。

hash = crc32(command.key) slot_index = hash % 1024 redis = slot[slot_index].redis redis.do(command)

繼續閱讀