天天看點

Redis【第二篇總結】

文章目錄

  • ​​一、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      
Redis【第二篇總結】

3、解壓縮:

tar      
Redis【第二篇總結】

4、進入redis-6.2.4目錄執行make指令

Redis【第二篇總結】

5、執行安裝make install

Redis【第二篇總結】

6、驗證安裝成

cd      
Redis【第二篇總結】

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、前台啟動(不推薦)

Redis【第二篇總結】

9、背景啟動

(1) 複制配置檔案

cp      

(2)修改參數配置,将daemonize no改為daemonize yes,讓服務支援在背景啟動

[root@localhost redis-6.2.4]# cd /opt/
[root@localhost opt]# vi redis.conf      
Redis【第二篇總結】

(3)啟動redis

[root@localhost bin]# cd /usr/local/bin/
[root@localhost bin]# redis-server /opt/redis.conf 
[root@localhost bin]# ps -ef|grep redis      
Redis【第二篇總結】

(4)使用redis-cli測試

Redis【第二篇總結】

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      
Redis【第二篇總結】

(3)擷取值:get

get k1      
Redis【第二篇總結】

(4)追加值:append,傳回總長度

append k1 abcd      
Redis【第二篇總結】

(5)擷取值的長度:strlen

strlen k1      
Redis【第二篇總結】

(6)當key存在時操作:setnx,設定成功傳回1,設定失敗傳回0

setnx k1 v1      
Redis【第二篇總結】

(7)将數字類型值+1/-1:incr/decr,原子性操作,不受多線程機制打斷。

incr k3
decr k3      
Redis【第二篇總結】

(8)将key存儲的數字值遞增x/遞減x:incrby/decrby

incrby k3 10
decrby k3 5      
Redis【第二篇總結】
msetnx k11 v11 k12 v12 k13 v13
msetnx k1 v11 k4 v4      

(9)同時設定一個或多個key-value鍵值對:mset

mset k1 v1 k2 v2 k3 v3      
Redis【第二篇總結】

(10)同時擷取一個或多個value:mget

mget k1 k2 k3      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

(12)擷取範圍的值(開始-結束):getrange

127.0.0.1:6379> getrange name 0 3
"luck"      
Redis【第二篇總結】

(13)設定範圍的值(開始位置-覆寫):setrange,傳回總長度

127.0.0.1:6379> setrange name 3 abc
(integer) 8      
Redis【第二篇總結】

(14)設定key的同時設定過期時間:setex

127.0.0.1:6379> setex age 20      
Redis【第二篇總結】

(15)以新值換舊值(顯示舊值):getset

127.0.0.1:6379> getset name jack
"lucabcry"      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

(4)從左邊或右邊取出一個值:lpop/rpop

127.0.0.1:6379> lpop k1
"v3"
127.0.0.1:6379> rpop k1
"v9"      
Redis【第二篇總結】

(5)從k1清單右邊吐出一個值,插入到v2清單的左邊:rpoplpush

127.0.0.1:6379> rpoplpush k1 k2
"v1"      
Redis【第二篇總結】

(6)按照索引下标(單值)擷取元素(從左到右):lindex

127.0.0.1:6379> lindex k2 0
"v1"      
Redis【第二篇總結】

(7)擷取清單的長度:llen

llen k1      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

(9)從左邊删除n個對應的value:lrem

127.0.0.1:6379> lrem k1 2 "new11"
(integer) 2      
Redis【第二篇總結】

(10)将清單key下标為index的值替換成value:lset

127.0.0.1:6379> lset k1 1 "new31"      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(3)取出集合中的所有值:smembers

127.0.0.1:6379> smembers k1
1) "v3"
2) "v2"
3) "v1"      
Redis【第二篇總結】

(4)判斷key集合中是否包含對應的value:sismember,1有0無

127.0.0.1:6379> sismember k1 v1
(integer) 1      
Redis【第二篇總結】

(5)傳回集合中元素個數:scard

127.0.0.1:6379> scard k1
(integer) 3      
Redis【第二篇總結】

(6)從集合中删除某一個或多個元素:srem

127.0.0.1:6379> srem k1 v1
(integer) 1      
Redis【第二篇總結】

(7)随機從該集合吐出一個元素:spop

127.0.0.1:6379> spop k1
"v3"      
Redis【第二篇總結】

(8)随機從集合中取出n個值,不會從集合中删除:srandmember

127.0.0.1:6379> srandmember k1 2
1) "v1"
2) "v2"      
Redis【第二篇總結】

(9)把集合中的一個值從一個集合移動到另一個集合:smove

127.0.0.1:6379> smove k1 k2 v3
(integer) 1      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(3)從集合中取出資料(key-field):hget

127.0.0.1:6379> hget user:1001 id
"1"
127.0.0.1:6379> hget user:1001 name
"zhangsan"      
Redis【第二篇總結】

(4)批量添加資料:hmet

127.0.0.1:6379> hmset user:1002 id 2 name lisi age 30      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

(6)檢視哈希表中所有field:hkeys

127.0.0.1:6379> hkeys user:1002
1) "id"
2) "name"
3) "age"      
Redis【第二篇總結】

(7)檢視哈希表内所有value:hvals

127.0.0.1:6379> hvals user:1002
1) "2"
2) "lisi"
3) "30"      
Redis【第二篇總結】

(8)對應的key、field的值增量+1:hincrby

127.0.0.1:6379> hincrby user:1002 age 2
(integer) 32      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

(6)為元素score加上增量:zincrby< key>< increment>< value>

127.0.0.1:6379> zincrby topn 50 java
"250"      
Redis【第二篇總結】

(7)删除該集合中下,指定元素的值:zrem< key>< value>

127.0.0.1:6379> zrem topn php
(integer) 1      
Redis【第二篇總結】

(8)統計該集合,分數區間内的元素個數:zcount< key>< min>< max>

127.0.0.1:6379> zcount topn 200 300
(integer) 2      
Redis【第二篇總結】

(9)傳回該值在集合中的排名,從0開始:zrank< key>< value>

127.0.0.1:6379> zrank topn c++
(integer) 1      
Redis【第二篇總結】

四、Redis6配置檔案詳解

1、units機關:

隻支援bytes,支援bit,不區分大小寫

Redis【第二篇總結】

2、INCLUDES:

包含其他的配置檔案

Redis【第二篇總結】

3、NETWORK:網絡相關配置

(1)bind:限定是否隻能本機連接配接等

Redis【第二篇總結】

(2)protected-mode:是否開啟本機保護模式,隻可本機通路

Redis【第二篇總結】

(3)port:預設端口号6379

Redis【第二篇總結】

(4)tcp-backlog:正在進行三次握手的隊列總和預設值為511

Redis【第二篇總結】

(5)timeout:逾時時間預設0,永不逾時

Redis【第二篇總結】

(6)tcp-keepalive:檢測心跳時間預設300秒

Redis【第二篇總結】

(7)daemonize:是否支援背景啟動

Redis【第二篇總結】

(8)pidfile:儲存對應的程序号檔案

Redis【第二篇總結】

(9)loglevel:儲存日志的級别

Redis【第二篇總結】

(10)logfile:設定日志的路徑

Redis【第二篇總結】

(11)databases:預設使用16個庫

Redis【第二篇總結】

(12)Security密碼設定:

  • foobared 取消注釋,設定對應的密碼資訊
Redis【第二篇總結】

(13)LIMITS限制:

  • maxclients:最大連接配接數,預設10000
Redis【第二篇總結】

(14)maxmemory:記憶體上限:

Redis【第二篇總結】

五、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      
Redis【第二篇總結】

(2)打開另一個用戶端,給channel1釋出消息hello

127.0.0.1:6379> publish channel1 hello
(integer) 1      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

六、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      
Redis【第二篇總結】

(3)擷取Bitmaps中某個偏移量的值:getbit< key>< offset>

127.0.0.1:6379> getbit users:20210101 1
(integer) 1      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(3)統計HLL的pfcount< key>

127.0.0.1:6379> pfcount program
(integer) 4      
Redis【第二篇總結】

(4)将一個或多個HLL合并的結果存儲在另一個HLL:pfmeger

127.0.0.1:6379>      
Redis【第二篇總結】

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      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

(4)擷取兩個位置之間的直線距離:geodist< key>< member2>< member2><機關>

127.0.0.1:6379> geodist china:city beijing shanghai km
"1068.1535"      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

七、Jedis操作Redis6

1、idea建立maven工程

Redis【第二篇總結】

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工程

Redis【第二篇總結】

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來放棄組隊

正常場景:

Redis【第二篇總結】

異常場景2種:

Redis【第二篇總結】
Redis【第二篇總結】

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)      
Redis【第二篇總結】

(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)>      
Redis【第二篇總結】

(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)      
Redis【第二篇總結】

(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)      
Redis【第二篇總結】

9.4、事務沖突的問題

場景:多個人同時使用一個賬戶,參加雙十一搶購,購買不同的商品,未加事務會産生沖突。

(1)悲觀鎖

每次拿資料适都認為别人會修改,是以每次在拿資料适都會上鎖,這樣别人想拿資料就會block直到它拿到鎖。傳統的關系型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖,讀鎖,寫鎖,都是操作前上鎖。

Redis【第二篇總結】

(2)樂觀鎖

每次拿資料的适合都認為别人不會修改,是以不會上鎖,但是在更新的适合會判斷一下在此期間别人有沒有取更新這個資料,可以使用版本号等機制,樂觀鎖适用于多讀的應用類型,這樣可以提高吞吐量。Redis就是利用check-and-set機制實作事務的。

Redis【第二篇總結】

(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>      
Redis【第二篇總結】

(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檔案更加高效,缺點是最後一次持久化的資料可能丢失。

Redis【第二篇總結】

(3)Fork:寫時複制技術:新程序的所有資料(變量、環境變量、程式計數器等)數值都有原程序一緻。

(4)redis.conf配置内RDB相關配置(SNAPSHOTTING内配置)

rdb檔案名:dbfilename dump.rdb

檔案産生的路徑,預設值(啟動程式的位置):dir ./

dbfilename dump.rdb
dir ./      
Redis【第二篇總結】

快照的時間間隔:

# save 3600 1
# save 300 100
# save 60 10000      
Redis【第二篇總結】

設定手動持久化或自動持久化

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      
Redis【第二篇總結】
Redis【第二篇總結】

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檔案中的寫操作達到資料恢複的目的

Redis【第二篇總結】

(3) redis.conf關于AOF相關配置:

開啟AOF預設:,預設不開啟no,開啟需要修改為yes

appendonly no      
Redis【第二篇總結】

AOF生成檔案名:預設為appendonly.aof,生成的路徑同RDB

appendfilename "appendonly.aof"      
Redis【第二篇總結】

儲存檔案生成,同時開啟AOF與RDB時,系統會使用AOF儲存資料:

Redis【第二篇總結】

(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      
Redis【第二篇總結】

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      
Redis【第二篇總結】

異常恢複:

當AOF檔案損壞,可通過如下指令執行檔案修複:

redis-check-aof --fix appendonly.aof      
Redis【第二篇總結】

(5)AOF同步頻率設定

設定始終同步:appendfsync always,性能較差,但是資料完整性好

每秒同步:appendfsync everysec

把同步時機交給作業系統:appendfsync no

# appendfsync always
appendfsync everysec
# appendfsync no      
Redis【第二篇總結】

(6)Rewrite壓縮

當AOF檔案大小超過所設定的門檻值時(>=64M*2),Redis會啟動AOF的内容壓縮,隻保留可以恢複資料的最小指令集,可以使用指令bgrewriteaof開啟此功能。

使用fork子程序将原來檔案重寫,把rdb的快照已二進制形式附在新的aof頭部,作為已有的曆史資料據,替換原有的流水賬操作。

no-appendfsync-on-rewrite no      
Redis【第二篇總結】

十一、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      
Redis【第二篇總結】

(6)檢視三個redis主機運作情況

[root@localhost myredis]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

11.4、test主從測試

場景:主服務6379做寫操作,檢視從伺服器,且從伺服器不能做寫操作

Redis【第二篇總結】

11.5、常用三招

(1)一主兩從:

從伺服器挂掉後,重新開機會變成主服務,需要重新加入,資料會從主伺服器重新複制一份

主伺服器挂掉後,從伺服器還是從伺服器,主伺服器重新開機後還是主伺服器

(2)薪火相傳:

從伺服器可以配置為别的從伺服器的從伺服器

Redis【第二篇總結】

(3)反客為主:

當一個master當機後,可以讓一台slave升為master,其後面的slave不用做任何修改

slaveof no one      
Redis【第二篇總結】

11.6、主從複制原理

(1)當從伺服器連上主伺服器之後,從伺服器向主伺服器發送進行資料同步消息

(2)主伺服器接到從伺服器發送過來同步消息,把主伺服器資料進行持久化,生成RDB檔案,把RDB檔案發送給從伺服器,從伺服器拿到RDB進行讀取

(3)每次主伺服器進行寫操作之後,和從伺服器進行數同步

11.7、哨兵模式(sentinel)

(1)含義:(自動選舉老大的模式)

反客為主的自動版,能否背景監控主機是否故障,如果故障了根據投票數自動将從庫轉換為主庫。

哨兵模式是一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,作為程序,它會獨立運作。其原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控運作的多個Redis執行個體。

Redis【第二篇總結】

這裡的哨兵有兩個作用:

  1. 通過發送指令,讓Redis伺服器傳回監控其運作狀态,包括主伺服器和從伺服器。
  2. 當哨兵監測到master當機,會自動将slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。

然而一個哨兵程序對Redis伺服器進行監控,可能會出現問題,為此,我們可以使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就形成了多哨兵模式。

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

Redis【第二篇總結】

(3)當主機挂掉,從伺服器選舉成為主伺服器:

Redis【第二篇總結】
Redis【第二篇總結】

(4)再次啟動原來的主伺服器,變為從伺服器

Redis【第二篇總結】

(5)配置哨兵的優先級:

redis.conf配置檔案中:slave-priority 100,值越小優先級越高。

Redis【第二篇總結】

當優先級相同時選舉偏移量最大的

當偏移量一樣的時選舉runid最小的(随機)

十二、Reids叢集

12.1、叢集概念

Redis叢集實作了對Redis的水準擴容,即啟動N個redis節點,将整個資料庫分布存儲在這N個節點中,每個節點存儲總數量的1/N。

Redis叢集通過分區(partition)來提供一定程式的可用性(avaliability):即使叢集中有一部分節點失效或者無法通訊,叢集也可以繼續處理指令請求。

Redis叢集的優勢:實作擴容、分攤壓力、無中心配置相對簡單

Redis叢集的不足:多鍵操作不被支援、多鍵事務不支援(lua腳本不支援)、技術出現較晚,已有redis服務遷移到叢集複雜度較高

12.2、redis叢集搭建

(1)清除原備份檔案,并将appendonly配置關閉

Redis【第二篇總結】

(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      
Redis【第二篇總結】

(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]      
Redis【第二篇總結】

(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      
Redis【第二篇總結】

[ERR] Node 192.168.37.8:6379 is not configured as a cluster node.錯誤需要将redis.conf下的cluster-enabled yes 的注釋打開

Redis【第二篇總結】

配置6379、6380、6381為master,6389、6390、6391為slaver,yes确認

配置完成:

Redis【第二篇總結】

(5)連接配接叢集并檢視:

[root@localhost src]# redis-cli -c -p 6379
127.0.0.1:6379>      
Redis【第二篇總結】

12.3、redis叢集配置設定原則

配置設定原則:盡量保證每個主資料運作在不同的IP位址,每個主庫和從庫不在一個IP位址上

選項 --cluster-replicas 1表示我們希望為叢集中的每個主節點建立一個從節點。

12.4、slots(插槽)

Redis【第二篇總結】

一個Redis叢集包含16384個插槽(hash slot),資料庫中每個鍵都屬于這16384個插槽的其中之一。

叢集使用公式CRC16(key)%16384來計算鍵key屬于哪個槽,其中CRC176(key)語句用于計算鍵key和CRC16校驗和。

叢集中的每個節點負責處理一部分插槽。

Redis【第二篇總結】

添加資料,即往插槽内添加資料

Redis【第二篇總結】

添加多個資料時會報錯

Redis【第二篇總結】

如要插入多條資料,需要分組操作

Redis【第二篇總結】

計算key對應的插槽值

cluster keyslot k1      

計算對應插槽中的數值數量(隻能看到屬于自己叢集的插槽)

cluster countkeysinslot 12706      

傳回操作中n個數值

cluster getkeysinslot 449 1      
Redis【第二篇總結】

12.5、故障恢複

(1)使6379叢集shutdown,6380從機替換變為主機

Redis【第二篇總結】

(2)主-從均挂掉的情況

cluster-require-full-coverage為yes,那麼某一段插槽主從挂掉,整個叢集都挂掉

cluster-require-full-coverage為no,那麼某一段插槽主從挂掉,該段叢集的插槽不能提供服務,其他插槽依然可以提供服務

Redis【第二篇總結】

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、緩存穿透(查不到)

Redis【第二篇總結】

(1)現象:

應用伺服器壓力變大

redis命中率降低

一直查詢資料庫

(2)造成原因:

redis查詢不到資料庫

出現很多非正常url通路

(3)解決方案:

對空值進行緩存:緩存空結果null值

設定通路白名單:使用bitmaps類型定義一個可以通路的名單,每次通路時進行攔截

布隆過濾器:(Bloom Filter)1970年布隆提出的,它實際上是一個很長的二進制向量(位圖)和一系列随機映射函數(哈希函數)。布隆過濾器用于檢索一個元素是否在一個集合,但是也存在誤識别的情況

進行實時監控:當發現Redis的命中率開始急速降低,需要排查通路對象和通路的資料,和運維人員配合,設定黑名單

13.2、緩存擊穿(量太大,突然緩存過期)

Redis【第二篇總結】

(1)現象:

資料庫的通路壓力瞬時增加、redis裡面沒有出現大量key過期、redis正常運作。

這裡需要注意和緩存擊穿的差別,緩存擊穿,是指一個key非常熱點,在不停的扛着大并發,大并發集中對這一個點進行通路,當這個key在失效的瞬間,持續的大并發就穿破緩存,直接請求資料庫,就像在一個屏障上鑿開了一個洞。

當某個key在過期的瞬間,有大量的請求并發通路,這類資料一般是熱點資料,由于緩存過期,會同時通路資料庫來查詢最新資料,并且回寫緩存,會導使資料庫瞬間壓力過大。

(2)造成原因:

redis某個key過期了,而此時還有大量的通路在使用這個key

(3)解決方案:

預先設定熱門資料:在redis高峰通路之前,把一些熱門資料提前存入到redis内,加大這些熱門資料key的時長

實時調整:現場監控哪些資料熱門,實時調整key的過期時長

使用鎖的方式:設定排它鎖:在根據key獲得的value值為空時,先鎖上,再從資料庫加載,加載完畢,釋放鎖。若其他線程發現擷取鎖失敗,則睡眠一段時間後重試

13.3、緩存雪崩

Redis【第二篇總結】

(1)現象:

資料庫壓力變大、伺服器崩潰。

緩存雪崩,是指在某一個時間段,緩存集中過期失效。Redis 當機!

(2)造成原因:

在極少的時間段,查詢大量key的集中過期情況。

産生雪崩的原因之一,比如雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了緩存,假設緩存一個小時。那麼到了淩晨一點鐘的時候,這批商品的緩存就都過期了。而對這批商品的通路查詢,都落到了資料庫上,對于資料庫而言,就會産生周期性的壓力波峰。于是所有的請求都會達到存儲層,存儲層的調用量會暴增,造成存儲層也會挂掉的情況。

Redis【第二篇總結】

其實集中過期,倒不是非常緻命,比較緻命的緩存雪崩,是緩存伺服器某個節點當機或斷網。因為自然形成的緩存雪崩,一定是在某個時間段集中建立緩存,這個時候,資料庫也是可以頂住壓力的。無非就是對資料庫産生周期性的壓力而已。而緩存服務節點的當機,對資料庫伺服器造成的壓力是不可預知的,很有可能瞬間就把資料庫壓垮。

(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      
Redis【第二篇總結】

删除key釋放setnx分布式鎖:

192.168.37.8:6381> del users      
Redis【第二篇總結】

使用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【第二篇總結】

為防止上鎖後redis機器故障,使用set nx ex上鎖同時設定過期時間:(原子操作)

set users 10 nx ex 12      
Redis【第二篇總結】

(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"      
Redis【第二篇總結】

另一個視窗通過ab壓力測試工具進行測試,1000個請求,100個請求并發,并且觸發分布式鎖

ab -n 1000 -c 100 http://192.168.31.12:8080/redisTest/testLock      
Redis【第二篇總結】

檢視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>      
Redis【第二篇總結】

使用acl cat檢視添權重限的指令類别

Redis【第二篇總結】

檢視目前acl使用者:

127.0.0.1:6379> acl whoami      
Redis【第二篇總結】

添加acl使用者:(可用,包含密碼,可操作包含cached的key,隻能get指令操作)

127.0.0.1:6379> acl setuser mary on >password ~cached:* +get      
Redis【第二篇總結】

切換使用者,進行測試:

127.0.0.1:6379>      
Redis【第二篇總結】

14.2、IO多線程

(1)簡介:

Redis6加入了多線程:值得是用戶端互動部分的網絡IO互動處理模闆多線程,而非執行指令多線程,Redis6執行指令依然是單線程的。

(2)原理架構:

Redis的多線程部分隻是使用者處理網路資料的讀寫和協定解析,執行指令依然是單線程的,因為是需要控制key、lua、事務。

多線程預設是不開啟的,需要配置檔案中配置

io-threads 4      
Redis【第二篇總結】

3、工具支援cluster