文章目錄
- 一、Nosql與Redis概述
- 二、Redis6安裝與使用
- 三、常用五大資料類型
- 3.1、Redis key操作
- 3.2、Redis字元串(String)
- 3.3、Redis清單(List)
- 3.4、Redis集合(Set)
- 3.5、Redis哈希(Hash)
- 3.6、Redis有序集合(Zset)
- 四、Redis6配置檔案詳解
- 五、Redis6的釋出和訂閱
- 六、Redis6新資料類型
- 6.1、Bitmaps
- 6.2、HyperLogLog
- 6.3、Geospatial
- 七、Jedis操作Redis6
- 八、Redis6與Spring Boot整合
- 九、Redis6的事務操作
- 9.1、Redis事務定義
- 9.2、Multi、Exec、discard
- 9.3、事務指令示範
- 9.4、事務沖突的問題
- 9.5、Redis事務三特性
- 十、Reids6持久化
- 10.1、簡介
- 10.2、RDB
- 10.3、AOF
- 十一、Redis6的主從複制
- 11.1、概念
- 11.2、優勢
- 11.3、主從複制的實作
- 11.4、test主從測試
- 11.5、常用三招
- 11.6、主從複制原理
- 11.7、哨兵模式(sentinel)
- 十二、Reids叢集
- 12.1、叢集概念
- 12.2、redis叢集搭建
- 12.3、redis叢集配置設定原則
- 12.4、slots(插槽)
- 12.5、故障恢複
- 12.6、叢集的jedis開發
- 十三、Redis6應用問題解決
- 13.1、緩存穿透(查不到)
- 13.2、緩存擊穿(量太大,突然緩存過期)
- 13.3、緩存雪崩
- 13.4、分布式鎖
- 十四、Redis6新功能
- 14.1、ACL(通路控制清單)
- 14.2、IO多線程
一、Nosql與Redis概述
1、Nosql的優勢
(1)使用nosql解決cpu與記憶體壓力
(2)使用nosql解決I/O壓力
2、Nosql資料庫的概述
(1)NoSql= Not Only SQL
(2)采用key-value模式存儲
(3)不遵循SQL标準
(4)性能遠超過SQL
3、使用場景
(1)資料的高并發讀寫
(2)海量資料讀寫
(3)資料可擴充性
4、不适用場景
(1)需要事務的支援
(2)基于sql的結構化查詢存儲,需要即席查詢
5、 Redis概述
(1)開源的key-value系統
(2)支援String、List、Set、zset、hash等資料類型
(3)資料庫支援push/pop/add/remove操作
(3)支援不同方式的排序
(4)可寫入記憶體也可以持久化
(5)主從同步功能
二、Redis6安裝與使用
1、官網下載下傳:放入liunx對應目錄内
https://redis.io/
2、安裝gcc編譯環境
yum install centos-release-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable devtoolset-8 bash
測試gcc版本
gcc -version

