基礎
Redis 數據結構
常用數據結構有:字符串 String、列表 List、字典 Hash、集合 Set、有序集合 SortedSet。
常用數據結構說明
String
- 命令
命令 | 說明 |
---|---|
set key value | 設置一個key,值爲value,類型爲String類型;如果這個key已經存在,則更新這個key的值。返回值:1 表示成功、0 表示失敗 |
setnx key value | 如果這個key不存在,則設置一個key,值爲value;如果key存在,則不做更新。返回值:1 表示成功、0 表示失敗 |
append key value | 如果key已經存在,則將value追加到這個key原先的value值的末尾。如果這個key不存在,則執行set操作。 |
incr key | 將 key對應的value中儲存的數字值增一,然後返回。注意:[1]如果這個key不存在,那麼key的值會先被初始化爲0,然後再執行incr操作。[2]如果這個key對應的value值不是數字,則會返回一個錯誤。 |
incrby key step | 將key對應的value值增加指定的step。注意:類似同incr。 |
decr key | 將 key 對應的value中儲存的數字值減一,然後返回。注意:類似同incr。 |
decrby key decrement | 將key減少對應的步長值。注意:類似同incr。 |
help @string | 查看string類型的幫助文檔 |
- 場景
- 緩存:k-v 緩存,mysql 做持久層,降低數據庫讀寫壓力
- 計數器:點贊功能等
- 搶購秒殺:redis 是單線程模型,命令順序執行
- session:SpringSession + Redis 實現 session 共享,Redisson + Redis 實現分佈式鎖
List
Redis 使用雙端鏈表實現 List,有序,值可重複。
【1】基於Linked List實現。
【2】元素是字符串類型。
【3】列表頭尾增刪快,中間增刪慢,增刪元素是常態。
【4】從左至右,從0開始,從右至左,從-1開始。
【5】元素可以重複出現。
【6】最多包含2^32-1元素。
- 命令
- lpush + lpop = Stack(棧)
- lpush + rpop = Queue(隊列)
- lpush + ltrim = Capped Collection(有限集合)
- lpush + rpop = Message Queue(消息隊列)
- 場景
- timeline:微博時間軸等
- 微博評論
- 聊天室
Hash
【1】hash內容由field和與之關聯的value組成map鍵值對組成
【2】key、field和value是字符串類型
【3】一個hash中最多包含2^32-1鍵值對
- 命令 hget、hset、hdel 等
- 場景
- 緩存:數據結構更清晰,更節省空間
Set
集合中元素無序、不重複,支持集合間操作,如:交集、並集、差集。
【1】無序的、無重複的。
【2】元素是字符串類型。
【3】最多包含2^32-1元素。
- 命令 sadd、spop、srem、scard、smemebers、sismember 等
- 場景
- 標籤:對某事物添加標籤,可計算多個事物的集合關係,如共同關注的人等
- 點贊、收藏、關注等數量及集合的計算信息
SortedSet
Set 集合的有序版本,值不可重複,元素可排序,每個元素有數值得分,該得分用於排序等。
【1】類似Set集合
【2】有序的、無重複的
【3】元素是字符串類型
【4】每一個元素都關聯着一個浮點數分值(Score),並按照分值從小到大的順序排列集合中的元素。注意:分值可以相同
【5】最多包含2^32-1元素
- 命令 zadd、zrange、zscore、acount、zrank、zrem 等
- 場景
- 排行榜: 榜單等
HyperLogLog
基數:一個集合(注意:這裏集合的含義是 Object 的聚合,可以包含重複元素)中不重複元素的個數。例如集合
{1,2,3,1,2}
,它有5個元素,但它的 基數(Distinct 數)爲3。
Redis HyperLogLog 是用來做基數統計的算法,是近似統計海量去重元素數量的算法。HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。
在 Redis 裏面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。
但是,因爲 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
- 基數不大,數據量不大就用不上,會大材小用、浪費空間
- 有侷限性,就是隻能統計基數數量,無法知道具體內容
- HyperLogLog 去重比 bitmap 方便
- 一般可以 bitmap 和 hyperloglog 配合使用,bitmap 標識哪些用戶活躍,hyperloglog 計數
- 命令
命令 | 說明 |
---|---|
pfadd key element [element...] | 添加指定元素到 HyperLogLog 中。將任意數量的元素添加到指定的 HyperLogLog 裏面。時間複雜度: 每添加一個元素的複雜度爲 O(1) 。如果 HyperLogLog 估計的近似基數(approximated cardinality)在命令執行之後出現了變化, 那麼命令返回 1 , 否則返回 0 。 如果命令執行時給定的鍵不存在, 那麼程序將先創建一個空的 HyperLogLog 結構, 然後再執行命令。 |
pfcount key [key...] | 返回給定 HyperLogLog 的基數估算值 |
pfmerge destkey sourcekey [sourcekey...] | 將多個 HyperLogLog 合併爲一個 HyperLogLog |
- 應用
- 統計註冊 IP 數
- 統計每日訪問 IP 數
- 統計頁面實時 UV 數
- 統計在線用戶數
- 統計用戶每天搜索不同詞條的個數
Geo
- 命令
命令 | 說明 |
---|---|
geoadd key longitude latitude member [longitude latitude member ...] | 將指定的地理空間位置(緯度、經度、名稱)添加到指定的key中 |
geodist key member1 member2 [unit] | 返回兩個給定位置之間的距離。如果兩個位置之間的其中一個不存在,那麼命令返回空值 |
geopos key member [member ...] | 從key裏返回所有給定位置元素的位置(經度和緯度) |
geohash key member [member ...] | 返回一個或多個位置元素的 Geohash 表示。通常使用表示位置的元素使用不同的技術,使用Geohash位置52點整數編碼。由於編碼和解碼過程中所使用的初始最小和最大座標不同,編碼的編碼也不同於標準。此命令返回一個標準的Geohash |
georadius key longitude latitude radius m\km\ft\mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] | 以給定的經緯度爲中心, 返回鍵包含的位置元素當中, 與中心的距離不超過給定最大距離的所有位置元素 |
GEORADIUSBYMEMBER key member radius m\km\ft\mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] | 與 GEORADIUS 命令一樣, 都可以找出位於指定範圍內的元素, 但是 GEORADIUSBYMEMBER 的中心點是由給定的位置元素決定的 |
GeoHash 算法
比較通用的地理位置距離排序算法是 GeoHash 算法,Redis 也使用 GeoHash 算法。GeoHash 算法將二維的經緯度數據映射到一維的整數,這樣所有的元素都將在掛載到一 條線上,距離靠近的二維座標映射到一維後的點之間距離也會很接近。當我們想要計算「附近的人時」,首先將目標位置映射到這條線上,然後在這個一維的線上獲取附近的點就行了。
- 應用
- 地圖距離:利用redis的GEO地理定位計算可以得出,數據庫中存放商家的經緯度(座標),通過geo計算得出距離
Pub/Sub
Pub/Sub功能(means Publish, Subscribe)即發佈及訂閱功能。基於事件的系統中,Pub/Sub是目前廣泛使用的通信模型,它採用事件作爲基本的通信機制,提供大規模系統所要求的鬆散耦合的交互模式:訂閱者(如客戶端)以事件訂閱的方式表達出它有興趣接收的一個事件或一類事件;發佈者(如服務器)可將訂閱者感興趣的事件隨時通知相關訂閱者。
Redis的pub/sub是一種消息通信模式,主要的目的是解除消息發佈者和消息訂閱者之間的耦合,Redis作爲一個pub/sub的server,在訂閱者和發佈者之間起到了消息路由的功能。
BloomFilter 布隆過濾器
RedisSearch
Redis-ML 機器學習模型服務器
Redis分佈式鎖
常用的分佈式鎖有三種解決方案:
- 基於數據庫實現
- 基於zookeeper的臨時序列化節點實現
- redis實現
先用 setnx 搶到鎖,搶到之後,使用 setnx 和 expire 合成一條命令對 redis 進行原子寫,保證寫數據和設置過期時間原子操作,避免請求中斷帶來的異常。命令如下:
# shell
# 從 Redis 2.6.12 版本開始, SET 命令的行爲可以通過一系列參數來修改
# EX second :設置鍵的過期時間爲 second 秒。 SET key value EX second 效果等同於 SETEX key second value 。
# PX millisecond :設置鍵的過期時間爲 millisecond 毫秒。 SET key value PX millisecond 效果等同於 PSETEX key millisecond value 。
# NX :只在鍵不存在時,纔對鍵進行設置操作。 SET key value NX 效果等同於 SETNX key value 。
# XX :只在鍵已經存在時,纔對鍵進行設置操作。
## 時間默認毫秒
$ set key value nx px 10000
# lua
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end
// java
public static boolean lock(String key,String lockValue,int expire){
if(null == key){
return false;
}
try {
Jedis jedis = getJedisPool().getResource();
String res = jedis.set(key,lockValue,"NX","EX",expire);
jedis.close();
return res!=null && res.equals("OK");
} catch (Exception e) {
return false;
}
}
Redis裏面有1億個key,其中有10w個key是以某個固定的已知的前綴開頭的,如何將它們全部找出來
- 使用
keys
指令獲取指定模式的 key 列表 - 若該 redis 正在提供線上業務,直接使用
keys
會導致線程阻塞,因爲 redis 是單線程的,此時線上服務會停頓,知道指定執行完畢。此時,可使用scan
指令,scan
可無責色的提取指定模式的 key 列表,但有一定的重複概率,在客戶端進行去重即可,但比直接使用keys
指令耗時長。
SCAN
是一個基於遊標的迭代器。這意味着命令每次被調用都需要使用上一次這個調用返回的遊標作爲該次調用的遊標參數,以此來延續之前的迭代過程。
當 SCAN
指令的遊標參數(即cursor)被設置爲 0 時, 服務器將開始一次新的迭代, 而當服務器向用戶返回值爲 0 的遊標時, 表示迭代已結束。
# KEYS pattern
redis> mset aa 1 bb 2 cc 3
redis> mset a1 1 b1 2 c1 3
redis> keys a*
1) "a1"
2) "aa"
redis> keys ?1
1) "a1"
2) "c1"
3) "b1"
# SCAN cursor [MATCH pattern] [COUNT count]
redis> scan 0 MATCH a* COUNT 100
Redis 如何實現異步隊列
- 使用 List 實現異步消息列表,使用
lpush/rpop
或rpush/lpop
生產/消費消息。當無消息時,適當sleep
線程後重試。 - 在不使用
sleep
時,使用blpop/brpop
阻塞讀。阻塞讀在隊列沒有數據的時候,會立即進入休眠狀態,一旦數據到來,則立即醒來,延遲幾乎爲零。但若長時間無新消息到來,該線程長時間阻塞就造成了空閒連接,此時服務器通常會主動斷開連接,因此編寫代碼時需要捕獲異常及重試。 - 生產一次消費多次:使用 pub/sub 主題訂閱者模式,可實現 1:N 的消息隊列。
- pub/sub 缺點:在消費者離線時,生產的消息將丟失,可考慮 RabbitMQ 等。
- 如何實現延時隊列:延時隊列是在客戶端處理請求時加鎖失敗時,將當前衝入的請求集中放到另一個隊列進行延後處理以避免衝突。延時隊列可使用
zset
實現,消息內容作爲key 調用zadd
來生產消息,消費者用zrangebyscore
指令獲取N秒之前的數據輪詢進行處理。多線程執行時可能發生一個線程搶到任務但無法獲取 redis 命令執行的情況,因此需要線程使用lua
腳本在服務端進行原子執行。
Redis有大量key在同一時間過期,如何處理
Redis 在同一時間有大量 key 過期,可能會出現短暫的卡頓。
這類事件以預防爲主,通常需要在設置時將 key 的過期時間加入隨機值,核心思想是使過期時間儘量分散。
Redis如何做持久化
AOF(AppendOnly File):命令 bgrewriteaof
,用於異步執行一個 AOF 文件重寫操作,進行增量持久化。舊 AOF 在 Bgrewriteaof 成功前不會被修改。從 2.4 開始,AOF 自行觸發,bgrewriteaof
僅用於手動觸發重寫操作。
RDB(Redis Bgsave):命令 bgsave
,用於後臺異步保存當前數據庫的數據到磁盤,進行全量鏡像持久化。
使用 RDB 耗時較長,實時性不足,可能會導致一定時間內的數據丟失,所以需要配合 AOF。Redis 重啓時,優先使用 AOF 回覆內存狀態,如果沒有 AOF,使用 RDB 恢復。 通常使用方式,AOF 保證數據不丟失,RDB 做冷備。
- 若 AOF 文件過大,導致恢復時間過長怎麼辦? Redis 會定期進行 AOF 文件重寫,重寫會創建一個基於當前 AOF 文件的體積優化版本。
- 混合持久化 混合持久化結合了 RDB 和 AOF 的有點,寫入時,先把當前的數據以 RDB 的形式寫入文件開頭,再將後續操作命令以 AOF 格式寫入文件,這樣既能保證 Redis 重啓速度,又能降低數據丟失風險。
- 宕機影響 丟失數據取決於 AOF 的 sync 屬性配置,若不要求性能,可對每條命令都執行一次 sync 磁盤同步。但通常使用定時 sync,如 1秒1次,則宕機最多丟失 1 秒數據。
Pipeline有什麼好處
Pipeline指的是管道技術,指的是客戶端允許將多個請求依次發給服務器,過程中不需要等待請求的回覆,減少請求執行過程中的往返時間,等全部請求執行完成後再讀取結果。
Pipeline 支持多命令,本身非原子性,配合 multi
、exec
實現事務控制。
Redis的同步機制
舊版實現
Redis 複製功能分爲同步(sync)和命令傳播(command propagate) 兩個過程。
- 同步:當客戶端向 slave 發送
slaveof
命令要求 slave 同步 master 時,slave 首先執行同步操作,同步 master 當前數據庫狀態。- slave 向 master 發送
sync
命令 - master 收到
sync
後,執行bgsave
生成 RDB 鏡像,同時使用一個緩衝區記錄新之星的所有操作 - master 將 RDB 發送給 slave,slave 通過該 RDB 文件恢復數據
- master 將緩衝區的命令發送給 slave,slave 進行狀態更新
- slave 向 master 發送
- 命令傳播:在同步操作執行完成後,主從達到狀態一致,但此時客戶端新命令到達 master 後,主從一直被打破,爲了保證主從一致,master 將把這些新執行的命令發送給 slave。
缺陷:Redis 主從複製時若發生斷線重複制現象,master 對重連後的複製命令依然執行全量 bgsave
,若兩次複製操作間隔時間很短,則相當於第二次 bgsave
僅比第一次多了有限少數量的新命令,這對服務器會造成一些性能損耗。因爲,sync
是一個非常消耗資源的操作,通常體現在生成 RDB 時的 CPU、內存、磁盤消耗,RDB 發送給 slave 時的網絡消耗,slave 恢復 RDB 時的性能消耗等。
因此,Redis 需要保證在有必要的時候執行 sync
。
新版實現(since 2.8)
爲解決舊版斷線重複制的低效問題,從 2.8 開始使用 psync
執行復制時的同步操作。
psync
有完整重同步(full resynchronuzation)和部分重同步(partial resynchronization)兩種模式。
- 完整重同步:用於初次複製
- 部分重同步:用於斷線後重複製
部分重同步的設計核心有三塊:
- master 的複製偏移量(replication offset)和 slave 的複製偏移量
- master 的複製積壓緩衝區(replication backlog)
- 服務器的運行 ID(run ID)
Redis集羣原理
Redis 有三種集羣模式:
- 主從模式
- Sentinel(哨兵) 模式
- Cluster(集羣) 模式
主從模式
主從模式特點:
- 主庫可讀寫,當讀寫操作導致數據變化時,變化會自動將數據同步給從庫
- 從庫一般只讀
- 一個 master 可有多個 slave,一個 slave 只有一個 master
- slave 宕機不影響其他機器,重啓該 slave 將觸發 master 數據同步
- master 宕機後,集羣不會重選 master,此時 slave 可繼續讀,但集羣不再提供寫,master 重啓後集羣恢復寫
安全設置(當 master 設置密碼後):
- 客戶端訪問 master 需要密碼
- 啓動 slave 需要密碼,可將密碼直接配置到配置文件中
- 客戶端訪問 slave 不需要密碼
缺點:
- master 節點在集羣中唯一
- master 宕機後,集羣無法繼續提供寫服務
Sentinel 模式
Sentinel 模式特點:
- sentinel 建立在主從模式上,如果只有一個 redis,sentinel 將失去意義
- 當 master 宕機,sentinel 會從 slave 中選擇一個新的 master
- 當舊 master 重新啓動後,它將作爲 slave 接收新 master 的同步數據
- sentinel 是一個進程,可能該進程會掛掉,所以 sentinel 會啓動多個而形成一個 sentinel 集羣
- 多 sentinel 配置時,sentinel 之間會自動監控
- 當主從模式需要密碼時,sentinel 也需要密碼
- 一個 sentinel 或 sentinel 集羣可以管理多個主從集羣,多個 sentinel 也可以監控同一個主從集羣
- sentinel 與 Redis 最好部署在不同機器上
Sentinel 模式集羣工作機制:
- 每個 sentinel 每秒向它所知的 master、slave 以及其他 sentinel 實例發送一個 PING
- 在一般情況下,每個 sentinel 每 10 秒向它所知的 master、slave 發送 INFO
- 當一個實例距離最後一次有效回覆 PING 的時間超過
down-after-milliseconds
配置時間,該實例被 sentinel 標記爲主觀下線 - 當一個 master 被標記爲主觀下線,則監控該 master 的 sentinel 每秒一次確認該 master 的確進入了主觀下線
- 當有足夠多的 sentinel 在指定時間內確認該 master 進入了主觀下線,則該 master 被 sentinel 標記爲客觀下線
- 當 master 被標記爲客觀下線,sentinel 向該 master 的所有 slave 發送 INFO 的頻率變爲每秒一次
- 若沒有足夠的 sentinel 同意該 master 已經下線,該 master 的客觀下線狀態解除
- 若該 master 重新向 sentinel 的 PING 返回有效回覆,則其主觀下線狀態解除
缺點: 當數據量大於一臺服務器承受能力時,主從模式或 sentinel 模式將不能滿足需求,此時需要對存儲的數據進行分片,將數據存儲到多個 Redis 實例中。
Cluster 模式
Cluster 模式解決了單機 Redis 容量有限的問題,將 Redis 的數據根據一定規則分配到多臺機器。
Cluster 模式集成了主從模式和 sentinel 模式的有點,通過 cluster 可實現主從麼 master 重選功能。每個集羣至少需要三個主數據庫才能正常運行。
Cluster 模式集羣特點:
- 多個 Redis 階段網絡互聯,數據共享
- 所有節點都是一主一從(也可一主多從),其中從不提供服務,僅作爲備用
- 不支持同時處理多個 key(如 mset/mget),因爲 redis 需要把 key 均勻分不到各個節點,併發量很高的情況下同時創建 k-v 會降低性能並導致不可預測的行爲
- 支持在線增減節點
- 客戶端可連接任何一個主節點進行讀寫
- 新增的節點都以 master 加入集羣