日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

Redis 数据结构与内存管理策略(上)

發布時間:2023/12/31 数据库 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Redis 数据结构与内存管理策略(上) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Redis 數據結構與內存管理策略(上)

標簽: Redis Redis數據結構 Redis內存管理策略 Redis數據類型 Redis類型映射


  • Redis 數據類型特點與使用場景
    • StringListHashSetZset
    • 案例:滬江團購系統大促?hot-top?接口?cache?設計
  • Redis 內存數據結構與編碼
    • OBJECT?encoding key、DEBUG OBJECT?key
    • 簡單動態字符串(simple dynamic string)
    • 鏈表(linked list)
    • 字典(dict)
    • 跳表(skip list)
    • 整數集合(int set)
    • 壓縮表(zip list)
    • Redis Object 類型與映射
  • Redis 內存管理策略
    • 鍵 過期時間、生存時間
    • 過期鍵刪除策略
    • AOF?、RDB?處理過期鍵策略
    • Redis?LRU?算法
  • Redis 持久化方式
    • AOF?(Append-only file)
    • RDB?(Redis DataBase)

Redis 數據類型特點與使用場景

redis?為我們提供了?5?種數據類型,基本上我們使用頻率最高的就是?string?,而對其他四種數據類型使用的頻次稍弱于?string?。

一方面是由于?string?使用起來比較簡單,可以方便存儲復雜大對象,使用場景比較多。還有一個原因就是由于?redis expire time?只能設置在?key?上,像?listhashsetzset?屬于集合類型,會管理一組?item,我們無法在這些集合的?item?上設置過期時間,所以使用?expire time?來處理集合的?cache?失效會變得稍微復雜些。但是?string?使用?expire time?來管理過期策略會比較簡單,因為它包含的項少。這里說的集合是寬泛的類似集合。

導致我們習慣性的使用?string?而忽視其他四種數據類型的另一個深層次原因,大多是由于我們對另外四種數據類型的使用和原理不是太了解。這個時候往往會忽視在特定場景下使用某種數據類型可能會比?string?性能高出很多,比如使用?hash?結構來提高某個實體的某個項的修改等。

這里我們不打算羅列這?5?種數據類型的使用方法,這些資料網上有很多。我們主要討論這?5?種數據類型的功能特點,這些特點分別適合用于處理哪些現實的業務場景,最重要的是我們如何組合性的使用這?5?種數據類型來解決復雜的?cache?問題。

String、List、Hash、Set、Zset

String

string?是?redis?提供的字符串類型。可以針對?string?類型獨立設置?expire time?。通常用來存儲長字符串數據,比如,某個對象的?json?字符串。

string?類型我們在使用上最巧妙的是可以動態拼接?key。通常我們可以將一組?id?放在?set?里,然后動態查找?string?還是否存在,如果不存在說明已經過期或者由于數據修改主動?delete?了,需要再做一次?cache?數據?load?。

雖然?set?無法設置?item?的過期時間,但是我們可以將?set item?與?string key?關聯來達到相同的效果。

上圖中的左邊是一個?key?為?set:order:ids?的?set?集合,它可能是一個全量集合,也可能是某個查詢條件獲取出來的一個集合。

有時候復雜點的場景需要多個?set?集合來支撐計算,在?redis 服務器?里可能會有很多類似這樣的集合。

這些集合我們可以稱為?功能數據,這些數據是用來輔助?cache?計算的,當進行各種集合運算之后會得出當前查詢需要返回的子集,最后我們才會去獲取某個訂單真正的數據。

這些?string:order:{orderId}?字符串?key?并不一定是為了服務一種場景,而是整個系統最底層的數據,各種場景最后都需要獲取這些數據。那些?set?集合可以認為是查詢條件數據,用來輔助查詢條件的計算。

redis?為我們提供了?TYPE?命令來查看某個?key?的數據類型,如:string?類型:

SET string:order:100 order-100 TYPE string:order:100string

List

list?在提高?throughput?的場景中非常適用,因為它特有的?LPUSHRPUSHLPOPRPOP?功能可以無縫的支持生產者、消費者架構模式。

這非常適合實現類似?Java Concurrency?Fork/Join?框架中的?work-stealing 算法 (工作竊取)?。

java fork/join?框架使用并行來提高性能,但是會帶來由于并發?take task?帶來的?race condition (競態條件)?問題,所以采用?work-stealing 算法?來解決由于競爭問題帶來的性能損耗。

上圖中模擬了一個典型的支付?callback?峰值場景。在峰值出現的地方一般我們都會使用加?buffer?的方式來加快請求處理速度,這樣才能提高并發處理能力,提高?throughput?。

支付?gateway?收到?callback?之后不做任何處理直接交給?分發器?。分發器?是一個無狀態的?cluster?,每個?node?通過向?注冊中心?pull handler queue list?,也就是獲取下游處理器注冊到注冊中心里的消息通道。

每一個分發器?node?會維護一個本地?queue list?,然后順序推送消息到這些?queue list?即可。這里會有點小問題,就是?支付?gateway?調用分發器的時候是如何做?load balance?,如果不是平均負載可能會有某個?queue list?高出其他?queue list?。

而分發器不需要做?soft load balance?,因為哪怕某個?queue list?比其他?queue list?多也無所謂,因為下游?message handler?會根據?work-stealing?算法來竊取其他消費慢的?queue list?。

redis list 的?LPUSHRPUSHLPOPRPOP?特性確實可以在很多場景下提高這種橫向擴展計算能力。

Hash

hash?數據類型很明顯是基于?hash?算法的,對于項的查找時間復雜度是?O(1)?的,在極端情況下可能出現項?hash?沖突問題,redis?內部是使用鏈表加?key?判斷來解決的。具體?redis?內部的數據結構我們在后面有介紹,這里就不展開了。

hash?數據類型的特點通常可以用來解決帶有映射關系,同時又需要對某些項進行更新或者刪除等操作。如果不是某個項需要維護,那么一般可以通過使用?string?來解決。

如果有需要對某個字段進行修改,使用?string?很明顯是會多出很多開銷,需要讀取出來反序列化成對象然后操作,然后再序列化寫回?redis?,這中間可能還有并發問題。

那我們可以使用?redis hash?提供的實體屬性?hash?存儲特性,我們可以認為?hash value?是一個?hash table?,實體的每一個屬性都是通過?hash?得到屬性的最終數據索引。

上圖使用?hash?數據類型來記錄頁面的?a/b metrics?,左邊的是首頁?index?的各個區域的統計,右邊是營銷?marketing的各個區域統計。

在程序里我們可以很方便的使用?redis?的?atomic?特性對?hash?某個項進行累加操作。

HMSET hash:mall:page:ab:metrics:index topbanner 10 leftbanner 5 rightbanner 8 bottombanner 20 productmore 10 topshopping 8 OK HGETALL hash:mall:page:ab:metrics:index1) "topbanner"2) "10"3) "leftbanner"4) "5"5) "rightbanner"6) "8"7) "bottombanner"8) "20"9) "productmore" 10) "10" 11) "topshopping" 12) "8" HINCRBY hash:mall:page:ab:metrics:index topbanner 1 (integer) 11

使用?redis hash increment?進行原子增加操作。HINCRBY?命令可以原子增加任何給定的整數,也可以通過?HINCRBYFLOAT?來原子增加浮點類型數據。

Set

set?集合數據類型可以支持集合運算,不能存儲重復數據。

set?最大的特點就是集合的計算能力,inter 交集union 并集diff 差集,這些特點可以用來做高性能的交叉計算或者剔除數據。

set?集合在使用場景上還是比較多和自由的。舉個簡單的例子,在應用系統中比較常見的就是商品、活動類場景。用一個?set?緩存有效商品集合,再用一個?set?緩存活動商品集合。如果商品出現上下架操作只需要維護有效商品?set?,每次獲取活動商品的時候需要過濾下是否有下架商品,如果有就需要從活動商品中剔除。

