banner
Violet

Violet's Blog

A Web <Developer />. Code_ for Fun.
x
github
bilibili
email

Redis基礎&數據結構

簡介#

什麼是 Redis#

Redis 是內存高速緩存數據庫。全稱為:Remote Dictionary Server(遠程數據服務)。

Redis 由 c 語言編寫,key-value 存儲系統,支持 string、list、set、zset、hash 物種數據類型。

Redis 可用於緩存、事件發布或訂閱、高速隊列等場景。基於內存,可持久化。

Redis 的優勢#

  • 讀寫性能優異
  • 豐富的數據類型
  • 原子性
  • 豐富的特性(發布訂閱、通知、過期)
  • 支持持久化(RDB 和 AOF 方式持久化)
  • 分佈式

Redis 可以做什麼#

  • 數據緩存
  • 限時業務
  • 計數器(incrby)
  • 分佈式鎖(基於 Redisson 實現分佈式鎖)
  • 限流(使用 Redis 中的 List 作為消息隊列)
  • 消息隊列

安裝 / 啟動#

安裝#

Centos8 安裝 Redi🔗

啟動#

# 在前台啟動
redis-server
# 在後台啟動
brew services start redis
# 關閉
brew services stop redis

連接#

redis-cli

默認 6379 端口

redis 默認有 16 個數據庫,0-15

使用select 1來切換

基礎命令#

  • keys *查看當前數據庫中有哪些 key(有 16 個數據庫)
  • exists key判斷這個 key 是否存在
  • type key查看這個 key 的類型
  • del keyunlink key刪除某個 key
    • unlink異步刪除
  • expire key 10設置 key 過期時間 秒
  • ttl key查看 key 多少秒過期 -1 永遠不會過期 -2 已經過期
  • select切換數據庫
  • dbsize查看當前數據庫中 key 數量
  • flushdb清除當前數據庫所有 key

基礎類型(5 種)#

string(字符串)#

  • string 類型是二進制安全的,可以包含任何數據
  • string 是 redis 的基本數據類型,一個 value 最大 512Mb

命令#

  • set <key> <value>
  • get <key>查看
  • append <key> <value>在 key 後追加 value
    • 如果這個 key 不存在,則會創建
  • strlen <key>獲取這個 key 對應 value 長度
  • setnx <key> <value>只有當 key 不存在是創建
  • incr <key>這個 key 的 value+1
  • decr <key>value-1
  • incrby/decrby <key> <num>key 的 value 加 num 或者減 num

這些操作是原子性操作,在執行操作期間不會被其他線程打斷,因為 redis 是單線程

  • mset <key> <value> <key> <value> <key> <value>一次設置多個 key
  • mget <key> <key> <key>一次獲取多個 key
  • msetnx <key> <value> <key> <value>一次設置多個 當 key 不存在時
    • 原子性操作 一個失敗全部失敗
  • getrange <key> <開始> <結束>獲取 key 中第 <開始>< 結束 > 中間的所有 value
  • setrange <key> <offset> <value>在 key 中第 offset 之後覆蓋設置 value
  • setex <key> <過期時間> <value>在設置 key 時設置過期時間
  • get set <key> <value>取之前的,取完之後再設置新的

數據結構#

Arraylist

使用場景#

  1. 緩存:使用 Redis 作為 MySQL 緩存,降低 MySQL 讀寫壓力
  2. 計數器:Redis 是單線程的優勢
  3. session:spring session + redis 實現 session 共享

list(列表)#

單鍵多值,底層雙向鏈表

命令#

  • lpush/rpush <key> <v1> <v2> <v3>從雙向鏈表左右兩邊 push 值
  • Large <key> 0 -1編列鏈表,獲取所有
  • rpop/lpop <key>從左右獲取並刪除值
  • rpoplpush <k1> <k2>從 k1 右邊 pop 一個值,在 push 到 k2 左邊
  • lindex <key> idnex獲取第 index 個值
  • llen <k>獲取長度
  • linsert before/after <v1> <v2>在 v1 前 / 後插入 v2
  • lrem <key> <n> <v>從做百年刪除 n 個 value
  • lset <k> <idnex> <v>將 index 設置成 v

