Redis6使用指導(完整版)


一、Nosql與Redis概述

 二、Redis6安裝

三、常用五大數據類型

四、Redis6配置文件詳解

五、Redis6的發布和訂閱

六、Redis6新數據類型

七、Jedis操作Redis6(Maven)

八、Redis6與SpringBoot整合

九、Redis6的事務操作

十、Reids6持久化

十一、Redis6的主從復制

十二、Redis集群

十三、Redis6應用問題解決

十四、Redis6新功能

---------------------------------------分割線:正文--------------------------------------------------------

一、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、不適用場景

需要事務的支持

基於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

yum install -y gcc-c++

測試gcc版本

gcc --version

 3、解壓縮:

tar zxvf redis-6.2.4.tar.gz

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

 5、執行安裝make install

6、驗證安裝成

cd /usr/local/bin ll

 7、相關軟件介紹:

redis-benchmar:性能測試工具

redis-check-aof:修改有問題的AOF

redis-check-rdb:修改有問題的rdb文件

redis-sentinel:Redis的集群使用

redis-server:Redis服務器集群使用

redis-cli:客戶端,操作入口

8、前台啟動(不推薦)

9、后台啟動

(1) 復制配置文件

cp -r redis.conf /opt/

(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(進程)

 

三、常用五大數據類型

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 k1 string

(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) -2

(10)切換數據庫:select

127.0.0.1:6379[1]> select 0 OK

(11)查看當前數據庫的key數量:dbsize

127.0.0.1:6379> dbsize (integer) 1

(12)清空當前庫內數據(慎用)

127.0.0.1:6379> flushdb OK

(13)通殺所有庫內數據(慎用)

127.0.0.1:6379> flushall OK

2、Redis字符串(String)

(1)簡介:字符串,一個key對應一個value,是二進制安全的,是Redis最基本數據類型,value最多512M,底層為動態字符串,ArrayList

(2)設置值,相同key值覆蓋:set

set k1 v100

 (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 value30 OK

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

127.0.0.1:6379> getset name jack "lucabcry"

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" OK

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"

 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 OK

(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

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新數據類型

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) 2
127.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

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> pfmerge k100 k1 program OK

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的事務操作

1、Redis事務定義

Redis事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序的執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。

Redis事務的主要作用就是串聯多個命令防止別的命令插隊

2、Multi、Exec、discard

(1)基本概念

輸入Multi命令開始:輸入的命令都會依次進入命令隊列中,但不會執行,直到輸入Exec后,Redis會將之前的命令隊列中的命令依次執行。

組隊的過程中可以通過discard來放棄組隊

正常場景:

異常場景2種:

 

 

 

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) OK

(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)> discard OK

(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) EXECABORT Transaction discarded because of previous errors.

(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) OK

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的監視。

5、Redis事務三特性:

(1)單獨的隔離操作:

事務種的所有名歷經都會序列化、按順利執行,事務在執行的過程中,不會被其他客戶端發送來的命令所打斷

(2)沒有隔離級別的概念:

隊列中的命令沒有提交之前不會實際被執行,因為事務提交前任何執行都不會被實際執行

(3)不保證原子性

事務中如果有一條命令執行失敗,其中的命令任然會被執行,沒有回滾

 

十、Reids6持久化

1、簡介

兩種持久化方式

(1)RDB(Redis DataBase):內存中數據直接寫入文件中

(2)AOF(Append Of File):以追加的行為把內容寫入文件中

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

 

 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 k13 v13 OK
-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 06:16 redis-server

  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的主從復制

1、概念:

主機數據更新后根據配置和策略,自動同步到備機的master/slaver機制,Master以寫為主,Slave以讀為主

2、優勢:

(1)讀寫分離,性能擴展

(2)容災快速恢復

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 dbfilename dump6379.rdb
include /myredis/redis.conf pidfile /var/run/redis_6380.pid port 6380 dbfilename dump6380.rdb
include /myredis/redis.conf pidfile /var/run/redis_6381.pid port 6381 dbfilename dump6381.rdb

(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 role:master connected_slaves:0 master_failover_state:no-failover master_replid:cc2e7d7d4336c03f7e4333a94a56f4bc9fdbf464 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0

 (7)配從(庫)不配主(庫):

slaveof <ip><port>:成為某個實例的從服務器:

在6380與6381上執行

127.0.0.1:6380> slaveof 127.0.0.1 6379
127.0.0.1:6381> slaveof 127.0.0.1 6379

4、test主從測試:

場景:主服務6379做寫操作,查看從服務器,且從服務器不能做寫操作

5、常用三招:

(1)一主兩從:

從服務器掛掉后,重啟會變成主服務,需要重新加入,數據會從主服務器重新復制一份

主服務器掛掉后,從服務器還是從服務器,主服務器重啟后還是主服務器

(2)薪火相傳:

從服務器可以配置為別的從服務器的從服務器

(3)反客為主:

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

slaveof no one

6、主從復制原理

(1)當從服務器連上主服務器之后,從服務器向主服務器發送進行數據同步消息

(2)主服務器接到從服務器發送過來同步消息,把主服務器數據進行持久化,生成RDB文件,把RDB文件發送給從服務器,從服務器拿到RDB進行讀取

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

7、哨兵模式(sentinel)

(1)含義:

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

(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集群

1、集群概念:

Redis集群實現了對Redis的水平擴容,即啟動N個redis節點,將整個數據庫分布存儲在這N個節點中,每個節點存儲總數量的1/N。

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

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

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

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> cluster nodes

 3、redis集群分配原則:

分配原則:盡量保證每個主數據運行在不同的IP地址,每個主庫和從庫不在一個IP地址上

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

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

5、故障恢復

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

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

cluster-require-full-coverage為yes,那么某一段插槽主從掛掉,整個集群都掛掉

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

 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應用問題解決

1、緩存穿透:

(1)現象:

應用服務器壓力變大

redis命中率降低

一直查詢數據庫

(2)造成原因:

redis查詢不到數據庫

出現很多非正常url訪問

(3)解決方案:

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

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

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

進行實時監控:當發現Redis的命中率開始急速降低,需要排查訪問對象和訪問的數據,和運維人員配合,設置黑名單

2、緩存擊穿:

(1)現象:

數據庫的訪問壓力瞬時增加、redis里面沒有出現大量key過期、redis正常運行

(2)造成原因:

redis某個key過期了,大量訪問使用這個key

(3)解決方案:

預先設置熱門數據:在redis高峰訪問之前,把一些熱門數據提前存入到redis內,加大這些熱門數據key的時長

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

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

3、緩存雪崩:

 

(1)現象:

數據庫壓力變大、服務器崩潰

(2)造成原因:

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

(3)解決方案:

構建多級緩存架構:nginx緩存+redis緩存+其他緩存(ehcache等)

使用鎖或隊列:用加鎖或者隊列保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量並發請求落到底層存儲系統上,不適用於高並發情況

設置過期標志更新緩存:記錄緩存數據是否過期(設置提前量),如果過期會觸發通知另外的線程在后台更新實際key的緩存

將緩存失效實際分散開:可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鍾隨機,這樣每一個緩存的過期時間的重復率就會降低,很難引發集體失效的事件

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新功能

1、ACL(訪問控制列表):

(1)簡介

Access Control List:Redis6提供ACL功能對用戶進行更細粒度的權限控制。

(2)命令

使用acl list展現用戶權限列表

127.0.0.1:6379> acl list

使用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> auth mary password

 2、IO多線程

(1)簡介:

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

(2)原理架構:

Redis的多線程部分只是用戶處理網路數據的讀寫和協議解析,執行命令依然是單線程的,因為是需要控制key、lua、事務。

多線程默認是不開啟的,需要配置文件中配置

io-threads 4

 3、工具支持cluster

Redis5之前的版本搭建集合需要單獨安裝ruby環境,Redis5講redis-trib.rb的功能集成到了redis-cli,另外官方redis-benchmark工具開始支持cluster模式,通過多線程的方式對多個分片進行壓測。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM