總結篇4:redis 核心數據存儲結構及核心業務模型實現應用場景
redis 和memcached 有什麼區別?爲什麼在高併發下,單線程的redis 比多線程的效率高?
- mc 可以緩存圖片和視頻,redis 支持除更多的數據結構。redis 典型的應用場景是用戶訂單列表,用戶消息,帖子評論等。
- redis 可以使用虛擬內存,redis 可持久化和aof 災難恢復,支持主從數據備份。如果redis 掛了,內存能夠快速恢復熱數據,不會將壓力瞬間壓在數據庫上,沒有cache 預熱的過程。對於只讀和數據一致性要求不高的場景可以採用持久化存儲。
- redis 可以做消息隊列。redis 支持集羣,可以實現主動複製,讀寫分離,mc 如果想實現高可用,需要進行二次開發。
- mc 存儲的vlaue 最大爲1M。
選擇mc 的場景:
- 1 純kv, 數據量非常大的業務。
原因是: - 1 mc 的內存分配採用的是預分配內存池的管理方式,能夠省去內存分配的時間。redis 是臨時申請空間,可以導致碎片化
- 2 虛擬內存使用,mc 將所有的數據存儲在物理內存裏,redis 有自己的vm 機制,理論上能夠存儲比物理內在更多的數據,當數據超量時,引發swap, 把冷數據刷新到磁盤上。從這點上看,數據量大時,mc 更快
- 3 網絡模型。mc 使用非阻塞的io 複用模型,redis 也是使用非阻塞的io 複用模型,但是redis 還提供了一些非kv 存儲之外的排序,聚合功能,複雜的cpu 計算,會阻塞整個io 調度,從這點上由於redis 提供的功能較多,mc 更快一些。
- 4 線程模型,mc 使用多線程,主線程監聽,worker 子線程接受請求,執行讀寫,這個過程可能存在鎖衝突。redis 使用單線程,雖然無鎖衝突,但是難以利用多核的特性提升吞吐量。
選擇redis 場景:
- 1 存儲方式上:mc 會把數據全部存儲在內存中,斷電後會掛掉,數據不能超過內存的大小。redis 有部分數據存在硬盤上,這樣能保證數據持久性。
- 2 數據支持類型上:redis 支持更豐富的數據類型
- 3 使用底層模型不同:底層實現方式以及客戶端之間通信的應用協議不同。redis 直接構建了vm 機制,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
- 4 value 大小。redis 可以達到1 G,而mc 只有1 M。
mc 多線程模型引入了緩存一致性和鎖,加鎖帶來了性能損耗。爲什麼 redis 單線程還如此快?因爲底層有高效數據存儲結構。整個redis 存儲結構是全局哈希表,哈希運算非常快,時間複雜度爲常量。
redis 常見性能問題和解決方案
- 1 master 最好不要做持久化工作,如RDB 內存快照和AOF 日誌文件
- 2 如果數據比較重要,某個slave 開啓AOF 備份,策略設置成每秒同步一次
- 3 爲了主從複製的速度和連接的穩定性,master 和slave 最好在一個局域網內
- 4 儘量避免在壓力大得主庫上增加從庫
- 主從複製不要採用網狀結構,儘量是線性結構。
redis set key value, 其中key 類型都是string 類型。五種基本類型都是對應的value。
String 應用場景
- 字符串常用操作
set key value // 存入字符串鍵值對
mset key value [key value] // 批量存儲字符串鍵值對
- 單值緩存
1 set key value
2 get key
- 對象緩存
1 set user:1 value(json 格式數據)
2 mset user:1 name zhuge user:1:balance 888
mget user:1:name user:1:balance
- 分佈式鎖
setnx product:100001 true // 返回1 代表獲取鎖成功
setnx product:100001 true // 返回0代表獲取鎖失敗
。。。執行業務操作
del product:100001 // 執行完業務釋放鎖
set product:100001 true ex 10 nx // 防止程序意外終止導致死鎖
- 計數器
incr article:readcount:{文章id} // 文章點贊數
get article:readcount:{文章id}
- 分佈式系統全局序列號
incrby orderid 100 、、 redis 批量生成序列號提升性能
一般我們設置數據庫自增主鍵,並設置相應索引。如果數據量很大,分庫分表時就實現不了。可以用緩存實現。爲提升性能,我們可以優化,做一個批量內存的自增,比如一次生成100個。
hash 常用操作
優點:
- 同類數據歸類整合儲存,方便數據管理
- 相比 string 操作消耗內在與cpu 更小
缺點:
- 過期功能不能使用在field 上,只能用在key 上
- redis 集羣架構下不適合大規模使用
hset key field value // 存儲一個哈希表key 的鍵值
hsetnx key field value // 存儲一個不存在的哈希表key 的鍵值
hmset key field value [field value ...] // 在一個哈希表key 中存儲多個鍵值對
hget key field // 獲取哈希表key 中多個field 鍵值
hmget key field [field...] // 批量獲取哈希表key 中多個field 鍵值
hdel key field [field...] // 刪除哈希表key 中的field 鍵值
hlen key // 返回哈希表key 中field 的數據
hgetall key // 返回哈希表key 中所有的鍵值
hincrby key field increment // 爲哈希表key 中field 鍵的值加上增量 increment
場景:購物車
以用戶id 爲key
以商品id 爲field
商品數據爲value
購物車操作
添加商品 hset cart:1001 10088 1
增加商品 hincrby cart:1001 10088 1
商品數量 hlen cart:1001
刪除商品 hdel cart:1001 10088
獲取購物車所有商品 hgetall cart:1001
list 結構
list 常用操作
lpush key value [value..] //插入到列表最左邊
rpush key value [value...] //插入到列表最右邊
lpop key // 移除列表的頭元素
rpop key // 移除列表的尾元素
lrange key start stop // 列表區間內元素
blpop key [key ...] timeout // 從列表表頭中彈出第一個元素。如果元素爲空,陰塞等待timeout 秒。如果timeout 爲0, 一直等待
brpop key [key ...] timeout // 從列表表尾彈出元素
常用數據結構
stack (棧) = lpush + lpop = filo
queue (隊列) = lpush + rpop
blocking MQ(陰塞隊列) = lpush + brpop
場景:
消息流頁面:訂閱號,微博等
(這塊功能如果用數據庫也可實現,但是一般性能比較慢。如果有order by 等排序功能,還容易導致索失效。)
1 發消息id爲18 lpush msh:{nameId} 18
2 發消息id爲20 lpush msh:{nameId} 20
3 查看消息流 lrange msg:{nameId} 0 9
每個消息流推送給每個粉絲,如果粉絲量大,lpush 命令需要優化。如pipeline,或優先推送在線用戶。
set 結構
set 常用操作
sadd key member [member ...] // 往集合key 中存入元素, 元素存在則忽略
srem key member [member ...] // 從集合key 中刪除元素
smembers key // 獲取集合key 中所有元素
scard key // 獲取集合key 的元素個數
sismember key member // 判斷member 元素是否存在集合key 中
srandmember key [count] // 從集合key k 中選出count 個元素,元素不從key 中刪除
spop key [count] // 從集合key 中選出count 個元素,元素從key 中刪除
應用場景:
抽獎
1 點擊抽獎加入集合 sadd key {userid}
2 查看所有抽獎用戶 smembers key
3 抽取count 名中獎者
srandmember key [count] / spop key [count]
點贊
sadd like:{消息id}{用戶id}
取消點贊
srem like:{消息id}{用戶id}
檢查用戶是否點過贊
sismember like:{消息id}{用戶id}
獲取點贊用戶列表
smembers like:{消息id}
獲取點贊用戶數
scard like:{消息id}
社交類產品的重要場景,叫社交關注模型
set 運算操作
sinter key [key ...] // 交集運算
sinterstore destination key [key ...] // 將交集結果存入新集合destination 中
sunion key [key ...] // 並集運算
sunionstore destination key [key ...] // 將並集結果存入新集合destination 中
sdiff key [key ...] // 差集運算
sdiffstore destination key [key ...] // 將差集結果存入新集合destination 中
社交關注模型
應用場景:
1 nameA關注的人 nameAset
2 nameB關注的人 nameBset
3 nameC關注的人 nameCset
4 我和nameA共同關注的人 sinter
5 我關注的人也關注了他 sismember
6 我可能認識的人 sdiff
商品篩選模型
zset 有序集合
常用操作
zadd key score member [[score member]...] // 往有序集合key 中加入帶分值元素
zrem key member [member ...] // 從有序集合中刪除元素
zcore key member // 返回有序集合key 中元素member 的分值
zincrby key increment member // 爲有序集合key 中元素member的分值加上increment
zcard key // 返回有序集合key 中元素個數
zrange key start stop [withscores] // 正序獲取有序集合key 從start 下標到stop 下標的元素
zrevrange key start stop [withscores] // 倒序
集合操作
zunionstore destkey numkeys key [key ...] 並集計算
zinterstore destkey numkeys key [key ...] 交集計算
應用場景
1 點擊新聞
zincrby hotnews:202008 1 name
2 單日排行榜
zrevrange hotnews:202208 0 9 withscores
3 七日搜索榜單計算
zunionstore hotnews:20220801-20220807 7
4 展示七日榜前十
zrevrange hotnews:20220801-20220807 0 9 withscores
redis 數據結構
數組:根據序號隨機查找很快,但是插入與刪除很慢,需要挪動很多元素。
鏈表:插入與刪除很快,只需要修改相鄰元素指針,但是查找很慢,需要從第一個元素逐個遍歷查找。
有序數組支持折半查找,鏈表不支持折半查找。
有序數組的折半查找操作速度很快,但是插入、刪除操作很慢。
跳錶(O(logN)):將有序列表改造爲支持“折半查找”算法,可以進行快速的插入、刪除、查找操作。
文:一隻阿木木