數據結構#

元素較少的情況下是 ziplist,元素多了之後會將多個 ziplist 組成一個 quicklist

使用場景#

  1. 消息隊列

set(集合)#

set 集合中的元素不能重複,底層 hash 表,增刪改查都是 O (1) 複雜度

命令#

  • sadd <k> <v1> <v2>添加
  • smamber <k>取出該集合中所有值
  • sismamber <k> <v>判斷是否存在這個 value
  • scard <k>返回這個集合長度
  • srem <k> <v> <v>刪除
  • spop <k>隨機取出並刪除一個
  • srandmameber <k> <n>隨機取出 n 個,不刪除
  • smove <k1> <k2> <v>把 k1 中的 value 取出來,放到 k2 中
  • sinter <k1> <k2>返回 交集
  • sunion <k1> <k2> 返回 並集
  • sdiff <k1> <k2>返回差集

使用場景#

  1. 點讚、轉發、收藏等

hash(散列)#

類似於 java 中的 map<string,object>,key 是對象名,value 裡邊是 field 和 value

命令#

  • hset <k> <field> <value>設置 key 和 key 中的 field 和 value
  • hget <k> <field> 獲取 k 中 field 中的 value
  • hmset <k> <field1> <v1> <field2> <v2>一次設置多個
  • hexists <k1> <field>查看 key 中的 field 是否存在
  • hkeys <key>獲取該 key 的所有 field
  • havals <key>獲取該 key 的所有 value,沒有 field
  • hincrby <k> <f> <n>給 f 的 value 加 n
  • hsetnx <k> <f> <v>field 不存在則設置 value

數據結構#

當 field-value 長度短且個數少時使用 ziplist,否則使用 hashtable

使用場景#

  1. 緩存

Zset(有序集合)#

有序集合

每個元素都有評分(score),使用這個 score 來給集合排序,score 可以重複

加權排序 set,類似於 Java 中優先隊列,只不過 redis 中將隊列更換為 set

命令#

  • zadd <k> <score> <k> <score> <k> <score>添加 key 和 score,會根據 score 排名
  • arange <k> <start> <stop> withscores獲取 scores 在開始和結束之間的元素
  • zrangebyscore <k> <mim> <max>返回並且排名 最小 scores 和最大 scores
  • zincrbg <k> <n> <v>為元素 v 添加 n 的 scores
  • zrem <k> <v>刪除
  • zcount <k> <min> <max>統計 min scores 和 max scores 之間元素個數
  • zrank <k> <v>獲取該元素的排名,從 0 開始

數據結構#

hash 跳躍表

使用場景#

  1. 排行榜

特殊類型(3 種)#

以下內容的學習均來自於此,使用了代碼片段

原文地址🔗

HyperLogLogs(基數統計)#

什麼是基數#

兩個集合的全集

例如:A = {1,2,3,4,5,6} ; B = {5,6,7,8,9} ; 基數 = {1,2,3,4,7,8,9} ,即不重複元素。

使用場景#

統計和計數,用於統計註冊 IP 數,每日訪問 IP 數,頁面實時 UV,線上用戶數等。

優勢#

基於基數估算的算法,能比較準確的估算出基數,可以使用少量固定的內存存儲並識別集合中唯一的原色。

這個算法並不一定準確,有 0.81% 的錯誤率,基數統計適合接受一定容錯的場景,比如統計訪客數量等。

命令#

127.0.0.1:6379> pfadd key1 a b c d e f g h i	# 創建第一組元素
(integer) 1
127.0.0.1:6379> pfcount key1					# 統計元素的基數數量
(integer) 9
127.0.0.1:6379> pfadd key2 c j k l m e g a		# 創建第二組元素
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 8
127.0.0.1:6379> pfmerge key3 key1 key2			# 合併兩組:key1 key2 -> key3 並集
OK
127.0.0.1:6379> pfcount key3
(integer) 13

Bitmap(位存儲)#

Bitmap 是通過二進制記錄信息,只有 0 和 1 兩個狀態