當然,下架的時候可以直接刪除緩存的活動商品,但是活動是從?marketing?系統中?load?出來的,就算我將?cache?里的活動商品刪除,當下次再從?marketing?系統中?load?活動商品時候還是會有下架商品。當然這只是舉例,一個場景有不同的實現方法。

上圖中左右兩邊是兩個不同的集合,左邊是營銷域中的可用商品ids集合,右邊是營銷域中活動商品ids集合,中間計算出兩個集合的交集。

SADD set:marketing:product:available:ids 1000100 1000120 1000130 1000140 1000150 1000160 SMEMBERS set:marketing:product:available:ids 1) "1000100" 2) "1000120" 3) "1000130" 4) "1000140" 5) "1000150" 6) "1000160" SADD set:marketing:activity:product:ids 1000100 1000120 1000130 1000140 1000200 1000300 SMEMBERS set:marketing:activity:product:ids 1) "1000100" 2) "1000120" 3) "1000130" 4) "1000140" 5) "1000200" 6) "1000300" SINTER set:marketing:product:available:ids set:marketing:activity:product:ids 1) "1000100" 2) "1000120" 3) "1000130" 4) "1000140"

在一些復雜的場景中,也可以使用?SINTERSTORE?命令將交集計算后的結果存儲在一個目標集合中。 這在使用?pipeline?命令管道中特別有用,將?SINTERSTORE?命令包裹在?pipeline?命令串中可以重復使用計算出來的結果集。

由于?redis?是?Signle-Thread 單線程模型?,基于這個特性我們就可以使用?redis?提供的?pipeline 管道?來提交一連串帶有邏輯的命令集合,這些命令在處理期間不會被其他客戶端的命令干擾。

Zset

zset?排序集合與?set?集合類似,但是?zset?提供了排序的功能。在介紹?set?集合的時候我們知道?set?集合中的成員是無序的,zset?填補了集合可以排序的空隙。

zset?最強大的功能就是可以根據某個?score 比分值?進行排序,這在很多業務場景中非常急需。比如,在促銷活動里根據商品的銷售數量來排序商品,在旅游景區里根據流入人數來排序熱門景點等。

基本上人們在做任何事情都需要根據某些條件進行排序。

其實?zset?在我們應用系統中能用到地方到處都是,這里我們舉一個簡單的例子,在團購系統中我們通常需要根據參團人數來排序成團列表,大家都希望參加那些即將成團的團。

上圖是一個根據團購code創建的zset,score 分值?就是參團人數累加和。

ZADD zset:marketing:groupon:group:codes 5 G_PXYJY9QQFA 8 G_4EXMT6NZJQ 20 G_W7BMF5QC2P 10 G_429DHBTGZX 8 G_KHZGH9U4PP ZREVRANGEBYSCORE zset:marketing:groupon:group:codes 1000 0 1) "G_W7BMF5QC2P" 2) "G_ZMZ69HJUCB" 3) "G_429DHBTGZX" 4) "G_KHZGH9U4PP" 5) "G_4EXMT6NZJQ" 6) "G_PXYJY9QQFA" ZREVRANGEBYSCORE zset:marketing:groupon:group:codes 1000 0 withscores1) "G_W7BMF5QC2P"2) "20"3) "G_ZMZ69HJUCB"4) "10"5) "G_429DHBTGZX"6) "10"7) "G_KHZGH9U4PP"8) "8"9) "G_4EXMT6NZJQ" 10) "8" 11) "G_PXYJY9QQFA" 12) "5"

zset?本身提供了很多方法用來進行集合的排序,如果需要?score?分值可以使用?withscore?字句帶出每一項的分值。

在一些比較特殊的場合可能需要組合排序,可能有多個?zset?分別用來對同一個實體在不同維度的排序,按時間排序、按人數排序等。這個時候就可以組合使用?zset?帶來的便捷性,利用?pipeline?再結合多個?zset?最終得出組合排序集合。

案例:滬江團購系統大促?hot-top?接口?cache?設計

