Redis基礎整理(一)

概述

  • Redis(Remote Dictionary Server, 遠程字典服務)是用 ANSI C編寫的鍵值對(key-value pair)型內存數據庫. 支持的數據類型有 strings(字符串), hashes(哈希), lists(列表), sets(集合), sorted sets(帶範圍查詢的排序集合), bitmaps(位圖), hyperloglogs算法, geospatial indexes with radius queries(地理空間索引) 和 streams, 這些數據類型的操作都是原子性的. 還有就是支持事務, Lua腳本, 數據持久化和讀寫分離(master-slave, 主從複製), 且搭建大型緩存集羣時, 可通過內置哨兵機制給多分區提供高可用性

5種常用數據類型

  1. strings(字符串)
  • 當設置值時, 如果是已有的鍵將會覆蓋任何與之前相關的參數於值
  • 設置值 SET key value, 獲取值 GET key
  1. hashes(哈希)
  • 當設置值時, 如果是已有的鍵將會覆蓋任何與之前相關的參數於值.
  • 設置值 HMSET myhash field1 “Hello” field2 “World”, 獲取值 HGET myhash. 從 Redis 4.0.0, HMSET被視爲不推薦使用, 推薦使用 HSET
  • 設置值 HSET myhash field1 “Hello”, 獲取值 HGET myhash. 從 Redis 4.0.0, HSET是可變的, 允許多個字段/值對
  • 值的內部實現是 HashMap, 不過按成員數有不同實現, 爲了節省內存空間成員少時使用一維數組的方式存儲, 當成員數增大時將會自動轉成 HashMap
  • 常用於存儲結構化的對象
  1. lists(列表)
  • 當鍵保存的值不是列表時, 將返回錯誤
  • list的頭部添加字符串元素 LPUSH mylist “hello”, 尾部添加字符串元素 RPUSH mylist “world”, 添加元素後返回列表長度 https://redis.io/commands/lpush
  • 刪除並返回列表的第一個元素 LPOP mylist, 返回[“hello”]. 刪除並返回列表的最後一個元素 RPOP mylist, 返回[“world”], 當鍵不存在時爲nil
  • 通過索引指定範圍列表 LRANGE mylist 0 0, 返回[“hello”]. LRANGE mylist -2 1, 返回[“hello”, “world”], 0是列表的第一個元素(頭), 1是下一個, 負數索引爲尾部開始鎖定, -1是列表的最後一個元素(尾), 此功能適合做分頁功能
  • 返回列表的長度 LLEN mylist, 返回2, 當鍵不存在, 返回0
  • 可以實現 Stack棧(FILO 先進後出), Queue隊列(FIFO 先進先出), 以及還支持消息隊列
  1. sets(集合)
  • 當存儲在鍵上的值不是集合時返回錯誤
  • set是值不重複的無序集合, 添加元素值不重複返回1否則0 SADD myset “Hello”, 返回1. 再次 SADD myset “Hello”, 返回0 https://redis.io/commands/sadd
  • 通過 SINTER(交集), SDIFF(差集), SUNION(並集)等功能, 以及去重特性, 可以實現全部喜好, 共同喜好, 自己獨有的喜好, 點贊等業務功能
  1. sorted sets(帶範圍查詢的排序集合)
  • 基本與 set集合相同, 只不過這是有序集合, 參數中多了 double類型的分數, 排序是通過這個分數來做的, 分數是可以重複的
  • 通過排序特性, 可以做排行榜等業務功能

數據持久化(RDB& AOF)

  1. RDB(Redis DataBase) Snapshot(快照)存儲 - 默認
  • 指定的週期內將數據以快照的形式保存到磁盤上的擴展名爲 .rdb的二進制文件
  • 相關基本參數 redis.conf配置
參數 默認值 說明
dir ./ 快照文件存儲目錄
dbfilename dump.rdb 快照文件名稱
save 900 1 300 10 60 10000 共三行分別是: 900秒(15分鐘)後, 至少更換了一個鑰匙, 300秒(5分鐘)後, 至少10個鍵發生變化, 60秒後, 至少10000個密鑰更改. 如果要禁用: save “”
stop-writes-on-bgsave-error yes 當 RDB持久化時, 如果磁盤滿了或壞了, Redis會禁止新的寫操作命令執行, 如 no放開此限制
rdb-save-incremental-fsync yes 當保存 RDB文件時, 如果啓用了以下選項, 則每生成32MB數據, 將對文件進行fsync. 這對於以遞增方式將文件提交到磁盤時, 避免大延遲非常有用
  1. AOF(Append-only file)
  • AOF方式是將每次對服務器的操作命令記錄下來, 按 Redis協議格式追加到持久化文件的末尾保存
