高性能內存kv數據庫Redis

引言

在當今數據驅動的時代,高效的數據存儲和檢索對于各類應用程序至關重要。Redis(Remote Dictionary Server)作為一款開源的內存鍵值數據庫,憑借其出色的性能、豐富的數據結構和靈活的特性,在眾多場景中得到了廣泛應用。本文將深入探討 Redis 的基本概念、核心特性、常用命令以及實際應用案例,幫助讀者全面了解和掌握 Redis

一.Redis相關命令詳解及其原理

1.redis是什么?

Redis 是 Remote Dictionary Service 的簡稱;也是遠程字典服務,通過tcp和redis建立連接交互,通過字典的方式索引存儲數據。
Redis 是內存數據庫,KV 數據庫,數據結構數據庫,數據都在內存中。

2.redis中存儲數據的數據結構都有哪些?

①string:是一個安全的二進制字符串。

②雙端隊列(鏈表)list :有序(插入有序)。
③散列表 hash :對順序不關注, field 是唯一的;
④無序集合 set :對順序不關注,里面的值都是唯一的;
⑤有序集合 zset :對順序是關注的,里面的值是唯一的;根據
member 來確定唯一;根據 score 來確定有序;

3.redis的存儲結構(KV)

4.reidis中value編碼

數據量 的時候,存儲效率高為主;
數據量 的時候,運行速度快;

5.string的基本原理和相關命令

5.1基本原理
字符數組,該字符串是動態字符串 raw ,字符串長度小于 1M
時,加倍擴容;超過 1M 每次只多擴 1M ;字符串最大長度為 512M
注意: redis 字符串是二進制安全字符串;可以存儲圖片,二進
制協議等二進制數據;
5.2基礎命令
# 設置 key 的 value 值
SET key val
# 獲取 key 的 value
GET key
# 執行原子加一的操作
INCR key
# 執行原子加一個整數的操作
INCRBY key increment
# 執行原子減一的操作
DECR key
# 執行原子減一個整數的操作
DECRBY key decrement
# 如果key不存在,這種情況下等同SET命令。當key存在時,什
么也不做
# set Not eXist ? ok 這個命令是否執行了 0,1 是不是操
作結果是不是成功
SETNX key value
# 刪除 key val 鍵值對
DEL key
# 設置或者清空key的value(字符串)在offset處的bit值。
setbit embstr raw int
# 動態字符串能夠節約內存
SETBIT key offset value
# 返回key對應的string在offset處的bit值
GETBIT key offset
# 統計字符串被設置為1的bit數.
BITCOUNT key

我們來舉一些簡單的例子

5.3string存儲結構
字符串長度小于等于 20 且能轉成整數,則使用 int 存儲
字符串長度小于等于 44 ,則使用 embstr 存儲
字符串長度大于 44 ,則使用 raw 存儲

5.4應用場景

對象存儲

SET role:10001 '{["name"]:"lion",["sex"]:"male",
["age"]:30}'
SET role:10002 '{["name"]:"xiaoyu",["sex"]:"female",
["age"]:30}'
我們應該如何設置Key 才能讓它更有意義呢?
單個功能的一個key:取有意義名字的key
相同功能的多個key:我們可以以:作為分割
執行上述命令
累加器
# 統計閱讀數 累計加1
incr reads
# 累計加100
incrby reads 100

分布式鎖

# 加鎖 ? 加鎖 和 解析 redis 實現是 非公平鎖 ? ? ectd
zk 用來實現公平鎖
# 阻塞等待 ? 阻塞連接的方式
# 介紹簡單的原理: 事務
setnx lock 1 ? # 不存在才能設置 定義加鎖行為 占用鎖 ?
setnx lock uuid ?# expire 30 過期
set lock uuid nx ex 30
# 釋放鎖
del lock
if (get(lock) == uuid)del(lock);

位運算

# 猜測一下 string 是用的 int 類型 還是 string 類型
# 月簽到功能 10001 用戶id 202106 2021年6月份的簽到 6月
份的第1天
setbit sign:10001:202106 1 1
# 計算 2021年6月份 的簽到情況
bitcount sign:10001:202106
# 獲取 2021年6月份 第二天的簽到情況 1 已簽到 0 沒有簽到
getbit sign:10001:202106 2

6.list的基本原理和相關命令

6.1基本原理
雙向鏈表實現,列表首尾操作(刪除和增加)時間復雜度
O(1) ;查找中間元素時間復雜度為 O(n)
列表中數據是否壓縮的依據:
1. 元素長度小于 48 ,不壓縮;
2. 元素壓縮前后長度差不超過 8 ,不壓縮;
6.2基礎命令
# 從隊列的左側入隊一個或多個元素
LPUSH key value [value ...]
# 從隊列的左側彈出一個元素
LPOP key
# 從隊列的右側入隊一個或多個元素
RPUSH key value [value ...]
# 從隊列的右側彈出一個元素
RPOP key 
# 返回從隊列的 start 和 end 之間的元素 0, 1 2 負索引
LRANGE key start end
# 從存于 key 的列表里移除前 count 次出現的值為 value 的
元素
# list 沒有去重功能 ? hash set zset
LREM key count value
# 它是 RPOP 的阻塞版本,因為這個命令會在給定list無法彈出
任何元素的時候阻塞連接
BRPOP key timeout ?# 超時時間 + 延時隊列

舉栗子

我們采用lpush從左邊插入五個人名

我們采用的是從左邊插入的方法 也就是所謂的頭插法? 簡易鏈表展示如下

lion

xiaoyu-->lion

xiaolan-->xiaoyu-->lion

xiaohuang-->xiaolan-->xiaoyu-->lion

xiaobai-->xiaohuang-->xiaolan-->xiaoyu-->lion

?我們分別從左邊和右邊彈出一個元素 結果如下

6.3存儲結構
/* Minimum ziplist size in bytes for attempting
compression. */
#define MIN_COMPRESS_BYTES 48
/* quicklistNode is a 32 byte struct describing a
ziplist for a quicklist.
* We use bit fields keep the quicklistNode at 32
bytes.
* count: 16 bits, max 65536 (max zl bytes is
65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is
temporary decompressed for usage.
* attempted_compress: 1 bit, boolean, used for
verifying during testing.
* extra: 10 bits, free for future use; pads out
the remainder of 32 bits */
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;
typedef struct quicklist {quicklistNode *head;quicklistNode *tail;unsigned long count; ? ? ? ?/* total count of
all entries in all ziplists */unsigned long len; ? ? ? ? ?/* number of
quicklistNodes */int fill : QL_FILL_BITS; ? ? ? ? ? ? ?/* fill
factor for individual nodes */unsigned int compress : QL_COMP_BITS; /*
depth of end nodes not to compress;0=off */unsigned int bookmark_count: QL_BM_BITS;quicklistBookmark bookmarks[];
} quicklist;
6.4應用場景
棧(先進后出 FILO)
LPUSH + LPOP
# 或者
RPUSH + RPOP
隊列(先進先出 FIFO)
LPUSH + RPOP
# 或者
RPUSH + LPOP
阻塞隊列(blocking queue)
LPUSH + BRPOP
# 或者
RPUSH + BLPOP
異步消息隊列
操作與隊列一樣,但是在不同系統間;生成者和消費者;
獲取固定窗口記錄(戰績)
lua腳本
實際項目中需要保證命令的原子性,所以一般用 lua 腳本 或者
使用 pipeline 命令;
-- redis lua腳本
local record = KEYS[1]
redis.call("LPUSH", "says", record)
redis.call("LTRIM", "says", 0, 4)

7.hash的基本原理和相關命令

7.1基本原理
散列表,在很多高級語言當中包含這種數據結構; c++
unordered_map 通過 key 快速索引 value
7.2基礎命令
# 獲取 key 對應 hash 中的 field 對應的值
HGET key field
# 設置 key 對應 hash 中的 field 對應的值
HSET key field value
# 設置多個hash鍵值對
HMSET key field1 value1 field2 value2 ... fieldn
valuen
# 獲取多個field的值
HMGET key field1 field2 ... fieldn
# 給 key 對應 hash 中的 field 對應的值加一個整數值
HINCRBY key field increment
# 獲取 key 對應的 hash 有多少個鍵值對
HLEN key
# 刪除 key 對應的 hash 的鍵值對,該鍵為field
HDEL key field

舉栗子

7.3存儲結構
節點數量大于 512 hash-max-ziplist-entries ) 或所有字符串長
度大于 64 hash-max-ziplist-value ),則使用 dict 實現;

節點數量小于等于 512 且有一個字符串長度小于 64 ,則使用
ziplist 實現;
?7.4應用場景

存儲對象

hmset hash:10001 name lion age 18 sex male
# 與 string 比較
set hash:10001 '{["name"]:"lion",["sex"]:"male",
["age"]:18}'
# 假設現在修改 lion的年齡為19歲
# hash:hset hash:10001 age 19
# string:get hash:10001# 將得到的字符串調用json解密,取出字段,修改 age 值# 再調用json加密set hash:10001 '{["name"]:"lion",
["sex"]:"male",["age"]:19}'
購物車
# 將用戶id作為 key
# 商品id作為 field
# 商品數量作為 value
# 注意:這些物品是按照我們添加順序來顯示的;# 添加商品:hmset MyCart:10001 40001 1 cost 5099 desc "戴爾
筆記本14-3400"lpush MyItem:10001 40001
# 增加數量:
hincrby MyCart:10001 40001 1hincrby MyCart:10001 40001 -1 // 減少數量1
# 顯示所有物品數量:hlen MyCart:10001
# 刪除商品:hdel MyCart:10001 40001lrem MyItem:10001 1 40001
# 獲取所有物品:lrange MyItem:10001# 40001 40002 40003hget MyCart:10001 40001hget MyCart:10001 40002hget MyCart:10001 40003

8.set的基本原理和相關命令

8.1基本原理
集合;用來存儲唯一性字段,不要求有序;
存儲不需要有序,操作(交并差集的時候排序)?
?8.2基礎命令
# 添加一個或多個指定的member元素到集合的 key中
SADD key member [member ...]
# 計算集合元素個數
SCARD key
# SMEMBERS key
SMEMBERS key
# 返回成員 member 是否是存儲的集合 key的成員
SISMEMBER key member
# 隨機返回key集合中的一個或者多個元素,不刪除這些元素
SRANDMEMBER key [count]
# 從存儲在key的集合中移除并返回一個或多個隨機元素
SPOP key [count]# 返回一個集合與給定集合的差集的元素
SDIFF key [key ...]
# 返回指定所有的集合的成員的交集
SINTER key [key ...]
# 返回給定的多個集合的并集中的所有成員
SUNION key [key ...]

舉栗子

8.3存儲結構
元素都為整數且節點數量小于等于 512 set-max-intset
entries ),則使用整數數組存儲;
元素當中有一個不是整數或者節點數量大于 512 ,則使用字典
存儲;
8.4應用場景?

抽獎

# 添加抽獎用戶sadd Award:1 10001 10002 10003 10004 10005
10006sadd Award:1 10009
# 查看所有抽獎用戶smembers Award:1
# 抽取多名幸運用戶srandmember Award:1 10
# 如果抽取一等獎1名,二等獎2名,三等獎3名,該如何操作?

共同關注

sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
sinter follow:A follow:C
推薦好友
sadd follow:A mark king darren mole vico
sadd follow:C mark king darren
# C可能認識的人:sdiff follow:A follow:C

9.zset的基本原理和相關命令

9.1基本原理
有序集合;用來實現排行榜;它是一個有序唯一;
?9.2基礎命令
# 添加到鍵為key有序集合(sorted set)里面
ZADD key [NX|XX] [CH] [INCR] score member [score
member ...]
# 從鍵為key有序集合中刪除 member 的鍵值對
ZREM key member [member ...]
# 返回有序集key中,成員member的score值
ZSCORE key member
# 為有序集key的成員member的score值加上增量increment
ZINCRBY key increment member
# 返回key的有序集元素個數
ZCARD key
# 返回有序集key中成員member的排名
ZRANK key member
# 返回存儲在有序集合key中的指定范圍的元素 ? order by id
limit 1,100
ZRANGE key start stop [WITHSCORES]
# 返回有序集key中,指定區間內的成員(逆序)
ZREVRANGE key start stop [WITHSCORES]

舉栗子

9.3存儲結構
節點數量大于 128 或者有一個字符串長度大于 64 ,則使用跳表
skiplist );
節點數量小于等于 128 zset-max-ziplist-entries )且所有字符
串長度小于等于 64 zset-max-ziplist-value ),則使用
ziplist 存儲;
數據少的時候,節省空間O(n)
數據多的時候,訪問性能O(1) or O(logn)
?9.4應用場景

百度熱搜

# 點擊新聞:zincrby hot:20230612 1 10001zincrby hot:20230612 1 10002zincrby hot:20230612 1 10003zincrby hot:20230612 1 10004zincrby hot:20230612 1 10005zincrby hot:20230612 1 10006zincrby hot:20230612 1 10007zincrby hot:20230612 1 10008zincrby hot:20230612 1 10009zincrby hot:20230612 1 10010# 獲取排行榜:zrevrange hot:20230612 0 9 withscores
延時隊列
將消息序列化成一個字符串作為 zset member ;這個消息
的到期處理時間作為 score ,然后用多個線程輪詢 zset 獲取到
期的任務進行處理。
def delay(msg):msg.id = str(uuid.uuid4()) #保證 member 唯一value = json.dumps(msg)retry_ts = time.time() + 5 # 5s后重試redis.zadd("delay-queue", retry_ts, value)
# 使用連接池
def loop():while True:values = redis.zrangebyscore("delayqueue", 0, time.time(), start=0, num=1)if not values:time.sleep(1)continuevalue = values[0]success = redis.zrem("delay-queue",
value)if success:msg = json.loads(value)handle_msg(msg)
# 缺點:loop 是多線程競爭,兩個線程都從zrangebyscore獲
取到數據,但是zrem一個成功一個失敗,
# 優化:為了避免多余的操作,可以使用lua腳本原子執行這兩個
命令
# 解決:漏斗限流

?分布式定時器

生產者將定時任務 hash 到不同的 redis 實體中,為每一個
redis 實體分配一個 dispatcher 進程,用來定時獲取 redis 中超
時事件并發布到不同的消費者中;

時間窗口限流?

系統限定用戶的某個行為在指定的時間范圍內(動態)只能發
N 次;
# 指定用戶 user_id 的某個行為 action 在特定時間內
period 只允許發生該行為做大次數 max_count
local function is_action_allowed(red, userid,
action, period, max_count)local key = tab_concat({"hist", userid,
action}, ":")local now = zv.time()red:init_pipeline()-- 記錄行為red:zadd(key, now, now)-- 移除時間窗口之前的行為記錄,剩下的都是時間窗口內的
記錄red:zremrangebyscore(key, 0, now - period
*100)-- 獲取時間窗口內的行為數量red:zcard(key)-- 設置過期時間,避免冷用戶持續占用內存 時間窗口的長
度+1秒red:expire(key, period + 1)local res = red:commit_pipeline()return res[3] <= max_count
end# 維護一次時間窗口,將窗口外的記錄全部清理掉,只保留窗口內
的記錄;# 缺點:記錄了所有時間窗口內的數據,如果這個量很大,不適合
做這樣的限流;漏斗限流
# 注意:如果用 key + expire 操作也能實現,但是實現的是熔
斷限流,這里是時間窗口限流的功能;score

?10.redis的抽象層次

補充:redis設置過期時間

①expire

②setex

③ex

二.協議與異步方式

1.redis pipeline

redis pipeline 是一個客戶端提供的機制,而不是服務端提供
的;
pipeline 不具備事務性;
目的:節約網絡傳輸時間;
通過一次發送多次請求命令,從而減少網絡傳輸的時間
一次性發送多個請求包,redis按序響應
與http1.1解決的問題類似

2.發布訂閱模式

2.1基本原理
為了支持消息的多播機制, redis 引入了發布訂閱模塊;
消息不一定可達;分布式消息隊列;
stream 的方式確保一定可 達;
2.2基礎命令?
# 訂閱頻道
subscribe 頻道
# 訂閱模式頻道
psubscribe 頻道
# 取消訂閱頻道
unsubscribe 頻道
# 取消訂閱模式頻道
punsubscribe 頻道
# 發布具體頻道或模式頻道的內容
publish 頻道 內容
# 客戶端收到具體頻道內容
message 具體頻道 內容
# 客戶端收到模式頻道內容
pmessage 模式頻道 具體頻道 內容

舉栗子?

2.3應用場景
發布訂閱功能一般要區別命令連接重新開啟一個連接;因為命
令連接嚴格遵循請求回應模式;而 pubsub 能收到 redis 主動推
送的內容;所以實際項目中如果支持 pubsub 的話,需要 另開一
條連接 用于處理發布訂閱;
2.4缺點
發布訂閱的生產者傳遞過來一個消息, redis 會直接找到相應的
消費者并傳遞過去;假如沒有消費者,消息直接丟棄;假如開
始有 2 個消費者,一個消費者突然掛掉了,另外一個消費者依然
能收到消息,但是如果剛掛掉的消費者重新連上后,在斷開連
接期間的消息對于該消費者來說徹底丟失了;
另外, redis 停機重啟, pubsub 的消息是不會持久化的,所有
的消息被直接丟棄;

3.redis事務

3.1什么是事務,事務具有什么特性
事務:用戶定義一系列數據庫操作,這些操作視為一個完整的
邏輯處理工作單元 ,要么全部執行,要么全部不執行,是不可
分割的工作單元。
MULTI 開啟事務,事務執行過程中,單個命令是入隊列操作,
直到調用 EXEC 才會一起執行;
樂觀鎖實現,所以失敗需要重試,增加業務邏輯的復雜度;
acid
①原子性
②一致性
③隔離性
④持久性
3.2實現事務的基礎命令(不常用)
?
MULTI
開啟事務
begin / start transactionEXEC
提交事務
commitDISCARD
取消事務
rollbackWATCH
檢測 key 的變動,若在事務執行中,key 變動則取消事務;在事
務開啟前調用,樂觀鎖實現(cas);
若被取消則事務返回 nil ;
3.3應用場景

事務實現 zpop?

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC
事務實現 加倍操作
WATCH score:10001
val = GET score:10001
MULTI
SET score:10001 val*2
EXEC

4.lua腳本(常用)

4.1lua腳本怎么實現
lua 腳本實現原子性;

redis 中加載了一個 lua 虛擬機;用來執行 redis lua 腳本; redis
lua 腳本的執行是原子性的;當某個腳本正在執行的時候,不會
有其他命令或者腳本被執行;
lua 腳本當中的命令會直接修改數據狀態;
lua 腳本 mysql 存儲區別: MySQL 存儲過程不具備事務性,所以
也不具備原子性;
注意 :如果項目中使用了 lua 腳本,不需要使用上面的事務命
令;
4.2.lua腳本一些基礎命令?
# 從文件中讀取 lua腳本內容
cat test1.lua | redis-cli script load --pipe
# 加載 lua腳本字符串 生成 sha1
> script load 'local val = KEYS[1]; return val'
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
# 檢查腳本緩存中,是否有該 sha1 散列值的lua腳本
> script exists
"b8059ba43af6ffe8bed3db65bac35d452f8115d8"
1) (integer) 1
# 清除所有腳本緩存
> script flush
OK
# 如果當前腳本運行時間過長(死循環),可以通過 script kill
殺死當前運行的腳本
> script kill
(error) NOTBUSY No scripts in execution right
now.

?EVAL

# 測試使用
EVAL script numkeys key [key ...] arg [arg ...]
EVALSHA
# 線上使用
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
舉栗子
4.3應用場景
# 1: 項目啟動時,建立redis連接并驗證后,先加載所有項目中使
用的lua腳本(script load);
# 2: 項目中若需要熱更新,通過redis-cli script flush;然
后可以通過訂閱發布功能通知所有服務器重新加載lua腳本;
# 3:若項目中lua腳本發生阻塞,可通過script kill暫停當前阻
塞腳本的執行;

?5.ACID特性分析

A 原子性;事務是一個不可分割的工作單位,事務中的操作要
么全部成功,要么全部失敗; redis 不支持回滾;即使事務隊列
中的某個命令在執行期間出現了錯誤,整個事務也會繼續執行
下去,直到將事務隊列中的所有命令都執行完畢為止。
C 一致性;事務的前后,所有的數據都保持一個一致的狀態,
不能違反數據的一致性檢測;這里的一致性是指預期的一致性
而不是異常后的一致性;所以 redis 也不滿足;這個爭議很大:
redis 能確保事務執行前后的數據的完整約束;但是并不滿足業
務功能上的一致性;比如轉賬功能,一個扣錢一個加錢;可能
出現扣錢執行錯誤,加錢執行正確,那么最終還是會加錢成
功;系統憑空多了錢;
I 隔離性;各個事務之間互相影響的程度; redis 是單線程執
行,天然具備隔離性;

D 持久性; redis 只有在 aof 持久化策略的時候,并且需要在
redis.conf appendfsync=always 才具備持久性;實際項
目中幾乎不會使用 aof 持久化策略;
面試時候回答: lua 腳本滿足原子性和隔離性;一致性和持久性
不滿足;

6.redis異步連接?

6.1redis協議圖
協議實現的第一步需要知道如何界定數據包:
1. 長度 + 二進制流
2. 二進制流 + 特殊分隔符
6.2異步連接?
同步連接方案采用阻塞 io 來實現;優點是代碼書寫是同步的,
業務邏輯沒有割裂;缺點是阻塞當前線程,直至 redis 返回結
果;通常用多個線程來實現線程池來解決效率問題;
異步連接方案采用非阻塞 io 來實現;優點是沒有阻塞當前線
程, redis 沒有返回,依然可以往 redis 發送命令;缺點是代碼
書寫是異步的(回調函數),業務邏輯割裂,可以通過協程解
決( openresty skynet );配合 redis6.0 以后的 io 多線程(前
提是有大量并發請求),異步連接池,能更好解決應用層的數
據訪問性能;
6.3實現方案?
hiredis + libevent
舉栗子舉栗子
同步sync
同步執行從0加到1000所花費時間100ms
異步async
異步執行從1000加到2000所花費時間為5ms
顯然異步IO比同步IO的效率高很多
reactor補充
6.4原理是什么
hiredis 提供異步連接方式,提供 可以替換 IO 檢測 的接口;
關鍵替換 addRead delRead addWrite delWrite
cleanup scheduleTimer ,這幾個檢測接口;其他 io 操作,
比如 connect read write close 等都交由 hiredis 來處
理;
同時需要提供連接建立成功以及斷開連接的回調;
用戶可以使用當前項目的網絡框架來替換相應的操作;從而實
現跟項目網絡層兼容的異步連接方案;

三.存儲原理與數據模型

1.redis是單線程還是多線程

? ? ??

我們通過gdb調試redis 可以查看 起線程明顯有多個

這些線程的作用分別如下圖所示

?2.redis中說的單線程究竟是什么?

命令處理是在一個單線程中

處理客戶端請求的核心流程是單線程的 。

即從接收客戶端請求(網絡讀取 )、解析命令、執行命令到返回結果(網絡寫入 ),都由一個主線程順序串行處理 。

所有命令在這個過程中逐個執行,不會有兩條命令被同時執行,確保了操作的原子性,也避免了線程安全問題和不必要的上下文切換。

比如執行SET key value?、GET key?等各種數據操作命令時,都是在這個單線程流程中完成 。

?3.命令處理為什么是單線程

3.1單線程的局限性(redis要避免)

不能有耗時的操作:cpu運算和阻塞的io

對于redis而言會影響性能

3.2redis有沒有io密集型和cpu密集型(有的)

IO密集型分為磁盤IO和網絡IO

redis在磁盤IO中可以fork子進程,在子進程中做持久化,還可以異步刷盤

redis在網絡IO中服務多個客戶,造成IO密集,數據請求和返回量比較大,redis會開啟IO多線程處理這個問題

4.命令處理為什么不采用多線程

加鎖復雜,鎖的粒度不好控制

頻繁的cpu上下文切換,抵消多線程的優勢

5.單線程是怎么做到這么快的呢?

5.1采用了哪些機制

內存型數據庫

數據的組織方式O(1):hashtable 可以擴容,縮容,還能漸進式rehash在cpu運算中,每步操作rehash,定時rehash 1ms 之類的.

數據高效:在執行效率與空間占用保持平衡,數據結構切換

高效的reactor網絡模型

5.2做了哪些優化?

分治思想

把rehash分攤到每一個操作步驟當中

在定時器當中,以100為步長最大rehash 1ms

耗時阻塞的操作,在其他線程處理

對象類型采用不同的數據結構實現

?6.負載因子,擴縮容機制,漸進式rehash

負載因子
負載因子 = used / size used 是數組存儲元素的個數,
size 是數組的長度;
負載因子越小,沖突越小;負載因子越大,沖突越大;
redis 的負載因子是 1

擴容?

如果負載因子 > 1 ,則會發生擴容;擴容的規則是翻倍;
如果正在 fork (在 rdb aof 復寫以及 rdb-aof 混用情況下)
時,會阻止擴容;但是此時若負載因子 > 5 ,索引效率大大降
低, 則馬上擴容;這里涉及到寫時復制原理;

縮容

如果負載因子 < 0.1 ,則會發生縮容;縮容的規則是 恰好 包含
used 的 ;
恰好 的理解:假如此時數組存儲元素個數為 9 ,恰好包含該元素
的就是 ,也就是 16

?

?漸進式rehash

hashtable 中的元素過多的時候,不能一次性 rehash
ht[1] ;這樣會長期占用 redis ,其他命令得不到響應;所以需
要使用漸進式 rehash
rehash 步驟
ht[0] 中的元素重新經過 hash 函數生成 64 位整數,再對
ht[1] 長度進行取余,從而映射到 ht[1]
漸進式規則
1. 分治的思想,將 rehash 分到之后的每步增刪改查的操作當
中;
2. 在定時器中,最大執行一毫秒 rehash ;每次步長 100 個數
組槽位;
面試
處于漸進式 rehash 階段時,是否會發生擴容縮容?不會!

7.scan

scan cursor [MATCH pattern] [COUNT count] [TYPE
type]

采用高位進位加法的遍歷順序, rehash 后的槽位在遍歷順序上
是相鄰的;
遍歷目標是:不重復,不遺漏 ;
會出現一種重復的情況:在 scan 過程當中,發生兩次縮容的時
候,會發生數據重復;
注意 :上課時有一個問題表述錯誤;關于 scan scan 要達到的
目的是從 scan 開始那刻起 redis 已經存在的數據進行遍歷,不
會重復和遺漏(例外是 scan 過程中兩次縮容可能造成數據重
復), 因為比如我 scan 已經快結束了,現在插入大量數據,這
些數據肯定遍歷不到;
擴容和縮容造成映射算法發生改變,但是使用高位進位累加的
算法,可以對 scan 那刻起已經存在數據的遍歷不會出錯;

舉栗子?

8.expire

# 只支持對最外層key過期;
expire key seconds
pexpire key milliseconds
ttl key
pttl key
惰性刪除
分布在每一個命令操作時檢查 key 是否過期;若過期刪除 key
再進行命令操作;

定時刪除

在定時器中檢查庫中指定個數(25)個 key;?

#define ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP 20 /*
Keys for each DB loop. */
/*The default effort is 1, and the maximum
configurable effort* is 10. */
config_keys_per_loop =
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP +ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP/4*effort,
int activeExpireCycleTryExpire(redisDb *db,
dictEntry *de, long long now);

?舉栗子

9.大Key

redis 實例中形成了很大的對象,比如一個很大的 hash 或很
大的 zset ,這樣的對象在擴容的時候,會一次性申請更大的一塊
內存,這會導致卡頓;如果這個大 key 被刪除,內存會一次性
回收,卡頓現象會再次產生;
如果觀察到 redis 的內存大起大落,極有可能因為大 key 導致
的;
# 每隔0.1秒 執行100條scan命令
redis-cli -h 127.0.0.1 --bigkeys -i 0.1

10.對象編碼(補充)

?11.skiplist(用在zset中)

redis 跳表
從節約內存出發, redis 考慮犧牲一點時間復雜度讓跳表結構更
加變扁平,就像二叉堆改成四叉堆結構;并且 redis 還限制了跳
表的最高層級為 32
節點數量大于 128 或者有一個字符串長度大于 64 ,則使用跳表
skiplist );

?數據結構

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough
for 2^64 elements */
#define ZSKIPLIST_P 0.25 ? ? ?/* Skiplist P = 1/4
*/
/* ZSETs use a specialized version of Skiplists
*/
typedef struct zskiplistNode {sds ele;double score; // WRN: score 只能是浮點數struct zskiplistNode *backward;struct zskiplistLevel {struct zskiplistNode *forward;unsigned long span; // 用于 zrank} level[];
} zskiplistNode;
typedef struct zskiplist {struct zskiplistNode *header, *tail;unsigned long length; // zcardint level; // 最高層
} zskiplist;
typedef struct zset {dict *dict; // 幫助快速索引到節點zskiplist *zsl;
} zset;

結構圖
補充

12.建議閱讀redis中的源碼加強理解

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/75753.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75753.shtml
英文地址,請注明出處:http://en.pswp.cn/web/75753.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

自動化測試概念篇

文章目錄 目錄1. 自動化1.1 自動化概念1.1.1 回歸測試 1.2 自動化分類1.3 自動化測試金字塔 2. web自動化測試2.1 驅動2.1.1 安裝驅動管理2.1.2 selenium庫 3. Selenium3.1 一個簡單的web自動化示例3.2 selenium驅動瀏覽器的工作原理 目錄 自動化web自動化測試Selenium 1. 自…

《AI大模型應知應會100篇》第17篇:大模型的偏見與公平性問題

第17篇&#xff1a;大模型的偏見與公平性問題 摘要 在人工智能迅速發展的今天&#xff0c;大型語言模型&#xff08;LLM&#xff09;已經深入到我們的日常生活和工作中。然而&#xff0c;這些模型并非完美無缺&#xff0c;它們可能攜帶并放大數據中的偏見&#xff0c;導致不公…

【踩坑】GitHub Actions 運行的 Linux 環境中,文件名是大小寫敏感的

在使用 VuePress 搭建個人博客并部署到 GitHub Pages 的過程中&#xff0c;我遇到了一個頗為棘手的問題&#xff1a;本地打包一切正常&#xff0c;但在 GitHub Actions 自動執行打包流程時&#xff0c;卻提示找不到 README.md 文件&#xff0c;導致整個流程失敗。經過一番深入排…

C# 13新特性 - .NET 9

轉載&#xff1a; C# 13 中的新增功能 | Microsoft Learn C# 13 包括以下新增功能。 可以使用最新的 Visual Studio 2022 版本或 .NET 9 SDK 嘗試這些功能&#xff1a;Introduced in Visual Studio 2022 Version 17.12 and newer when using C# 13 C# 13 中的新增功能 | Micr…

numpy.ma.masked_where:屏蔽滿足條件的數組

1.函數功能 屏蔽滿足條件的數組內容&#xff0c;返回值為掩碼數組 2.語法結構 np.ma.masked_where(condition, a, copyTrue)3. 參數 參數含義condition屏蔽條件a要操作的數組copy布爾值&#xff0c;取值為True時&#xff0c;結果復制數組(原始數據不變)&#xff0c;否則返回…

【Redis】數據結構和內部編碼

先來復習一下之前學過的幾個基本的全局命令&#xff1a; keys&#xff1a;用來查看匹配規則的keyexists&#xff1a;用來判定執行key是否存在del&#xff1a;刪除指定的keyexpire&#xff1a;給key設置過期時間ttl&#xff1a;查詢key的過期時間type&#xff1a;查詢key對應的…

OBOO鷗柏如何以智能教育室內外觸摸屏一體機AI變革硬件

在AI技術蓬勃發展的當下&#xff0c;OBOO鷗柏室外觸摸屏一體機通過融入AI科技&#xff0c;為教育領域帶來了翻天覆地的變化。這款一體機不僅為高校和大學校園提供了革命性的數字化教學解決方案&#xff0c;更引領了引體向上成績提升一體機帶訓室外終端屏幕設備的新潮流。其創新…

從零搭建高并發體育直播網站:架構設計、核心技術與性能優化實戰

本文從技術視角拆解體育直播網站開發全流程&#xff0c;涵蓋高并發架構設計、低延遲視頻流傳輸、實時彈幕系統實現等核心模塊&#xff0c;并附可復用的代碼片段與優化方案。適合中高級開發者進階實戰參考。 一、需求分析與技術選型 1. 典型業務場景 核心需求&#xff1a;支持1…

【Python內置函數的深度解析與應用】id

目錄 前言&#xff1a;技術背景與價值當前技術痛點解決方案概述目標讀者說明 一、技術原理剖析核心概念圖解關鍵技術模塊技術選型對比 二、實戰演示環境配置要求核心代碼實現1. 基礎身份驗證2. 不可變對象優化3. 對象生命周期追蹤 運行結果驗證 三、性能對比測試方法論量化數據…

