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對應目錄內

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

瀏覽器訪問驗證://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模式,通過多執行緒的方式對多個分片進行壓測。

Tags: