目錄
- 1.NoSQL介紹
- 1.1 什么是NoSQL
- 1.2 NoSQL的特點
- 1.2.1 應用場景
- 1.2.2 不適用場景
- 1.3 NoSQL資料庫
- 1.3.1 memcache
- 1.3.2 redis介紹
- 1.3.3 mongoDB介紹
- 1.3.4 列式存盤HBase介紹
- 2. Redis介紹
- 2.1 Redis的基本介紹
- 2.2 Redis的應用場景
- 2.2.1 取最新N個資料的操作
- 2.2.2 排行榜應用,取TOP-N操作
- 2.2.3 需要精準設定過期時間的應用
- 2.2.4 計數器應用
- 2.2.5 Uniq操作,獲取某段時間所有資料排重值
- 2.2.6 實時系統,反垃圾系統
- 2.2.7 快取
- 2.3 Redis的特點
- 3. Redis單機環境安裝(Linux)
- 3.1 下載redis安裝包
- 3.2 解壓redis壓縮包到指定目錄
- 3.3 安裝C程式運行環境
- 3.4 安裝較新版本的tcl
- 3.4.1 使用壓縮包進行安裝
- 3.4.2 在線安裝tcl(推薦)
- 3.5 編譯redis
- 3.5.1 修改redis組態檔
- 3.5.2 啟動redis
- 3.5.3 關閉redis
- 3.5.4 連接redis客戶端
- 4. Redis的資料型別
- 4.1 對字串string的操作
- 4.2 對hash串列的操作
- 4.3 對list串列的操作
- 4.4 對set集合的操作
- 4.5 對key的操作
- 4.6 對ZSet的操作
- 4.7 對位圖BitMaps的操作
- 4.7.1 設定值
- 4.7.2 獲取值
- 4.7.3 獲取Bitmaps指定范圍值為1的個數
- 4.7.4 Bitmaps間的運算
- 4.8 對HyperLogLog結構的操作
- 4.8.1 應用場景
- 4.8.2 UV計算示例
- 4.8.3 HyperLogLog為什么適合做大量資料的統計
- 5. Redis Java API操作
- 5.1 創建maven工程并匯入依賴
- 5.1.1 創建Maven工程
- 5.1.2 匯入POM依賴
- 5.2 創建包結構和類
- 5.3 連接以及關閉redis客戶端
- 5.4 操作string型別資料
- 5.5 操作hash串列型別資料
- 5.6 操作list型別資料
- 5.7 操作set型別的資料
- 6. Redis的持久化
- 6.1 RDB持久化方案
- 6.1.1 介紹
- 6.1.2 RDB方案優點
- 6.1.3 RDB方案缺點
- 6.1.4 RDB配置
- 6.2 AOF持久化方案
- 6.2.1 介紹
- 6.2.2 開啟AOF
- 6.2.3 配置AOF
- 6.2.4 AOF rewrite
- 6.2.5 AOF優點
- 6.2.6 AOF的缺點
- 6.3 RDB or AOF
- 7. Redis 高級使用
- 7.1 Redis 事務
- 7.1.1 Redis事務簡介
- 7.1.2 Redis事務演示
- 7.1.3 為什么Redis不支持事務回滾?
- 7.2 Redis 過期策略
- 7.3 記憶體淘汰策略
- 8. Redis的主從復制架構
- 8.1 簡介
- 8.1.1 一主一從
- 8.1.2 一主多從
- 8.2 主從復制原理
- 8.3 主從復制的應用場景
- 8.3.1 備份容錯
- 8.3.2 讀寫分離
- 8.3.3 從資料庫持久化
- 8.4 另外兩臺服務器安裝Redis
- 8.4.1 安裝Redis依賴環境
- 8.4.2 上傳Redis壓縮包
- 8.4.3 服務器安裝tcl
- 8.4.4 編譯redis
- 8.4.5 修改redis組態檔
- bigdata-pro-m08服務器修改組態檔
- bigdata-pro-m09服務器修改組態檔
- 8.5 啟動Redis服務
- 9. Redis中的Sentinel架構
- 9.1 Sentinel介紹
- 9.2 配置哨兵
- 9.2.1 三臺機器修改哨兵組態檔
- 9.2.2 三臺機器啟動哨兵服務
- 9.2.3 bigdata-pro-m07服務器殺死redis服務行程
- 9.3 Redis的sentinel模式代碼開發連接
- 10. Redis 集群
- 10.1 引言
- 10.1.1 Redis集群解決的問題
- 10.1.2 分布式存盤的重點——磁區
- 10.2 Redis Cluster 設計
- 10.3 Redis Cluster 搭建
- 10.3.1 環境準備
- 10.3.2 上傳和解壓
- 10.3.3 編譯安裝
- 10.3.4 拷貝組態檔
- 10.3.5 修改組態檔
- 10.3.6 發送安裝包
- 10.4 啟動Redis服務
- 10.4.1 啟動集群
- 10.4.2 測驗集群
- 10.4.3 啟動關閉集群
- 10.4.4 主從切換
- 10.5 Redis Cluster 管理
- 10.6 JavaAPI操作redis集群
- 10.7 Redis集群面試題
- 11. Redis高頻面試題
- 11.1 快取穿透
- 11.2 快取擊穿
- 11.3 快取雪崩
- 11.4 Redis的命名規范是?
課程目標
- 能夠掌握Redis不同資料型別操作
- 能夠使用Java API操作Redis
- 能夠理解Redis的兩種持久化方式
- 能夠理解Redis的主從復制架構
- 能夠理解Redis的Sentinel架構
- 能夠理解Redis集群架構
1.NoSQL介紹
1.1 什么是NoSQL
- NoSQL最常見的解釋是“non-relational”, 很多人也說它是“Not Only SQL”
- NoSQL僅僅是一個概念,泛指非關系型的資料庫
- 區別于關系資料庫,它們不保證關系資料的ACID特性
- NoSQL是一項全新的資料庫革命性運動,提倡運用非關系型的資料存盤,相對于鋪天蓋地的關系型資料庫運用,這一概念無疑是一種全新的思維的注入
1.2 NoSQL的特點
1.2.1 應用場景
- 高并發的讀寫
- 海量資料讀寫
- 高可擴展性
- 速度快
1.2.2 不適用場景
- 需要事務支持
- 基于sql的結構化查詢存盤,處理復雜的關系,需要即席查詢(用戶自定義查詢條件的查詢)
1.3 NoSQL資料庫
1.3.1 memcache
- 很早出現的NoSql資料庫
- 資料都在記憶體中,一般不持久化
- 支持簡單的key-value模式
- 一般是作為快取資料庫輔助持久化的資料庫
1.3.2 redis介紹
- 幾乎覆寫了Memcached的絕大部分功能
- 資料都在記憶體中,支持持久化,主要用作備份恢復
- 除了支持簡單的key-value模式,還支持多種資料結構的存盤,比如 list、set、hash、zset等,
- 一般是作為快取資料庫輔助持久化的資料庫
- 現在市面上用得非常多的一款記憶體資料庫
1.3.3 mongoDB介紹
- 高性能、開源、模式自由(schema free)的檔案型資料庫
- 資料都在記憶體中, 如果記憶體不足,把不常用的資料保存到硬碟
- 雖然是key-value模式,但是對value(尤其是json)提供了豐富的查詢功能
- 支持二進制資料及大型物件
- 可以根據資料的特點替代RDBMS ,成為獨立的資料庫,或者配合RDBMS,存盤特定的資料,
1.3.4 列式存盤HBase介紹
HBase是Hadoop專案中的資料庫,它用于需要對大量的資料進行隨機、實時讀寫操作的場景中,HBase的目標就是處理資料量非常龐大的表,可以用普通的計算機處理超過10億行資料,還可處理有數百萬列元素的資料表,
2. Redis介紹
redis官網地址:
https://redis.io/
中文網站
http://www.redis.cn/
2.1 Redis的基本介紹
- Redis是當前比較熱門的NoSQL系統之一
- 它是一個開源的、使用ANSI C語言撰寫的key-value存盤系統(區別于MySQL的二維表格形式存盤)
- 和Memcache類似,但很大程度補償了Memcache的不足,Redis資料都是快取在計算機記憶體中,不同的是,Memcache只能將資料快取到記憶體中,無法自動定期寫入硬碟,這就表示,一斷電或重啟,記憶體清空,資料丟失
2.2 Redis的應用場景
- 計數器
- TopN、排行榜(微博的熱搜榜、熱門話題、抖音直播間的熱門直播間、淘寶電商的排行榜)
- 去重的計數
- 實時系統,用于存盤一些規則
- 定時過期的一些應用(短信驗證碼)
- 快取(保護資料庫不被高并發壓垮)
2.2.1 取最新N個資料的操作
比如典型的取網站最新文章,可以將最新的5000條評論ID放在Redis的List集合中,并將超出集合部分從資料庫獲取
2.2.2 排行榜應用,取TOP-N操作
這個需求與上面需求的不同之處在于,前面操作以時間為權重,這個是以某個條件為權重,比如按頂的次數排序,可以使用Redis的sorted set,將要排序的值設定成sorted set的score,將具體的資料設定成相應的value,每次只需要執行一條ZADD命令即可
2.2.3 需要精準設定過期時間的應用
比如可以把上面說到的sorted set的score值設定成過期時間的時間戳,那么就可以簡單地通過過期時間排序,定時清除過期資料了,不僅是清除Redis中的過期資料,你完全可以把Redis里這個過期時間當成是對資料庫中資料的索引,用Redis來找出哪些資料需要過期洗掉,然后再精準地從資料庫中洗掉相應的記錄,
2.2.4 計數器應用
Redis的命令都是原子性的,你可以輕松地利用INCR,DECR命令來構建計數器系統,
2.2.5 Uniq操作,獲取某段時間所有資料排重值
這個使用Redis的set資料結構最合適了,只需要不斷地將資料往set中扔就行了,set意為集合,所以會自動排重,
2.2.6 實時系統,反垃圾系統
通過上面說到的set功能,你可以知道一個終端用戶是否進行了某個操作,可以找到其操作的集合并進行分析統計對比等,沒有做不到,只有想不到,
2.2.7 快取
將資料直接存放到記憶體中,性能優于Memcached,資料結構更多樣化,
2.3 Redis的特點
-
高效性
- 速度非常快,單機能夠支持的并發、讀寫的速度達10W以上(Kafka更快——80W-150W)
-
原子性
- Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全并后的原子性執行,
-
支持多種資料結構型別,操作非常靈活
- string(字串)
- list(串列)
- hash(哈希)
- set(集合)
- zset(有序集合)
-
穩定性:持久化,主從復制(集群)
-
其他特性:支持過期時間,支持事務,訊息訂閱,
3. Redis單機環境安裝(Linux)
3.1 下載redis安裝包
bigdata-pro-m07服務器執行以下命令下載redis安裝包
cd /opt/software
wget http://download.redis.io/releases/redis-3.2.8.tar.gz
chmod u+x redis-3.2.8.tar.gz
3.2 解壓redis壓縮包到指定目錄
tar -zxvf redis-3.2.8.tar.gz -C /opt/modules/
3.3 安裝C程式運行環境
yum -y install gcc-c++
3.4 安裝較新版本的tcl
3.4.1 使用壓縮包進行安裝
cd /opt/software
wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
chmod u+x tcl8.6.1-src.tar.gz
# 解壓tcl
tar -zxvf tcl8.6.1-src.tar.gz -C /opt/modules/
# 進入指定目錄
cd /opt/modules/tcl8.6.1/unix
# 進入root
su
./configure
make && make install
3.4.2 在線安裝tcl(推薦)
yum -y install tcl
3.5 編譯redis
cd /opt/modules/redis-3.2.8
#或者使用命令 make 進行編譯
make MALLOC=libc
make test && make install PREFIX=/opt/modules/redis-3.2.8
3.5.1 修改redis組態檔
cd /opt/modules/redis-3.2.8
mkdir log
mkdir data
vim redis.conf
# 修改第61行
bind node1.itcast.cn
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/modules/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/modules/redis-3.2.8/data
3.5.2 啟動redis
cd /opt/modules/redis-3.2.8/
bin/redis-server redis.conf
3.5.3 關閉redis
bin/redis-cli -h bigdata-pro-m07 shutdown
注意:
- 在生產環境,關閉redis的時候,不用使用 kill -9,應該使用redis-cli -h 主機名 -p 埠 shutdown
- 因為如果直接kill -9,可能redis的一些資料會丟失
3.5.4 連接redis客戶端
bin/redis-cli -h bigdata-pro-m07

4. Redis的資料型別
redis當中一共支持五種資料型別,分別是:
- string字串
- list串列
- set集合
- zset有序集合
通過這五種不同的資料型別,可以實作各種不同的功能,也可以應用在各種不同的場景,

Redis當中各種資料型別結構如上圖:
Redis當中各種資料型別的操作
https://www.runoob.com/redis/redis-keys.html
4.1 對字串string的操作
下表列出了常用的 redis 字串命令
| 命令及描述 | 示例 |
|---|---|
| SET key value 設定指定 key 的值 | 示例:SET hello world |
| GET key 獲取指定 key 的值 | 示例:GET hello |
| GETSET key value將給定 key 的值設為 value ,并回傳 key 的舊值(old value) | 示例:GETSET hello world2 |
| MGET key1 [key2…]獲取所有(一個或多個)給定 key 的值 | 示例:MGET hello world |
| SETEX key seconds value將值 value 關聯到 key ,并將 key 的過期時間設為 seconds (以秒為單位) | 示例:SETEX hello 10 world3 |
| SETNX key value只有在 key 不存在時設定 key 的值 | 示例:SETNX itcast redisvalue |
| STRLEN key回傳 key 所儲存的字串值的長度 | 示例:STRLEN itcast |
| MSET key value [key value …]同時設定一個或多個 key-value 對 | 示例:MSET itcast2 itcastvalue2 itcast3 itcastvalue3 |
| MSETNX key value [key value …] 同時設定一個或多個 key-value 對,當且僅當所有給定 key 都不存在 | 示例:MSETNX itcast4 itcastvalue4 itcast5 itcastvalue5 |
| PSETEX key milliseconds value 這個命令和 SETEX 命令相似,但它以毫秒為單位設定 key 的生存時間,而不是像 SETEX 命令那樣,以秒為單位, | 示例:PSETEX itcast6 6000 itcast6value |
| INCR key 將 key 中儲存的數字值增一, | 示例:SET itcast7 1 、INCR itcast7 、GET itcast7 |
| INCRBY key increment將 key 所儲存的值加上給定的增量值(increment) | 示例:INCRBY itcast7 2、GET itcast7 |
| INCRBYFLOAT key increment 將 key 所儲存的值加上給定的浮點增量值(increment) | 示例:INCRBYFLOAT itcast7 0.8 |
| DECR key 將 key 中儲存的數字值減一, | 示例:SET itcast8 1、DECR itcast8、GET itcast8 |
| DECRBY key decrement key 所儲存的值減去給定的減量值(decrement) | 示例:DECRBY itcast8 3 |
| APPEND key value如果 key 已經存在并且是一個字串, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾, | 示例:APPEND itcast8 hello |
注意:
- 在執行一些累加器的操作時,千萬不能使用 set/get來操作
- 要使用INCR/DESC/INCRBY

4.2 對hash串列的操作
Redis hash 是一個string型別的field和value的映射表,hash特別適合用于存盤物件,
Redis 中每個 hash 可以存盤 2的32次方 - 1 鍵值對(40多億)
下表列出了 redis hash 基本的相關命令:
| 命令及描述 | 示例 |
|---|---|
| HSET key field value 將哈希表 key 中的欄位 field 的值設為 value , | 示例:HSET key1 field1 value1 |
| HSETNX key field value 只有在欄位 field 不存在時,設定哈希表欄位的值, | 示例:HSETNX key1 field2 value2 |
| HMSET key field1 value1 [field2 value2 ] 同時將多個 field-value (域-值)對設定到哈希表 key 中, | 示例:HMSET key1 field3 value3 field4 value4 |
| HEXISTS key field 查看哈希表 key 中,指定的欄位是否存在, | 示例:HEXISTS key1 field4、HEXISTS key1 field6 |
| HGET key field 獲取存盤在哈希表中指定欄位的值, | 示例:HGET key1 field4 |
| HGETALL key 獲取在哈希表中指定 key 的所有欄位和值 | 示例:HGETALL key1 |
| HKEYS key 獲取所有哈希表中的欄位 | 示例:HKEYS key1 |
| HLEN key 獲取哈希表中欄位的數量 | 示例:HLEN key1 |
| HMGET key field1 [field2] 獲取所有給定欄位的值 | 示例:HMGET key1 field3 field4 |
| HINCRBY key field increment 為哈希表 key 中的指定欄位的整數值加上增量 increment , | 示例:HSET key2 field1 1、HINCRBY key2 field1 1、HGET key2 field1 |
| HINCRBYFLOAT key field increment 為哈希表 key 中的指定欄位的浮點數值加上增量 increment , | 示例:HINCRBYFLOAT key2 field1 0.8 |
| HVALS key 獲取哈希表中所有值 | 示例:HVALS key1 |
| HDEL key field1 [field2] | |
| 洗掉一個或多個哈希表欄位 | 示例:HDEL key1 field3 、HVALS key1 |
4.3 對list串列的操作
Redis串列是簡單的字串串列,按照插入順序排序,你可以添加一個元素到串列的頭部(左邊)或者尾部(右邊)
一個串列最多可以包含 2的32次方 - 1 個元素 (4294967295, 每個串列超過40億個元素),
下表列出了串列相關的基本命令:
| 命令及描述 | 示例 |
|---|---|
| LPUSH key value1 [value2] 將一個或多個值插入到串列頭部 | 示例:LPUSH list1 value1 value2 |
| LRANGE key start stop 查看list當中所有的資料 | 示例:LRANGE list1 0 -1 |
| LPUSHX key value 將一個值插入到已存在的串列頭部 | 示例:LPUSHX list1 value3、LINDEX list1 0 |
| RPUSH key value1 [value2] 在串列中添加一個或多個值到尾部 | 例:RPUSH list1 value4 value5、LRANGE list1 0 -1 |
| RPUSHX key value 為已存在的串列添加單個值到尾部 | 示例:RPUSHX list1 value6 |
| LINSERT key BEFORE /AFTER pivot value 在串列的元素前或者后插入元素 | 示例:LINSERT list1 BEFORE value3 beforevalue3 |
| LINDEX key index 通過索引獲取串列中的元素 | 示例:LINDEX list1 0 |
| LSET key index value 通過索引設定串列元素的值 | 示例:LSET list1 0 hello |
| LLEN key 獲取串列長度 | 示例:LLEN list1 |
| LPOP key 移出并獲取串列的第一個元素 | 示例:LPOP list1 |
| RPOP key 移除串列的最后一個元素,回傳值為移除的元素, | 示例:RPOP list1 |
| BLPOP key1 [key2 ] timeout 移出并獲取串列的第一個元素, 如果串列沒有元素會阻塞串列直到等待超時或發現可彈出元素為止, | 示例:BLPOP list1 2000 |
| BRPOP key1 [key2 ] timeout 移出并獲取串列的最后一個元素, 如果串列沒有元素會阻塞串列直到等待超時或發現可彈出元素為止, | 示例:BRPOP list1 2000 |
| RPOPLPUSH source destination 移除串列的最后一個元素,并將該元素添加到另一個串列并回傳 | 示例:RPOPLPUSH list1 list2 |
| BRPOPLPUSH source destination timeout 從串列中彈出一個值,將彈出的元素插入到另外一個串列中并回傳它; 如果串列沒有元素會阻塞串列直到等待超時或發現可彈出元素為止, | 示例:BRPOPLPUSH list1 list2 2000 |
| LTRIM key start stop 對一個串列進行修剪(trim),就是說,讓串列只保留指定區間內的元素,不在指定區間之內的元素都將被洗掉, | 示例:LTRIM list1 0 2 |
| DEL key1 key2 洗掉指定key的串列 | 示例:DEL list2 |
4.4 對set集合的操作
- Redis 的 Set 是 String 型別的無序集合,集合成員是唯一的,這就意味著集合中不能出現重復的資料
- Redis 中集合是通過哈希表實作的,所以添加,洗掉,查找的復雜度都是 O(1),
- 集合中最大的成員數為 232 - 1 (4294967295, 每個集合可存盤40多億個成員),
下表列出了 Redis 集合基本命令:
| 命令及描述 | 示例 |
|---|---|
| SADD key member1 [member2] 向集合添加一個或多個成員 | 示例:SADD set1 setvalue1 setvalue2 |
| SMEMBERS key 回傳集合中的所有成員 | 示例:SMEMBERS set1 |
| SCARD key 獲取集合的成員數 | 示例:SCARD set1 |
| SDIFF key1 [key2] 回傳給定所有集合的差集 | 示例:SADD set2 setvalue2 setvalue3、SDIFF set1 set2 |
| SDIFFSTORE destination key1 [key2] 回傳給定所有集合的差集并存盤在 destination 中 | 示例:SDIFFSTORE set3 set1 set2 |
| SINTER key1 [key2] 回傳給定所有集合的交集 | 示例:SINTER set1 set2 |
| SINTERSTORE destination key1 [key2] 回傳給定所有集合的交集并存盤在 destination 中 | 示例:SINTERSTORE set4 set1 set2 |
| SISMEMBER key member 判斷 member 元素是否是集合 key 的成員 | 示例:SISMEMBER set1 setvalue1 |
| SMOVE source destination member 將 member 元素從 source 集合移動到 destination 集合 | 示例:SMOVE set1 set2 setvalue1 |
| SPOP key 移除并回傳集合中的一個隨機元素 | 示例:SPOP set2 |
| SRANDMEMBER key [count] 回傳集合中一個或多個亂數 | 示例:SRANDMEMBER set2 2 |
| SREM key member1 [member2] 移除集合中一個或多個成員 | 示例:SREM set2 setvalue1 |
| SUNION key1 [key2] 回傳所有給定集合的并集 | 示例:SUNION set1 set2 |
| SUNIONSTORE destination key1 [key2] 所有給定集合的并集存盤在 destination 集合中 | 示例:SUNIONSTORE set5 set1 set2 |
4.5 對key的操作
下表給出了與 Redis 鍵相關的基本命令:
| 命令及描述 | 示例 |
|---|---|
| DEL key 該命令用于在 key 存在時洗掉 key, | 示例:del itcast5 |
| DUMP key | |
| 序列化給定 key ,并回傳被序列化的值, | 示例:DUMP key1 |
| EXISTS key 檢查給定 key 是否存在, | 示例:exists itcast |
| EXPIRE key seconds 為給定 key 設定過期時間,以秒計, | 示例:expire itcast 5 |
| PEXPIRE key milliseconds 設定 key 的過期時間以毫秒計, | 示例:PEXPIRE set3 3000 |
| KEYS pattern 查找所有符合給定模式( pattern)的 key | 示例:keys * |
| PERSIST key 移除 key 的過期時間,key 將持久保持, | 示例:persist set2 |
| PTTL key 以毫秒為單位回傳 key 的剩余的過期時間, | 示例:pttl set2 |
| TTL key 以秒為單位,回傳給定 key 的剩余生存時間(TTL, time to live), | 示例:ttl set2 |
| RANDOMKEY 從當前資料庫中隨機回傳一個 key , | 示例: randomkey |
| RENAME key newkey 修改 key 的名稱 | 示例:rename set5 set8 |
| RENAMENX key newkey 僅當 newkey 不存在時,將 key 改名為 newkey , | 示例:renamenx set8 set10 |
| TYPE key 回傳 key 所儲存的值的型別, | 示例:type set10 |
4.6 對ZSet的操作
- Redis有序集合和集合一樣也是string型別元素的集合,且不允許重復的成員
- 它用來保存需要排序的資料,例如排行榜,一個班的語文成績,一個公司的員工工資,一個論壇的帖子等,
- 有序集合中,每個元素都帶有score(權重),以此來對元素進行排序
- 它有三個元素:key、member和score,以語文成績為例,key是考試名稱(期中考試、期末考試等),member是學生名字,score是成績,
下表列出了串列相關的基本命令:
| 命令及描述 | 示例 |
|---|---|
| ZADD key score1 member1 [score2 member2] 向有序集合添加一個或多個成員,或者更新已存在成員的分數 | 向ZSet中添加頁面的PV值 ZADD pv_zset 120 page1.html 100 page2.html 140 page3.html |
| ZCARD key 獲取有序集合的成員數 | 獲取所有的統計PV頁面數量 ZCARD pv_zset |
| ZCOUNT key min max 計算在有序集合中指定區間分數的成員數 | 獲取PV在120-140在之間的頁面數量 ZCOUNT pv_zset 120 140 |
| ZINCRBY key increment member 有序集合中對指定成員的分數加上增量 increment | 給page1.html的PV值+1 ZINCRBY pv_zset 1 page1.html |
| ZINTERSTORE destination numkeys key [key …] 計算給定的一個或多個有序集的交集并將結果集存盤在新的有序集合 key 中 | 創建兩個保存PV的ZSET:ZADD pv_zset1 10 page1.html 20 page2.html、ZADD pv_zset2 5 page1.html 10 page2.html、ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset2 |
| ZRANGE key start stop [WITHSCORES] 通過索引區間回傳有序集合指定區間內的成員 | 獲取所有的元素,并可以回傳每個key對一個的score ZRANGE pv_zset_result 0 -1 WITHSCORES |
| ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通過分數回傳有序集合指定區間內的成員 | 獲取ZSET中120-140之間的所有元素 ZRANGEBYSCORE pv_zset 120 140 |
| ZRANK key member 回傳有序集合中指定成員的索引 | 獲取page1.html的pv排名(升序) ZRANK pv_zset page3.html |
| ZREM key member [member …] 移除有序集合中的一個或多個成員 | 移除page1.html ZREM pv_zset page1.html |
| ZREVRANGE key start stop [WITHSCORES] 回傳有序集中指定區間內的成員,通過索引,分數從高到低 | 按照PV降序獲取頁面 ZREVRANGE pv_zset 0 -1 |
| ZREVRANK key member 回傳有序集合中指定成員的排名,有序集成員按分數值遞減(從大到小)排序 | 獲取page2.html的pv排名(降序) ZREVRANK pv_zset page2.html |
| ZSCORE key member 回傳有序集中,成員的分數值 | 獲取page3.html的分數值 ZSCORE pv_zset page3.html |
4.7 對位圖BitMaps的操作
- 計算機最小的存盤單位是位bit,Bitmaps是針對位的操作的,相較于String、Hash、Set等存盤方式更加節省空間
- Bitmaps不是一種資料結構,操作是基于String結構的,一個String最大可以存盤512M,那么一個Bitmaps則可以設定2^32個位
- Bitmaps單獨提供了一套命令,所以在Redis中使用Bitmaps和使用字串的方法不太相同,可以把Bitmaps想象成一個以位為單位的陣列,陣列的每個單元只能存盤0和1,陣列的下標在Bitmaps中叫做偏移量

- BitMaps 命令說明:將每個獨立用戶是否訪問過網站存放在Bitmaps中, 將訪問的用戶記做1, 沒有訪問的用戶記做0, 用偏移量作為用戶的id ,
4.7.1 設定值
SETBIT key offset value
setbit命令設定的vlaue只能是0或1兩個值
- 設定鍵的第offset個位的值(從0算起),假設現在有20個用戶,uid=0,5,11,15,19的用戶對網站進行了訪問, 那么當前Bitmaps初始化結果如圖所示

- 具體操作程序如下, unique:users:2016-04-05代表2016-04-05這天的獨立訪問用戶的Bitmaps
setbit unique:users:2016-04-05 0 1
setbit unique:users:2016-04-05 5 1
setbit unique:users:2016-04-05 11 1
setbit unique:users:2016-04-05 15 1
setbit unique:users:2016-04-05 19 1
- 很多應用的用戶id以一個指定數字(例如10000) 開頭, 直接將用戶id和Bitmaps的偏移量對應勢必會造成一定的浪費, 通常的做法是每次做setbit操作時將用戶id減去這個指定數字,
- 在第一次初始化Bitmaps時, 假如偏移量非常大, 那么整個初始化程序執行會比較慢, 可能會造成Redis的阻塞,
4.7.2 獲取值
GETBIT key offset
- 獲取鍵的第offset位的值(從0開始算),例:下面操作獲取id=8的用戶是否在2016-04-05這天訪問過, 回傳0說明沒有訪問過
getbit unique:users:2016-04-05 8
4.7.3 獲取Bitmaps指定范圍值為1的個數
BITCOUNT key [start end]
例:下面操作計算2016-04-05這天的獨立訪問用戶數量:
bitcount unique:users:2016-04-05
4.7.4 Bitmaps間的運算
BITOP operation destkey key [key, …]
- bitop是一個復合操作, 它可以做多個Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(異或) 操作并將結果保存在destkey中, 假設2016-04-04訪問網站的userid=1, 2, 5, 9, 如圖3-13所示:

setbit unique:users:2016-04-04 1 1
setbit unique:users:2016-04-04 2 1
setbit unique:users:2016-04-04 5 1
setbit unique:users:2016-04-04 9 1
例1:下面操作計算出2016-04-04和2016-04-05兩天都訪問過網站的用戶數量, 如下所示,
bitop and unique:users:and:2016-04-04_05 unique:users:2016-04-04 unique:users:2016-04-05
bitcount unique:users:2016-04-04_05
例2:如果想算出2016-04-04和2016-04-03任意一天都訪問過網站的用戶數量(例如月活躍就是類似這種) , 可以使用or求并集, 具體命令如下:
bitop or unique:users:or:2016-04-04_05 unique:users:2016-04-04 unique:users:2016-04-05
bitcount unique:users:or:2016-04-04_05

4.8 對HyperLogLog結構的操作
4.8.1 應用場景
HyperLogLog常用于大資料量的統計,比如頁面訪問量統計或者用戶訪問量統計,
要統計一個頁面的訪問量(PV),可以直接用redis計數器或者直接存資料庫都可以實作,如果要統計一個頁面的用戶訪問量(UV),一個用戶一天內如果訪問多次的話,也只能算一次,這樣,我們可以使用SET集合來做,因為SET集合是有去重功能的,key存盤頁面對應的關鍵字,value存盤對應的userid,這種方法是可行的,但如果訪問量較多,假如有幾千萬的訪問量,這就麻煩了,為了統計訪問量,要頻繁創建SET集合物件,
Redis實作HyperLogLog演算法,HyperLogLog 這個資料結構的發明人 是Philippe Flajolet(菲利普·弗拉若萊)教授,Redis 在 2.8.9 版本添加了 HyperLogLog 結構,
4.8.2 UV計算示例
bigdata-pro-m07:6379> help @hyperloglog
PFADD key element [element ...]
summary: Adds the specified elements to the specified HyperLogLog.
since: 2.8.9
PFCOUNT key [key ...]
summary: Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).
since: 2.8.9
PFMERGE destkey sourcekey [sourcekey ...]
summary: Merge N different HyperLogLogs into a single one.
since: 2.8.9
Redis集成的HyperLogLog使用語法主要有pfadd和pfcount,顧名思義,一個是來添加資料,一個是來統計的,為什么用pf?是因為HyperLogLog 這個資料結構的發明人 是Philippe Flajolet教授 ,所以用發明人的英文縮寫,這樣容易記住這個語法了,
下面我們通過一個示例,來演示如何計算uv,
bigdata-pro-m07:6379> PFADD uv1 user1
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 1
bigdata-pro-m07:6379> pfadd uv1 user2
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 2
bigdata-pro-m07:6379> pfadd uv1 user3
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 3
bigdata-pro-m07:6379> pfadd uv1 user4
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 4
bigdata-pro-m07:6379> pfadd uv1 user5 user6 user7 user8 user9 user10
(integer) 1
bigdata-pro-m07:6379> pfcount uv1
(integer) 10
bigdata-pro-m07:6379> pfadd uv1 user5 user6 user7 user8 user9 user1
(integer) 0
bigdata-pro-m07:6379> pfcount uv1
(integer) 10
HyperLogLog演算法一開始就是為了大資料量的統計而發明的,所以很適合那種資料量很大,然后又沒要求不能有一點誤差的計算,HyperLogLog 提供不精確的去重計數方案,雖然不精確但是也不是非常不精確,標準誤差是 0.81%,不過這對于頁面用戶訪問量是沒影響的,因為這種統計可能是訪問量非常巨大,但是又沒必要做到絕對準確,訪問量對準確率要求沒那么高,但是性能存盤方面要求就比較高了,而HyperLogLog正好符合這種要求,不會占用太多存盤空間,同時性能不錯
pfadd和pfcount常用于統計,需求:假如兩個頁面很相近,現在想統計這兩個頁面的用戶訪問量呢?這里就可以用pfmerge合并統計了,語法如例子:
bigdata-pro-m07:6379> pfadd page1 user1 user2 user3 user4 user5
(integer) 1
bigdata-pro-m07:6379> pfadd page2 user1 user2 user3 user6 user7
(integer) 1
bigdata-pro-m07:6379> pfmerge page1+page2 page1 page2
OK
bigdata-pro-m07:6379> pfcount page1+page2
(integer) 7
4.8.3 HyperLogLog為什么適合做大量資料的統計
- Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、并且是很小的,
- 在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基數,這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比,
- 但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,回傳輸入的各個元素,
什么是基數?
比如:資料集{1, 3, 5, 7, 5, 7, 8},那么這個資料集的基數集{1, 3, 5, 7, 8},基數(不重復元素)為5,基數估計就是在誤差可接受的范圍內,快速計算基數,
5. Redis Java API操作
Redis不僅可以通過命令列進行操作,也可以通過JavaAPI操作,通過使用Java API來對Redis資料庫中的各種資料型別操作,
離線架構和實時架構流程:

5.1 創建maven工程并匯入依賴
5.1.1 創建Maven工程
| groupId | cn.itcast |
|---|---|
| artifactId | redis_op |
5.1.2 匯入POM依賴
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<!-- <verbal>true</verbal>-->
</configuration>
</plugin>
</plugins>
</build>
5.2 創建包結構和類
- 在test目錄創建 cn.itcast.redis.api_test 包結構
- 創建RedisTest類
5.3 連接以及關閉redis客戶端
因為后續測驗都需要用到Redis連接,所以,我們先創建一個JedisPool用于獲取Redis連接,此處,我們基于TestNG來測驗各類的API,使用@BeforeTest在執行測驗用例前,創建Redis連接池,使用@AfterTest在執行測驗用例后,關閉連接池,
實作步驟:
- 創建JedisPoolConfig配置物件,指定最大空閑連接為10個、最大等待時間為3000毫秒、最大連接數為50、最小空閑連接5個
- 創建JedisPool
- 使用@Test注解,撰寫測驗用例,查看Redis中所有的key
a) 從Redis連接池獲取Redis連接
b) 呼叫keys方法獲取所有的key
c) 遍歷列印所有key
package cn.itcast.redis.api_test;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Set;
/**
* @author :caizhengjie
* @description:
* 1. 創建JedisPoolConfig配置物件,指定最大空閑連接為10個、最大等待時間為3000毫秒、最大連接數為50、最小空閑連接5個
* 2. 創建JedisPool
* 3. 使用@Test注解,撰寫測驗用例,查看Redis中所有的key
* a) 從Redis連接池獲取Redis連接
* b) 呼叫keys方法獲取所有的key
* c) 遍歷列印所有key
* @date :2021/1/26 11:28 上午
*/
public class RedisTest {
private JedisPool jedisPool;
@BeforeTest
public void redisConnectionPool(){
// 創建JedisPoolConfig配置物件
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空閑連接為10個
config.setMaxIdle(10);
// 最小空閑連接5個
config.setMinIdle(5);
// 最大等待時間為3000毫秒
config.setMaxWaitMillis(3000);
// 最大連接數為50
config.setMaxTotal(50);
jedisPool = new JedisPool(config,"bigdata-pro-m07");
}
@Test
public void keysTest(){
// 從redis連接池獲取redis連接
Jedis jedis = jedisPool.getResource();
// 呼叫keys方法獲取所有的key
Set<String> keySet = jedis.keys("*");
for (String key : keySet){
System.out.println(key);
}
}
@AfterTest
public void afterTest(){
// 關閉連接池
jedisPool.close();
}
}
注意:
- 操作Redis一般要使用Jedis的連接池,這樣可以有效的復用連接資源
- 在IDEA中,有時候提示可能不完整,其實Jedis連接池,可以指定埠號
5.4 操作string型別資料
- 添加一個string型別資料,key為pv,用于保存pv的值,初始值為0
- 查詢該key對應的資料
- 修改pv為1000
- 實作整形資料原子自增操作 +1
- 實作整形該資料原子自增操作 +1000
/**
* 操作string型別資料
*/
@Test
public void stringTest(){
// 從redis連接池獲取redis連接
Jedis jedis = jedisPool.getResource();
// 1.添加一個string資料型別,key為pv,用于保存pv的值,初始值為0
jedis.set("pv","0");
// 2.查詢key對應的資料
System.out.println("pv:"+jedis.get("pv"));
// 3.修改pv為1000
jedis.set("pv","1000");
// 4.實作整形資料原子自增操作+1
jedis.incr("pv");
// 5.實作整形資料原子自增操作+1000
jedis.incrBy("pv",1000);
System.out.println(jedis.get("pv"));
}
注意:
- Redis操作string其實和SHELL命令是一樣
- 將來在撰寫Flink程式/Spark Streaming程式操作Redis的時候,注意操作完Redis之后,執行close,將連接回傳到連接池,
5.5 操作hash串列型別資料
- 往Hash結構中添加以下商品庫存
a) iphone11 => 10000
b) macbookpro => 9000 - 獲取Hash中所有的商品
- 新增3000個macbookpro庫存
- 洗掉整個Hash的資料
/**
* 操作hash串列型別資料
*/
@Test
public void hashTest(){
// 從redis連接池獲取redis連接
Jedis jedis = jedisPool.getResource();
// 1.往Hash結構中添加以下商品庫存
// (a)iPhone11 => 10000
// (b)MacBookPro => 9000
jedis.hset("goods","iPhone11","10000");
jedis.hset("goods","MacBookPro","9000");
// 2.獲取Hash中所有的商品
Set<String> goodSet = jedis.hkeys("goods");
System.out.println("所有商品:");
for (String good : goodSet) {
System.out.println(good);
}
// 3.新增3000個MacBookPro庫存
// String storeMacBook = jedis.hget("goods","MacBookPro");
// long longStore = Long.parseLong(storeMacBook);
// long addStore = longStore + 3000;
// jedis.hset("goods","MacBookPro",addStore + "");
jedis.hincrBy("goods","MacBookPro",3000);
// 4.洗掉整個Hash的資料
jedis.del("goods");
jedis.close();
}
注意:
- 當我們后續在撰寫Flink、Spark Streaming流處理程式使用Java操作Redis時候,涉及到一些數字的累加
- 一定要使用incr、hincrBy
5.6 操作list型別資料
- 向list的左邊插入以下三個手機號碼:18511310001、18912301231、18123123312
- 從右邊移除一個手機號碼
- 獲取list所有的值
/**
* 操作list型別資料
*/
@Test
public void listTest(){
// 從redis連接池獲取redis連接
Jedis jedis = jedisPool.getResource();
// 1.向list的左邊插入以下三個手機號:18511310001、18511310002、18511310003
jedis.lpush("tel_list","18511310001","18511310002","18511310003");
// 2.從右面移除一個手機號碼
jedis.rpop("tel_list");
// 3.獲取list所有的值
List<String> telList = jedis.lrange("tel_list",0,-1);
for (String tel : telList) {
System.out.println(tel);
}
}
注意:
- List可以用來存盤重復的元素,而且是有序的
- 獲取所有的元素,lrange(key, 0, -1)
5.7 操作set型別的資料
使用set來保存uv值,為了方便計算,將用戶名保存到uv中,
- 往一個set中添加頁面 page1 的uv,用戶user1訪問一次該頁面
- user2訪問一次該頁面
- user1再次訪問一次該頁面
- 最后獲取 page1的uv值
/**
* 操作set型別的資料
*/
@Test
public void setTest(){
// 從redis連接池獲取redis連接
Jedis jedis = jedisPool.getResource();
// 求UV就是求獨立有多少個不重復
// 1.往一個set中添加頁面page1的uv,用戶user1訪問一次該頁面
jedis.sadd("uv","user1");
// 2.user2訪問一次該頁面
jedis.sadd("uv","user2");
// 3.user1訪問一次該頁面
jedis.sadd("uv","user1");
// 最后獲取page1的uv值
System.out.println("uv:" + jedis.scard("uv"));
jedis.close();
}
注意:
- 計算UV主要是去重
- 將來所有的一些要求高效率去重的業務場景,都可以使用Set操作
6. Redis的持久化
由于redis是一個記憶體資料庫,所有的資料都是保存在記憶體當中的,記憶體當中的資料極易丟失,所以redis的資料持久化就顯得尤為重要,在redis當中,提供了兩種資料持久化的方式,分別為RDB以及AOF,且Redis默認開啟的資料持久化方式為RDB方式,
6.1 RDB持久化方案
6.1.1 介紹
Redis會定期保存資料快照至一個rdb檔案中,并在啟動時自動加載rdb檔案,恢復之前保存的資料,可以在組態檔中配置Redis進行快照保存的時機:
save [seconds] [changes]
意為在seconds秒內如果發生了changes次資料修改,則進行一次RDB快照保存,例如
save 60 100
會讓Redis每60秒檢查一次資料變更情況,如果發生了100次或以上的資料變更,則進行RDB快照保存,可以配置多條save指令,讓Redis執行多級的快照保存策略,Redis默認開啟RDB快照,也可以通過SAVE或者BGSAVE命令手動觸發RDB快照保存, SAVE 和 BGSAVE 兩個命令都會呼叫 rdbSave 函式,但它們呼叫的方式各有不同:
- SAVE 直接呼叫 rdbSave ,阻塞 Redis 主行程,直到保存完成為止,在主行程阻塞期間,服務器不能處理客戶端的任何請求,
- BGSAVE 則 fork 出一個子行程,子行程負責呼叫 rdbSave ,并在保存完成之后向主行程發送信號,通知保存已完成, Redis 服務器在BGSAVE 執行期間仍然可以繼續處理客戶端的請求,
6.1.2 RDB方案優點
- 對性能影響最小,如前文所述,Redis在保存RDB快照時會fork出子行程進行,幾乎不影響Redis處理客戶端請求的效率,
- 每次快斬訓生成一個完整的資料快照檔案,所以可以輔以其他手段保存多個時間點的快照(例如把每天0點的快照備份至其他存盤媒介中),作為非常可靠的災難恢復手段,
- 使用RDB檔案進行資料恢復比使用AOF要快很多
6.1.3 RDB方案缺點
- 快照是定期生成的,所以在Redis crash時或多或少會丟失一部分資料
- 如果資料集非常大且CPU不夠強(比如單核CPU),Redis在fork子行程時可能會消耗相對較長的時間,影響Redis對外提供服務的能力
6.1.4 RDB配置
- 修改redis的組態檔
cd /opt/modules/redis-3.2.8
vim redis.conf
# 第202行
save 900 1
save 300 10
save 60 10000
save 5 1
這三個選項是redis的組態檔默認自帶的存盤機制,表示每隔多少秒,有多少個key發生變化就生成一份dump.rdb檔案,作為redis的快照檔案
例如:save 60 10000 表示在60秒內,有10000個key發生變化,就會生成一份redis的快照
- 重新啟動redis服務
每次生成新的dump.rdb都會覆寫掉之前的老的快照
ps -ef | grep redis
bin/redis-cli -h bigdata-pro-m07 shutdown
bin/redis-server redis.conf
6.2 AOF持久化方案
6.2.1 介紹
采用AOF持久方式時,Redis會把每一個寫請求都記錄在一個日志檔案里,在Redis重啟時,會把AOF檔案中記錄的所有寫操作順序執行一遍,確保資料恢復到最新,
6.2.2 開啟AOF
AOF默認是關閉的,如要開啟,進行如下配置:
# 第594行
appendonly yes
6.2.3 配置AOF
AOF提供了三種fsync配置:always/everysec/no,通過配置項[appendfsync]指定:
- appendfsync no:不進行fsync,將flush檔案的時機交給OS決定,速度最快
- appendfsync always:每寫入一條日志就進行一次fsync操作,資料安全性最高,但速度最慢
- appendfsync everysec:折中的做法,交由后臺執行緒每秒fsync一次
6.2.4 AOF rewrite
隨著AOF不斷地記錄寫操作日志,因為所有的寫操作都會記錄,所以必定會出現一些無用的日志,大量無用的日志會讓AOF檔案過大,也會讓資料恢復的時間過長,不過Redis提供了AOF rewrite功能,可以重寫AOF檔案,只保留能夠把資料恢復到最新狀態的最小寫操作集,
AOF rewrite可以通過BGREWRITEAOF命令觸發,也可以配置Redis定期自動進行:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
- Redis在每次AOF rewrite時,會記錄完成rewrite后的AOF日志大小,當AOF日志大小在該基礎上增長了100%后,自動進行AOF rewrite
- auto-aof-rewrite-min-size最開始的AOF檔案必須要觸發這個檔案才觸發,后面的每次重寫就不會根據這個變數了,該變數僅初始化啟動Redis有效,
6.2.5 AOF優點
- 最安全,在啟用appendfsync為always時,任何已寫入的資料都不會丟失,使用在啟用appendfsync everysec也至多只會丟失1秒的資料
- AOF檔案在發生斷電等問題時也不會損壞,即使出現了某條日志只寫入了一半的情況,也可以使用redis-check-aof工具輕松修復
- AOF檔案易讀,可修改,在進行某些錯誤的資料清除操作后,只要AOF檔案沒有rewrite,就可以把AOF檔案備份出來,把錯誤的命令洗掉,然后恢復資料,
6.2.6 AOF的缺點
- AOF檔案通常比RDB檔案更大
- 性能消耗比RDB高
- 資料恢復速度比RDB慢
Redis的資料持久化作業本身就會帶來延遲,需要根據資料的安全級別和性能要求制定合理的持久化策略:
- AOF + fsync always的設定雖然能夠絕對確保資料安全,但每個操作都會觸發一次fsync,會對Redis的性能有比較明顯的影響
- AOF + fsync every second是比較好的折中方案,每秒fsync一次
- AOF + fsync never會提供AOF持久化方案下的最優性能
使用RDB持久化通常會提供比使用AOF更高的性能,但需要注意RDB的策略配置
6.3 RDB or AOF
每一次RDB快照和AOF Rewrite都需要Redis主行程進行fork操作,fork操作本身可能會產生較高的耗時,與CPU和Redis占用的記憶體大小有關,根據具體的情況合理配置RDB快照和AOF Rewrite時機,避免過于頻繁的fork帶來的延遲
Redis在fork子行程時需要將記憶體分頁表拷貝至子行程,以占用了24GB記憶體的Redis實體為例,共需要拷貝48MB的資料,在使用單Xeon 2.27Ghz的物理機上,這一fork操作耗時216ms,

7. Redis 高級使用
7.1 Redis 事務
7.1.1 Redis事務簡介
Redis 事務的本質是一組命令的集合,事務支持一次執行多個命令,一個事務中所有命令都會被序列化,在事務執行程序,會按照順序串行化執行佇列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中,
總結說:Redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列命令
Redis事務沒有隔離級別的概念:
- 批量操作在發送 EXEC 命令前被放入佇列快取,并不會被實際執行,也就不存在事務內的查詢要看到事務里的更新,事務外查詢不能看到,
Redis不保證原子性:
- Redis中,單條命令是原子性執行的,但事務不保證原子性,且沒有回滾,事務中任意命令執行失敗,其余的命令仍會被執行,
一個事務從開始到執行會經歷以下三個階段:- 第一階段:開始事務
- 第二階段:命令入隊
- 第三階段、執行事務
Redis事務相關命令:
- MULTI:開啟事務,redis會將后續的命令逐個放入佇列中,然后使用EXEC命令來原子化執行這個命令佇列
- EXEC:執行事務中的所有操作命令
- DISCARD:取消事務,放棄執行事務塊中的所有命令
- WATCH:監視一個或多個key,如果事務在執行前,這個key(或多個key)被其他命令修改,則事務被中斷,不會執行事務中的任何命令
- UNWATCH:取消WATCH對所有key的監視
7.1.2 Redis事務演示
- MULTI開始一個事務:給k1、k2分別賦值,在事務中修改k1、k2,執行事務后,查看k1、k2值都被修改,
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> set k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) OK
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"22"
- 事務失敗處理:語法錯誤(編譯器錯誤),在開啟事務后,修改k1值為11,k2值為22,但k2語法錯誤,最終導致事務提交失敗,k1、k2保留原值,
bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> sets k2 22
(error) ERR unknown command 'sets'
bigdata-pro-m07:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
bigdata-pro-m07:6379> get k1
"v1"
bigdata-pro-m07:6379> get k2
"v2"
Redis型別錯誤(運行時錯誤),在開啟事務后,修改k1值為11,k2值為22,但將k2的型別作為List,在運行時檢測型別錯誤,最終導致事務提交失敗,此時事務并沒有回滾,而是跳過錯誤命令繼續執行, 結果k1值改變、k2保留原值,
bigdata-pro-m07:6379> flushdb
OK
bigdata-pro-m07:6379> keys *
(empty list or set)
bigdata-pro-m07:6379> set k1 v1
OK
bigdata-pro-m07:6379> set k2 v2
OK
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k1 11
QUEUED
bigdata-pro-m07:6379> lpush k2 22
QUEUED
bigdata-pro-m07:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
bigdata-pro-m07:6379> get k1
"11"
bigdata-pro-m07:6379> get k2
"v2"
DISCARD取消事務
bigdata-pro-m07:6379> multi
OK
bigdata-pro-m07:6379> set k6 v6
QUEUED
bigdata-pro-m07:6379> set k7 v7
QUEUED
bigdata-pro-m07:6379> discard
OK
bigdata-pro-m07:6379> get k6
(nil)
bigdata-pro-m07:6379> get k7
(nil)
7.1.3 為什么Redis不支持事務回滾?
多數事務失敗是由語法錯誤或者資料結構型別錯誤導致的,語法錯誤說明在命令入隊前就進行檢測的,而型別錯誤是在執行時檢測的,Redis為提升性能而采用這種簡單的事務,這是不同于關系型資料庫的,特別要注意區分,Redis之所以保持這樣簡易的事務,完全是為了保證高并發下的核心問題——性能,
7.2 Redis 過期策略
Redis是key-value資料庫,可以設定Redis中快取的key的過期時間,Redis的過期策略就是指當Redis中快取的key過期了,Redis如何處理,
過期策略通常有以下三種:
- 定時過期
每個設定過期時間的key都需要創建一個定時器,到過期時間就會立即清除,該策略可以立即清除過期的資料,對記憶體很友好;但是會占用大量的CPU資源去處理過期的資料,從而影響快取的回應時間和吞吐量, - 惰性過期
只有當訪問一個key時,才會判斷該key是否已過期,過期則清除,該策略可以最大化地節省CPU資源,卻對記憶體非常不友好, 極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,占用大量記憶體, - 定期過期
每隔一定的時間,會掃描一定數量的資料庫的expires字典中一定數量的key,并清除其中已過期的key,該策略是前兩者的一個折中方案,通過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果,
7.3 記憶體淘汰策略
Redis的記憶體淘汰策略是指在Redis的用于快取的記憶體不足時,怎么處理需要新寫入且需要申請額外空間的資料,在Redis的組態檔中描述如下:
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#最大記憶體策略:當到達最大使用記憶體時,你可以在下面5種行為中選擇,Redis如何選擇淘汰資料庫鍵
#當記憶體不足以容納新寫入資料時
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# volatile-lru :在設定了過期時間的鍵空間中,移除最近最少使用的key,這種情況一般是把 redis 既當快取,又做持久化存盤的時候才用,
# allkeys-lru -> remove any key according to the LRU algorithm
# allkeys-lru : 移除最近最少使用的key (推薦)
# volatile-random -> remove a random key with an expire set
# volatile-random : 在設定了過期時間的鍵空間中,隨機移除一個鍵,不推薦
# allkeys-random -> remove a random key, any key
# allkeys-random : 直接在鍵空間中隨機移除一個鍵,弄啥叻
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# volatile-ttl : 在設定了過期時間的鍵空間中,有更早過期時間的key優先移除 不推薦
# noeviction -> don't expire at all, just return an error on write operations
# noeviction : 不做過鍵處理,只回傳一個寫操作錯誤, 不推薦
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
# 上面所有的策略下,在沒有合適的淘汰洗掉的鍵時,執行寫操作時,Redis 會回傳一個錯誤,下面是寫入命令:
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
# 過期策略默認是:
# The default is:
# maxmemory-policy noeviction
實際專案中設定記憶體淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key,
8. Redis的主從復制架構
8.1 簡介
主從復制,是指將一臺Redis服務器的資料,復制到其他的Redis服務器,前者稱為主節點(master),后者稱為從節點(slave),資料的復制是單向的,只能由主節點到從節點,

默認情況下,每臺Redis服務器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點,
8.1.1 一主一從
如下圖所示左邊是Master節點,右邊是slave節點,即主節點和從節點,從節點也是可以對外提供服務的,主節點是有資料的,從節點可以通過復制操作將主節點的資料同步過來,并且隨著主節點資料不斷寫入,從節點資料也會做同步的更新,

從節點起到的就是資料備份的效果,
8.1.2 一主多從
除了一主一從模型之外,Redis還提供了一主多從的模型,也就是一個master可以有多個slave,也就相當于有了多份的資料副本,

可以做一個更加高可用的選擇,例如一個master和一個slave掛掉了,還能有其他的slave資料備份,
8.2 主從復制原理
- 當從資料庫啟動后,會向主資料庫發送SYNC命令
- 主資料庫接收到SYNC命令后開始在后臺保存快照(RDB持久化),并將保存快照期間接收到的命令快取下來
- 快照完成后,Redis(Master)將快照檔案和所有快取的命令發送給從資料庫
- Redis(Slave)接收到RDB和快取命令時,會開始載入快照檔案并執行接收到的快取的命令
- 一旦初始化完成,后續每當主資料庫接收到寫命令時,就會將命令同步給從資料庫,所以3和4只會在初始化的時候執行
8.3 主從復制的應用場景
8.3.1 備份容錯
- 如果只有一個節點,會存在單點故障問題
8.3.2 讀寫分離

- 通過主從復制可以實作讀寫分離,以提高服務器的負載能力
- 在常見的場景中(例如:電商網站),讀的頻率大于寫
- 當單機Redis無法應付大量的讀請求時(尤其是消耗資源的請求),就可以通過主從復制功能來建立多個從資料庫節點,主資料庫只進行寫操作,從資料庫負責讀操作
- 這種主從復制,比較適合用來處理讀多寫少的場景,而當單個主資料庫不能滿足需求時,就需要使用Redis 3.0后推出的集群功能
8.3.3 從資料庫持久化
- Redis中相對耗時的操作就是持久化,為了提高性能,可以通過主從復制創建一個或多個從資料庫,并在從資料庫中啟用持久化,同時在主資料庫中禁用持久化(例如:禁用AOF)
- 當從資料庫崩潰重啟后主資料庫會自動將資料同步過來,無需擔心資料丟失
- 而當主資料庫崩潰時,后續我們可以通過哨兵(Sentinel)來解決
8.4 另外兩臺服務器安裝Redis
8.4.1 安裝Redis依賴環境
在bigdata-pro-m08和bigdata-pro-m09執行以下命令安裝依賴環境
yum -y install gcc-c++
8.4.2 上傳Redis壓縮包
在bigdata-pro-m08和bigdata-pro-m09服務器上面上傳Redis壓縮包,然后進行解壓,并將安裝包上傳到/opt/software路徑下
cd /opt/software
tar -zxvf redis-3.2.8.tar.gz -C /opt/softwares/
8.4.3 服務器安裝tcl
在bigdata-pro-m08和bigdata-pro-m09服務器執行以下命令在線裝TCL
yum -y install tcl
8.4.4 編譯redis
bigdata-pro-m08和bigdata-pro-m09執行以下命令進行編譯Redis
執行以下命令進行編譯:
cd /opt/software/redis-3.2.8/
#或者使用命令 make 進行編譯
make MALLOC=libc
make test && make install PREFIX=/opt/software/redis-3.2.8/
8.4.5 修改redis組態檔
bigdata-pro-m08服務器修改組態檔
執行以下命令修改Redis組態檔
cd /opt/software/redis-3.2.8/
mkdir data
mkdir log
vim redis.conf
# 修改第61行
bind bigdata-pro-m08
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/software/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/software/redis-3.2.8/data
# 修改第266行,配置bigdata-pro-m08為第一臺服務器的slave節點
slaveof bigdata-pro-m07 6379
bigdata-pro-m09服務器修改組態檔
執行以下命令修改Redis組態檔
cd /opt/software/redis-3.2.8/
mkdir data
mkdir log
vim redis.conf
# 修改第61行
bind bigdata-pro-m08
# 修改第128行
daemonize yes
# 修改第163行
logfile "/opt/software/redis-3.2.8/log/redis.log"
# 修改第247行
dir /opt/software/redis-3.2.8/data
# 修改第266行,配置bigdata-pro-m09為第二臺服務器的slave節點
slaveof bigdata-pro-m07 6379
8.5 啟動Redis服務
bigdata-pro-m08和bigdata-pro-m089執行以下命令啟動Redis服務
bin/redis-server redis.conf
啟動成功便可以實作redis的主從復制,bigdata-pro-m07可以讀寫操作,bigdata-pro-m08與bigdata-pro-m09只支持讀取操作,
9. Redis中的Sentinel架構
9.1 Sentinel介紹
- 哨兵是主要用來保障Redis主從復制架構是高可用的,是能夠自動進行主節點切換的
- 它可以監控主從復制中的節點,當主節點崩潰的時候,會自動進行切換
- 一般哨兵的配置節點數不能是1個,最好是有幾個主從節點,就配置幾個哨兵,不能哨兵自己出現單點故障
- 哨兵在Linux系統上是一個獨立的行程,它的默認埠號是26379
- 當我們去查看操作哨兵的時候,需要指定客戶端的連接埠號為:26379
例如:

在Server1 掉線后:

升級Server2 為新的主服務器:

9.2 配置哨兵

9.2.1 三臺機器修改哨兵組態檔
三臺機器執行以下命令修改redis的哨兵組態檔
vim sentinel.conf
配置監聽的主服務器
- 修改bigdata-pro-m07的sentinel.conf檔案
#修改第15行, bind配置,每臺機器修改為自己對應的主機名
bind bigdata-pro-m07
# 在下方添加配置,讓sentinel服務后臺運行
daemonize yes
#修改第71行,三臺機器監控的主節點,現在主節點是bigdata-pro-m07服務器
sentinel monitor mymaster bigdata-pro-m07 6379 2
引數說明
- sentinel monitor代表監控
- mymaster代表服務器的名稱,可以自定義
- bigdata-pro-m07代表監控的主服務器,6379代表埠
- 2代表只有兩個或兩個以上的哨兵認為主服務器不可用的時候,才會進行failover操作,
如果Redis是有密碼的,需要指定密碼
# sentinel author-pass定義服務的密碼,mymaster是服務名稱,123456是Redis服務器密碼
# sentinel auth-pass <master-name> <password>
- 分發到bigdata-pro-m08和bigdata-pro-m09
scp sentinel.conf bigdata-pro-m08:$PWD
scp sentinel.conf bigdata-pro-m09:$PWD
- 分別修改配置中bind的服務器主機名
bigdata-pro-m08
# 修改第18行
bind bigdata-pro-m08
bigdata-pro-m09
# 修改第18行
bind bigdata-pro-m09
9.2.2 三臺機器啟動哨兵服務
bin/redis-sentinel sentinel.conf
三臺服務器的行程資訊:
[caizhengjie@bigdata-pro-m07 redis-3.2.8]$ ps -ef | grep redis
caizhen+ 16605 1 0 00:37 ? 00:00:03 bin/redis-sentinel bigdata-pro-m07:26379 [sentinel]
root 16681 1 0 00:53 ? 00:00:00 bin/redis-server bigdata-pro-m07:6379
caizhen+ 16685 1233 0 00:53 pts/0 00:00:00 grep --color=auto redis
9.2.3 bigdata-pro-m07服務器殺死redis服務行程
查看Sentinel master的狀態
bin/redis-cli -h bigdata-pro-m08 -p 26379
使用ping命令檢查哨兵是否作業,如果正常會回傳PONG
bigdata-pro-m08:26379> ping
PONG
bigdata-pro-m08:26379> info
... ... ...
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=10.211.55.9:6379,slaves=2,sentinels=3
這時的主節點是bigdata-pro-m07
使用kill -9命令殺死redis服務行程,模擬redis故障宕機情況
過一段時間之后,就會在bigdata-pro-m08與bigdata-pro-m09服務器選擇一臺服務器來切換為主節點
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=10.211.55.11:6379,slaves=2,sentinels=3
這時的主節點是bigdata-pro-m09
9.3 Redis的sentinel模式代碼開發連接
通過哨兵連接,要指定哨兵的地址,并使用JedisSentinelPool來創建連接池,
實作步驟:
- 在 cn.itcast.redis.api_test 包下創建一個新的類 ReidsSentinelTest
- 構建JedisPoolConfig配置物件
- 創建一個HashSet,用來保存哨兵節點配置資訊(記得一定要寫埠號)
- 構建JedisSentinelPool連接池
- 使用sentinelPool連接池獲取連接
package cn.itcast.redis.api_test;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;
/**
* @author :caizhengjie
* @description:
* 1. 在 cn.itcast.redis.api_test 包下創建一個新的類 RedisSentinelTest
* 2. 構建JedisPoolConfig配置物件
* 3. 創建一個HashSet,用來保存哨兵節點配置資訊(記得一定要寫埠號)
* 4. 構建JedisSentinelPool連接池
* 5. 使用sentinelPool連接池獲取連接
* @date :2021/1/28 4:07 下午
*/
public class RedisSentinelTest {
private JedisSentinelPool jedisSentinelPool;
@BeforeTest
public void redisConnectionPool(){
// 創建JedisPoolConfig配置物件
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空閑連接為10個
config.setMaxIdle(10);
// 最小空閑連接5個
config.setMinIdle(5);
// 最大等待時間為3000毫秒
config.setMaxWaitMillis(3000);
// 最大連接數為50
config.setMaxTotal(50);
HashSet<String> sentinelSet = new HashSet<>();
sentinelSet.add("bigdata-pro-m07:26379");
sentinelSet.add("bigdata-pro-m08:26379");
sentinelSet.add("bigdata-pro-m09:26379");
jedisSentinelPool = new JedisSentinelPool("mymaster",sentinelSet,config);
}
@Test
public void keysTest(){
// 1.要操作redis,先要獲取redis連接,現在通過哨兵連接池來獲取連接
Jedis jedis = jedisSentinelPool.getResource();
// 2.執行keys操作
Set<String> keySet = jedis.keys("*");
// 3.遍歷所有key
for (String key : keySet) {
System.out.println(key);
}
// 4.再將連接回傳到連接池
jedis.close();
}
@AfterTest
public void afterTest(){
// 關閉連接池
jedisSentinelPool.close();
}
}
運行結果:
k1
k2
===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================
10. Redis 集群
Redis最開始使用主從模式做集群,若master宕機需要手動配置slave轉為master;后來為了高可用提出來哨兵模式,該模式下有一個哨兵監視master和slave,若master宕機可自動將slave轉為master,但它也有一個問題,就是不能動態擴充;所以在Redis 3.x提出cluster集群模式,
10.1 引言
Redis Cluster是Redis官方提供的Redis集群功能,為什么要實作Redis Cluster?
- 主從復制不能實作高可用
- 隨著公司發展,用戶數量增多,并發越來越多,業務需要更高的QPS,而主從復制中單機的QPS可能無法滿足業務需求;
- 資料量的考慮,現有服務器記憶體不能滿足業務資料的需要時,單純向服務器添加記憶體不能達到要求,此時需要考慮分布式需求,把資料分布到不同服務器上;
- 網路流量需求,業務的流量已經超過服務器的網卡的上限值,可考慮使用分布式來進行分流;
- 離線計算,需要中間環節緩沖等其他需求;
在存盤引擎框架(MySQL、HDFS、HBase、Redis、Elasticsearch等)中,只要資料量很大時,單機無法承受壓力,最好的方式就是:資料分布進行存盤管理,
對Redis 記憶體資料庫來說:全量資料,單機Redis節點無法滿足要求,按照磁區規則把資料分到若干個子集當中,

10.1.1 Redis集群解決的問題
- 高可用
- 解決單機Redis記憶體是有限的問題(技術組件不是記憶體越多越好,因為記憶體配置得越高,例如JVM的Heap記憶體配置得很高后,就會導致記憶體碎片整理很耗時,垃圾回識訓發生卡頓,導致集群的效率下降)
- 解決單機Redis網路受限的問題
10.1.2 分布式存盤的重點——磁區
- 順序分布:MySQL——根據順序磁區的方式,例如:根據主鍵來進行磁區(分庫分表),一般是在Java web開發中會遇到
- 按照哈希取余的方式來進行磁區(類似于MapReduce的默認磁區策略)
- 問題:當磁區的數量發生變化的時候,會導致key產生較大影響,原先分布在第一個節點上的資料,磁區數量調整后,指定到了其他的磁區
- 按照一致性Hash的方式來進行磁區
- 是一個環狀的Hash空間,它的磁區演算法是和哈希取余演算法不一樣的
- 首先將每一個磁區的標號(0、1、2)進行演算法計算,然后將計算出來的值,放入到環狀的Hash空間空
- 再將key同樣進行演算法計算,然后將計算出來的值,同樣也放入到環狀的Hash空間中
- 最后,找到key在hash空間中距離自己位置最近的磁區,放入到該磁區中
- 這樣,當磁區的數量發生變化的時候,影響不會太大
- Redis集群是使用槽的方式來進行磁區的
- 現有有一個槽的空間(0-16383),需要將這些空間分布到不同的節點中
- node1: 0 -3xxx
- node2: 3xxx- 6xxx
- …
- 有一個key,首先進行CRC16演算法&16383 = 值,Redis會判斷這個值應該在哪個槽中
一致性哈希磁區圖解:

虛擬槽磁區圖解:

10.2 Redis Cluster 設計
Redis Cluster是分布式架構,有多個節點,每個節點都負責進行資料讀寫操作,每個節點之間會進行通信,Redis Cluster采用無中心結構,每個節點保存資料和整個集群狀態,每個節點都和其他所有節點連接,

結構特點:
- 所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬;
- 節點的fail是通過集群中超過半數的節點檢測失效時才生效;
- 客戶端與redis節點直連,不需要中間proxy層,客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可;
- redis-cluster 把所有的物理節點映射到[0-16383]slot上(不一定是平均分配),cluster 負責維護node<->slot<->value;
- Redis集群預分好16384個桶(Slot),當需要在 Redis 集群中放置一個 key-value 時,根據 CRC16(key) & 16384的值,決定將一個key放到哪個桶中;

Redis 集群的優勢:
- 快取永不宕機:啟動集群,永遠讓集群的一部分起作用,主節點失效了子節點能迅速改變角色成為主節點,整個集群的部分節點失敗或者不可達的情況下能夠繼續處理命令;
- 迅速恢復資料:持久化資料,能在宕機后迅速解決資料丟失的問題;
- Redis可以使用所有機器的記憶體,變相擴展性能;
- 使Redis的計算能力通過簡單地增加服務器得到成倍提升,Redis的網路帶寬也會隨著計算機和網卡的增加而成倍增長;
- Redis集群沒有中心節點,不會因為某個節點成為整個集群的性能瓶頸;
- 異步處理資料,實作快速讀寫;
Redis 3.0以后,節點之間通過去中心化的方式提供了完整的sharding(資料分片)、replication(復制機制、Cluster具備感知準備的能力)、failover解決方案,

10.3 Redis Cluster 搭建
Redis3.0及以上版本實作,集群中至少應該有奇數個節點,所以至少有三個節點,官方推薦三主三從的配置方式,Redis 3.x和Redis4.x 搭建集群是需要手動安裝ruby組件的,比較麻煩,
2018年十月 Redis 發布了穩定版本的 5.0 版本,推出了各種新特性,其中一點是放棄 Ruby的集群方式,改為 使用 C語言撰寫的redis-cli的方式,是集群的構建方式復雜度大大降低,Redis cluster tutorial:https://redis.io/topics/cluster-tutorial

基于Redis-5.0.8版本,在三臺機器上搭建6個節點的Redis集群:三主三從架構,
10.3.1 環境準備
關閉以前Redis主從復制和哨兵模式監控的所有服務,備注:如果以前沒有安裝過Redis服務,不用執行此步驟操作,
# ============= node1.itcast.cn、node2.itcast.cn和node3.itcast.cn =============
# 關閉哨兵服務SentinelServer
ps -ef | grep redis
kill -9 哨兵的行程ID
# 關閉Redis服務
redis-cli -h bigdata-pro-m07 -p 6379 SHUTDOWN
redis-cli -h bigdata-pro-m08 -p 6379 SHUTDOWN
redis-cli -h bigdata-pro-m09 -p 6379 SHUTDOWN
安裝Redis編譯環境:GCC和TCL,
yum -y install gcc-c++ tcl
10.3.2 上傳和解壓
將Redis-5.0.8軟體安裝包上傳至 /opt/software 目錄,并解壓與安裝,
chmod u+x redis-5.0.8.tar.gz
tar -zxvf redis-5.0.8.tar.gz -C /opt/modules/
10.3.3 編譯安裝
編譯Redis 原始碼,并安裝至【/opt/modules/redis-5.0.8-bin】目錄,
# bigdata-pro-m07, 編譯、安裝、創建軟連接
# 進入原始碼目錄
cd /opt/modules/redis-5.0.8
# 編譯
make
# 安裝至指定目錄
make PREFIX=/opt/modules/redis-5.0.8-bin install
# 創建安裝目錄軟連接
ln -s redis-5.0.8-bin redis
配置環境變數(如果以前安裝過Redis,配置過環境變數,就不用配置),
vim /etc/profile
# ======================== 添加如下內容 ========================
# REDIS HOME
export REDIS_HOME=/opt/modules/redis
export PATH=:$PATH:$REDIS_HOME/bin
# 執行生效
source /etc/profile
10.3.4 拷貝組態檔
從Redis-5.0.8原始碼目錄下拷貝組態檔:redis.conf至Redis 安裝目錄,
# ====================== bigdata-pro-m07 上操作 ======================
# 拷貝組態檔
cd /opt/modules/redis-5.0.8
cp redis.conf /opt/modules/redis
10.3.5 修改組態檔
每臺機器上啟動2個Redis服務,一個主節點服務:7001,一個從節點服務:7002,如下圖所示:

在Redis安裝目錄下創建7001和7002目錄,分別存盤Redis服務組態檔、日志及資料檔案,
# 創建目錄:7001和7002
cd /opt/modules/redis
mkdir -p 7001 7002
拷貝組態檔:redis.conf至7001目錄,并重命名為redis_7001.conf,
cd /opt/modules/redis
cp redis.conf 7001/redis_7001.conf
編輯組態檔:redis_7001.conf,內容如下:
cd /opt/modules/redis/7001
vim redis_7001.conf
## =========================== 修改內容說明如下 ===========================
## 69行,配置redis服務器接受鏈接的網卡
bind 0.0.0.0
## 88行,關閉保護模式
protected-mode no
## 92行,設定埠號
port 7001
## 136行,redis后臺運行
daemonize yes
## 158行,Redis服務行程PID存盤檔案名稱
pidfile /var/run/redis_7001.pid
## 171行,設定redis服務日志存盤路徑
logfile "/opt/modules/redis-5.0.8-bin/7001/log/redis.log"
## 263行,設定redis持久化資料存盤目錄
dir /opt/modules/redis-5.0.8-bin/7001/data/
## 699行,啟動AOF方式持久化
appendonly yes
## 832行,啟動Redis Cluster
cluster-enabled yes
## 840行,Redis服務配置保存檔案名稱
cluster-config-file nodes-7001.conf
## 847行,超時時間
cluster-node-timeout 15000
創建日志目錄和資料目錄:
mkdir log
mkdir data
配置7002埠號啟動Redis服務,操作命令如下:
## 拷貝組態檔
cd /opt/modules/redis
cp 7001/redis_7001.conf 7002/redis_7002.conf
## 修改組態檔:redis_7002.conf
cd /opt/modules/redis/7002
vim redis_7002.conf
# 進入vim編輯之后,執行以下代碼將7001全部替換成7002
:%s/7001/7002/g # 表示:%s/old/new/g g表示全部替換
# 創建目錄
mkdir log
mkdir data
10.3.6 發送安裝包
將bigdata-pro-m07上配置好的Redis安裝包,發送至bigdata-pro-m08和bigdata-pro-m09,每臺機器運行2個Redis服務,埠號分別為7001和7002,具體命令如下:
# 發送安裝包
cd /opt/modules/
scp -r redis-5.0.8-bin bigdata-pro-m08:$PWD
scp -r redis-5.0.8-bin bigdata-pro-m09:$PWD
# 創建軟連接
ln -s redis-5.0.8-bin redis
# 配置環境變數
vim /etc/profile
# ======================== 添加如下內容 ========================
# REDIS HOME
export REDIS_HOME=/opt/modules/redis
export PATH=:$PATH:$REDIS_HOME/bin
# 執行生效
source /etc/profile
10.4 啟動Redis服務
在三臺機器,分別啟動6個Redis服務,命令如下:
# 啟動7001埠Redis服務
cd /opt/modules/redis
bin/redis-server 7001/redis_7001.conf
# 啟動7002埠Redis服務
bin/redis-server 7002/redis_7002.conf
Redis服務啟動完成以后,查看如下:
[root@bigdata-pro-m07 redis]# ps -ef | grep redis
root 25362 1 0 03:37 ? 00:00:00 bin/redis-server 0.0.0.0:7001 [cluster]
root 25367 1 0 03:37 ? 00:00:00 bin/redis-server 0.0.0.0:7002 [cluster]
root 25372 20674 0 03:37 pts/0 00:00:00 grep --color=auto redis
10.4.1 啟動集群
Redis5.x版本之后,通過redis-cli客戶端命令來進行創建集群,注意:Redis對主機名決議不友好,使用IP地址,
# 任意選擇一臺機器執行如下命令,創建集群
bin/redis-cli --cluster create 10.211.55.9:7001 10.211.55.9:7002 10.211.55.10:7001 10.211.55.10:7002 10.211.55.11:7001 10.211.55.11:7002 --cluster-replicas
啟動集群日志資訊如下:
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.211.55.10:7002 to 10.211.55.9:7001
Adding replica 10.211.55.11:7002 to 10.211.55.10:7001
Adding replica 10.211.55.9:7002 to 10.211.55.11:7001
M: 85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001
slots:[0-5460] (5461 slots) master
S: 0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002
replicates b870f6c001ba485caffa2ade9a152d163909a548
M: 29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001
slots:[5461-10922] (5462 slots) master
S: 644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002
replicates 85967aece18ad0a0dbba0bd8ab5dc231daa37211
M: b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001
slots:[10923-16383] (5461 slots) master
S: aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002
replicates 29b88a543dd91233f5c0b7f25b8fa05495799f9f
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 10.211.55.9:7001)
M: 85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002
slots: (0 slots) slave
replicates b870f6c001ba485caffa2ade9a152d163909a548
M: b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002
slots: (0 slots) slave
replicates 85967aece18ad0a0dbba0bd8ab5dc231daa37211
S: aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002
slots: (0 slots) slave
replicates 29b88a543dd91233f5c0b7f25b8fa05495799f9f
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
10.4.2 測驗集群
在任意一臺機器,使用redis-cli客戶端命令連接Redis服務:
redis-cli -c -p 7001
輸入命令:cluster nodes(查看集群資訊)和info replication(主從資訊):
127.0.0.1:7001> cluster nodes
29b88a543dd91233f5c0b7f25b8fa05495799f9f 10.211.55.10:7001@17001 master - 0 1611909783813 3 connected 5461-10922
0e819904c77c695451f50b83c6a65fd83d1e4760 10.211.55.9:7002@17002 slave b870f6c001ba485caffa2ade9a152d163909a548 0 1611909784865 5 connected
85967aece18ad0a0dbba0bd8ab5dc231daa37211 10.211.55.9:7001@17001 myself,master - 0 1611909779000 1 connected 0-5460
b870f6c001ba485caffa2ade9a152d163909a548 10.211.55.11:7001@17001 master - 0 1611909786961 5 connected 10923-16383
644fe21de168518e8ece15785a96078fd8926498 10.211.55.10:7002@17002 slave 85967aece18ad0a0dbba0bd8ab5dc231daa37211 0 1611909788005 4 connected
aed488ce6dcbde4fc23e8b160a0ea8583ec783ab 10.211.55.11:7002@17002 slave 29b88a543dd91233f5c0b7f25b8fa05495799f9f 0 1611909785916 6 connected
127.0.0.1:7001> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=10.211.55.10,port=7002,state=online,offset=280,lag=0
master_replid:f67ea78cd33268a6c217b99b249858c8372837ae
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280
測驗資料,設定Key值和查詢Key的值,
127.0.0.1:7001> keys *
(empty list or set)
127.0.0.1:7001> set k1 v1
-> Redirected to slot [12706] located at 10.211.55.11:7001
OK
10.211.55.11:7001> set k2 v2
-> Redirected to slot [449] located at 10.211.55.9:7001
OK
10.211.55.9:7001> set k3 v3
OK
10.211.55.9:7001> get k1
-> Redirected to slot [12706] located at 10.211.55.11:7001
"v1"
10.211.55.11:7001> get k2
-> Redirected to slot [449] located at 10.211.55.9:7001
"v2"
10.211.55.9:7001> get k3
"v3"
10.211.55.9:7001> KEYS *
1) "k3"
2) "k2"
10.4.3 啟動關閉集群
撰寫腳本,方便啟動和關閉Redis集群:redis-cluster-start.sh和redis-cluster-stop.sh,
- 進入Redis安裝目錄中bin目錄,創建腳本檔案
# 進入bigdata-pro-m07
cd /opt/modules/redis/bin
touch redis-cluster-start.sh
touch redis-cluster-stop.sh
# 給以執行權限
chmod u+x redis-cluster-start.sh
chmod u+x redis-cluster-stop.sh
- 啟動集群:redis-cluster-start.sh
#!/bin/bash
REDIS_HOME=/opt/modules/redis
# Start Server
## bigdata-pro-m07
ssh bigdata-pro-m07 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m07 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
## bigdata-pro-m08
ssh bigdata-pro-m08 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m08 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
## bigdata-pro-m09
ssh bigdata-pro-m09 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7001/redis_7001.conf"
ssh bigdata-pro-m09 "${REDIS_HOME}/bin/redis-server /opt/modules/redis/7002/redis_7002.conf"
- 關閉集群:redis-cluster-stop.sh
#!/bin/bash
REDIS_HOME=/opt/modules/redis
# Stop Server
## bigdata-pro-m07
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m07 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m07 -p 7002 SHUTDOWN
## bigdata-pro-m08
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m08 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m08 -p 7002 SHUTDOWN
## bigdata-pro-m09
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m09 -p 7001 SHUTDOWN
${REDIS_HOME}/bin/redis-cli -h bigdata-pro-m09 -p 7002 SHUTDOWN
啟動腳本的時候注意root權限和無密鑰登陸,
10.4.4 主從切換
測驗Redis Cluster中主從服務切換,首先查看集群各個服務狀態:

在bigdata-pro-m09上將7001埠Redis 服務關掉:SHUTDOWN
redis-cli -h bigdata-pro-m09 -p 7001 SHUTDOWN
重新啟動bigdata-pro-m09上7001埠Redis服務,再次查看集群狀態資訊:
bin/redis-server 7001/redis_7001.conf
# 連接Redis集群集群
redis-cli -c -p 7001
cluster nodes

10.5 Redis Cluster 管理
redis-cli集群命令幫助:
redis-cli --cluster help

在實際專案中可能由于Redis Cluster中節點宕機或者增加新節點,需要操作命令管理,主要操作如下,

10.6 JavaAPI操作redis集群
連接Redis集群,需要使用JedisCluster來獲取Redis連接,
實作步驟:
- 在cn.itcast.redis.api_test包下創建一個新的類:RedisClusterTest
- 創建一個HashSet,用于保存集群中所有節點的機器名和埠號
- 創建JedisPoolConfig物件,用于配置Redis連接池配置
- 創建JedisCluster物件
- 使用JedisCluster物件設定一個key,然后獲取key對應的值
- JedisPool——操作單機版本的Redis
- JedisSentinelPool——操作哨兵系統的主從結構
- JedisCluster——操作Redis集群的
注意事項:
- 在構建JedisCluster的時候,需要將集群中的主、從節點所有節點都添加到Set里面
- 如果使用JedisCluster操作Redis時候,不再需要獲取Redis連接,直接去操作Redis即可,因為JedisCluster已經封裝好了對應的操作
package cn.itcast.redis.api_test;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
import java.io.IOException;
import java.util.HashSet;
/**
* @author :caizhengjie
* @description:TODO
* @date :2021/1/29 8:54 下午
*/
public class RedisClusterTest {
private JedisPoolConfig jedisPoolConfig;
private JedisCluster jedisCluster;
@BeforeTest
public void beforeTest(){
// 1. 創建一個HashSet<HostAndPort>,用于保存集群中所有節點的機器名和埠號
HashSet<HostAndPort> hostAndPortSet = new HashSet<>();
hostAndPortSet.add(new HostAndPort("bigdata-pro-m07", 7001));
hostAndPortSet.add(new HostAndPort("bigdata-pro-m07", 7002));
hostAndPortSet.add(new HostAndPort("bigdata-pro-m08", 7001));
hostAndPortSet.add(new HostAndPort("bigdata-pro-m08", 7002));
hostAndPortSet.add(new HostAndPort("bigdata-pro-m09", 7001));
hostAndPortSet.add(new HostAndPort("bigdata-pro-m09", 7002));
// 2. 創建JedisPoolConfig物件,用于配置Redis連接池配置
JedisPoolConfig config = new JedisPoolConfig();
// 指定最大空閑連接為10個
config.setMaxIdle(10);
// 最小空閑連接5個
config.setMinIdle(5);
// 最大等待時間為3000毫秒
config.setMaxWaitMillis(3000);
// 最大連接數為50
config.setMaxTotal(50);
// 3. 創建JedisCluster物件
jedisCluster = new JedisCluster(hostAndPortSet);
}
@Test
public void clusterOpTest(){
// 設定一個key
jedisCluster.set("pv", "1");
// 獲取key
System.out.println(jedisCluster.get("pv"));
}
@AfterTest
public void afterTest(){
try {
jedisCluster.close();
} catch (IOException e) {
System.out.println("關閉Cluster集群連接失敗!");
e.printStackTrace();
}
}
}
10.7 Redis集群面試題
從Redis 3.0發布提供Redis Cluster以后,經歷Redis 4.x、Redis5.x和Redis 6.x一系列版本,Redis Cluster更加成熟、穩定,推薦企業使用此種架構,通常公司也是使用此種架構,如果使用Redis Cluster集群,面試中碰到的問題有一些坑,還望注意,
- 問題一:Redis的多資料庫機制,了解多少?

- 問題二:懂Redis的批量操作么?

- 問題三:Redis集群機制中,你覺得有什么不足的地方嗎?

- 問題四:在Redis集群模式下,如何進行批量操作?

- 問題五:懂Redis事務么?

11. Redis高頻面試題
在應用程式和MySQL資料庫中建立一個中間層:Redis快取,通過Redis快取可以有效減少查詢資料庫的時間消耗,但是引入redis又有可能出現快取穿透、快取擊穿、快取雪崩等問題,

11.1 快取穿透
快取穿透: key對應的資料在資料源并不存在,每次針對此key的請求從快取獲取不到,請求都會到資料源,從而可能壓垮資料源,
一言以蔽之:查詢Key,快取和資料源都沒有,頻繁查詢資料源
比如用一個不存在的用戶id獲取用戶資訊,無論論快取還是資料庫都沒有,若黑客利用此漏洞進行攻擊可能壓垮資料庫,
解決快取穿透的方案主要有兩種:
- 方案一:當查詢不存在時,也將結果保存在快取中,但是這可能會存在一種問題:大量沒有查詢結果的請求保存在快取中,這時我們就可以將這些請求的key設定得更短一些;
- 方案二:提前過濾掉不合法的請求,可以使用Redis中布隆過濾器:布隆過濾器可以快速地過濾掉快取中不存在的key,但是有一個問題:布隆過濾器不能準確地判斷這個已經存在的key真的存在,
11.2 快取擊穿
快取擊穿: key對應的資料庫存在,但在redis中過期,此時若有大量并發請求過來,這些請求發現快取過期一般都會從后端DB加載資料并回設到快取,這個時候大并發的請求可能會瞬間把后端DB壓垮,
一言以蔽之:查詢Key,快取過期,大量并發,頻繁查詢資料源
業界比較常用的做法:使用互斥鎖,簡單地來說,就是在快取失效的時候(判斷拿出來的值為空),不是立即去load db(查詢資料庫),而是先使用快取工具的某些帶成功操作回傳值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,就是只讓一個執行緒構建快取,其他線程等待構建快取的執行緒執行完,重新從快取獲取資料,
String get(String key) {
String value = redis.get(key);
if (value == null) {
// 如果key不存在,則設定為1
if (redis.setnx(key_mutex, "1")) {
// 設定key的過期時間為3分鐘
redis.expire(key_mutex, 3 * 60)
// 從db中加載資料,但注意:只有一個執行緒能進入到這里,其他執行緒訪問的時候已有課key_mutex
value = db.get(key);
// 從資料庫中加載成功,則設定對應的資料
redis.set(key, value);
redis.delete(key_mutex);
} else {
//其他執行緒休息50毫秒后重試
Thread.sleep(50);
get(key);
}
}
}
通過sexnx(“互斥鎖”, 1)
- sexnx表示如果key不存在的時候,才會設定一個key,如果存在就直接回傳
- 這種方式可以確保只有一個執行緒能夠進入到加載資料庫的邏輯中
11.3 快取雪崩
快取雪崩: 當快取服務器重啟或者大量快取集中在某一個時間段失效,會出現大量并發去直接訪問資料庫,導致資料庫的壓力過大,系統崩潰,
一言以蔽之:快取不可用(服務器重啟或快取失效),頻繁查詢資料源
與快取擊穿的區別在于這里針對很多key快取,前者則是某一個key,快取正常從Redis中獲取,示意圖如下:

快取失效瞬間示意圖如下:

快取失效時的雪崩效應對底層系統的沖擊非常可怕!大多數系統設計者考慮用加鎖或者佇列的方式保證來保證不會有大量的執行緒對資料庫一次性進行讀寫,從而避免失效時大量的并發請求落到底層存盤系統上,還有一個簡單方案就時將快取失效時間分散開,比如可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個快取的過期時間的重復率就會降低,就很難引發集體失效的事件,
解決辦法:
- 不能讓所有的key集中在某一個時刻失效,可以將過期時間設定為隨機
- 不然后臺系統直接操作資料庫,通過訊息佇列來隔離業務系統和資料
11.4 Redis的命名規范是?
- 使用統一的命名規范
- 一般使用業務名(或資料庫名)為前綴,用冒號分隔,例如,業務名:表名:id,
- 例如:shop:usr:msg_code(電商:用戶:驗證碼)
- 控制key名稱的長度,不要使用過長的key
- 在保證語意清晰的情況下,盡量減少Key的長度,有些常用單詞可使用縮寫,例如,user縮寫為u,messages縮寫為msg,
- 名稱中不要包含特殊字符
- 包含空格、單雙引號以及其他轉義字符
以上內容僅供參考學習,如有侵權請聯系我洗掉!
如果這篇文章對您有幫助,左下角的大拇指就是對博主最大的鼓勵,
您的鼓勵就是博主最大的動力!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/254513.html
標籤:其他
上一篇:紅黑樹(Red Black Tree)相關概念以及不同添加操作的詳解
下一篇:樂優商城day08 Linux All mirror URLs are not using ftp, http[s] or file.問題解決