3.vtkProp 和vtkProp3D

文章目錄 vtkProp 和vtkProp3D使用vtkProp3D使用vtkPro vtkProp 和vtkProp3D vtkProp 和 vtkProp3D 都是VTK&#xff08;Visualization Toolkit&#xff09;庫中的類&#xff0c;它們用于在渲染場景中表示可視化元素。理解這兩個類的區別和用途對于有效地使用VTK進行三維數據可…

【ZYNQ Linux移植】2-獲取設備樹

0 寫在前面 這是一個系列博客&#xff0c;詳細介紹如何在 ZYNQ 與 ZYNQ MP 平臺上如何移植 Linux 系統。目前網絡上的大部分教程都是全程基于 Petalinux 的開發&#xff0c;雖然這樣簡化了開發流程&#xff0c;但對于初學者深入理解掌握 Linux 是不利的&#xff0c;所以&#x…

基礎算法篇(5)(藍橋杯常考點)—動態規劃(C/C++)

文章目錄 動態規劃前言線性dp路徑類dp經典線性dp背包問題分類01背包問題完全背包問題多重背包分組背包問題混合背包問題多維費用的背包問題區間dp 動態規劃 前言 在競賽中&#xff0c;如果遇到動態規劃的題目&#xff0c;只要不是經典題型&#xff0c;那么大概率就是以壓軸題的…

obsidian寫文章的圖床設置方法

目標 要達成的需求&#xff1a; 復制到obsidian的圖片&#xff0c;自動上傳到Picgo配置的圖床。可以自定義大小。可以一鍵下載當前文章的圖片到本地。 obsidian配置圖床 安裝并配置插件 image auto upload plugin&#xff0c;配置信息如下圖。 滾輪alt自定義大小 安裝并…

QPaintDevice繪圖設備

1.QPixmap 對不同平臺做了顯示的優化&#xff0c;可以將畫的圖保存到磁盤上 頭文件&#xff1a; #include"QPixmap" #include"QPainter" 1.1QPixmap畫圖 代碼&#xff1a; //Pixmap繪圖設備QPixmap pix(300,300);//聲明畫家QPainter painter(&pix…

數據結構有哪些類型(對于數據結構的簡述)

在學習計算機時&#xff0c;數據結構是不可忽視的一點&#xff0c;從考研時的408課程&#xff0c;再到工作中編寫軟件&#xff0c;網站&#xff0c;要想在計算機領域站住腳跟&#xff0c;數據結構是必備的 在這里&#xff0c;我對于數據結構進行了匯總&#xff0c;并簡要描述&…

L2TP實驗(無圖后補)

拓撲圖 一、搭建拓撲并配置基礎 IP 地址 設備選型與拓撲搭建&#xff1a;在 eNSP 中&#xff0c;拖入所需設備&#xff0c;包括 LAC&#xff08;L2TP Access Concentrator&#xff0c;L2TP 接入集中器 &#xff09;、LNS&#xff08;L2TP Network Server&#xff0c;L2TP 網絡服…

【C#】CAN通信的使用

在C#中實現CAN通信通常需要借助第三方庫或硬件設備的驅動程序&#xff0c;因為C#本身并沒有直接內置支持CAN通信的功能。以下是一個關于如何使用C#實現CAN通信的基本指南&#xff0c;包括所需的步驟和常用工具。 1. 硬件準備 要進行CAN通信&#xff0c;首先需要一個支持CAN協…

02_C++入門案例習題while循環練習案例:猜數字

案例描述&#xff1a;系統隨機生成一個1到100之間的數字&#xff0c;玩家進行猜測&#xff0c;如果猜錯&#xff0c;提示玩家數字過大或過小&#xff0c;如果猜對恭喜玩家勝利&#xff0c;并且退出游戲。 需要引入隨機數種子 #include <cstdlib> #include <ctime>…

深入理解哈希沖突:原理、解決方案及 Java 實踐

概述&#xff1a;在計算機科學領域&#xff0c;哈希表是一種非常重要的數據結構&#xff0c;它通過哈希函數將鍵映射到存儲桶中&#xff0c;從而實現快速的數據查找、插入和刪除操作。然而&#xff0c;哈希表在實際應用中會面臨 哈希沖突的問題。本文將深入探討哈希沖突的原理、…

opencv(C++)處理圖像顏色

文章目錄 介紹使用策略設計模式比較顏色實現方案計算兩個顏色向量之間的距離1. 簡單方法&#xff1a;曼哈頓距離計算&#xff08;Manhattan Distance&#xff09;2.使用 OpenCV 的 cv::norm 函數3.使用 OpenCV 的 cv::absdiff 函數錯誤示例 使用 OpenCV 函數實現顏色檢測實現方…