我們總結了?redis?提供的?5?種數據類型的各自特點和一般的使用場景。但是我們不僅僅可以分開使用這些數據類型,我們完全可以綜合使用這些數據類型來完成復雜的?cache?場景。

下面我們分享一個使用多個?zset?、string?來優化?團購系統?前臺接口的例子。由于篇幅和時間限制,這里只介紹跟本次案例相關的信息。

hot-top?接口是指熱點、排名接口的意思,表示它的瀏覽量、并發量比較高,一般大促的時候都會有幾個這種性能要求比較高的接口。

我們先來分析一個查詢接口所包含的常規信息。

首先一個查詢接口肯定是有?query condition 查詢條件?,然后是?sort 排序信息?、最后是?page 分頁信息?。這是一般接口所承擔的基本職責,當然,特殊場景下還需要支持?master/slave replication?時關于數據?session 一致性?的要求,需要提供跟蹤標記來回?master?查詢數據,這里就不展開了。

我們可以抽象出這幾個維度的信息:

query condition?
查詢條件,companyid=100,sellerid=1010101 諸如此類。
sort?
排序信息,一般是默認一個列排序,但是在復雜的場景下會有可能讓接口使用者定制排序字段,比如一些租戶信息列。
page?
分頁信息,簡單理解就是數據記錄排完序之后的第幾行到第幾行。

由于這里我們純粹用?redis?來提高?cache?能力,不涉及到有關于任何搜索的能力,所以這里忽略其他復雜查詢的情況。其實我們在復雜的地方使用了?elastcsearch?來提高搜索能力。

上述我們分析總結出了一個查詢接口的基本信息,這里還有一個有關于高并發接口的設計原則就是將?hot-top?接口和一般?search?接口分離開,因為只有分而治之才能分別根據特點選用不同的技術。如果我們不分職責將所有的查詢場景封裝在一個接口里,那么在后面優化接口性能的時候基本就很麻煩了,有些場景是無法或者很難用?cache?來解決的,因為接口里耦合了各種場景邏輯,就算勉強能實現性能也不會高。

前面做這些鋪墊是為了能在介紹案例的時候達成一個基本的共識。現在我們來看下這個團購系統的?hot-top?接口的具體邏輯。

在大促的時候需要展現團購列表,這個接口的訪問量是非常大的,團購活動需要根據參團人數倒序排序,并且分頁返回指定數量的團列表。

我們假設這個接口名為?getTopGroups(getTopGroupsRequest request)

query condition 查詢條件問題

我們來仔細分析下,首先不同的查詢條件從?DB?里查詢出來的數據是不一樣的,也就是說查詢出來的團列表是不一樣的,可能有?company 公司?、channel 渠道?等過濾條件。由于一個團購活動下不會有太多團,頂多上百個是極限了,所以一個查詢條件出來的團列表也頂多幾十個,而且根據場景分析熱點查詢條件不會超過十個,所以我們選擇將?查詢條件 hash?出一個?code?來緩存本次查詢條件的全量團列表集合,但是這些結果集是沒有任何排序的。

sort 排序問題

再看根據參團人數排序問題,我們立刻就可以想到使用?zset?來處理團排序問題,因為只有一個排序維度,所以一個?zset?就夠了。我們使用一個?zset來緩存所有團的參團人數集合,它是一個全量的團排序集合。

那么我們如何將用戶的查詢條件出來的團列表根據參團人數排序尼,剛好可以使用?zset?的交集運算直接計算出當前這個集合的?zset?子集。

page 分頁問題

通過對已經排序之后的團列表?zset?使用?zrange?來獲取出分頁集合。

我們來看下完整的流程,如何處理查詢、排序、分頁的。

上圖從?query condition?計算?hash code?,然后通過?DB?查詢出當前條件全量團列表。
zset:marketing:groupon:hottop:available:group key?表示全量團的參團人數,用一個?zset?來緩存。接著將這兩個?zset?計算交集,就可以得出當前查詢所需要的帶有參團人數的?zset?,最后在使用?zrevrange?獲取分頁區間。