3、解壓縮:
tar
4、進入redis-6.2.4目錄執行make指令
5、執行安裝make install
6、驗證安裝成
cd
7、相關軟體介紹:
(1)redis-benchmar:性能測試工具
(2)redis-check-aof:修改有問題的AOF
(3)redis-check-rdb:修改有問題的rdb檔案
(4)redis-sentinel:Redis的叢集使用
(5)redis-server:Redis伺服器叢集使用
(6)redis-cli:用戶端,操作入口
8、前台啟動(不推薦)
9、背景啟動
(1) 複制配置檔案
cp
(2)修改參數配置,将daemonize no改為daemonize yes,讓服務支援在背景啟動
[root@localhost redis-6.2.4]# cd /opt/
[root@localhost opt]# vi redis.conf
(3)啟動redis
[root@localhost bin]# cd /usr/local/bin/
[root@localhost bin]# redis-server /opt/redis.conf
[root@localhost bin]# ps -ef|grep redis
(4)使用redis-cli測試
10、redis關閉
(1)redis-cli shutdown(進入終端shutdown也可以)
(2)kill -9 xxx(程序)
三、常用五大資料類型
3.1、Redis key操作
(1)檢視所有key:keys *
127.0.0.1:6379> keys *
(empty array)
(2)添加 key value:set
127.0.0.1:6379> set k1 lucy
OK
127.0.0.1:6379> set k2 mary
OK
127.0.0.1:6379> set k3 jack
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
(3)判斷key是否存在 exists
127.0.0.1:6379> exists k1
(integer) 1
127.0.0.1:6379> exists k4
(integer) 0
(4)檢視key的類型:type
127.0.0.1:6379> type
(5)删除key資料:del
127.0.0.1:6379> del k1
(integer) 1
(6)選擇非阻塞删除:unlink(異步删除)
127.0.0.1:6379> unlink k2
(integer) 1
(7)設定key的過期時間(秒):expire
127.0.0.1:6379> expire k3 10
(integer) 1
(9)檢視ttl過期時間(秒):ttl(-1永久不過期,-2已經過期)
127.0.0.1:6379> ttl k3
(integer)
(10)切換資料庫:select
127.0.0.1:6379[1]> select 0
(11)檢視目前資料庫的key數量:dbsize
127.0.0.1:6379> dbsize
(integer) 1
(12)清空目前庫内資料(慎用)
127.0.0.1:6379>
(13)通殺所有庫内資料(慎用)
127.0.0.1:6379>
3.2、Redis字元串(String)
(1)簡介:字元串,一個key對應一個value,是二進制安全的,是Redis最基本資料類型,value最多512M,底層為動态字元串,ArrayList
(2)設定值,相同key值覆寫:set
set
(3)擷取值:get
get k1
(4)追加值:append,傳回總長度
append k1 abcd
(5)擷取值的長度:strlen
strlen k1
(6)當key存在時操作:setnx,設定成功傳回1,設定失敗傳回0
setnx k1 v1
(7)将數字類型值+1/-1:incr/decr,原子性操作,不受多線程機制打斷。
incr k3
decr k3
(8)将key存儲的數字值遞增x/遞減x:incrby/decrby
incrby k3 10
decrby k3 5
msetnx k11 v11 k12 v12 k13 v13
msetnx k1 v11 k4 v4
(9)同時設定一個或多個key-value鍵值對:mset
mset k1 v1 k2 v2 k3 v3
(10)同時擷取一個或多個value:mget
mget k1 k2 k3
(11)設定多個key-value(當key都不存在時設定成功):msetnx
127.0.0.1:6379> msetnx k11 v11 k12 v12 k13 v13
(integer) 1
127.0.0.1:6379> msetnx k1 v11 k4 v4
(integer) 0
(12)擷取範圍的值(開始-結束):getrange
127.0.0.1:6379> getrange name 0 3
"luck"
(13)設定範圍的值(開始位置-覆寫):setrange,傳回總長度
127.0.0.1:6379> setrange name 3 abc
(integer) 8
(14)設定key的同時設定過期時間:setex
127.0.0.1:6379> setex age 20
(15)以新值換舊值(顯示舊值):getset
127.0.0.1:6379> getset name jack
"lucabcry"
3.3、Redis清單(List)
(1)簡介:單鍵多值的字元串清單,可以按照插入順序排序,底層為雙向連結清單(zipList(資料少時)->quickList(資料多時))
(2)從左邊/右邊插入一個或多個值:lpush/rpush,傳回數組長度
127.0.0.1:6379> lpush k1 v1 v2 v3
(integer) 3
127.0.0.1:6379> rpush k1 v7 v8 v9
(integer) 6
(3)按照索引下标(範圍)擷取元素,從左到右(0表示左邊第一個,-1表示右邊第一個):lrange
127.0.0.1:6379> lrange k1 0 -1
1) "v3"
2) "v2"
3) "v1"
4) "v7"
5) "v8"
6) "v9"
(4)從左邊或右邊取出一個值:lpop/rpop
127.0.0.1:6379> lpop k1
"v3"
127.0.0.1:6379> rpop k1
"v9"
(5)從k1清單右邊吐出一個值,插入到v2清單的左邊:rpoplpush
127.0.0.1:6379> rpoplpush k1 k2
"v1"
(6)按照索引下标(單值)擷取元素(從左到右):lindex
127.0.0.1:6379> lindex k2 0
"v1"
(7)擷取清單的長度:llen
llen k1
(8)在key對應的value前面/後面插入new value:linset before/after
127.0.0.1:6379> linsert k1 before "v3" "v31"
(integer) 3
127.0.0.1:6379> linsert k1 after "v2" "v21"
(integer) 4
(9)從左邊删除n個對應的value:lrem
127.0.0.1:6379> lrem k1 2 "new11"
(integer) 2
(10)将清單key下标為index的值替換成value:lset
127.0.0.1:6379> lset k1 1 "new31"
3.4、Redis集合(Set)
(1)Redis Set是String類型的無序集合,它的底層其實是一個value為null的hash表,value自動排重且無序
(2)将一個或多個元素加入到集合key中:sadd,已經存在元素将忽略
127.0.0.1:6379> sadd k1 v1 v2 v3
(integer) 3
(3)取出集合中的所有值:smembers
127.0.0.1:6379> smembers k1
1) "v3"
2) "v2"
3) "v1"
(4)判斷key集合中是否包含對應的value:sismember,1有0無
127.0.0.1:6379> sismember k1 v1
(integer) 1
(5)傳回集合中元素個數:scard
127.0.0.1:6379> scard k1
(integer) 3
(6)從集合中删除某一個或多個元素:srem
127.0.0.1:6379> srem k1 v1
(integer) 1
(7)随機從該集合吐出一個元素:spop
127.0.0.1:6379> spop k1
"v3"
(8)随機從集合中取出n個值,不會從集合中删除:srandmember
127.0.0.1:6379> srandmember k1 2
1) "v1"
2) "v2"
(9)把集合中的一個值從一個集合移動到另一個集合:smove
127.0.0.1:6379> smove k1 k2 v3
(integer) 1
(10)取兩個集合的交集/并集/差集(key1中存在,key2中不存在):sinter/sunoin/sdiff
127.0.0.1:6379> sinter k2 k3
1) "v4"
127.0.0.1:6379> sunion k2 k3
1) "v3"
2) "v5"
3) "v4"
4) "v7"
5) "v6"
127.0.0.1:6379> sdiff k2 k3
1) "v3"
2) "v5"
3.5、Redis哈希(Hash)
(1)簡介:是一個String類型的field和value的映射表,hash适合用來存儲對象。類似java中Map<String,Object>,底層為zipList(資料量少)或hashtable(資料量較多)
(2)向hash内添加資料(key-field-value):hset
127.0.0.1:6379> hset user:1001 id 1
(integer) 1
127.0.0.1:6379> hset user:1001 name zhangsan
(integer) 1
(3)從集合中取出資料(key-field):hget
127.0.0.1:6379> hget user:1001 id
"1"
127.0.0.1:6379> hget user:1001 name
"zhangsan"
(4)批量添加資料:hmet
127.0.0.1:6379> hmset user:1002 id 2 name lisi age 30
(5)判斷哈希表key中,field是否存在:hexists,1有0無
127.0.0.1:6379> hexists user:1002 id
(integer) 1
127.0.0.1:6379> hexists user:1002 name
(integer) 1
127.0.0.1:6379> hexists user:1002 gender
(integer) 0
(6)檢視哈希表中所有field:hkeys
127.0.0.1:6379> hkeys user:1002
1) "id"
2) "name"
3) "age"
(7)檢視哈希表内所有value:hvals
127.0.0.1:6379> hvals user:1002
1) "2"
2) "lisi"
3) "30"
(8)對應的key、field的值增量+1:hincrby
127.0.0.1:6379> hincrby user:1002 age 2
(integer) 32
(9)添加資料,僅當field不存在時:hsetnx
127.0.0.1:6379> hsetnx user:1002 age 40
(integer) 0
127.0.0.1:6379> hsetnx user:1002 gender 1
(integer) 1
3.6、Redis有序集合(Zset)
(1)簡介:有序的,類似set,沒有重複元素,關聯了score并可以進行排序,底層架構類似Map<String,value>,Zset底層為hash以及跳躍表
(2)将一個或多個元素以及score < key> < score1> < value1> < score2> < value2>加入到有序集合key中:zadd
127.0.0.1:6379> clear
127.0.0.1:6379> zadd topn 200 java 300 c++ 400 mysql 500 php
(integer) 4
(3)取出傳回有序集合key中,下标在< start>< stop>之間:zrange,自動按照score排序,[withscores]可以傳回評分
127.0.0.1:6379> zrange topn 0 -1
1) "java"
2) "c++"
3) "mysql"
4) "php"
127.0.0.1:6379> zrange topn 0 -1 withscores
1) "java"
2) "200"
3) "c++"
4) "300"
5) "mysql"
6) "400"
7) "php"
8) "500"
(4)取出score值介于min和max之間的成員,按照score從小到大排序:zrangebyscore < key>< min>< max>【withscores ][ limit offset count ]
127.0.0.1:6379> zrangebyscore topn 300 500 withscores
1) "c++"
2) "300"
3) "mysql"
4) "400"
5) "php"
6) "500"
(5)zrevngebyscore < key>< max>< min>【withscores ][ limit offset count ]
127.0.0.1:6379> zrevrangebyscore topn 500 300 withscores
1) "php"
2) "500"
3) "mysql"
4) "400"
5) "c++"
6) "300"
(6)為元素score加上增量:zincrby< key>< increment>< value>
127.0.0.1:6379> zincrby topn 50 java
"250"
(7)删除該集合中下,指定元素的值:zrem< key>< value>
127.0.0.1:6379> zrem topn php
(integer) 1
(8)統計該集合,分數區間内的元素個數:zcount< key>< min>< max>
127.0.0.1:6379> zcount topn 200 300
(integer) 2
(9)傳回該值在集合中的排名,從0開始:zrank< key>< value>
127.0.0.1:6379> zrank topn c++
(integer) 1
四、Redis6配置檔案詳解
1、units機關:
隻支援bytes,支援bit,不區分大小寫
2、INCLUDES:
包含其他的配置檔案
3、NETWORK:網絡相關配置
(1)bind:限定是否隻能本機連接配接等
(2)protected-mode:是否開啟本機保護模式,隻可本機通路
(3)port:預設端口号6379
(4)tcp-backlog:正在進行三次握手的隊列總和預設值為511
(5)timeout:逾時時間預設0,永不逾時
(6)tcp-keepalive:檢測心跳時間預設300秒
(7)daemonize:是否支援背景啟動
(8)pidfile:儲存對應的程序号檔案
(9)loglevel:儲存日志的級别
(10)logfile:設定日志的路徑
(11)databases:預設使用16個庫
(12)Security密碼設定:
- foobared 取消注釋,設定對應的密碼資訊
(13)LIMITS限制:
- maxclients:最大連接配接數,預設10000
(14)maxmemory:記憶體上限:
五、Redis6的釋出和訂閱
1、釋出與訂閱:
(1)發送者:pub發送消息
(2)訂閱者:sub接受消息
redis用戶端可以訂閱任意數量的頻道
2、釋出訂閱流程
(1)用戶端可以訂閱頻道
(2)當這個頻道釋出消息後,消息就會發送給訂閱的用戶端
3、釋出訂閱的指令行實作
(1)打開一個用戶端訂閱channel1
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
(2)打開另一個用戶端,給channel1釋出消息hello
127.0.0.1:6379> publish channel1 hello
(integer) 1
(3)打開第一個用戶端,可以看到發送的消息
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello"
六、Redis6新資料類型
6.1、Bitmaps
(1)簡介:實作對字元串的位的操作的字元串。是一個以位位單元的數組,數組每個單元隻能存儲0與1,下标與偏移量,與set相比節省gongjinaq
(2)設定Bitmaps中某個偏移量的值(0或1):setbit< key>< offset>< value>
127.0.0.1:6379> setbit users:20210101 1 1
(integer) 0
127.0.0.1:6379> setbit users:20210101 6 1
(integer) 0
127.0.0.1:6379> setbit users:20210101 11 1
(integer) 0
127.0.0.1:6379> setbit users:20210101 15 1
(integer) 0
127.0.0.1:6379> setbit users:20210101 19 1
(integer) 0
(3)擷取Bitmaps中某個偏移量的值:getbit< key>< offset>
127.0.0.1:6379> getbit users:20210101 1
(integer) 1
(4)統計字元串被設定位1的bit數量:bitcount【begin][end]
127.0.0.1:6379> bitcount users:20210101
(integer) 5
127.0.0.1:6379> bitcount users:20210101 1 5
(integer) 3
(5)複合操作(交集/并集/非/異或):bitop and/or/not/xor
設定初始資料:
127.0.0.1:6379> setbit unique:users:20201104 1 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201104 2 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201104 5 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201104 9 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201103 0 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201103 1 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201103 4 1
(integer) 0
127.0.0.1:6379> setbit unique:users:20201103 9 1
(integer) 0
計算出兩天都通路過網站的使用者數量:(1與9号使用者兩天都通路了)
127.0.0.1:6379> bitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104
(integer) 2127.0.0.1:6379> bitcount unique:users:and:20201104_03
(integer) 2
計算出任意一天都通路過網站的使用者數量
127.0.0.1:6379> bitop or unique:users:or:20201104_03 unique:users:20201103 unique:users:20201104
(integer) 2
127.0.0.1:6379> bitcount unique:users:or:20201104_03
(integer) 6
6.2、HyperLogLog
(1)簡介:适用于獨立IP數、搜尋記錄等需要去重和計數時。
(2)添加指定元素到HyperLogLog:pdadd< key>< element>[element],1成功0失敗
127.0.0.1:6379> pfadd program "java"
(integer) 1
127.0.0.1:6379> pfadd program "php"
(integer) 1
127.0.0.1:6379> pfadd program "java"
(integer) 0
127.0.0.1:6379> pfadd program "c++" "mysql"
(integer) 1
(3)統計HLL的pfcount< key>
127.0.0.1:6379> pfcount program
(integer) 4
(4)将一個或多個HLL合并的結果存儲在另一個HLL:pfmeger
127.0.0.1:6379>
6.3、Geospatial
(1)簡介:redis3.2後增加GEO類型,即地理資訊的縮寫,提供了經緯度的設定、查詢、範圍查詢、舉例查詢、經緯度hash等
(2)加地理位置(經度、緯度、名稱):geoadd< key>< longitude>< latitude>< member>[< longitude>< latitude>< member>]…
有效經緯度:-180°-180°,緯度:-85.05112878°-85.05112878°
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
(integer) 3
(3)擷取指定地區的坐标值:geoos< key>< member>[member]…
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.38000041246414185"
2) "39.90000009167092543"
(4)擷取兩個位置之間的直線距離:geodist< key>< member2>< member2><機關>
127.0.0.1:6379> geodist china:city beijing shanghai km
"1068.1535"
(5)以給定的經緯度為中心,找出某一半徑内的元素:georadius< key>< longitude>< latitude>radius m|km|ft|mi
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqing"
2) "shenzhen"
七、Jedis操作Redis6
1、idea建立maven工程
2、引入相關依賴:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
3、jedis連接配接redis測試(Maven)
package com.testbk.jedis;
import redis.clients.jedis.Jedis;
public class jedisDemo1 {
public static void main(String[] args) {
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//測試
String value = jedis.ping();
System.out.println(value);
}
}
顯示結果如下:
PONG
4、Jedis-API:操作key
//操作key
@Test
public void demo1(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.set("k1","v1");
jedis.set("k2","v2");
jedis.set("k3","v3");
//查詢所有key值
Set<String> keys = jedis.keys("*");
for(String key:keys){
System.out.println(key);
}
//根據key擷取value
String value = jedis.get("k1");
System.out.println("k1對應的value為:"+value);
}
檢視運作結果:
k3
k1
k2
k1對應的value為:v1
5、Jedis-API:操作String
//操作String
@Test
public void demo2(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加多個資料
jedis.mset("str1","v1","str2","v2","str3","v3");
//查詢所有key值
System.out.println(jedis.mget("str1","str2","str3"));
}
檢視運作結果:
[v1, v2, v3]
6、Jedis-API:操作List
//操作List
@Test
public void demo3(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.lpush("k1","lucy","mary","jack");
//查詢資料
List<String> value = jedis.lrange("k1", 0, -1);
System.out.println(value);
}
檢視運作結果:
[jack, mary, lucy]
7、Jedis-API:操作set
//操作set
@Test
public void demo4(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.sadd("name","luck","mary","jack");
//查詢資料
Set<String> names = jedis.smembers("name");
System.out.println(names);
}
檢視運作結果:
[jack, mary, luck]
8、Jedis-API:操作set
//操作set
@Test
public void demo4(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.sadd("orders","order1");
jedis.sadd("orders","order2");
jedis.sadd("orders","order3");
jedis.sadd("orders","order4");
//查詢資料
Set<String> orders1 = jedis.smembers("orders");
System.out.println(orders1);
//删除後再查詢
jedis.srem("orders","order1");
Set<String> orders2 = jedis.smembers("orders");
System.out.println(orders2);
}
檢視運作結果:
[order3, order4, order1, order2]
[order3, order4, order2]
9、Jedis-API:操作Hash
//操作Hash
@Test
public void demo5(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.hset("users","age","20");
//查詢資料
String hget = jedis.hget("users", "age");
System.out.println(hget);
}
檢視運作結果:
20
10、Jedis-API:操作Zset
//操作Zset
@Test
public void demo6(){
//建立Jedis對象,需要修改redis.conf的bind(注釋)與protected-mode(no)配置
Jedis jedis =new Jedis("192.168.37.8",6379);
//清空redis
jedis.flushDB();
//添加資料
jedis.zadd("china",100d,"shanghai");
//查詢資料
Set<String> china = jedis.zrange("china", 0, -1);
System.out.println(china);
}
檢視運作結果:
[shanghai]
八、Redis6與Spring Boot整合
1、idea建立springboot工程
2、pom檔案引入springboot-redis的兩個依賴
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.X集合redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
3、springboot配置檔案中配置redis相關内容
檔案位置為resources下面的application.properties
# Redis伺服器位址
spring.redis.host=192.168.37.8
# Redis伺服器連接配接端口
spring.redis.port=6379
# Redis伺服器連接配接密碼(預設為空)
spring.redis.password=
# Redis資料庫索引(預設為0)
spring.redis.database=0
# 連接配接逾時時間(毫秒)
spring.redis.timeout=1800000
# 連接配接池最大連接配接數(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=20
# 連接配接池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接配接池中的最大空閑連接配接
spring.redis.jedis.pool.max-idle=10
# 連接配接池中的最小空閑連接配接
spring.redis.jedis.pool.min-idle=0
4、建立redis配置類:
package com.testbk.redis_springboot.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解決查詢緩存轉換異常的問題
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解決亂碼的問題),過期時間600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
5、編寫RedisTestControll添加測試方法:
package com.testbk.redis_springboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis(){
//設定值到redis
redisTemplate.opsForValue().set("name","lucy");
//從redis擷取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
6、啟動類啟動Springboot類:RedisSpringbootApplication
顯示運作結果:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.1)
浏覽器通路驗證:http://localhost:8080/redisTest,顯示結果:
lucy
九、Redis6的事務操作
9.1、Redis事務定義
Redis事務是一個單獨的隔離操作:事務中的所有指令都會序列化、按順序的執行。事務在執行的過程中,不會被其他用戶端發送來的指令請求所打斷。
Redis事務的主要作用就是串聯多個指令防止别的指令插隊
9.2、Multi、Exec、discard
(1)基本概念
輸入Multi指令開始:輸入的指令都會依次進入指令隊列中,但不會執行,直到輸入Exec後,Redis會将之前的指令隊列中的指令依次執行。
組隊的過程中可以通過discard來放棄組隊
正常場景:
異常場景2種:
9.3、事務指令示範
(1)組隊-執行案例
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2)
(2)組隊-取消案例
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set a1 v1
QUEUED
127.0.0.1:6379(TX)> set a2 v2
QUEUED
127.0.0.1:6379(TX)>
(3)組隊-錯誤處理
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set b1 v1
QUEUED
127.0.0.1:6379(TX)> set b2 v2
QUEUED
127.0.0.1:6379(TX)> set b3
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> exec
(error)
(4)組隊-執行-錯誤處理
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set c1 v1
QUEUED
127.0.0.1:6379(TX)> incr c1
QUEUED
127.0.0.1:6379(TX)> set c2 v2
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3)
9.4、事務沖突的問題
場景:多個人同時使用一個賬戶,參加雙十一搶購,購買不同的商品,未加事務會産生沖突。
(1)悲觀鎖
每次拿資料适都認為别人會修改,是以每次在拿資料适都會上鎖,這樣别人想拿資料就會block直到它拿到鎖。傳統的關系型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖,讀鎖,寫鎖,都是操作前上鎖。
(2)樂觀鎖
每次拿資料的适合都認為别人不會修改,是以不會上鎖,但是在更新的适合會判斷一下在此期間别人有沒有取更新這個資料,可以使用版本号等機制,樂觀鎖适用于多讀的應用類型,這樣可以提高吞吐量。Redis就是利用check-and-set機制實作事務的。
(3)WATCH key[key …]
含義:在執行multi之前,先執行wath key1[key2] 可以監視一個或多個key,如果在事務執行之前這些key被其他指令所改動,那麼事務講被打斷。
舉例,同時打開兩個用戶端,都watch 同一個key,然後第一個視窗exec,第二個視窗再執行exec,被樂觀鎖住:
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> keys *
1) "balance"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby balance 10
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 110
127.0.0.1:6379>
127.0.0.1:6379> keys *
1) "balance"
127.0.0.1:6379> watch balance
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby balance 20
QUEUED
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379>
(4)UNWATCH:
取消指令對所有key的監視。
9.5、Redis事務三特性
(1)單獨的隔離操作:
事務種的所有名曆經都會序列化、按順利執行,事務在執行的過程中,不會被其他用戶端發送來的指令所打斷
(2)沒有隔離級别的概念:
隊列中的指令沒有送出之前不會實際被執行,因為事務送出前任何執行都不會被實際執行
(3)不保證原子性
事務中如果有一條指令執行失敗,其中的指令任然會被執行,沒有復原
十、Reids6持久化
10.1、簡介
兩種持久化方式
(1)RDB(Redis DataBase):記憶體中資料直接寫入檔案中
(2)AOF(Append Of File):以追加的行為把内容寫入檔案中
10.2、RDB
(1)概念:
指定的時間間隔内講記憶體中的資料集快照寫入磁盤,它恢複時可以将快照檔案直接讀到記憶體裡
(2)RDB持久化流程:
Redis會單獨建立fork一個子程序來進行持久化,先将資料寫入一個臨時檔案中,待持久化過程結束後,再用這個臨時檔案替換上次持久化的檔案,RDB方式比AOF檔案更加高效,缺點是最後一次持久化的資料可能丢失。
(3)Fork:寫時複制技術:新程序的所有資料(變量、環境變量、程式計數器等)數值都有原程序一緻。
(4)redis.conf配置内RDB相關配置(SNAPSHOTTING内配置)
rdb檔案名:dbfilename dump.rdb
檔案産生的路徑,預設值(啟動程式的位置):dir ./
dbfilename dump.rdb
dir ./
快照的時間間隔:
# save 3600 1
# save 300 100
# save 60 10000
設定手動持久化或自動持久化
save Vs bgsave:建議設定自動持久化
# save ""
Redis無法寫入磁盤的化,直接關掉Redis的寫操作,預設yes
stop-writes-on-bgsave-error yes
是否進行檔案壓縮:rdbcompre,預設yes
rdbcompression yes
檢查資料的完整性:rdbchecksum yes,預設yes
rdbchecksum yes
(5)RDB恢複備份檔案
将持久化的備份檔案恢複,重新redis,資料恢複
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> shutdown
not connected> exit
[root@localhost bin]# /usr/local/bin/redis-server /opt/redis.conf
[root@localhost bin]# /usr/local/bin/redis-cli
127.0.0.1:6379> keys *
1) "program"
2) "k100"
3) "k1"
4) "china:city"
另一個指令行恢複操作:
[root@localhost bin]# ll
total 18884
-rw-r--r--. 1 root root 92 Jun 14 09:57 dump.rdb
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb.bak
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20 06:16 redis-server
[root@localhost bin]#
[root@localhost bin]# rm -rf dump.rdb
[root@localhost bin]# mv dump.rdb.bak dump.rdb
10.3、AOF
(1)概念:
以日志形式來記錄每個寫操作(增量儲存),将Redis執行過的所有寫指令記錄下來(讀操作不記錄),隻許追加檔案但不可以改寫檔案,redis啟動之初會讀取該檔案重新建構數。
優點:備份機制更穩健,丢失資料機率更低,通過操作AOF檔案可以處理誤操作
缺點:比RDB占用更多磁盤,恢複備份速度慢,每次讀寫都同步的話有性能壓力
(2)AOF持久化流程
用戶端在請求寫指令時會被append追加到AOF緩沖區内
AOF緩沖區根據AOF持久化政策[always,everysec,no]将操作sync同步到磁盤的AOF檔案中
AOF檔案大小超過重寫政策或手動重寫時,會對AOF檔案rewrite重寫,壓縮AOF檔案容量
Redis服務重新開機時,會重新load加載AOF檔案中的寫操作達到資料恢複的目的
(3) redis.conf關于AOF相關配置:
開啟AOF預設:,預設不開啟no,開啟需要修改為yes
appendonly no
AOF生成檔案名:預設為appendonly.aof,生成的路徑同RDB
appendfilename "appendonly.aof"
儲存檔案生成,同時開啟AOF與RDB時,系統會使用AOF儲存資料:
(4)AOF使用與恢複
執行操作指令:appendonly.aof檔案追加了内容:
127.0.0.1:6379> set k11 v11
OK
127.0.0.1:6379> set k12 v12
OK
127.0.0.1:6379> set
-rw-r--r--. 1 root root 0 Jun 14 11:29 appendonly.aof
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20 06:16 redis-server
[root@localhost bin]#
[root@localhost bin]#
[root@localhost bin]#
[root@localhost bin]# ll
total 18884
-rw-r--r--. 1 root root 116 Jun 14 11:33 appendonly.aof
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20
AOF備份恢複
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k13"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> shutdown
not connected> exit
[root@localhost bin]# redis-server /opt/redis.conf
[root@localhost bin]# redis-cli
127.0.0.1:6379> keys *
1) "k11"
2) "k13"
3) "k12"
[root@localhost bin]# cp -r appendonly.aof appendonly.aof.bak
[root@localhost bin]# rm -rf appendonly.aof
[root@localhost bin]# mv appendonly.aof.bak appendonly.aof
異常恢複:
當AOF檔案損壞,可通過如下指令執行檔案修複:
redis-check-aof --fix appendonly.aof
(5)AOF同步頻率設定
設定始終同步:appendfsync always,性能較差,但是資料完整性好
每秒同步:appendfsync everysec
把同步時機交給作業系統:appendfsync no
# appendfsync always
appendfsync everysec
# appendfsync no
(6)Rewrite壓縮
當AOF檔案大小超過所設定的門檻值時(>=64M*2),Redis會啟動AOF的内容壓縮,隻保留可以恢複資料的最小指令集,可以使用指令bgrewriteaof開啟此功能。
使用fork子程序将原來檔案重寫,把rdb的快照已二進制形式附在新的aof頭部,作為已有的曆史資料據,替換原有的流水賬操作。
no-appendfsync-on-rewrite no
十一、Redis6的主從複制
11.1、概念
主機資料更新後根據配置和政策,自動同步到備機的master/slaver機制,Master以寫為主,Slave以讀為主
11.2、優勢
(1)讀寫分離,性能擴充
(2)容災快速恢複
11.3、主從複制的實作
(1)建立/myredis檔案夾
(2)複制redis.conf配置檔案到檔案夾中
(3)配置一主兩從,建立三個配置檔案
redis6379.conf
redis6380.conf
redis6381.conf
(4)在三個配置檔案中寫入内容
配置先關閉AOF或改名,配置redis6379、redis6380、redis6381配置檔案
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
include /myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
include /myredis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
(5)啟動三台redis伺服器并檢視程序:
[root@localhost myredis]# redis-server redis6379.conf
[root@localhost myredis]# redis-server redis6380.conf
[root@localhost myredis]# redis-server redis6381.conf
[root@localhost myredis]# ps -ef | grep redis
root 3906 1 0 11:53 ? 00:00:08 redis-server *:6379
root 4024 1 0 13:07 ? 00:00:00 redis-server *:6380
root 4030 1 0 13:07 ? 00:00:00 redis-server *:6381
root 4036 3743 0 13:07 pts/3 00:00:00 grep --color=auto redis
(6)檢視三個redis主機運作情況
[root@localhost myredis]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
(7)隻配從(庫),不配主(庫):
slaveof < ip>< port>:成為某個執行個體的從伺服器:
在6380與6381上執行(讓它們成為6379的從機)
127.0.0.1:6380> slaveof 127.0.0.1 6379
127.0.0.1:6381> slaveof 127.0.0.1 6379
11.4、test主從測試
場景:主服務6379做寫操作,檢視從伺服器,且從伺服器不能做寫操作
11.5、常用三招
(1)一主兩從:
從伺服器挂掉後,重新開機會變成主服務,需要重新加入,資料會從主伺服器重新複制一份
主伺服器挂掉後,從伺服器還是從伺服器,主伺服器重新開機後還是主伺服器
(2)薪火相傳:
從伺服器可以配置為别的從伺服器的從伺服器
(3)反客為主:
當一個master當機後,可以讓一台slave升為master,其後面的slave不用做任何修改
slaveof no one
11.6、主從複制原理
(1)當從伺服器連上主伺服器之後,從伺服器向主伺服器發送進行資料同步消息
(2)主伺服器接到從伺服器發送過來同步消息,把主伺服器資料進行持久化,生成RDB檔案,把RDB檔案發送給從伺服器,從伺服器拿到RDB進行讀取
(3)每次主伺服器進行寫操作之後,和從伺服器進行數同步
11.7、哨兵模式(sentinel)
(1)含義:(自動選舉老大的模式)
反客為主的自動版,能否背景監控主機是否故障,如果故障了根據投票數自動将從庫轉換為主庫。
哨兵模式是一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,作為程序,它會獨立運作。其原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控運作的多個Redis執行個體。
這裡的哨兵有兩個作用:
- 通過發送指令,讓Redis伺服器傳回監控其運作狀态,包括主伺服器和從伺服器。
- 當哨兵監測到master當機,會自動将slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。
然而一個哨兵程序對Redis伺服器進行監控,可能會出現問題,為此,我們可以使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就形成了多哨兵模式。
假設主伺服器當機,哨兵1先檢測到這個結果,系統并不會馬上進行failover過程,僅僅是哨兵1主觀的認為主伺服器不可用,這個現象成為主觀下線。
當後面的哨兵也檢測到主伺服器不可用,并且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行failover[故障轉移]操作。切換成功後,就會通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器實作切換主機,這個過程稱為客觀下線。
(2)啟動哨兵模式:
如一主二從的場景下,在myredis檔案夾下建立sentinel.conf,配置哨兵模式,啟動哨兵
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster為監控對象别名,1為至少多少個哨兵同意遷移的數量
[root@localhost myredis]# redis-sentinel sentinel.conf
哨兵預設端口為26379
(3)當主機挂掉,從伺服器選舉成為主伺服器:
(4)再次啟動原來的主伺服器,變為從伺服器
(5)配置哨兵的優先級:
redis.conf配置檔案中:slave-priority 100,值越小優先級越高。
當優先級相同時選舉偏移量最大的
當偏移量一樣的時選舉runid最小的(随機)
十二、Reids叢集
12.1、叢集概念
Redis叢集實作了對Redis的水準擴容,即啟動N個redis節點,将整個資料庫分布存儲在這N個節點中,每個節點存儲總數量的1/N。
Redis叢集通過分區(partition)來提供一定程式的可用性(avaliability):即使叢集中有一部分節點失效或者無法通訊,叢集也可以繼續處理指令請求。
Redis叢集的優勢:實作擴容、分攤壓力、無中心配置相對簡單
Redis叢集的不足:多鍵操作不被支援、多鍵事務不支援(lua腳本不支援)、技術出現較晚,已有redis服務遷移到叢集複雜度較高
12.2、redis叢集搭建
(1)清除原備份檔案,并将appendonly配置關閉
(2)制作6個執行個體,6379,6380,6381,6389,6390,6391
[root@localhost myredis]# vi redis6379.conf
include /myredis/redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
#開啟叢集模式
cluster-enabled yes
#設定節點的名字
cluster-config-file nodes-6379.conf
#逾時切換時間
cluster-node-timeout 15000
同樣方式複制并修改(VI指令替換操作:%s/6379/6380):
[root@localhost myredis]# cp redis6379.conf redis6380.conf
[root@localhost myredis]# ll
total 104
-rw-r--r--. 1 root root 244 Jun 27 17:42 redis6379.conf
-rw-r--r--. 1 root root 244 Jun 27 17:44 redis6380.conf
-rw-r--r--. 1 root root 93721 Jun 27 17:38 redis.conf
-rw-r--r--. 1 root root 392 Jun 27 15:45 sentinel.conf
[root@localhost myredis]# cp redis6379.conf redis6381.conf
[root@localhost myredis]# cp redis6379.conf redis6389.conf
[root@localhost myredis]# cp redis6379.conf redis6390.conf
[root@localhost myredis]# cp redis6379.conf redis6391.conf
[root@localhost myredis]# vi redis6380.conf
[root@localhost myredis]# vi redis6381.conf
[root@localhost myredis]# vi redis6381.conf
[root@localhost myredis]# vi redis6389.conf
[root@localhost myredis]# vi redis6390.conf
[root@localhost myredis]# vi redis6391.conf
(3)啟動6個redis服務
[root@localhost myredis]# redis-server redis6379.conf
[root@localhost myredis]# redis-server redis6380.conf
[root@localhost myredis]# redis-server redis6381.conf
[root@localhost myredis]# redis-server redis6389.conf
[root@localhost myredis]# redis-server redis6390.conf
[root@localhost myredis]# redis-server redis6391.conf
[root@localhost myredis]# ps -ef |grep redis
root 1596 1 0 15:33 ? 00:00:11 redis-server *:6380
root 1603 1 0 15:33 ? 00:00:10 redis-server *:6381
root 1644 1 0 15:42 ? 00:00:18 redis-sentinel *:26379 [sentinel]
root 1661 1 0 15:52 ? 00:00:09 redis-server *:6379
root 1926 1 0 17:53 ? 00:00:00 redis-server *:6389 [cluster]
root 1932 1 0 17:53 ? 00:00:00 redis-server *:6390 [cluster]
root 1938 1 0 17:53 ? 00:00:00 redis-server *:6391 [cluster]
(4)将6個節點合成一個叢集
[root@localhost myredis]# cd /opt/redis-6.2.4/src/
[root@localhost src]# redis-cli --cluster create --cluster-replicas 1 192.168.37.8:6379 192.168.37.8:6380 192.168.37.8:6381 192.168.37.8:6389 192.168.37.8:6390 192.168.37.8:6391
[ERR] Node 192.168.37.8:6379 is not configured as a cluster node.錯誤需要将redis.conf下的cluster-enabled yes 的注釋打開
配置6379、6380、6381為master,6389、6390、6391為slaver,yes确認
配置完成:
(5)連接配接叢集并檢視:
[root@localhost src]# redis-cli -c -p 6379
127.0.0.1:6379>
12.3、redis叢集配置設定原則
配置設定原則:盡量保證每個主資料運作在不同的IP位址,每個主庫和從庫不在一個IP位址上
選項 --cluster-replicas 1表示我們希望為叢集中的每個主節點建立一個從節點。
12.4、slots(插槽)
一個Redis叢集包含16384個插槽(hash slot),資料庫中每個鍵都屬于這16384個插槽的其中之一。
叢集使用公式CRC16(key)%16384來計算鍵key屬于哪個槽,其中CRC176(key)語句用于計算鍵key和CRC16校驗和。
叢集中的每個節點負責處理一部分插槽。
添加資料,即往插槽内添加資料
添加多個資料時會報錯
如要插入多條資料,需要分組操作
計算key對應的插槽值
cluster keyslot k1
計算對應插槽中的數值數量(隻能看到屬于自己叢集的插槽)
cluster countkeysinslot 12706
傳回操作中n個數值
cluster getkeysinslot 449 1
12.5、故障恢複
(1)使6379叢集shutdown,6380從機替換變為主機
(2)主-從均挂掉的情況
cluster-require-full-coverage為yes,那麼某一段插槽主從挂掉,整個叢集都挂掉
cluster-require-full-coverage為no,那麼某一段插槽主從挂掉,該段叢集的插槽不能提供服務,其他插槽依然可以提供服務
12.6、叢集的jedis開發
package com.testbk.jedis;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
/**
* 示範redis叢集操作
*/
public class RedisClusterDemo {
public static void main(String[] args) {
//建立對象
HostAndPort hostAndPort = new HostAndPort("192.168.37.8", 6379);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
//進行操作
jedisCluster.set("b1","value1");
String value = jedisCluster.get("b1");
System.out.println(value);
//關閉jedis連接配接
jedisCluster.close();
}
}
檢視運作結果:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
value1
十三、Redis6應用問題解決
13.1、緩存穿透(查不到)
(1)現象:
應用伺服器壓力變大
redis命中率降低
一直查詢資料庫
(2)造成原因:
redis查詢不到資料庫
出現很多非正常url通路
(3)解決方案:
對空值進行緩存:緩存空結果null值
設定通路白名單:使用bitmaps類型定義一個可以通路的名單,每次通路時進行攔截
布隆過濾器:(Bloom Filter)1970年布隆提出的,它實際上是一個很長的二進制向量(位圖)和一系列随機映射函數(哈希函數)。布隆過濾器用于檢索一個元素是否在一個集合,但是也存在誤識别的情況
進行實時監控:當發現Redis的命中率開始急速降低,需要排查通路對象和通路的資料,和運維人員配合,設定黑名單
13.2、緩存擊穿(量太大,突然緩存過期)
(1)現象:
資料庫的通路壓力瞬時增加、redis裡面沒有出現大量key過期、redis正常運作。
這裡需要注意和緩存擊穿的差別,緩存擊穿,是指一個key非常熱點,在不停的扛着大并發,大并發集中對這一個點進行通路,當這個key在失效的瞬間,持續的大并發就穿破緩存,直接請求資料庫,就像在一個屏障上鑿開了一個洞。
當某個key在過期的瞬間,有大量的請求并發通路,這類資料一般是熱點資料,由于緩存過期,會同時通路資料庫來查詢最新資料,并且回寫緩存,會導使資料庫瞬間壓力過大。
(2)造成原因:
redis某個key過期了,而此時還有大量的通路在使用這個key
(3)解決方案:
預先設定熱門資料:在redis高峰通路之前,把一些熱門資料提前存入到redis内,加大這些熱門資料key的時長
實時調整:現場監控哪些資料熱門,實時調整key的過期時長
使用鎖的方式:設定排它鎖:在根據key獲得的value值為空時,先鎖上,再從資料庫加載,加載完畢,釋放鎖。若其他線程發現擷取鎖失敗,則睡眠一段時間後重試
13.3、緩存雪崩
(1)現象:
資料庫壓力變大、伺服器崩潰。
緩存雪崩,是指在某一個時間段,緩存集中過期失效。Redis 當機!
(2)造成原因:
在極少的時間段,查詢大量key的集中過期情況。
産生雪崩的原因之一,比如雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設緩存一個小時。那麼到了淩晨一點鐘的時候,這批商品的緩存就都過期了。而對這批商品的通路查詢,都落到了資料庫上,對于資料庫而言,就會産生周期性的壓力波峰。于是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會挂掉的情況。
其實集中過期,倒不是非常緻命,比較緻命的緩存雪崩,是緩存伺服器某個節點當機或斷網。因為自然形成的緩存雪崩,一定是在某個時間段集中建立緩存,這個時候,資料庫也是可以頂住壓力的。無非就是對資料庫産生周期性的壓力而已。而緩存服務節點的當機,對資料庫伺服器造成的壓力是不可預知的,很有可能瞬間就把資料庫壓垮。
(3)解決方案:
建構多級緩存架構:nginx緩存+redis緩存+其他緩存(ehcache等)
使用鎖或隊列:用加鎖或者隊列保證不會有大量的線程對資料庫一次性進行讀寫,進而避免失效時大量并發請求落到底層存儲系統上,不适用于高并發情況
設定過期标志更新緩存:記錄緩存資料是否過期(設定提前量),如果過期會觸發通知另外的線程在背景更新實際key的緩存
将緩存失效實際分散開:可以在原有的失效時間基礎上增加一個随機值,比如1-5分鐘随機,這樣每一個緩存的過期時間的重複率就會降低,很難引發集體失效的事件
13.4、分布式鎖
(1)解決問題:
随着業務發展的需要,原單體單機部署的系統被演化成分布式叢集系統後,由于分布式多線程,多程序并且分布在不同機器上,這将使原單機部署的情況下并發控制鎖政策失效,單純的Java API并不能提供分布式鎖的能力,為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的通路,這就是分布式鎖要解決的問題。
(2)分布式鎖主流的實作方案
基于資料庫實作分布式鎖
基于緩存Redis等
基于Zookeeper
(3)每一種分布式鎖解決方案都有各自的優缺點:
性能:redis最高
可靠性:zookeeper最高
這裡介紹的是基于redis實作的分布式鎖
(4)實作方案:使用redis實作分布式鎖
使用setnx實作分布式鎖:
127.0.0.1:6379> setnx users 10
删除key釋放setnx分布式鎖:
192.168.37.8:6381> del users
使用setnx設定分布式鎖,再設定過期時間,過期後自動解鎖
192.168.37.8:6381> setnx users 10
(integer) 1
192.168.37.8:6381> expire users 10
(integer) 1
192.168.37.8:6381> ttl users
為防止上鎖後redis機器故障,使用set nx ex上鎖同時設定過期時間:(原子操作)
set users 10 nx ex 12
(5)java代碼實作分布式鎖
springboot編寫的代碼如下:
package com.testbk.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
//1擷取鎖,sentne,并設定鎖的過期時間3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",3, TimeUnit.SECONDS);
//2擷取鎖成功、查詢num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判斷numb為空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就轉成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4釋放鎖,del
redisTemplate.delete("lock");
}
else {
//3擷取鎖失敗,每隔0.1秒再擷取
try{
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//設定值到redis
redisTemplate.opsForValue().set("name","lucy");
//從redis擷取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
運作,并在管理台測試,首先建立key->num指派為0
127.0.0.1:6379> set num "0"
OK
127.0.0.1:6379> get num
"0"
另一個視窗通過ab壓力測試工具進行測試,1000個請求,100個請求并發,并且觸發分布式鎖
ab -n 1000 -c 100 http://192.168.31.12:8080/redisTest/testLock
檢視num,值累加到1000
127.0.0.1:6379> get num
"1000"
(6)解決釋放錯鎖的問題(防誤删)
第一步:通過uuid表示不同的操作
set lock uuid nx ex 10
第二部:釋放鎖時候,首先判斷目前uuid和要适當鎖uuid是否一樣
改造測試代碼如下:
package com.testbk.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1擷取鎖,sentne,并設定鎖的過期時間3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3, TimeUnit.SECONDS);
//2擷取鎖成功、查詢num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判斷numb為空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就轉成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4釋放鎖,del
//判斷比較uuid值是否一樣
Object lockUuid = redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)){
redisTemplate.delete("lock");
}
}
else {
//3擷取鎖失敗,每隔0.1秒再擷取
try{
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//設定值到redis
redisTemplate.opsForValue().set("name","lucy");
//從redis擷取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
(7)解決删除操作非原子性問題:
場景:當比較uuid一樣,當a删除操作的時候,正要删除還沒有删除時,鎖到了過期時間自動釋放,此時b上了這把鎖,會導緻a把b的鎖删除掉。
可以通過定義lua腳本優化代碼
package com.testbk.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
//1聲音一個uuid,講作為一個value放入我們的key對應的值中
String uuid = UUID.randomUUID().toString();
//2定義一個鎖:lua腳本可以使用同一把鎖,來實作删除!
String skuId = "25";
String locKey= "lock" + skuId;
//3取鎖,sentne,并設定鎖的過期時間3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey,uuid,3, TimeUnit.SECONDS);
//2擷取鎖成功、查詢num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判斷numb為空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就轉成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",String.valueOf(++num));
/*使用lua腳本來鎖*/
//定義lua腳本
String script = "if redis.call('get',KEY[1]) == ARGV[1] then return redis.call('del',KEY[1]) else return 0 end";
//使用redis執行lua腳本
DefaultRedisScript<Long> redisScript= new DefaultRedisScript<>();
redisScript.setScriptText(script);
//設定一下傳回類型為Long
//因為删除的時候,傳回為0,給其封裝為資料類型,如果不封裝那麼預設傳回String
//那麼傳回字元串與0會發成錯誤
redisScript.setResultType(Long.class);
//第一個要是script腳本,第二個需要判斷的key,第三個就是key對應的值
redisTemplate.execute(redisScript, Arrays.asList(locKey),uuid);
}
else {
//3擷取鎖失敗,每隔0.1秒再擷取
try{
Thread.sleep(1000);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//設定值到redis
redisTemplate.opsForValue().set("name","lucy");
//從redis擷取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
(8)總結-分布式鎖可用性需要同時滿足四個條件:
- 互斥性:在任意時刻,隻有一個用戶端能持有鎖。
- 不發生死鎖:即使有一個用戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他用戶端能加鎖。
- 解鈴還須系鈴人:加鎖和解鎖必須是同一個用戶端,用戶端自己不能把别人加的鎖給解了。
- 加鎖和解鎖必須具有原子性。
十四、Redis6新功能
14.1、ACL(通路控制清單)
(1)簡介
Access Control List:Redis6提供ACL功能對使用者進行更細粒度的權限控制。
(2)指令
使用acl list展現使用者權限清單
127.0.0.1:6379>
使用acl cat檢視添權重限的指令類别
檢視目前acl使用者:
127.0.0.1:6379> acl whoami
添加acl使用者:(可用,包含密碼,可操作包含cached的key,隻能get指令操作)
127.0.0.1:6379> acl setuser mary on >password ~cached:* +get
切換使用者,進行測試:
127.0.0.1:6379>
14.2、IO多線程
(1)簡介:
Redis6加入了多線程:值得是用戶端互動部分的網絡IO互動處理模闆多線程,而非執行指令多線程,Redis6執行指令依然是單線程的。
(2)原理架構:
Redis的多線程部分隻是使用者處理網路資料的讀寫和協定解析,執行指令依然是單線程的,因為是需要控制key、lua、事務。
多線程預設是不開啟的,需要配置檔案中配置
io-threads 4
3、工具支援cluster