
Redis從入門到集群這一篇就夠了附踩坑實錄寫在前面本文整理自Redis全套學(xué)習(xí)筆記從基礎(chǔ)概念到主從、哨兵、集群實戰(zhàn)一條龍講透。建議收藏面試前翻出來再看一遍。一、Redis 是個啥RedisRemote Dictionary Server是完全開源的遵守 BSD 協(xié)議是一個高性能的key-value 數(shù)據(jù)庫。1.1 Redis 的三大特點數(shù)據(jù)持久化可以把內(nèi)存中的數(shù)據(jù)存到磁盤重啟后還能加載回來不然斷電就寄了豐富數(shù)據(jù)結(jié)構(gòu)不止支持簡單的 key-value還提供 list、set、zset、hash 等數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)備份支持 master-slave 模式的數(shù)據(jù)備份1.2 Redis 有多快對比項RedisMySQLQPS10萬~100萬幾千~幾萬響應(yīng)時間微秒級毫秒級存儲位置內(nèi)存磁盤白話理解Redis 就是個內(nèi)存里的超級大字典查東西直接翻內(nèi)存不用像MySQL那樣翻硬盤速度快幾個數(shù)量級。1.3 五種數(shù)據(jù)類型Redis 支持五種基礎(chǔ)數(shù)據(jù)類型string字符串最基礎(chǔ)hash哈希存對象list列表有序可做隊列set集合無序去重zset(sorted set)有序集合排行榜神器1.4 哈希槽是個啥Redis 集群把整個鍵空間劃分為16384 個哈希槽0-16383這是經(jīng)過大佬們權(quán)衡后拍板的固定數(shù)量。映射公式HASH_SLOT CRC16(key) mod 16384為啥是16384不是16萬也不是160萬163842^14這個數(shù)字在心跳包大小和集群擴展性之間取得了完美平衡。說白了就是大佬算過了這個數(shù)最合適。二、Redis 都能用在哪些地方2.1 熱點數(shù)據(jù)緩存最經(jīng)典電商、社交等高頻訪問場景把熱點數(shù)據(jù)商品詳情、用戶信息緩存到 Redis大幅降低數(shù)據(jù)庫壓力。實現(xiàn)要點緩存策略Cache-Aside 模式先查緩存沒命中再查庫并更新緩存過期控制設(shè)置合理的 TTL別讓臟數(shù)據(jù)一直待著鍵設(shè)計業(yè)務(wù)前綴:唯一標識如product:info:1001空值處理查不到的數(shù)據(jù)也緩存?zhèn)€空值短TTL防止緩存穿透2.2 分布式鎖分布式系統(tǒng)中多個服務(wù)要搶共享資源時用SET NX命令實現(xiàn)分布式鎖。# 加鎖不存在才設(shè)置過期時間30秒SET lock:order:1001 uuid123 NX PX30000# 解鎖必須用Lua腳本原子判斷刪除不然會誤刪別人的鎖EVALif redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end1lock:order:1001 uuid1232.3 計數(shù)器與限流INCR原子遞增命令天然適合做計數(shù)器結(jié)合過期時間就是限流。文章閱讀量、視頻播放次數(shù)接口訪問頻率限制每分鐘最多100次秒殺活動庫存計數(shù)2.4 排行榜系統(tǒng)Sorted Set有序集合完美適配排行榜按分數(shù)實時排序。商品銷量榜、用戶積分榜游戲排行榜實時投票結(jié)果2.5 其他常見場景場景用啥實現(xiàn)分布式會話String/Hash 存session簡單消息隊列List LPUSH/BRPOP用戶簽到Bitmap 位圖附近的人/店鋪GEO 地理坐標三、Redis 為啥這么快3.1 單線程模型重點面試必問Redis 6.0 之前一直是單線程處理請求為啥單線程還這么快純內(nèi)存操作數(shù)據(jù)都在內(nèi)存里不像數(shù)據(jù)庫要折騰磁盤核心功能簡單就是讀寫內(nèi)存不像數(shù)據(jù)庫還有約束、聯(lián)合查詢啥的額外開銷避免線程競爭單線程不用加鎖沒有線程切換的開銷IO多路復(fù)用用 epoll 一個線程管一堆 socket??注意說Redis是單線程其實不太準確早期版本是單進程單線程處理請求但后臺還有其他線程干特定的活比如fsync刷盤、關(guān)閉文件描述符。3.2 IO多路復(fù)用epoll舉個生活化的例子select模式你去買燒烤、餃子、炒菜自己在三個攤位之間來回跑著問好了沒——效率低epoll模式你先去三個攤位下單跟老板說好了叫我然后該干嘛干嘛哪個好了老板喊你——一個人等三份飯效率高Linux 上三套IO多路復(fù)用APIselect你自己挨個問沒人提醒你性能差poll和select差不多有點改進epoll事件通知/回調(diào)機制哪個socket有數(shù)據(jù)了內(nèi)核主動通知你Redis用的就是這個3.3 Redis 使用注意事項一次只執(zhí)行一條命令別搞長命令避免慢命令keys *、flushall、flushdb、大集合操作、慢Lua腳本生產(chǎn)環(huán)境千萬別隨便用keys *分分鐘把Redis打掛四、緩存三大坑穿透、擊穿、雪崩這三個概念面試100%會考必須分清楚4.1 緩存雪崩定義緩存層大面積失效比如同一時間批量過期或者Redis服務(wù)宕機所有請求瞬間涌入數(shù)據(jù)庫把數(shù)據(jù)庫搞崩。通俗理解游樂場所有出口的閘機同時壞了幾千人一下子全沖向檢票口大門直接被擠爆。發(fā)生場景批量Key同時過期比如電商首頁1000個商品緩存都設(shè)了10分鐘時間一到全打庫Redis集群宕機請求無路可走直接穿透到DB解決方案過期時間加隨機值10分鐘 1~5分鐘隨機打散過期時間搭建高可用集群多主多從、哨兵模式避免單點故障服務(wù)限流熔斷用Sentinel或Hystrix限制并發(fā)保護數(shù)據(jù)庫多級緩存本地緩存Guava RedisRedis掛了本地還有數(shù)據(jù)4.2 緩存穿透定義查詢一個根本不存在的數(shù)據(jù)緩存里沒有數(shù)據(jù)庫里也沒有每次請求都繞過緩存直接查數(shù)據(jù)庫。通俗理解你拿張假身份證去檢票檢票員Redis說沒這票讓你去后臺查數(shù)據(jù)庫。后臺查半天也沒有就讓你回去了。結(jié)果來一萬個拿假身份證的后臺直接累癱。發(fā)生場景攻擊者惡意查詢user_id -1或不存在的商品ID業(yè)務(wù)代碼Bug導(dǎo)致反復(fù)查無效Key解決方案接口層校驗ID做基礎(chǔ)校驗id0的直接攔截緩存空結(jié)果空結(jié)果也緩存起來短TTL下次就不用再查庫了布隆過濾器把所有可能存在的數(shù)據(jù)Key提前存到布隆過濾器請求來了先過一遍布隆過濾器不存在的直接攔截4.3 緩存擊穿定義某個熱點Key訪問量極大在某個時刻過期恰好此時大量并發(fā)請求同時打過來全部去查數(shù)據(jù)庫。通俗理解游樂場只有一個VIP通道熱點Key這個通道剛好關(guān)閉了過期了但此時一萬人正排隊等著通過大家一擁而上把旁邊的閘機數(shù)據(jù)庫擠壞了。雪崩 vs 擊穿 區(qū)別重點雪崩大面積掛很多Key一起過期擊穿單點掛一個熱點Key過期但它特別重要解決方案加鎖排隊查數(shù)據(jù)庫的時候加鎖只放一個請求去查其他等結(jié)果熱點Key永不過期特別熱點的數(shù)據(jù)直接設(shè)為永不過期數(shù)據(jù)更新時主動刷新緩存五、Pipeline 流水線為啥能提速5.1 RTT 是啥Redis客戶端執(zhí)行一條命令分4步發(fā)送命令 → 命令排隊 → 命令執(zhí)行 → 返回結(jié)果這個過程叫Round Trip TimeRTT往返時間。算筆賬客戶端在北京Redis服務(wù)端在上海兩地距離約1300公里。光在光纖里跑的時間 1300×2 / (30萬×2/3) ≈ 13毫秒。也就是說一次RTT就要13ms1秒也就執(zhí)行80次命令這跟Redis百萬級QPS的高并發(fā)特性完全背道而馳啊5.2 Pipeline 原理Redis提供了mget、mset等批量命令來節(jié)約RTT但大部分命令不支持批量比如你要執(zhí)行100次hgetall沒有mhgetall命令啊。Pipeline 就是來解決這個問題的把一組命令打包通過一次RTT全部發(fā)給Redis再按順序返回所有結(jié)果。不用Pipeline執(zhí)行N條命令需要N次RTT用了Pipeline執(zhí)行N條命令只需要1次RTT結(jié)論網(wǎng)絡(luò)延遲越大Pipeline的性能提升越明顯。同機房可能感覺不出來跨城跨區(qū)那提升是肉眼可見的。六、Redis 安裝實戰(zhàn)CentOS 7/86.1 CentOS 7 源碼安裝# 1. 安裝依賴包yuminstalltcl gcc gcc-c-y# 2. 解壓安裝包tarzxvf redis-6.2.14.tar.gz-C/usr/local/# 3. 編譯安裝cd/usr/local/redis-6.2.14/makemakeinstall# 4. 啟動并放后臺redis-server redis.conf# 5. 查看啟動端口netstat-antp|grep6379# 6. 客戶端連接redis-cli127.0.0.1:63796.2 CentOS 8 yum 安裝# 安裝redisdnfinstall-yredis# 設(shè)置開機自啟并啟動systemctlenableredis--now# 查看端口ss-tunlp|grep6379# 測試連接redis-cli127.0.0.1:6379pingPONG6.3 開啟多實例# 再開一個6380端口的實例redis-server--port6380# 連接指定端口redis-cli-p63806.4 為啥默認端口是6379這其實是作者的一個冷知識Redis 作者 antirez 當年選端口號的時候直接用了手機鍵盤上“MERZ”對應(yīng)的數(shù)字?!癕ERZ” 是他和朋友用來形容愚蠢的俚語源自一位意大利女演員 Alessia Merz。說白了就是——大佬隨便起的沒有任何技術(shù)含義 七、Redis 配置文件詳解vim/etc/redis.conf配置項默認值說明bind127.0.0.1監(jiān)聽地址遠程訪問要改成0.0.0.0記得配密碼protected-modeyes保護模式?jīng)]密碼且綁外網(wǎng)IP時只允許本地連接port6379監(jiān)聽端口tcp-backlog511TCP連接隊列長度高并發(fā)要調(diào)大timeout0客戶端空閑超時0表示永不超時tcp-keepalive300每300秒發(fā)一次探測包檢測死連接daemonizeno是否后臺運行容器里一般保持no八、持久化RDB vs AOFRedis 數(shù)據(jù)都在內(nèi)存里斷電就沒了所以需要持久化到磁盤。Redis 提供了兩種持久化方式RDB和AOF。8.1 RDB快照模式原理某一時刻把內(nèi)存數(shù)據(jù)全量快照保存到dump.rdb文件。觸發(fā)方式# 方式1save命令同步會阻塞生產(chǎn)別用127.0.0.1:6379save# 方式2bgsave命令異步fork子進程來做127.0.0.1:6379bgsave Background saving started# 方式3配置文件自動觸發(fā)# 900秒內(nèi)改了1個key就觸發(fā)save9001# 300秒內(nèi)改了10個key就觸發(fā)save30010# 60秒內(nèi)改了10000個key就觸發(fā)save6010000RDB 優(yōu)缺點優(yōu)點缺點恢復(fù)速度快直接加載二進制文件宕機會丟失最后一次快照后的數(shù)據(jù)文件緊湊適合備份save命令會阻塞主進程用子進程做對性能影響小bgsave時fork子進程還是會短暫阻塞8.2 AOF追加日志模式原理記錄每一次寫操作命令追加到appendonly.aof文件末尾。重啟時重新執(zhí)行這些命令來恢復(fù)數(shù)據(jù)。開啟方式# redis.conf配置appendonlyyes# 開啟AOFappendfilenameappendonly.aofappendfsync everysec# 寫入策略dir/var/lib/redis??大坑預(yù)警第一次開啟AOF千萬別直接改配置文件重啟因為AOF優(yōu)先級高于RDB但AOF文件是空的重啟后所有數(shù)據(jù)直接清零正確姿勢先在線執(zhí)行CONFIG SET appendonly yesRedis會自動觸發(fā)AOF重寫把現(xiàn)有數(shù)據(jù)寫入AOF文件然后再改配置文件永久生效。三種寫入策略策略說明安全性性能always每次寫操作都fsync到磁盤最安全最慢everysec每秒fsync一次默認最多丟1秒數(shù)據(jù)折中no交給操作系統(tǒng)決定什么時候?qū)懽畈话踩羁?.3 AOF 重寫AOF文件會越來越大Redis提供了AOF重寫機制來壓縮文件。舉個例子# 原來6條命令rpush listArpush listBrpush listCrpush listDrpush listErpush listF# 重寫后變成1條rpush listABCDEF本質(zhì)不是去解析舊AOF文件而是直接從當前數(shù)據(jù)庫狀態(tài)讀出來用最少的命令重新生成。跟快照差不多但是以命令形式保存。九、Redis 數(shù)據(jù)類型與常用命令9.1 String 類型# 設(shè)置值setnamezhangsan# 獲取值get name# 數(shù)字遞增原子操作incr counter# 1incrby counter10# 10# 設(shè)置過期時間settoken abc123 EX3600# 3600秒后過期9.2 Hash 類型存對象神器# 設(shè)置字段hset user:1001 name張三age25# 獲取字段hget user:1001 name hgetall user:1001# 刪字段hdel user:1001 age9.3 List 類型隊列/棧# 左邊插入棧lpush mylistabc# 右邊插入隊列rpush mylistd# 彈出lpop mylist# 從左彈rpop mylist# 從右彈# 范圍查詢lrange mylist0-1# 查全部9.4 Set 類型無序集合去重# 添加元素sadd mysetabc# 差集、交集、并集sdiffset1 set2# 差集sinter set1 set2# 交集sunion set1 set2# 并集9.5 ZSet 類型有序集合排行榜# 添加元素帶分數(shù)zadd rank100player1200player2150player3# 按分數(shù)排序從高到低zrevrank rankplayer1zrange rank0-1WITHSCORES# 排行榜Top3zrevrange rank02WITHSCORES十、主從復(fù)制10.1 啥是主從復(fù)制主從復(fù)制就是一個Master可以有多個SlaveMaster負責寫Slave負責讀數(shù)據(jù)從Master單向同步到Slave。特點一個Master可以有多個Slave一個Slave只能有一個Master數(shù)據(jù)流向Master → Slave 單向Master可讀可寫Slave只讀10.2 同步原理Slave向Master發(fā)送sync命令Master啟動后臺存盤進程收集所有修改命令Master存盤完成后把整個數(shù)據(jù)文件發(fā)給SlaveSlave接收數(shù)據(jù)文件加載到內(nèi)存完成首次完全同步后續(xù)新數(shù)據(jù)產(chǎn)生時Master繼續(xù)把修改命令發(fā)給Slave同步10.3 配置實戰(zhàn)實驗環(huán)境1主2從主機名IP地址系統(tǒng)master192.168.108.10CentOS 7.9slave01192.168.108.11CentOS 7.9slave02192.168.108.12CentOS 7.9所有節(jié)點配置vimredis.conf# 監(jiān)聽所有網(wǎng)卡bind0.0.0.0 -::1從節(jié)點配置slave01、slave02都要配vimredis.conf# 指向Master的IP和端口replicaof192.168.108.106379 也可以在線配置不用改文件重啟# 新版推薦127.0.0.1:6379REPLICAOF192.168.108.106379# 舊版即將淘汰127.0.0.1:6379SLAVEOF192.168.108.106379驗證# Master上查看127.0.0.1:6379info replication role:master connected_slaves:2# Slave上查看127.0.0.1:6379info replication role:slave master_host:192.168.108.10 master_link_status:up取消主從同步# 在從節(jié)點執(zhí)行127.0.0.1:6379REPLICAOF NO ONE# 注意取消后從節(jié)點數(shù)據(jù)不會清空只是不再同步了十一、哨兵模式Sentinel主從復(fù)制有個問題Master掛了需要手動把一個Slave提升為Master這就很麻煩。哨兵模式就是來解決這個問題的——自動故障轉(zhuǎn)移。11.1 哨兵是干啥的哨兵是一個獨立的分布式監(jiān)控系統(tǒng)主要功能監(jiān)控持續(xù)檢查Master和Slave是不是還活著通知Redis實例出問題了通過API通知管理員自動故障轉(zhuǎn)移Master掛了自動選一個Slave當新Master配置提供者客戶端通過哨兵獲取當前Master地址哨兵本身不存數(shù)據(jù)只負責監(jiān)控和協(xié)調(diào)。11.2 故障轉(zhuǎn)移流程主觀下線SDOWN某個哨兵發(fā)現(xiàn)Master沒響應(yīng)標記為主觀下線客觀下線ODOWN多個哨兵達到quorum數(shù)量都確認Master掛了標記為客觀下線選舉Leader哨兵之間通過Raft算法選一個Leader來執(zhí)行故障轉(zhuǎn)移提升新主Leader選一個最優(yōu)的Slave執(zhí)行REPLICAOF NO ONE變成新Master重配從節(jié)點其他Slave重新指向新Master通知客戶端告訴客戶端Master換人了11.3 配置實戰(zhàn)延續(xù)上面的1主2從實驗三臺機器都要配哨兵# 備份配置文件cpsentinel.conf sentinel.conf.bak# 編輯配置vimsentinel.conf# 第84行監(jiān)控的Master名稱、IP、端口、投票數(shù)2個及以上哨兵確認才算客觀下線sentinel monitor mymaster192.168.108.1063792# 第125行超過30秒沒響應(yīng)就主觀下線單位毫秒sentinel down-after-milliseconds mymaster30000啟動哨兵redis-sentinel sentinel.conf查看哨兵狀態(tài)redis-cli-p26379127.0.0.1:26379info sentinel master0:namemymaster,statusok,address192.168.108.10:6379,slaves2,sentinels3??哨兵要部署奇數(shù)個3個、5個不然容易腦裂。而且哨兵和Redis節(jié)點最好分開部署不然Redis掛了哨兵也跟著掛了那就沒人干監(jiān)控的活了。十二、Redis Cluster3主3從集群哨兵模式解決了高可用問題但沒有解決數(shù)據(jù)分片問題——所有數(shù)據(jù)都存在一個Master上內(nèi)存不夠了咋辦這時候就需要Redis Cluster了。12.1 核心概念Redis Cluster 是無中心架構(gòu)的分布式系統(tǒng)核心就是三件套數(shù)據(jù)分片 主從復(fù)制 去中心化故障轉(zhuǎn)移。關(guān)鍵角色Master節(jié)點處理讀寫、管理哈希槽、參與故障投票Slave節(jié)點復(fù)制Master數(shù)據(jù)Master掛了自動頂上哈希槽集群固定16384個槽0~16383是數(shù)據(jù)分片的最小單位12.2 哈希槽機制HASH_SLOT CRC16(key) mod 16384比如3個Master的集群Master1負責 0 ~ 5460 號槽Master2負責 5461 ~ 10922 號槽Master3負責 10923 ~ 16383 號槽為啥不用一致性哈希Redis選了哈希槽方案好處是擴容縮容時遷移數(shù)據(jù)更可控。加一個節(jié)點從每個Master那里勻一段槽過去就行不用全量重哈希。12.3 請求路由流程客戶端隨便連一個節(jié)點發(fā)命令節(jié)點計算這個Key屬于哪個槽槽是自己的 → 直接處理槽不是自己的 → 返回MOVED重定向告訴客戶端正確的節(jié)點地址聰明的客戶端會緩存槽位映射表下次就直接連對節(jié)點了12.4 故障轉(zhuǎn)移某個Master超時沒響應(yīng)多數(shù)Master投票確認它掛了它的Slave們發(fā)起FAILOVER選舉請求Master們投票選一個Slave升級為新MasterGossip協(xié)議同步新拓撲集群恢復(fù)12.5 搭建實戰(zhàn)3主3從環(huán)境準備6臺機器或者一臺機器開6個實例# 所有節(jié)點配置redis.confvimredis.confbind0.0.0.0 -::1 port6379# 每個實例改不同端口daemonizeyescluster-enabledyes# 開啟集群模式cluster-config-file nodes-6379.conf cluster-node-timeout15000appendonlyyes啟動所有節(jié)點后創(chuàng)建集群redis-cli--clustercreate\192.168.108.21:6379\192.168.108.22:6379\192.168.108.23:6379\192.168.108.24:6379\192.168.108.25:6379\192.168.108.26:6379\--cluster-replicas1--cluster-replicas 1表示每個Master配一個Slave6個節(jié)點正好3主3從。十三、總結(jié)Redis 技術(shù)演進路線一張圖看懂Redis架構(gòu)是怎么一步步演進的單機Redis → 主從復(fù)制數(shù)據(jù)冗余 讀寫分離 → 加上自動故障轉(zhuǎn)移 → 哨兵模式高可用 → 再加上數(shù)據(jù)分片和寫擴展 → Redis Cluster分布式集群架構(gòu)解決啥問題適合場景單機簡單緩存測試環(huán)境、小項目主從讀寫分離、數(shù)據(jù)備份讀多寫少哨兵自動故障轉(zhuǎn)移、高可用中小規(guī)模高可用Cluster數(shù)據(jù)分片、水平擴展大規(guī)模高并發(fā)大數(shù)據(jù)量