使用場景#

  1. 統計用戶登錄信息,登錄和未登錄
  2. 統計用戶點讚信息,點了和沒點
  3. 統計員工打卡信息,打了和沒打

命令#

  • 使用setbit <key> <offset> <value>寫入數據,其中 key 可以任意,offset 只能為 Integer,value 只能為 0 或 1

    如果要統計員工一周的打卡情況,key 可以設置為 sign,offset 設置為 0-6,打卡為 0,未打卡為 1

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
  • 使用getbit <key> <offset>來查看某一個 key 的狀態,返回 0 或 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
  • 使用bitcount <key>統計某一項數據的總數
127.0.0.1:6379> bitcount sign
(integer) 3

geospatial(地理位置)#

可以用於推算地理位置信息,如兩地之間距離等

命令#

  • geoadd添加位置
127.0.0.1:6379> geoadd china:city 118.76 32.04 nanjing 112.55 37.86 taiyuan 123.43 41.80 shenyang
(integer) 3
127.0.0.1:6379> geoadd china:city 144.05 22.52 shengzhen 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 3
  • geopop獲取指定成員的地理位置
127.0.0.1:6379> geopos china:city taiyuan nanjing
1) 1) "112.54999905824661255"
   1) "37.86000073876942196"
2) 1) "118.75999957323074341"
   1) "32.03999960287850968"
  • geodist獲取兩地之間的距離,可以指定單位
127.0.0.1:6379> geodist china:city taiyuan shenyang m
"1026439.1070"
127.0.0.1:6379> geodist china:city taiyuan shenyang km
"1026.4391"
  • georadius

這裡有詳細解釋🔗

附近的人,獲得所有附近的人的地址,定位,通過半徑來查詢

  • georadiusbymember

link🔗

顯示與指定成員一定半徑範圍內的其他成員

Redis 中的數據結構#

  • 5 種基礎數據結構:string(字符串)、list(列表)、set(集合)、hash(散列)、zset(有序集合)
  • 3 種特殊數據結構:HyperLogLogs(基數統計)、Bitmap(位存儲)、Geospatial(地理位置)

對應關係#

圖片來自

image

動態字符串 SDS#

Redis 並沒有使用 C 中的字符串,而是自己定義了一種數據結構,即簡單動態字符串(Simple Dynamic String SDS)。Redis 中 String 的底層數據結構使用的就是 SDS。

SDS 內存結構#

  • len保存 SDS 保存字符串的長度
  • buf[]數組保存字符串的每個元素
  • alloc分別以 unint8、unint16、unint32、unint64 表示整個 SDS
  • flags始終為一字符,以低三位標示著頭部的類型

SDS 的優勢#

  • O (1) 複雜度獲取字符串長度:在 Redis 中如果要獲取字符串長度只需讀取 len 屬性即可,在 C 中如果要獲取字符串長度需要遍歷整個字符串,是 O (n) 複雜度。使用strlen <K>獲取字符串長度。
  • 防止緩衝區溢出:在 C 中,使用strcat中拼接兩字符串,如果沒有分配足夠的內存空間,會造成緩衝區溢出。SDS 會先根據 len 先推算拼接後的長度是否滿足內存空間要求。不滿足會先擴展,再拼接,從而防止緩衝區溢出。
  • 減少修改字符串內存重新分配次數:SDS 使用lenalloc實現空間預分配惰性空間釋放
    • 空間預分配:對字符串空間擴展時,擴展大於需求,從而減少字符串長度增加所需的內存重新分配次數。當字符串小於 1M 時,redis 會分配字符串大小一倍的空間,當字符串大小大於 1M 時,額外多分配 1M 空間。
    • 惰性空間釋放:字符串長度減少時,不立即歸還內存,而是使用alloc屬性將這些空間記錄下來,等待後續使用。如果覺得這樣會浪費空間,可以自行設置定時釋放未使用空間。
  • 二進制安全:C 中使用空字符來表示一個字符串的結束。但是如果 Redis 中保存的二進制文件(如圖片)中可能包含空字符,C 無法正確讀取。SDS 使用len屬性表示的長度來判斷字符串是否結束。

壓縮列表 ZipList#