ZADD zset:marketing:groupon:hottop:condition:2986080 0 G4ZD5732YZQ 0 G5VW3YF42UC 0 GF773FEJ7CC 0 GFW8DUEND8S 0 GKPKKW8XEY9 0 GL324DGWMZM (integer) 6 ZADD zset:marketing:groupon:hottop:available:group 5 GN7KQH36ZWK 10 GS7VB22AWD4 15 GF773FEJ7CC 17 G5VW3YF42UC 18 G4ZD5732YZQ 32 GTYJKCEJBRR 40 GKPKKW8XEY9 45 GL324DGWMZM 50 GFW8DUEND8S 60 GYTKY4ACWLT (integer) 10 ZINTERSTORE zset:marketing:groupon:hottop:condition:interstore 2 zset:marketing:groupon:hottop:condition:2986080 zset:marketing:groupon:hottop:available:group (integer) 6 ZRANGE zset:marketing:groupon:hottop:condition:interstore 0 -1 withscores1) "GF773FEJ7CC"2) "15"3) "G5VW3YF42UC"4) "17"5) "G4ZD5732YZQ"6) "18"7) "GKPKKW8XEY9"8) "40"9) "GL324DGWMZM" 10) "45" 11) "GFW8DUEND8S" 12) "50" ZREVRANGE zset:marketing:groupon:hottop:condition:interstore 2 4 withscores 1) "GKPKKW8XEY9" 2) "40" 3) "G4ZD5732YZQ" 4) "18" 5) "G5VW3YF42UC" 6) "17"

有了返回的團?code?集合之后就可以通過?mget?來批量獲取?string?類型的團詳情信息,這里就不貼出代碼了。

由于篇幅和時間關系,這里就不展開太多的業務場景介紹了。這其中還涉及到計算?cache?過期時間的問題,這也跟促銷活動的運營規則有關系,還涉及到有可能?query condition hash?沖突問題等,但是這些已經不與我們本節主題相關。

Redis 內存數據結構與編碼

我們已經了解了?redis?提供的?5?種數據類型,那么?redis?內部到底是如何支持這?5?種數據類型的,也就是說?redis?到底是使用什么樣的數據結構來存儲、查找我們設置在內存中的數據。

雖然我們使用?5?種數據類型來緩存數據,但是?redis?會根據我們存儲數據的不同而選用不同的數據結構和編碼。

我們日常使用的是?redis?提供的?5?種數據類型,但是這?5?種數據類型在內存中的數據結構和編碼有很多種。隨著我們存儲的數據類型的不同、數據量的大小不同都會引起內存數據結構的動態調整。

本節只是做數據結構和編碼的一般性介紹,不做過多細節討論,一方面是關于?redis?源碼分析的資料網上有很多,還有一個原因就是?redis?每一個版本的實現有很大差異,一旦展開細節討論每一個點每一個數據結構都會很復雜,所以我們這里就不展開討論這些,只是起到拋磚引玉作用。

OBJECT?encoding key、DEBUG OBJECT?key

我們知道使用?type?命令可以查看某個?key?是否是?5?種數據類型之一,但是當我們想查看某個?key?底層是使用哪種數據結構和編碼來存儲的時候可以使用?OBJECT encoding?命令。

SET string:orderid:10101010 10101010 OK OBJECT encoding string:orderid:10101010 "int" SET string:orderid:10101010 "orderid:10101010" OK OBJECT encoding string:orderid:10101010 "embstr"

同樣一個?key?,但是由于我們設置的值不同而?redis?選用了不同的內存數據結構和編碼。雖然?redis?提供的?string?數據類型,但是?redis?會自動識別我們?cache?的數據類型是?int?還是?string?。

如果我們設置的是字符串,且這個字符串長度不大于?39?字節那么將使用?embstr?來編碼,如果大于?39?字節將使用?raw?來編碼。redis 4.0?將這個閥值擴大了?45?個字節。

除了使用?OBJECT encoding?命令外,我們還可以使用?DEBUG OBJECT?命令來查看更多詳細信息。