參數 默認值 說明
appendonly no no關閉/ yes開啓
appendfilename “appendonly.aof” 指定文件名
appendfsync everysec always總是寫入(最安全)/ everysec每一秒寫入/ no寫入, 但不等待磁盤同步
no-appendfsync-on-rewrite no 此參數是 Redis做基本的 AOF持久化的同時做 bgrewriteaof AOF文件的重寫, 由於兩者都是操作磁盤, 且 bgrewriteaof是重寫整個內存中的數據到 AOF文件, 磁盤操作非常龐大, 由此會產生阻塞的情況發生, 此時如果選擇 no意思是承受阻塞的情況(同步), 但數據是安全的, 或選擇 yes的話, 主進程的 set不會被阻塞, 因爲內存完成所有的 set操作後纔會同步 AOF文件(也就是異步), 但此時服務宕機的話可能會丟失數據. 相關 Linux內核參數 overcommit_memory(默認0, 推薦設置1)
auto-aof-rewrite-percentage 100 觸發機制, AOF文件增長比例, 指當前 AOF文件比上次重寫的增長比例大小
auto-aof-rewrite-min-size 64mb 觸發機制, AOF文件重寫最小的文件大小, 即最開始 AOF文件必須要達到這個文件時才觸發, 之後每次重寫將不會根據這個變量(根據上一次重寫完成之後的大小)
aof-load-truncated yes 恢復時, 會忽略最後一條可能存在問題的指令
aof-rewrite-incremental-fsync yes 如果啓用了以下選項, 則當子級重寫AOF文件時, 每生成32MB的數據, 將對該文件進行 fsync. 這對於以遞增方式將文件提交到磁盤時, 避免大延遲非常有用

簡單比較& 選擇

  1. 如果能容忍數分鐘以內的數據丟失, 那就可以單使用 RDB持久化, 否則使用 AOF持久化. 但推薦兩種持久化方式同時開啓, 當服務宕機重啓時 Redis將優先選擇 AOF文件來恢復數據
  2. AOF比起 RDB數據更完整且更大
  3. RDB數據恢復要比 AOF快很多

管道模式

  • 裝載大量數據時, 使用 pipe mode(管道模式), 將一個命令請求一次響應一次的方式, 改爲一個請求執行大量命令, 省略了按每命令響應的資源浪費

$ cat data.txt | redis-cli --pipe

data.txt內每個命令都需遵守 Redis協議(RESP協議)格式, 例如: SET key value協議格式如下:
*3<cr><lf> # 參數個數(SET1 mykey1 myvalue1 共三個), 數組長度
$3<cr><lf> # 命令 SET的字符串長度3
SET<cr><lf>
$5<cr><lf> # mykey的字符串長度5
mykey<cr><lf>
$7<cr><lf> # myvalue的字符串長度7
myvalue<cr><lf>

將所有需插入的數據與命令按照上面的方式一個接一個的生成到文件內, 然後使用管道模式插入即可.
這裏的<cr>是對應 \r, <lf>是對應 \n

In RESP, the type of some data depends on the first byte:
 For Simple Strings the first byte of the reply is "+"
 For Errors the first byte of the reply is "-"
 For Integers the first byte of the reply is ":"
 For Bulk Strings the first byte of the reply is "$"
 For Arrays the first byte of the reply is "*"

  • Redis協議(Redis Serialization Protocol, RESP)特點是簡單, 解析快, 每個命令或數據都以\r\n結尾. 參考: http://www.redis.cn/topics/protocol.html

線程模型(單線程)

Redis基於 Reactor模式開發的網絡事件處理器, 又稱文件事件處理器(file event handler). Reactor模式是, 基於事件驅動的設計模式, 擁有一個或多個併發輸入源, 一個服務處理器和多個請求處理器. 服務處理器會將觸發的輸入源事件以多路複用的方式分發給相應的請求處理器. 常用於高併發系統, 用來替代多線程處理方式以節省系統資源, 並提高系統的吞吐量

Redis的文件事件處理器包含

  1. 套接字(Socket)
  2. I/O多路複用機制
  3. 文件事件分派器
  4. 事件處理器

工作流程

  • I/O多路複用機制負責監聽 N個套接字, 並根據套接字的當前任務, 關聯不同的事件處理器之後發送給文件事件分派器, 處理相關聯的事件, 儘管多個文件事件併發地出現 I/O多路複用機制依然可以將所有的套接字插入到一個隊列裏, 然後按順序發到文件事件分派器:每當處理完套接字的關聯事件後, I/O多路複用機制纔將下一個套接字發給文件事件分派器. 還有一個套接字又可讀又可寫, 服務器將會先讀後, 再寫

單線程

  • 作爲內存數據庫, 單線程是效率最高的, 因爲能避免多線程頻繁上下文切換帶來的性能損耗

數據過期策略& 內存淘汰機制