ziplist 是一種為提高存儲效率設計的特殊編碼的雙向鏈表。可用於存儲字符串或整數。

ziplist 內存結構#

  • zlbytes存儲整個 ziplist 所占用內存字節數
  • zltail存儲 ziplist 中最後一個 entry 的偏移量,用於快速定位最後一個 entry,方便快速 pop 操作
  • zllen存儲 ziplist 中 entry 的數量。
  • zlend是終止字節

Entry 結構#

  • prevlen前一個 entry 的大小
  • encoding當前 entry 的類型和長度
  • entry-dataentry 數據

PS:如果 entry-data 中保存的是整形數據,encoding 會和 entrydata 合為一,此時 entry 結構為

ziplist 優點#

節省內存

  • 普通 List 保存是,每個元素占用空間大小一致,均為當前 List 中最大元素所占的空間大小,造成了空間浪費。
  • ziplist 通過特殊的數據結構,盡量使每個元素占用與自身大小相同的空間,減少空間浪費。
  • 傳統 List 在內存中地址連續,ziplist 所占大小不一致,所以地址不連續,需要通過prelen字段保存上個元素的 length 來解決遍歷問題。

ziplist 缺點#

  • 不預留內存空間,list 中元素被移除之後 list 無用空間立即歸還,這導致每一次修改 list 都需要重新分配空間。

快表 QuickList#

以 ziplist 作為結點的雙端鏈表,如何理解 QuickList:List<List<ziplist>> quickList

quicklist 在內存中結構#

  • quicklistNode鏈表中的節點,在 quicklist 中,這個節點是 ziplist
  • quickListLZFziplist 經過 LZ4 算法壓縮後,可以包裝成一個quickListLZF結構
  • quicklistBookmark位於 quicklist 尾部
  • quicklist
    • head、tail 指向頭尾指針
    • len 表示鏈表中的節點
    • count 表示 quicklist 中 ziplist 中 entry 數目
    • fill 控制每個 quicklist 中 ziplist 最大所占空間
    • compress 控制是否要對每個 ziplist 以 LZ4 算法進行壓縮以節省空間
  • quicklistIter迭代器
  • quicklistEntry對 ziplist 中 entry 封裝

哈希表 - Dict-HashTable#

哈希表於大多數語言中的 HashTable 無異,差別主要存在於解決哈希衝突、擴容。

哈希衝突#

使用鏈地址法

擴容#

擴容步驟:

  1. 根據原哈希表所占空間大小創建一個新哈希表,大小為原哈希表一倍,(收縮也是減小為原來一倍大小)
  2. 使用哈希算法重新計算索引值
  3. 新表創建並賦值完畢之後刪除原表

擴容發生條件

  1. redis 沒有執行bgsavebgrewriteaof命令,負載因子大於等於 1
  2. redis 沒有執行bgsavebgrewriteaof命令,負載因子大於等於 5

負載因子 = 哈希表中節點數量 / 哈希表大小

整數集 IntSet#

intset 是 redis 中集合類型底層實現之一,當一個集合只包含整數數值元素,並且元素數量較少,redis 使用 intset 作為集合鍵的底層實現

內存結構#

  • encoding編碼方式
  • length存儲整數個數
  • contents指向實際存儲數值的連續內存區域

擴容#

intset 底層為數組,當新插入的元素大小大於原數組中最大元素的大小時,就需要對整個數組進行擴容,每個元素所占空間大小修改為最大元素的大小

會擴容但不會縮容!

跳表 ZSkipList#

跳躍表的目的類似於紅黑樹,為了性能。跳躍表可以在對數時間內完成插入、查找、刪除。但是跳躍表所占空間較大,屬於空間換時間的數據結構。

什麼是跳躍表#

傳統鏈表如果需要查詢元素,需要從頭遍歷整個鏈表知道找到查詢元素,時間複雜度 O (n)。跳躍表通過增加多級索引的方式減少遍歷長度,從而加快查找速度。跳躍表實現了平衡樹、哈希表類似的效果。

為什麼不使用平衡樹,而是跳躍表

鏈接🔗

圖片來自

image

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。