DEBUG OBJECT string:orderid:10101010 Value at:0x7fd190500210 refcount:1 encoding:int serializedlength:5 lru:6468044 lru_seconds_idle:8 DEBUG OBJECT string:orderid:10101010 Value at:0x7fd19043be60 refcount:1 encoding:embstr serializedlength:17 lru:6465804 lru_seconds_idle:1942

DEBUG OBJECT?能看到這個對象的?refcount 引用計數?、serializedlength 長度?、_lru_secondsidle 時間?,這些信息決定了這個?key?緩存清除策略。

簡單動態字符串(simple dynamic string)

簡單動態字符串簡稱?SDS?,在?redis?中所有涉及到字符串的地方都是使用?SDS?實現,當然這里不包括字面量。?SDS與傳統?C?字符串的區別就是?SDS?是結構化的,它可以高效的處理分配、回收、長度計算等問題。

struct sdshdr {unsigned int len;unsigned int free;char buf[]; };

這是?redis 3.0?版本的?sds.h?頭文件定義,3.0.0?之后變化比較大。len?表示字符串長度,free?表示空間長度,buf?數組表示字符串。

SDS?有很多優點,比如,獲取長度的時間復雜度?O(1)?,不需要遍歷所有?char buf[]?組數,直接返回?len?值。

static inline size_t sdslen(const sds s) {struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));return sh->len; }

當然還有空間分配檢查、空間預分配、空間惰性釋放等,這些都是?SDS?結構化字符串帶來的強大的擴展能力。

鏈表(linked list)

鏈表數據結構我們是比較熟悉的,最大的特點就是節點的增、刪非常靈活。redis List?數據類型底層就是基于鏈表來實現。這是?redis 3.0?實現。

typedef struct list {listNode *head;listNode *tail;void *(*dup)(void *ptr);void (*free)(void *ptr);int (*match)(void *ptr, void *key);unsigned long len; } list; typedef struct listNode {struct listNode *prev;struct listNode *next;void *value; } listNode;

在?redis 3.2.0?版本的時候引入了?quicklist?鏈表結構,結合了?linkedlist?和?ziplist?的優勢。

typedef struct quicklist {quicklistNode *head;quicklistNode *tail;unsigned long count; /* total count of all entries in all ziplists */unsigned int len; /* number of quicklistNodes */int fill : 16; /* fill factor for individual nodes */unsigned int compress : 16; /* depth of end nodes not to compress;0=off */ } quicklist; typedef struct quicklistNode {struct quicklistNode *prev;struct quicklistNode *next;unsigned char *zl;unsigned int sz; /* ziplist size in bytes */unsigned int count : 16; /* count of items in ziplist */unsigned int encoding : 2; /* RAW==1 or LZF==2 */unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */unsigned int recompress : 1; /* was this node previous compressed? */unsigned int attempted_compress : 1; /* node can't compress; too small */unsigned int extra : 10; /* more bits to steal for future usage */ } quicklistNode;

quicklist?提供了靈活性同時也兼顧了?ziplist?的壓縮能力,quicklist->encoding?指定了兩種壓縮算法。?quicklist->compress?表示我們可以進行?quicklist node?的深度壓縮能力。redis 提供了兩個有關于壓縮的配置。

list-max-ziplist-size:ziplist長度控制
list-compress-depth:控制鏈表兩端節點的壓縮個數,越是靠近兩端的節點被訪問的機率越大,所以可以將訪問機率大的節點不壓縮,其他節點進行壓縮

對比?redis 3.2?的?quicklist?與?redis 3.0?,很明顯?quicklist?提供了更加豐富的壓縮功能。redis 3.0?的版本是每個?listnode?直接緩存值,而?quicklistnode?還有強大的有關于壓縮能力。

LPUSH list:products:mall 100 200 300 (integer) 3 OBJECT encoding list:products:mall

"quicklist"





本文轉自 王清培 51CTO博客,原文鏈接:http://blog.51cto.com/wangqingpei557/2063316,如需轉載請自行聯系原作者



總結

以上是生活随笔為你收集整理的Redis 数据结构与内存管理策略(上)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。