常見刪除策略

  1. 定時刪除, 當生成定時的鍵多時會十分耗 CPU資源, 因爲設置鍵過期時間的同時, 將會創建一個 timer監控到期的鍵主動刪除
  2. 定期刪除, Redis默認每100ms檢查, 主動刪除已過期的鍵, 不過 Redis定期不是檢查所有的鍵, 而是隨機抽取部分(20個)鍵進行檢查, 因此只採用定期刪除策略, 將會導致很多已過期鍵的未被刪除
  3. 惰性刪除, 不會主動檢查, 只會在客戶端, 請求獲取指定鍵時纔會檢查一下, 如果過期了直接刪除, 否者正常返回, 因此未被請求過的鍵將永存於內存, 所以內存內會產生很多已過期鍵未被刪除
  • Redis的數據過期策略是定期刪除+惰性刪除策略, 由於這兩種策略都會產生已過期但未被刪除的鍵, 長此以往內存內會堆積很多已過期但未被刪除的垃圾數據, 這部分數據就得依靠內存淘汰機制清理了

內存淘汰機制

  • LRU(Least Recently Used)算法, 即最近最久未使用, 即選擇最近最久未使用的頁面予以淘汰(頁面置換算法)
  • LFU(Least Frequently Used)算法, 即最近訪問頻率最少, 即選擇最近一段時間內很少被訪問的頁面予以淘汰(頁面置換算法)
  • TTL(Time To Live)生存時間, Redis的3種 TTL返回:
  1. 未設置過期時間時 TTL值爲-1
  2. 有設置但已過期時 TTL值爲-2, 不過當 Redis slave設置成可寫, 且保持主從同步關係時 TTL在過期後是0同時 Redis爲了保證主從數據的一致性不會刪除指定鍵
  3. 有設置但未過期時, 正常返回剩餘時間
  • 相關基本參數 redis.conf配置
策略 說明
noeviction 不進行數據淘汰(默認)
volatile-lru 最少使用的鍵優先被淘汰. 未設置過期時間的鍵不會被淘汰(在淘汰策略中用的最多)
volatile-lfu 區別於 volatile-lru, 這個策略會選擇某段時間之內最少使用的鍵優先被淘汰
volatile-ttl 區別於 volatile-lru, 這個策略會選擇已設置過期時間的鍵中最早過期的鍵優先被淘汰
volatile-random 對已設置過期時間的鍵, 隨機清理
allkeys-lru 區別於 volatile-lru, 這個策略包含全體的鍵, 意味着未設置過期時間的鍵也會被淘汰
allkeys-lfu 區別於 volatile-lfu, 這個策略包含全體的鍵, 意味着未設置過期時間的鍵也會被淘汰
allkeys-random 對全體的鍵, 隨機清理
  • maxmemory-policy noeviction

Redis與 Memcached的簡單比較& 優勢

  1. Memcached只支持字符串類型值, Redis支持多種數據類型
  2. 由於 Memcached可以多線程, 所以處理100K以上數據時性能優於 Redis
  3. Redis支持持久化, Memcached不支持
  4. Memcached單個值默認限制爲1M, 可調最大128M, 如設置1M以上會有警告. Redis最大可以存到512M, 且能存的鍵多達2.5億個, 每個鍵的值也可以存到2.5億個行

常見問題& 解決方案

緩存雪崩(Cache Avalanche)

  • 在高併發場景中, 當大量緩存在同一時間段失效, 所以將所有的數據查詢壓力落到數據庫上, 導致系統崩潰的情況.
  1. 最常用的解決方案是, 給每個鍵設置不同的過期時間, 將鍵失效的時間分散

緩存穿透(Cache Penetration)

  • 指不存在的數據未緩存處理時, 當用戶每次請求指定不存在的鍵, 此時緩存中找不到數據的同時, 直接查詢數據庫肯定又找不到. 如果惡意的發起大量的此種請求將會給後端系統造成很大的壓力, 這就是緩存穿透
  • 解決方案:
  1. 將肯定不存在的鍵放入到足夠大的 Bitmap中, 當請求肯定不存在的鍵時攔截掉, 從而減輕數據庫查詢壓力
  2. 將查詢不到的數據也存入到 Redis中, 避免短時間段不停的請求重複做無意義的數據庫訪問的操作. 但此種鍵過期時間要設置的短一些

緩存擊穿(Cache Breakdown)

  • 指熱點鍵, 在高併發時緩存失效的瞬間高併發的訪問持續着, 導致緩存被擊穿, 直接高併發的訪問了數據庫的情況
  1. 加個分佈式鎖 SETNX(set if not exists), 同一時間訪問指定鍵生成數據緩存後, 刪除該鎖結束訪問

如果您覺得有幫助,歡迎點贊哦 ~ 謝謝!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章