1. 程式人生 > 其它 >pwn學習-bugku

pwn學習-bugku

NoSql概述

為什麼要用NoSql

1、單機MySQL的年代
在這裡插入圖片描述

90年代,一個基本的網站訪問量一般不會太大,單個數據庫完全足夠!

那個時候,更多的去使用靜態網頁Html~~伺服器根本沒有太大的壓力!

思考一下,這種情況下:整個網站的瓶頸是什麼?

1、資料量如果太大、一個機器放不下了!

2、資料的索引(B+ Tree ) ,一個機器記憶體也放不下

3、訪問量(讀寫混合),一個伺服器承受不了~

只要你開始出現以上的三種情況之一,那麼你就必須要晉級! |

2、Memcache(快取) + MySQL + 垂直拆分,讀寫分離

網站80%的情況都是在讀,每次都要去查詢資料庫的話就十分的麻煩!所以說我們希望減輕資料的壓力,我們可以使用快取來保證效率!

在這裡插入圖片描述

網站80%的情況都是在讀,每次都要去查詢資料庫的話就十分的麻煩!所以說我們希望減輕資料的壓力,我們可以使用快取來保證效率!

發展過程︰優化資料結構和索引–>檔案快取(IO ) —> Memcached (當時最熱門的技術)

3、分庫分表 + 水平拆分 + MySQL叢集

早些年MylSAM:表鎖,十分影響效率!高併發下就會出現嚴重的鎖問題

轉戰Innodb :行鎖

慢慢的就開始使用分庫分表來解決寫的壓力!

MySQL在哪個年代推出了表分割槽!這個並沒有多少公司使用!MySQL的叢集,很好滿足哪個年代的所有需求!

在這裡插入圖片描述

4、如今最近的年代

2010–2020十年之間,世界已經發生了翻天覆地的變化;(定位,也是一種資料,音樂,熱榜!)

MySQL等關係型資料庫就不夠用了!資料量很多,變化很快~!

MySQL有的使用它來儲存一些比較大的檔案,部落格,圖片!資料庫表很大,效率就低了!如果有一種資料庫來專門處理這種資料,

MySQL壓力就變得十分小(研究如何處理這些問題!)大資料的IO壓力下,表幾乎沒法更大!

目前一個基本的網際網路專案

在這裡插入圖片描述

為什麼要用NoSQL

使用者的個人資訊,社交網路,地理位置。使用者自己產生的資料,使用者日誌等爆發式增長

這時候我們就需要使用NoSQL資料庫的,NoSQL可以很好的處理以上情況。

什麼是NoSQL

NoSQL= Not Only sQL(不僅僅是SQL )關係型資料庫∶表格,行,列

泛指非關係型資料庫的,隨著web2.0網際網路的誕生!傳統的關係型資料庫很難對付web2.0時代!尤其是超大規模的高併發的社群!暴露出來很多難以克服的問題,

NoSQL在當今大資料環境下發展的十分迅速,Redis是發展最快的,而且是我們當下必須要掌握的一個技術!

很多的資料型別使用者的個人資訊,社交網路,地理位置。這些資料型別的儲存不需要一個固定的格式!不需要多餘的操作就可以橫向擴充套件的!Map<String,Object>使用鍵值對來控制!

NoSQL特點

解耦!

1、方便擴充套件(資料之間沒有關係,很好擴充套件!)

2、大資料量高效能(Redis 一秒寫8萬次,讀取11萬,NoSQL的快取記錄級,是一種細粒度的快取,效能會比較高!)

3、資料型別是多樣型的!(不需要事先設計資料庫!隨取隨用!如果是資料量十分大的表,很多人就無法設計了!)

4、傳統RDBMSNoSQL

傳統的 RDBMS
-結構化組織
-sQL
-資料和關係都存在單獨的表中
-操作操作,資料定義語言
-嚴格的一致性
-基礎的事務
-......
Nosql
-不僅僅是資料
-沒有固定的查詢語言
-鍵值對儲存,列儲存,文件儲存,圖形資料庫(社交關係)
-最終一致性,
-CAP定理和BASE(異地多活)
-高效能,高可用,高可擴
-......

瞭解3V+3高

  • 大資料時代的3V:主要是描述問題的

    • 海量Volume
    • 多樣Variety
    • 實時Velocity
  • 大資料時代的3高:主要是對程式的要求

    • 高併發
    • 高可拓
    • 高效能

NoSQL的四大分類

KV鍵值對

  • 新浪:Redis
  • 美團:redis+Tair
  • 阿里、百度:Redis+memecache

文件型資料庫(bson格式,和json一樣)

  • MongoDB
    • MongoDB是一個基於分散式檔案儲存的資料庫,C++編寫,主要用來處理大量的文件
    • MongoDB是一個介於關係型資料庫和非關係型資料庫中間的產品,MongoDB是非關係型資料庫中功能最豐富的,最像關係型資料庫。
  • ConchDB

列儲存資料庫

  • HBase
  • 分散式檔案系統

圖關係資料庫

  • 他不是存圖形,放的是關係,比如∶朋友圈社交網路,廣告推薦
  • Neo4jInfoGrid
    在這裡插入圖片描述

四種NoSQL對比

在這裡插入圖片描述

Redis入門

概述

redis是什麼?

Redis ( Remote Dictionary Server ),遠端字典服務

是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API,是當下最熱門的NoSQL技術之一!也被人們稱之為結構化資料庫!

Redis能幹嘛?

1、記憶體儲存,持久化,記憶體中斷電即失,所以說持久化很重要(rdbaof

2、效率高,可以用於快取記憶體

3、釋出訂閱系統

4、地圖資訊分析

5、計時器、計數器(瀏覽量!)

6、……

特性

1、多樣的資料型別

2、持久化

3、叢集

4、事務

Linux環境安裝redis

下載linux一次性開發工具

  • 預設的CentOS儲存庫包含一個名為“Development Tools”的軟體包組,其中包括GNU編譯器集合,GNU偵錯程式以及編譯軟體所需的其他開發庫和工具。
dnf -y group install "Development Tools"

檢測gcc版本,保證高於5.3

gcc --version

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Q8Bz5tuR-1609205739478)(redis.assets/image-20201123201738985.png)]

將redis壓縮包上傳到伺服器,程式建議放到opt目錄下,這裡我不移動了

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-evsKbell-1609205739479)(redis.assets/image-20201123201050663.png)]

解壓壓縮包

tar -zxvf redis-6.0.9.tar.gz

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zu6Z9ie0-1609205739479)(redis.assets/image-20201123201356891.png)]

把需要的檔案全部配置好

make

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-kvLFMOBH-1609205739480)(redis.assets/image-20201123201944782.png)]

再次make確認以下

make install

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-DH8jgU5v-1609205739481)(redis.assets/image-20201123202042571.png)]

redis的預設安裝路徑/usr/local/bin/

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-arJPU4D9-1609205739482)(redis.assets/image-20201123202349344.png)]

將redis配置檔案拷貝一份放到這個資料夾下

mkdir phzconfig
cp /root/redis-6.0.9/redis.conf redis.conf phzconfig

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-6kKX7PYd-1609205739482)(redis.assets/image-20201123202904353.png)]

  • 我們之後就可以用這個配置檔案啟動

redis預設不是後臺啟動的,需要修改配置檔案

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lJlcGyx5-1609205739483)(redis.assets/image-20201123203106211.png)]

修改為yes

啟動redis服務

#回到bin目錄
cd /usr/local/bin
#啟動服務
redis-server phzconfig/redis.conf
#使用redis客戶端進行連線
redis-cli -p 6379

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-MfVfUWJ5-1609205739484)(redis.assets/image-20201123203753723.png)]

測試

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lJbaG9Oo-1609205739485)(redis.assets/image-20201123203938793.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7tlswFdu-1609205739485)(redis.assets/image-20201123204058514.png)]

停止redis服務

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Bc1zR0WH-1609205739486)(redis.assets/image-20201123204245205.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-S0saiRR6-1609205739487)(redis.assets/image-20201123204259544.png)]

測試效能

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-pFyErwRg-1609205739488)(redis.assets/image-20201123204636666.png)]

redis-benchmark是官方自帶的一個壓力測試工具

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NCcLqexP-1609205739489)(redis.assets/image-20201123204758858.png)]

#測試100個併發連線,100000個請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-V5WhrK2U-1609205739489)(redis.assets/image-20201123205320140.png)]

基礎知識

redis預設有16個數據庫

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-J0TRkfgf-1609205739490)(redis.assets/image-20201123213559762.png)]

預設使用的是第0個

#切換資料庫
SELECT 2
#查詢資料庫大小
DBSIZE
#檢視當前資料庫中所有的key
keys *
#清空所有資料庫的值
FLUSHALL
#清空當前資料庫的值
FLUSHDB
  • redis6以前是單執行緒的,但是6支援了多執行緒

Redis是很快的,官方表示,Redis是基於記憶體操作,CPU不是Redis效能瓶頸,Redis的瓶頸是根據機器的記憶體和網路頻寬,既然可以使用單執行緒來實現,就使用單執行緒了!

Redis是C語言寫的,官方提供的資料為100000+的QPS,完全不比同樣是使用key-vale的Memecache差!

  • Redis為什麼單執行緒還這麼快?

誤區1︰高效能的伺服器一定是多執行緒的?

誤區2∶多執行緒(CPU上下文會切換!)一定比單執行緒效率高!

CPU>記憶體>硬碟
核心: redis是將所有的資料全部放在記憶體中的,所以說使用單執行緒去操作效率就是最高的,多執行緒(CPU上下文會切換∶耗時的操作),對於記憶體系統來說,如果沒有上下文切換效率就是最高的!多次讀寫都是在一個CPU上的,在記憶體情況下,這個就是最佳的方案!

五大資料型別

Redis-key

#檢視key對應value的型別
type name
#獲取某個key的值
get name
#檢視某個值的剩餘存活時間(搭配過期時間)
#time to live
ttl name
#移動某一個key的值(1指的是目標資料庫)
move name 1
#給某個值設定過期時間(單位秒)
EXPIRE name 10
#判斷某個鍵是否存在(存在返回1,不存在返回0)
EXISTS name

String

#設定鍵值對
set name phz

#給某個值追加資料
APPEND name hello word

#獲取某個key對應的值的長度
STRLEN age

#自增1
INCR num

#自減一
DECR num

#自定義步長增加
INCRBY num 20

#自定義步長減少
DECRBY num 20

#獲取某個值的一部分(閉區間,0,1,2,3)
GETRANGE name 0 3

#給某個值替換其中的部分欄位(其實字元下表和替換指定字串,不用指定長度,替換字串多長就替換多長)
SETRANGE name 0 haha

#setex (set with expire)設定過期時間
#setne (set if not exists)不存在再設定(在分散式鎖中經常使用)
setex name 30 phz#設定name值30s到期,值為phz
setnx name p#如果name不存在再設定p,如果存在設定失敗

#批量設定,這裡會出現三個鍵值對
mset k1 v1 k2 v2 k3 v3

#當然也可以在設定的過程中進行判斷是否存在
#下面這個例子中k1已經存在了,k4不存在,但並不會新建一個k4,因為這個設定值的過程是具有原子性的操作
msetnx k1 v0 k4 v4

#常規設定物件
set user:1 {name:phz,age:4}
#巧妙設計物件
mset user:1:name phz user:1:age 10 user:2:name p user:2:ag

#批量獲取
mget user:1:name user:2:name

#獲取馬上賦值,如果沒有這個db,返回的是(nil),但是這個值已經設定進去了,已經可以直接查詢到,如果有這個db,會輸出它的值,然後再對其重新賦值
getset db redis

String類似的使用場景: value除了是我們的字串還可以是我們的數字

List

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CIGY3NQR-1609205739491)(redis.assets/image-20201126160815090.png)]

在redis裡面,我們可以把list玩成,棧、佇列、阻塞佇列

所有的list命令都是以l開頭的

#設定list集合
LPUSH list one
LPUSH list two
LPUSH list three

#獲取指定區間的list集合中的值,取值是倒序,類似於棧,先進後出
LRANGE list 0 -1#查詢全部,也是倒序

#也可以反向插入,類似於雙向連結串列了
RPUSH list zero

#移除命令,當然也分為左移除和右移除
LPOP list#左邊的
RPOP list#右邊的

#通過下表獲取list中某一個值
lindex list 0

#獲取list長度
llen list

#移除list中指定的值
lrem list 1 one#如果存在多個相同值的資料,即多個one,那麼這個1表示從上到下(從左到右),刪除第一次出現的那個值,如果想兩個都移除,就填2

#截斷某一部分值(取出來)
ltrim list 0 1#把0和1號值取出來

#將list中的部分值拿出來放到新的ontherlist中
rpoplpush list otherlist

#更新list中指定位置的值,必須是存在的值
lset list 0 item

#在list某個位置插入值
linsert list before two three
linsert list after one zero

小結

1、他實際上是一個連結串列,before Node after , left , right都可以插入值,如果key不存在,建立新的連結串列

2、如果key存在,新增內容

3、如果移除了所有值,空連結串列,也代表不存在!

4、在兩邊插入或者改動值,效率最高!中間元素,相對來說效率會低一點~

5、訊息排隊!訊息佇列( Lpush Rpop ) ,棧( Lpush Lpop )

Set

set中的值是不能重複的

#給set賦值
sadd set hello

#獲取set中所有的值(無序的)
SMEMBERS set

#判斷某一個值是否再set中
SISMEMBER set hello

#獲取set集合中元素個數
scard set

#移除set集合中某一個值
srem set hello 

#隨機從set中選出一個值
SRANDMEMBER set

#隨機移除set集合中的一個值
spop set

#將一個的值移動到指定的另外一個key中,如果這個新的key不存在則自動建立
smove set set2 hello

#假設set1有a,b,c;set2有c,d,e
#輸出兩個set中不同的元素(差集)
sdiff set1 set2#輸出a,b
#輸出兩個set中相同的元素(交集)
sinter set1 set2#輸出c
#輸出兩個set中所有的元素(並集)
sunion set1 set2#輸出a,b,c,d,e

微博,A使用者將所有關注的人放在一個set集合中!將它的粉絲也放在一個集合中共同關注,共同愛好,二度好友,推薦好友!(六度分割理論)

Hash

Map集合,key-<key-vlaue>

#給hash設定值
HSET hash name phz

#獲取hash中指定鍵的值
HGET hash name

#同樣支援同時設定多個值
HMSET hash age 20 email [email protected]

#獲取多個值
HMGET hash age email

#獲取全部的值
HGETALL hash 

#刪除某一個值
HDEL hash age

#獲取hash中儲存了多少集合
HLEN hash 

#只獲取hash中的key
HKEYS hash

#只獲取hash中的value
HVALS hash

#給某個值設定自增
HINCRBY hash age 2

#給某個值設定自減
HINCRBY hash age -1
HDECRBY hash age 2

#如果某個欄位不存在設定,存在不設定
HSETNX hash name p

Zset(有序集合)

#自定義排序方式
zadd set 1 one 2 two 3 three
#例如
zadd salary 2500 zhangsan
zadd salary 5000 xiaohong
zadd salary 200 phz
#zrangebyscore salary -inf +inf(通過score排序,在值中處於負無窮大到正無窮大的進行排序,即所有元素,這裡如果寫0,-1將是空,預設是閉區間,如果要開區間,在對應數前面加一個“(”)
#ZREVRANGE salary 0 -1(這個地方引數就是下標而不是值)
zrange salary 0 -1 
1) "phz"
2) "zhangsan"
3) "xiaohong"

#移除某個元素
zrem salary phz

#獲取集合中的個數
zcard salary

#判斷某個區間中值的個數
zcount salary 3000 5000

三種特殊資料型別

geospatial地理位置

朋友的定位,附近的人,打車距離計算

Redis的Geo在Redis3.2版本就推出了!

這個功能可以推算地理位置的資訊,兩地之間的距離,方圓幾裡的人

可以查詢一些測試資料:

#新增地理位置
#規則:地球南北兩極無法新增,我們一般是下載城市資料通過java程式匯入
GEOADD china:city 116.46 39.92 beijing
GEOADD china:city 121.48 31.22 shanghai
GEOADD china:city 106.54 29.59 chongqing
GEOADD china:city 114.07 22.62 shengzheng
GEOADD china:city 120.19 30.26 hangzhou 
GEOADD china:city 108.95 34.27 xian
# 這種錯誤就是因為經緯度引數不合法
127.0.0.1:6379> GEOADD china:city 39.92 116.46 beijing
(error) ERR invalid longitude,latitude pair 39.920000,116.460000
#獲取指定位置的地理位置資訊
GEOPOS china:city beijing chongqing hangzhou
#獲取指定兩個地點的距離,可指定單位(也可不寫,預設m)
    #m表示單位為米。
    #km表示單位為千米。
    #mi表示單位為英里。
    #ft表示單位為英尺。
GEODIST china:city beijing chongqing km
#獲取以給定經緯度為中心,找出某一半徑內的元素(withdist顯示出直線距離,withcoord顯示經緯度,count 3 選出符合條件的指定個數的結果,前提是有這麼多)
GEORADIUS china:city 110 30 1000 km withdist withcoord count 3
#獲取指定成員為中心一定範圍內的其他成員
GEORADIUSBYMEMBER china:city xian 1000 km
#該命令將返回11個字元的Geohash字串!
GEOHASH china:city beijing
"wx4g455wfe0"(二維的經緯度轉換為一維的字串)

由於geospatial底層就是Zset實現的,所以通過Zset命令也可以操作

Hyperloglog

什麼是基數?

A(1,3,5,7,8,7)——5

B(1,3,5,7,8)——5

基數(不重複的元素),可以接受誤差

簡介

Redis Hyperloglog 基數統計的演算法

優點∶佔用的記憶體是固定,2*64不同的元素的基數,只需要花費12KB記憶體,如果要從記憶體角度來比較的話Hyperloglog首選

網頁的UV(一個人訪問一個網站多次,但是還是算作一個人)

傳統的方式,set 儲存使用者的id,然後就可以統計set 中的元素數量作為標準判斷

這個方式如果儲存大量的使用者id,就會比較麻煩,我們的目的是為了計數,而不是儲存使用者id ;

使用Hyperloglog獲取的基數,有0.81%錯誤率,統計UV任務,可以忽略不計

#往Hyperloglog中新增資料
PFadd key1 a b c d e f
PFadd key2 e f g h i j k 

#獲取基數
PFcount key1

#合併兩個key(並集,會過濾重複元素)
PFMERGE key3 key1 key2

如果允許容錯,就使用Hyperloglog

如果不允許容錯,就是用Set或者自己的資料型別

Bitmap

位儲存

是否活躍,是否登入,365天打卡,等兩個狀態的,都可以使用Bitmaps ,這種資料結構,都是操作二進位制位來進行記錄,就只有0和1兩個狀態。

365天= 365 bit 1位元組= 8bit 46個位元組左右

#設定一週打卡值,七天
setbit sign 0 1
setbit sign 1 0
setbit sign 2 1
setbit sign 3 0
setbit sign 4 0
setbit sign 5 1
setbit sign 6 1

#獲取某一天是否開啟
getbit sign 3

#獲取打卡天數(預設全部,後面可跟一個開始結束範圍)
bitcount sign

事務

Redis事務本質∶一組命令的集合

一個事務中的所有命令都會被序列化,在事務執行過程的中,會按照順序執行

一次性、順序性、排他性,執行一系列的命令

----- 佇列set set set執行------

Redis事務沒有沒有隔離級別的概念

所有的命令在事務中,並沒有直接被執行,只有發起執行命令的時候才會執行

Redis單條命令式儲存原子性的,但是事務不保證原子性

redis事務:

  • 開啟事務(multi)
  • 命令入隊(…)
  • 執行事務( exec)

正常執行事務

#執行事務
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name phz
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "phz"
4) "20"

#取消事務
DISCARD

編譯型異常(程式碼有問題!命令有錯! ),事務中所有的命令都不會被執行!

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name phz
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> sett email [email protected]
(error) ERR unknown command `sett`, with args beginning with: `email`, `[email protected]`, 
127.0.0.1:6379> get name 
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

執行時異常(1/0),如果事務佇列中存在語法性,那麼執行命令的時候,其他命令是可以正常執行的,錯誤命令丟擲異常!

127.0.0.1:6379> set name phz
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR name
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) "20"

監控

悲觀鎖:

  • 很悲觀,什麼時候都有可能出現問題,無論做什麼都會加鎖

樂觀鎖:

  • 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做。
  • 獲取version,一般是通過為資料庫表增加一個數字型別的 “version” 欄位來實現,即為資料增加一個版本標識。當讀取資料時,將version欄位的值一同讀出,資料每更新一次,對此version值加一。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的version值進行比對,如果資料庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期資料。

redis的監控watch,相當於是redis的樂觀鎖

  • 正常情況

    127.0.0.1:6379> set money 200
    OK
    127.0.0.1:6379> set out 0
    OK
    127.0.0.1:6379> WATCH money#可以同時監視多個
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> DECRBY money 20
    QUEUED
    127.0.0.1:6379> INCRBY out 20
    QUEUED
    127.0.0.1:6379> exec
    1) (integer) 180
    2) (integer) 20
    
  • 異常情況

    • 終端1
    127.0.0.1:6379> set money 200
    OK
    127.0.0.1:6379> set out 0
    OK
    127.0.0.1:6379> WATCH money
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> INCRBY money 20
    QUEUED
    127.0.0.1:6379> DECRBY out 20
    QUEUED
    #這個時候不立即執行事務操作換到終端2上執行修改操作
    #執行完畢這邊再提交事務
    127.0.0.1:6379> exec
    (nil)#表示修改失敗
    
    • 終端2
    127.0.0.1:6379> get money
    "200"
    127.0.0.1:6379> set money 1000
    OK
    
#如果發現事務執行失敗,放棄監視,也就是解鎖,重新監視
unwatch#取消監視所有的key
watch money

Jedis

我們要使用Java來操作Redis

什麼是jedis?是Redis官方推薦的java連線開發工具,使用]ava操作Redis的中介軟體。如果你要使用java操作redis,那麼一定要對Jedis十分的熟悉!

  • 使用Jedis

第一步:匯入依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

第二步,例項化Jedis物件

Jedis jedis = new Jedis("39.105.43.3", 6379);

連線遠端伺服器失敗

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ysBnLJ3J-1609205739492)(redis.assets/image-20201127121431779.png)]

解決方案

  • 第一步:

    找到redis的配置檔案,修改requirepass,即訪問redis密碼

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-7fcaqDVw-1609205739493)(redis.assets/image-20201127122208134.png)]

  • 第二步

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-5i1DOJRq-1609205739494)(redis.assets/image-20201127122151047.png)]

  • 第三步

    重啟redis

    redis-cli -p 6379 shutdown
    redis-server phzconfig/redis.conf
    redis-cli -p 6379
    

    提示輸入密碼

    auth 123456
    

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-D8woVVnj-1609205739494)(redis.assets/image-20201127123313776.png)]

  • 第四步

    開啟安全組配置(如果伺服器的防火牆處於開啟狀態,且沒有給他新增授權埠可能會連線失敗,要麼關閉,要麼新增埠)

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ynCdOGRw-1609205739495)(redis.assets/image-20201127123547736.png)]

第三步:給jedis設定訪問密碼

jedis.auth("123456");

第五步:測試連線

System.out.println(jedis.ping());

常用API

key

@Test
public void testPing() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");

    System.out.println("清空資料:" + jedis.flushDB());
    System.out.println("判斷某個鍵是否存在:" + jedis.exists("username"));
    System.out.println("新增<'username','phz'>的鍵值對:" + jedis.set("username", "phz"));
    System.out.println("新增<'password','password'>的鍵值對:" + jedis.set("password", "password"));
    System.out.print("系統中所有的鍵如下:");
    Set<String> keys = jedis.keys("*");
    System.out.println(keys);

    System.out.println("刪除鍵password: " + jedis.del("password"));
    System.out.println("判斷鍵password是否存在: " + jedis.exists("password"));
    System.out.println("檢視鍵username所儲存的值的型別:" + jedis.type("username"));
    System.out.println("隨機返回key空間的一個:" + jedis.randomKey());
    System.out.println("重新命名key:" + jedis.rename("username", "name"));
    System.out.println("取出改後的name: " + jedis.get("name"));
    System.out.println("按索引查詢:" + jedis.select(0));
    System.out.println("刪除當前選擇資料庫中的所有key:" + jedis.flushDB());
    System.out.println("返回當前資料庫中key的數目:" + jedis.dbSize());
    System.out.println("刪除所有資料庫中的所有key:" + jedis.flushAll());
}
================================================執行結果================================================
清空資料:OK
判斷某個鍵是否存在:false
新增<'username','phz'>的鍵值對:OK
新增<'password','password'>的鍵值對:OK
系統中所有的鍵如下:[password, username]
刪除鍵password: 1
判斷鍵password是否存在: false
檢視鍵username所儲存的值的型別:string
隨機返回key空間的一個:username
重新命名key:OK
取出改後的name: phz
按索引查詢:OK
刪除當前選擇資料庫中的所有key:OK
返回當前資料庫中key的數目:0
刪除所有資料庫中的所有key:OK

String

@Test
public void testString() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");

    jedis.flushDB();
    System.out.println("=========增加資料==========");
    System.out.println(jedis.set("key1", "value1"));
    System.out.println(jedis.set("key2", "value2"));
    System.out.println(jedis.set("key3", "value3"));
    System.out.println("刪除鍵key2 : " + jedis.del("key2"));
    System.out.println("獲取鍵key2 : " + jedis.get("key2"));
    System.out.println("修改key1 : " + jedis.set("key1", "value1Changed"));
    System.out.println("獲取key1的值 :" + jedis.get("key1"));
    System.out.println("在key3後面加入值 : " + jedis.append("key3", "End"));
    System.out.println("key3的值 : " + jedis.get("key3"));
    System.out.println("增加多個鍵值對 : " + jedis.mset("key01 ", "value01", "key02", "value02", "key03", "value03"));
    System.out.println(" 獲取多個鍵值對 : " + jedis.mget("key01", "key02", "key03"));
    System.out.println("獲取多個鍵值對 : " + jedis.mget("key01 ", " key02", "key03", "key04"));
    System.out.println("刪除多個鍵值對 : " + jedis.del("key01", "key02"));
    System.out.println("獲取多個鍵值對 : " + jedis.mget("key01 ", "key02", "key03"));

    jedis.flushDB();
    System.out.println("==========新增鍵值對防止覆蓋原先值===========");
    System.out.println(jedis.setnx("key1", "value1"));
    System.out.println(jedis.setnx("key2", "value2"));
    System.out.println(jedis.setnx("key2", "value2-new"));
    System.out.println(jedis.get("key1"));
    System.out.println(jedis.get("key2"));

    System.out.println("=========新增鍵值對並設定有效時間===========");
    System.out.println(jedis.setex("key3", 2, "value3"));
    System.out.println(jedis.get("key3"));
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(jedis.get("key3"));

    System.out.println("===========獲取原值,更新為新值=========");
    System.out.println(jedis.getSet("key2", "key2GetSet"));
    System.out.println(jedis.get("key2"));
    System.out.println("獲得key2的值的字串:" + jedis.getrange("key2", 2,4));
}
================================================執行結果================================================
=========增加資料==========
OK
OK
OK
刪除鍵key2 : 1
獲取鍵key2 : null
修改key1 : OK
獲取key1的值 :value1Changed
在key3後面加入值 : 9
key3的值 : value3End
增加多個鍵值對 : OK
 獲取多個鍵值對 : [null, value02, value03]
獲取多個鍵值對 : [value01, null, value03, null]
刪除多個鍵值對 : 1
獲取多個鍵值對 : [value01, null, value03]
==========新增鍵值對防止覆蓋原先值===========
1
1
0
value1
value2
=========新增鍵值對並設定有效時間===========
OK
value3
null
===========獲取原值,更新為新值=========
value2
key2GetSet
獲得key2的值的字串:y2G

List

@Test
public void testList() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    System.out.println("========新增一個list=========");
    jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WleakHashMap", "LinkedHlashNap");
    jedis.lpush("collections", "HashSet");
    jedis.lpush("collections", "TreeSet");
    jedis.lpush("col1ections", "TreeMap");
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));//-1代表倒數第一個元素-2 代表倒數第二個元素
    System.out.println("collections區間e-3的元素:" + jedis.lrange("collections", 0, 3));
    System.out.println("==============================");
    //刪除列表指定的值,第二個引數為刪除的個數(有重複時),後add進去的值先被刪,類似於出棧
    System.out.println("刪除指定元素個數:" + jedis.lrem("collections", 2, "HashMap"));
    System.out.println(" collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("刪除下表e-3區間之外的元素:" + jedis.ltrim("collections", 0, 3));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println(" collections列表出棧(左端): " + jedis.lpop("collections"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("collections新增元素,從列表右端,與lpush相對應。" + jedis.rpush("collections", "EnumMap"));
    System.out.println("collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("collections列表出棧(右端):" + jedis.rpop("collections"));
    System.out.println("collections的內容: " + jedis.lrange("collections", 0, -1));
    System.out.println("修改collections指定下標1的內容:" + jedis.lset("collections", 1, "LinkedArrayList"));
    System.out.println(" collections的內容:" + jedis.lrange("collections", 0, -1));
    System.out.println("==============================");
    System.out.println("collections的長度:" + jedis.llen("collections"));
    System.out.println("獲取collections下標為2的元素:" + jedis.lindex("collections", 2));
    System.out.println("==============================");
    jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
    System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
    System.out.println(jedis.sort("sortedList"));
    System.out.println(" sortedList排序後: " + jedis.lrange("sortedList", 0, -1));
}
================================================執行結果================================================
========新增一個list=========
collections的內容:[TreeSet, HashSet, LinkedHlashNap, WleakHashMap, HashMap, Stack, Vector, ArrayList]
collections區間e-3的元素:[TreeSet, HashSet, LinkedHlashNap, WleakHashMap]
==============================
刪除指定元素個數:1
 collections的內容:[TreeSet, HashSet, LinkedHlashNap, WleakHashMap, Stack, Vector, ArrayList]
刪除下表e-3區間之外的元素:OK
collections的內容:[TreeSet, HashSet, LinkedHlashNap, WleakHashMap]
 collections列表出棧(左端): TreeSet
collections的內容:[HashSet, LinkedHlashNap, WleakHashMap]
collections新增元素,從列表右端,與lpush相對應。4
collections的內容:[HashSet, LinkedHlashNap, WleakHashMap, EnumMap]
collections列表出棧(右端):EnumMap
collections的內容: [HashSet, LinkedHlashNap, WleakHashMap]
修改collections指定下標1的內容:OK
 collections的內容:[HashSet, LinkedArrayList, WleakHashMap]
==============================
collections的長度:3
獲取collections下標為2的元素:WleakHashMap
==============================
sortedList排序前:[4, 7, 0, 2, 6, 3]
[0, 2, 3, 4, 6, 7]
sortedList排序後: [4, 7, 0, 2, 6, 3]

Set

@Test
public void TestSet() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    System.out.println("========向集合中新增元素(不重複)=======");
    System.out.println(jedis.sadd("eleSet", "e1", "e2", " e4", " e3", " e0", " e8", " e7", "e5"));
    System.out.println(jedis.sadd("eleSet", "e6"));
    System.out.println(jedis.sadd("eleSet", "e6"));
    System.out.println("eleSet的所有元素為:" + jedis.smembers("eleSet"));
    System.out.println("刪除一個元素e0:" + jedis.srem("eleSet", "e0"));
    System.out.println("eleSet的所有元素為: " + jedis.smembers("e1eSet"));
    System.out.println("刪除兩個元素e7和e6: " + jedis.srem("eleSet", "e7", " e6"));
    System.out.println("eleSet的所有元素為: " + jedis.smembers("eleSet"));
    System.out.println("隨機的移除集合中的一個元素: " + jedis.spop("eleSet"));
    System.out.println("隨機的移除集合中的一個元素: " + jedis.spop("eleSet"));
    System.out.println("eleSet的所有元素為: " + jedis.smembers("eleSet"));
    System.out.println("eleSet中包含元素的個數:" + jedis.scard("eleSet"));
    System.out.println("e3是否在eleSet中: " + jedis.sismember("eleSet", "e3"));
    System.out.println("e1是否在eleSet中: " + jedis.sismember("eleSet", "e1"));
    System.out.println("el是否在eleSet中: " + jedis.sismember("eleSet", "e5"));
    System.out.println("=====================");
    System.out.println(jedis.sadd("eleSet1", "e1", "e2", " e4", " e3", "e0", " e8", "e7", "e5"));
    System.out.println(jedis.sadd("eleSet2", "e1", "e2", " e4", "e3", "e0", "e8"));
    System.out.println("將e1eSet1中刪除e1並存入eleSet3中:" + jedis.smove("eleSet1", "elset.", "e1"));//移到集合元素
    System.out.println("將e1eSet1中刪除e2並存入eleSet3中: " + jedis.smove("eleSet1", "eleSet3", " e2"));
    System.out.println("eleSet1中的元素: " + jedis.smembers("eleSet1"));
    System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
    System.out.println("===========集合運算==========");
    System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
    System.out.println("eleSet2中的元素。" + jedis.smembers("eleSet2"));
    System.out.println("eleSet1和eleSet2的交集: " + jedis.sinter("eleSet1", "eleSet2"));
    System.out.println("eleSet1和eleSet2的並集: " + jedis.sunion("eleSet1", "eleSet2"));
    System.out.println("eleSet1和eleSet2的差集: " + jedis.sdiff("eleSet1", "eleSet2"));//eLeset1中有,eleSet2中沒有
    jedis.sinterstore("eleSet4", "eleSet1", "eleSet2");//求交集並將交集儲存到dsthey的集合
    System.out.println("eleset4中的元素:" + jedis.smembers("eleSet4"));
}
================================================執行結果================================================
========向集合中新增元素(不重複)=======
8
1
0
eleSet的所有元素為:[ e7,  e3,  e8, e2,  e4, e5, e1,  e0, e6]
刪除一個元素e0:0
eleSet的所有元素為: []
刪除兩個元素e7和e6: 0
eleSet的所有元素為: [e5, e1,  e0,  e7,  e3,  e4, e2,  e8, e6]
隨機的移除集合中的一個元素: e6
隨機的移除集合中的一個元素: e2
eleSet的所有元素為: [ e0,  e7,  e3,  e4,  e8, e5, e1]
eleSet中包含元素的個數:7
e3是否在eleSet中: false
e1是否在eleSet中: true
el是否在eleSet中: true
=====================
8
6
將e1eSet1中刪除e1並存入eleSet3中:1
將e1eSet1中刪除e2並存入eleSet3中: 0
eleSet1中的元素: [e0, e7,  e3,  e8, e2,  e4, e5]
eleSet3中的元素:[]
===========集合運算==========
eleSet1中的元素:[e0, e7,  e3,  e8, e2,  e4, e5]
eleSet2中的元素。[e3, e1, e0, e8, e2,  e4]
eleSet1和eleSet2的交集: [e0, e2,  e4]
eleSet1和eleSet2的並集: [ e3, e8, e2,  e4,  e8, e5, e0, e7, e3, e1]
eleSet1和eleSet2的差集: [e5,  e3, e7,  e8]
eleset4中的元素:[e0,  e4, e2]

Hash

@Test
public void testHash() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    Map<String, String> map = new HashMap<>();
    map.put("key1", "value1");
    map.put("key2", "value2");
    map.put("key3", "value3");
    map.put("key4", "value4");
    //新增名稱為hash(hey) 的hash元素
    jedis.hmset("hash", map);
    //向名稱為hash的hash中新增key為key5. value為value5元素
    jedis.hset("hash", "key5", "value5");
    System.out.println("雜湊hash的所有鍵值對為:" + jedis.hgetAll("hash")); //return Map<String,String>
    System.out.println("雜湊hash的所有鍵為:" + jedis.hkeys("hash")); //return Set<String>
    System.out.println("雜湊hash的所有值為:" + jedis.hvals("hash")); //return List < String >
    System.out.println("將key6儲存的值加上一個整數,如果key6不存在則新增key6:" + jedis.hincrBy("hash", "key6", 1));
    System.out.println("雜湊hash的所有鍵值對為: " + jedis.hgetAll("hash"));
    System.out.println("將key6儲存的值加上一個整數,如果key6不存在則新增key6:" + jedis.hincrBy("hash", "key6", 1));
    System.out.println("雜湊hash的所有鍵值對為: " + jedis.hgetAll("hash"));
    System.out.println("刪除一個或者多個鍵值對:" + jedis.hdel(" hash ", " key2 "));
    System.out.println("雜湊hash的所有鍵值對為:" + jedis.hgetAll(" hash "));
    System.out.println("雜湊hash中鍵值對的個數: " + jedis.hlen("hash"));
    System.out.println("判斷hash中是否存在key2: " + jedis.hexists("hash", "key2"));
    System.out.println("判斷hash中是否存在key3:" + jedis.hexists(" hash ", " key3 "));
    System.out.println("獲取hash中的值:" + jedis.hmget(" hash ", " key3 "));
    System.out.println("獲取hash中的值:" + jedis.hmget("hash", "key3", "key4"));
}
================================================執行結果================================================
雜湊hash的所有鍵值對為:{key1=value1, key2=value2, key5=value5, key3=value3, key4=value4}
雜湊hash的所有鍵為:[key1, key2, key5, key3, key4]
雜湊hash的所有值為:[value1, value4, value2, value3, value5]
將key6儲存的值加上一個整數,如果key6不存在則新增key6:1
雜湊hash的所有鍵值對為: {key1=value1, key2=value2, key5=value5, key6=1, key3=value3, key4=value4}
將key6儲存的值加上一個整數,如果key6不存在則新增key6:2
雜湊hash的所有鍵值對為: {key1=value1, key2=value2, key5=value5, key6=2, key3=value3, key4=value4}
刪除一個或者多個鍵值對:0
雜湊hash的所有鍵值對為:{}
雜湊hash中鍵值對的個數: 6
判斷hash中是否存在key2: true
判斷hash中是否存在key3:false
獲取hash中的值:[null]
獲取hash中的值:[value3, value4]

通過Jedis再次理解事物

@Test
public void testTransaction() {
    Jedis jedis = new Jedis("39.105.43.3", 6379);
    jedis.auth("123456");
    jedis.flushDB();
    JSONObject jsonobject = new JSONObject();
    jsonobject.put("he1lo", "wor1d");
    jsonobject.put("name", "phz");//開啟事務
    Transaction multi = jedis.multi();
    String result = jsonobject.toJSONString();
    try {
        multi.set("user1", result);
        int i = 1/0;//程式碼丟擲異常,執行失敗
        multi.set("user2", result);
        multi.exec();//執行事務
    } catch (Exception e) {
        multi.discard();// 放棄事務
        e.printStackTrace();
    } finally {
        System.out.println(jedis.get("user1"));
        System.out.println(jedis.get("user2"));
        jedis.close();//關閉連線
    }
}

SpringBoot整合Redis

說明︰在SpringBoot2.x之後,原來使用的jedis被替換為了lettuce?

jedis :採用的直連,多個執行緒操作的話,是不安全的,如果想要避免不安全的,使用jedis pool連線池!更像BIO模式

lettuce :採用netty ,例項可以在多個執行緒中進行共享,不存線上程不安全的情況!可以減少執行緒資料了,更像NIO模式

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wxawA2DP-1609205739497)(redis.assets/image-20201127182923266.png)]

原始碼分析

//如果不存在這個bean才生效,所以我們可以自己定義一個RedisTemplate來替換
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    //預設的RedisTemplate沒有過多的配置,redis物件都需要被序列化
    //兩個泛型都是Object型別,我們在使用的時候都需要強制轉換<String,Object>
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//由於String是redis中常用的資料型別,所以單獨提取出來一個String的bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

如何配置redis?

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Peti1tf1-1609205739497)(redis.assets/image-20201127184225608.png)]

如上圖,所有redis配置類中定義的方法都可以被配置

spring.redis.host=39.105.43.3
spring.redis.port=6379
spring.redis.password=123456

配置完成以後我們就可以到測試類中探索一下springboot如何實現redis中的方法的

基本的資料操作可以直接用redisTemplate進行操作

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-4nfNo0KC-1609205739498)(redis.assets/image-20201127184832449.png)]

對資料庫的操作我們可以獲取一個redis資料庫連線物件

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-hrgB0d7v-1609205739499)(redis.assets/image-20201127185432437.png)]

簡單測試

@Test
void contextLoads() {
    RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection();
    connection.flushDb();
    redisTemplate.opsForValue().set("name","彭煥智");
    System.out.println(redisTemplate.opsForValue().get("name"));
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9gOGBWfK-1609205739500)(redis.assets/image-20201127190906608.png)]

但是我們觀察服務端的key資訊發現

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-6yi6C5hz-1609205739501)(redis.assets/image-20201127190811528.png)]

前面有一堆看不懂的亂碼,即我們設定的key為name,但是我們儲存的時候卻並不是name,這就是因為我們redis所有的操作都應該要序列化才行,

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-F68yKSDs-1609205739502)(redis.assets/image-20201127191009747.png)]

檢視序列化原始碼發現預設使用的jdk序列化

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1uq89FRx-1609205739502)(redis.assets/image-20201127191430602.png)]

所以我們就需要自己定義一個自己的RedisTemplate了,以下給出官方的RedisTemplate,我們直接仿寫

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
/**
 * @author PengHuAnZhi
 * @createTime 2020/12/4 7:58
 * @projectName HaveFun
 * @className RedisConfig.java
 * @description TODO
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    //固定模板,一般企業中可以直接使用
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //實際開發<String, Object>用的很多,所以就這裡指定
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //序列化配置
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        template.setKeySerializer(objectJackson2JsonRedisSerializer);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //value採用String的序列化方式
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        //hash的也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //hash的值採用jackson的序列化方式
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 配置一個CacheManager才能使用@Cacheable等註解
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //生成一個預設配置,通過config物件即可對快取進行自定義配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config
                // 設定 key為string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 設定value為json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(objectJackson2JsonRedisSerializer))
                // 不快取空值
                .disableCachingNullValues()
                // 設定快取的預設過期時間 60分鐘
                .entryTtl(Duration.ofHours(1L));

        //特殊快取空間應用不同的配置
        Map<String, RedisCacheConfiguration> map = new HashMap<>();
        //將輪播圖地址設定永不過期,因為基本不會改動它
        map.put("activity_constant", config.entryTtl(Duration.ofMinutes(-1L)));//provider1快取空間過期時間 30分鐘

        //使用自定義的快取配置初始化一個RedisCacheManager

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config) //預設配置
                .withInitialCacheConfigurations(map) //特殊快取
                .transactionAware() //事務
                .build();
    }
}

這個時候我們再進行操作

@Test
void test() throws JsonProcessingException {
    //真實開發環境中我們存資料不是以物件儲存,而是以json串來儲存,而且物件都需要實現序列化
    User user = new User("彭煥智", 21);
    String jsonUser = new ObjectMapper().writeValueAsString(user);
    redisTemplate.opsForValue().set("user",jsonUser);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-KryOMgd7-1609205739503)(redis.assets/image-20201127204101608.png)]

RedisUtil

package com.phz;

import org.springframework.data.redis.core.ZSetOperations.TypedTuple;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;


/**
 * Redis工具類
 *
 */
@Component
public class RedisUtil {

    @Resource
    private StringRedisTemplate redisTemplate;

    public void setRedisTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public StringRedisTemplate getRedisTemplate() {
        return this.redisTemplate;
    }


    /** -------------------key相關操作--------------------- */

    /**
     * 刪除key
     *
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 批量刪除key
     *
     * @param keys
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 序列化key
     *
     * @param key
     * @return
     */
    public byte[] dump(String key) {
        return redisTemplate.dump(key);
    }

    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 設定過期時間
     *
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 設定過期時間
     *
     * @param key
     * @param date
     * @return
     */
    public Boolean expireAt(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 查詢匹配的key
     *
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 將當前資料庫的 key 移動到給定的資料庫 db 當中
     *
     * @param key
     * @param dbIndex
     * @return
     */
    public Boolean move(String key, int dbIndex) {
        return redisTemplate.move(key, dbIndex);
    }

    /**
     * 移除 key 的過期時間,key 將持久保持
     *
     * @param key
     * @return
     */
    public Boolean persist(String key) {
        return redisTemplate.persist(key);
    }

    /**
     * 返回 key 的剩餘的過期時間
     *
     * @param key
     * @param unit
     * @return
     */
    public Long getExpire(String key, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }

    /**
     * 返回 key 的剩餘的過期時間
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 從當前資料庫中隨機返回一個 key
     *
     * @return
     */
    public String randomKey() {
        return redisTemplate.randomKey();
    }

    /**
     * 修改 key 的名稱
     *
     * @param oldKey
     * @param newKey
     */
    public void rename(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 僅當 newkey 不存在時,將 oldKey 改名為 newkey
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public Boolean renameIfAbsent(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 返回 key 所儲存的值的型別
     *
     * @param key
     * @return
     */
    public DataType type(String key) {
        return redisTemplate.type(key);
    }

    /** -------------------string相關操作--------------------- */

    /**
     * 設定指定 key 的值
     * @param key
     * @param value
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 獲取指定 key 的值
     * @param key
     * @return
     */
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key 中字串值的子字元
     * @param key
     * @param start
     * @param end
     * @return
     */
    public String getRange(String key, long start, long end) {
        return redisTemplate.opsForValue().get(key, start, end);
    }

    /**
     * 將給定 key 的值設為 value ,並返回 key 的舊值(old value)
     *
     * @param key
     * @param value
     * @return
     */
    public String getAndSet(String key, String value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 對 key 所儲存的字串值,獲取指定偏移量上的位(bit)
     *
     * @param key
     * @param offset
     * @return
     */
    public Boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 批量獲取
     *
     * @param keys
     * @return
     */
    public List<String> multiGet(Collection<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 設定ASCII碼, 字串'a'的ASCII碼是97, 轉為二進位制是'01100001', 此方法是將二進位制第offset位值變為value
     *
     * @param key
     * @param offset
     *            位置
     * @param value
     *            值,true為1, false為0
     * @return
     */
    public boolean setBit(String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 將值 value 關聯到 key ,並將 key 的過期時間設為 timeout
     *
     * @param key
     * @param value
     * @param timeout
     *            過期時間
     * @param unit
     *            時間單位, 天:TimeUnit.DAYS 小時:TimeUnit.HOURS 分鐘:TimeUnit.MINUTES
     *            秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
     */
    public void setEx(String key, String value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 只有在 key 不存在時設定 key 的值
     *
     * @param key
     * @param value
     * @return 之前已經存在返回false,不存在返回true
     */
    public boolean setIfAbsent(String key, String value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 用 value 引數覆寫給定 key 所儲存的字串值,從偏移量 offset 開始
     *
     * @param key
     * @param value
     * @param offset
     *            從指定位置開始覆寫
     */
    public void setRange(String key, String value, long offset) {
        redisTemplate.opsForValue().set(key, value, offset);
    }

    /**
     * 獲取字串的長度
     *
     * @param key
     * @return
     */
    public Long size(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    /**
     * 批量新增
     *
     * @param maps
     */
    public void multiSet(Map<String, String> maps) {
        redisTemplate.opsForValue().multiSet(maps);
    }

    /**
     * 同時設定一個或多個 key-value 對,當且僅當所有給定 key 都不存在
     *
     * @param maps
     * @return 之前已經存在返回false,不存在返回true
     */
    public boolean multiSetIfAbsent(Map<String, String> maps) {
        return redisTemplate.opsForValue().multiSetIfAbsent(maps);
    }

    /**
     * 增加(自增長), 負數則為自減
     *
     * @param key
     * @param increment
     * @return
     */
    public Long incrBy(String key, long increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     *
     * @param key
     * @param increment
     * @return
     */
    public Double incrByFloat(String key, double increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 追加到末尾
     *
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    /** -------------------hash相關操作------------------------- */

    /**
     * 獲取儲存在雜湊表中指定欄位的值
     *
     * @param key
     * @param field
     * @return
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 獲取所有給定欄位的值
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 獲取所有給定欄位的值
     *
     * @param key
     * @param fields
     * @return
     */
    public List<Object> hMultiGet(String key, Collection<Object> fields) {
        return redisTemplate.opsForHash().multiGet(key, fields);
    }

    public void hPut(String key, String hashKey, String value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public void hPutAll(String key, Map<String, String> maps) {
        redisTemplate.opsForHash().putAll(key, maps);
    }

    /**
     * 僅當hashKey不存在時才設定
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 刪除一個或多個雜湊表字段
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hDelete(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 檢視雜湊表 key 中,指定的欄位是否存在
     *
     * @param key
     * @param field
     * @return
     */
    public boolean hExists(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 為雜湊表 key 中的指定欄位的整數值加上增量 increment
     *
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public Long hIncrBy(String key, Object field, long increment) {
        return redisTemplate.opsForHash().increment(key, field, increment);
    }

    /**
     * 為雜湊表 key 中的指定欄位的整數值加上增量 increment
     *
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public Double hIncrByFloat(String key, Object field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 獲取所有雜湊表中的欄位
     *
     * @param key
     * @return
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 獲取雜湊表中欄位的數量
     *
     * @param key
     * @return
     */
    public Long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 獲取雜湊表中所有值
     *
     * @param key
     * @return
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 迭代雜湊表中的鍵值對
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }

    /** ------------------------list相關操作---------------------------- */

    /**
     * 通過索引獲取列表中的元素
     *
     * @param key
     * @param index
     * @return
     */
    public String lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 獲取列表指定範圍內的元素
     *
     * @param key
     * @param start
     *            開始位置, 0是開始位置
     * @param end
     *            結束位置, -1返回所有
     * @return
     */
    public List<String> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 儲存在list頭部
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, String... value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, Collection<String> value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * 當list存在的時候才加入
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushIfPresent(String key, String value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 如果pivot存在,再pivot前面新增
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String pivot, String value) {
        return redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPush(String key, String value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, String... value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, Collection<String> value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 為已存在的列表新增值
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushIfPresent(String key, String value) {
        return redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 在pivot元素的右邊新增值
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lRightPush(String key, String pivot, String value) {
        return redisTemplate.opsForList().rightPush(key, pivot, value);
    }

    /**
     * 通過索引設定列表元素的值
     *
     * @param key
     * @param index
     *            位置
     * @param value
     */
    public void lSet(String key, long index, String value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 移出並獲取列表的第一個元素
     *
     * @param key
     * @return 刪除的元素
     */
    public String lLeftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移出並獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param key
     * @param timeout
     *            等待時間
     * @param unit
     *            時間單位
     * @return
     */
    public String lBLeftPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除並獲取列表最後一個元素
     *
     * @param key
     * @return 刪除的元素
     */
    public String lRightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移出並獲取列表的最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param key
     * @param timeout
     *            等待時間
     * @param unit
     *            時間單位
     * @return
     */
    public String lBRightPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPop(key, timeout, unit);
    }

    /**
     * 移除列表的最後一個元素,並將該元素新增到另一個列表並返回
     *
     * @param sourceKey
     * @param destinationKey
     * @return
     */
    public String lRightPopAndLeftPush(String sourceKey, String destinationKey) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey);
    }

    /**
     * 從列表中彈出一個值,將彈出的元素插入到另外一個列表中並返回它; 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param sourceKey
     * @param destinationKey
     * @param timeout
     * @param unit
     * @return
     */
    public String lBRightPopAndLeftPush(String sourceKey, String destinationKey,
                                        long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey, timeout, unit);
    }

    /**
     * 刪除集合中值等於value得元素
     *
     * @param key
     * @param index
     *            index=0, 刪除所有值等於value的元素; index>0, 從頭部開始刪除第一個值等於value的元素;
     *            index<0, 從尾部開始刪除第一個值等於value的元素;
     * @param value
     * @return
     */
    public Long lRemove(String key, long index, String value) {
        return redisTemplate.opsForList().remove(key, index, value);
    }

    /**
     * 裁剪list
     *
     * @param key
     * @param start
     * @param end
     */
    public void lTrim(String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 獲取列表長度
     *
     * @param key
     * @return
     */
    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /** --------------------set相關操作-------------------------- */

    /**
     * set新增元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sAdd(String key, String... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * set移除元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 移除並返回集合的一個隨機元素
     *
     * @param key
     * @return
     */
    public String sPop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * 將元素value從一個集合移到另一個集合
     *
     * @param key
     * @param value
     * @param destKey
     * @return
     */
    public Boolean sMove(String key, String value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 獲取集合的大小
     *
     * @param key
     * @return
     */
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 判斷集合是否包含value
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 獲取兩個集合的交集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sIntersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 獲取key集合與多個集合的交集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sIntersect(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的交集儲存到destKey集合中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合與多個集合的交集儲存到destKey集合中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 獲取兩個集合的並集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, String otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 獲取key集合與多個集合的並集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sUnion(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的並集儲存到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * key集合與多個集合的並集儲存到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 獲取兩個集合的差集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<String> sDifference(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 獲取key集合與多個集合的差集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<String> sDifference(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().difference(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的差集儲存到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sDifference(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合與多個集合的差集儲存到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sDifference(String key, Collection<String> otherKeys,
                            String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 獲取集合所有元素
     *
     * @param key
     * @return
     */
    public Set<String> setMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 隨機獲取集合中的一個元素
     *
     * @param key
     * @return
     */
    public String sRandomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 隨機獲取集合中count個元素
     *
     * @param key
     * @param count
     * @return
     */
    public List<String> sRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 隨機獲取集合中count個元素並且去除重複的
     *
     * @param key
     * @param count
     * @return
     */
    public Set<String> sDistinctRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<String> sScan(String key, ScanOptions options) {
        return redisTemplate.opsForSet().scan(key, options);
    }

    /**------------------zSet相關操作--------------------------------*/

    /**
     * 新增元素,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     *
     * @param key
     * @param values
     * @return
     */
    public Long zAdd(String key, Set<TypedTuple<String>> values) {
        return redisTemplate.opsForZSet().add(key, values);
    }

    /**
     *
     * @param key
     * @param values
     * @return
     */
    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 增加元素的score值,並返回增加後的值
     *
     * @param key
     * @param value
     * @param delta
     * @return
     */
    public Double zIncrementScore(String key, String value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    /**
     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @return 0表示第一位
     */
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 返回元素在集合的排名,按元素的score值由大到小排列
     *
     * @param key
     * @param value
     * @return
     */
    public Long zReverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 獲取集合的元素, 從小到大排序
     *
     * @param key
     * @param start
     *            開始位置
     * @param end
     *            結束位置, -1查詢所有
     * @return
     */
    public Set<String> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 獲取集合元素, 並且把score值也獲取
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeWithScores(String key, long start,
                                                    long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 根據Score值查詢集合元素
     *
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<String> zRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 根據Score值查詢集合元素, 從小到大排序
     *
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                           double min, double max) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
    }

    /**
     *
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zRangeByScoreWithScores(String key,
                                                           double min, double max, long start, long end) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
                start, end);
    }

    /**
     * 獲取集合的元素, 從大到小排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 獲取集合的元素, 從大到小排序, 並返回score值
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeWithScores(String key,
                                                           long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
                end);
    }

    /**
     * 根據Score值查詢集合元素, 從大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
                                            double max) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 根據Score值查詢集合元素, 從大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<TypedTuple<String>> zReverseRangeByScoreWithScores(
            String key, double min, double max) {
        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                min, max);
    }

    /**
     *
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<String> zReverseRangeByScore(String key, double min,
                                            double max, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
                start, end);
    }

    /**
     * 根據score值獲取集合元素數量
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zCount(String key, double min, double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * 獲取集合大小
     *
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 獲取集合大小
     *
     * @param key
     * @return
     */
    public Long zZCard(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 獲取集合中value元素的score值
     *
     * @param key
     * @param value
     * @return
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 移除指定索引位置的成員
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 根據指定的score值的範圍來移除成員
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zRemoveRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    /**
     * 獲取key和otherKey的並集並存儲在destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return redisTemplate.opsForZSet()
                .unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, String otherKey,
                                   String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<TypedTuple<String>> zScan(String key, ScanOptions options) {
        return redisTemplate.opsForZSet().scan(key, options);
    }

}

實戰記錄

Spring快取

實際上是存在spring容器中的

第一步,在springboot全域性啟動類上加一個開去快取註解

//開啟快取
@EnableCaching
@SpringBootApplication
public class HavefunApplication {
        SpringApplication.run(HavefunApplication.class, args);
    }
}

空引數的快取

在需要查詢返回值的一個方法上加啟動快取的註解

@Cacheable(value = "test",key = "'testkey'")
public String[] getRotationChartPictures() {
    System.out.println("進來了");
    return new String[]{ValueConfig.SERVER_URL + "localPictures/1.png",
                        ValueConfig.SERVER_URL + "localPictures/2.png",
                        ValueConfig.SERVER_URL + "localPictures/3.png",
                        ValueConfig.SERVER_URL + "localPictures/4.png",};
}

測試的時候發現,除了第一次使用這個方法,第二次及以後都不在進入這個方法,為什麼呢?

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-KL1JRe8i-1609205739505)(redis.assets/image-20201203165616053.png)]

第一次訪問這個方法,先去快取中查詢有沒有testkey的記錄,發現沒有,就進入getRotationChartPictures方法,執行完畢返回後,將返回值標記為testkey,並存入test快取區,下一次請求,發現快取區有testkey,就不再進入方法,直接從快取區找。

但是會出現一個問題:當我們取的資料是從資料庫中查出來的,如果這個資料庫中的資料被修改了,那這個地方取出來的快取永遠都不會更新,所以我們就需要在有可能修改該資料的方法上面加上清除快取的註解

//也可以偷懶,不寫key,但是這樣就是清除所有test快取區中的資料,還得寫上allEntries = true
@CacheEvict(value = "test",key = "'testkey'")

單引數的快取

如果返回值方法上面有引數怎麼辦?如果第一次查詢id為1的資料,第二次查詢卻是2怎麼辦,還能使用快取嗎??很簡單,方式如下

@Cacheable(value = "test",key = "'testkey'+#id")

同樣的,如果我們對這個方法所返回的資料有更新,我們還是需要去清理快取,我們在清理快取的時候,必須考慮所有可能和這個返回資料有關係的地方,都需要進行刪除,但是我們不同的方法上面所使用的快取key是不一樣的,所以要同時清理很多的key的時候,我們只能依靠直接清理一個快取區來解決,這就要求了所有和這個資料有關聯的方法所產生的key的快取區都需要相同,不能隨意開啟不同的快取區

這個時候清理快取就需要用到新的引數

@CacheEvict(value = "test",AllEntries = true)

多引數的快取

@Cacheable(value = "test",key = "'testkey'+#id+','+#name")

無論是單參還是多參,總之都是字串的拼接,只是需要注意變數前面需要加上井號,唯一目的就只是作為不同的快取key的區分,比如下面有一個key23單參快取,然後還有一個雙參快取2和3,如果用key23就衝突了,所以加一個逗號表示key2,3,當然也可以加其他的符號

Redis快取

同樣需要引入redis依賴

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.4.0</version>
</dependency>

執行專案報錯

Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382) ~[na:1.8.0_261]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[na:1.8.0_261]
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355) ~[na:1.8.0_261]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[na:1.8.0_261]
	... 48 common frames omitted

引入commons-pool2依賴

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>

我們之前的spring快取註解不需要任何修改,可以直接執行,測試訪問

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zEMtjSwZ-1609205739506)(redis.assets/image-20201203194002747.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-o0EacLyc-1609205739507)(redis.assets/image-20201203194021440.png)]

使用redisTemplate實現下單30min不支付自動取消

redisTemplate.opsForValue().set(id,"一些描述資訊,無所謂,也可以直接存訂單的詳細資訊",Duration.ofMinutes(30L));

獲取訂單剩餘時間

Long time = redisTemplate.getExpire("key");

RedisConfig.java

/**
 * @author PengHuAnZhi
 * @createTime 2020/12/4 7:58
 * @projectName HaveFun
 * @className RedisConfig.java
 * @description TODO
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    //固定模板,一般企業中可以直接使用
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //實際開發<String, Object>用的很多,所以就這裡指定
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //序列化配置
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        template.setKeySerializer(objectJackson2JsonRedisSerializer);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //value採用String的序列化方式
        template.setValueSerializer(objectJackson2JsonRedisSerializer);
        //hash的也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //hash的值採用jackson的序列化方式
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 配置一個CacheManager才能使用@Cacheable等註解
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //生成一個預設配置,通過config物件即可對快取進行自定義配置
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        config = config
                // 設定 key為string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 設定value為json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(objectJackson2JsonRedisSerializer))
                // 不快取空值
                .disableCachingNullValues()
                // 設定快取的預設過期時間 60分鐘
                .entryTtl(Duration.ofHours(1L));

        //特殊快取空間應用不同的配置
        Map<String, RedisCacheConfiguration> map = new HashMap<>();
        //將輪播圖地址設定永不過期,因為基本不會改動它
        map.put("activity_constant", config.entryTtl(Duration.ofMinutes(-1L)));//provider1快取空間過期時間 30分鐘

        //使用自定義的快取配置初始化一個RedisCacheManager

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config) //預設配置
                .withInitialCacheConfigurations(map) //特殊快取
                .transactionAware() //事務
                .build();
    }
}

Redis.conf詳解

redis在啟動的時候,就是通過配置檔案來啟動的

單位,大小寫不敏感

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

還可以包含其他配置檔案

# include /path/to/local.conf
# include /path/to/other.conf

網路

  • 我們在配置idea遠端連線的時候,就把這裡的bind本地連線給註釋了
#bind 127.0.0.1
  • 保護模式,埠號,tcp連線佇列
protected-mode yes
port 6379
tcp-backlog 511

通用配置

  • 我們在安裝redis的時候這裡就把daemonize 設定為了yes,就是可以後臺執行,即守護程序執行
daemonize yes
  • 當我們開啟了守護程序執行,我們就必須指定一個pid檔案
pidfile /var/run/redis_6379.pid
  • loglevel表示日誌級別,具體選項都在上方註釋
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
  • 日誌生成的位置
logfile ""
  • 預設16個數據庫
databases 16
  • 是否總是顯示log
always-show-logo yes
  • 快照

    持久化,在規定的時間內,執行了多少次操作,則會持久化到檔案.rdb或. aof

    redis是記憶體資料庫,如果沒有持久化,那麼資料斷電即失

#900s內,如果有一個key被修改過,我們就進行持久化操作
save 900 1
#300s內,如果有十個key被修改過,我們就進行持久化操作
save 300 10
#60s內,如果有一萬個key被修改過,我們就進行持久化操作
save 60 10000
  • 如果持久化出錯,是否還需要繼續工作
stop-writes-on-bgsave-error yes
  • 是否需要壓縮rdb檔案,需要消耗一些cpu資源
rdbcompression yes
  • 儲存rdb檔案的時候,是否要進行一些錯誤的校驗,自動修復
rdbchecksum yes
  • 持久化生成的rdb檔案儲存的目錄
dir ./
  • 主從複製的一些配置,後面詳解,這裡不講
################################# REPLICATION #################################
  • 安全等
################################## SECURITY ###################################

#設定密碼
requirepass 123456
#命令列設定密碼
config set requirepass "123456"
#清除密碼
config set requirepass ""
#登入
auth 123456
  • 客戶端限制
################################### CLIENTS ####################################
#最多同時有10000個客戶端連線
# maxclients 10000
  • 記憶體限制
############################## MEMORY MANAGEMENT ################################
#redis配置最大的記憶體容量 
# maxmemory <bytes>

#記憶體滿了以後的處理策略,移除一些過期的key
# maxmemory-policy noeviction
1、volatile-lru:只對設定了過期時間的key進行LRU(預設值) 

2、allkeys-lru : 刪除lru演算法的key   

3、volatile-random:隨機刪除即將過期key   

4、allkeys-random:隨機刪除   

5、volatile-ttl : 刪除即將過期的   

6、noeviction : 永不過期,返回錯誤
  • AOF模式
############################## APPEND ONLY MODE ###############################

#預設不開啟aof模式,預設使用rdb持久化方式,在大部分情況下,rdb完全夠用了	  	
appendonly no
#持久化的檔案的名字
appendfilename "appendonly.aof"

# appendfsync always#每次修改都會同步,消耗效能
appendfsync everysec#每秒同步一次,可能會丟失這一秒的資料
# appendfsync no    #不同步,這個時候作業系統自己同步資料,速度最快,但不推薦

Redis持久化

Redis是記憶體資料庫,如果不將記憶體中的資料庫狀態儲存到磁碟,那麼一旦伺服器程序退出,伺服器中的資料庫狀態也會消失。所以Redis提供了持久化功能

RDB(Redis Database)

什麼是RDB

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HlzMsuYn-1609205739508)(redis.assets/image-20201128215857745.png)]

在指定的時間間隔內將記憶體中的資料集快照寫入磁碟,也就是行話講的Snapshot快照,它恢復時是將快照檔案直接讀到記憶體裡。Redis會單獨建立( fork )一個子

程序來進行持久化,會先將資料寫入到一個臨時檔案中,待持久化過程都結束了,再用這個臨時檔案替換上次持久化好的檔案。整個過程中,主程序是不進行任何

IO操作的。這就確保了極高的效能。如果需要進行大規模資料的恢復,且對於資料恢復的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺點

是最後一次持久化後的資料可能丟失。

我們的rdb檔案在配置檔案中都有定義

dump.rdb

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-3mU0z4y5-1609205739508)(redis.assets/image-20201128221032724.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xw7sh4LF-1609205739509)(redis.assets/image-20201128220924265.png)]

測試

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9JtaIAee-1609205739510)(redis.assets/image-20201128221354007.png)]

關閉服務再次測試

  • 有dump.rdb檔案的時候
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> shutdown
not connected> exit
[root@iZ2ze5wj5w33v3gyd9kv1bZ bin]# redis-server phzconfig/redis.conf 
[root@iZ2ze5wj5w33v3gyd9kv1bZ bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> get k1
"v1"
  • 沒有dump.rdb檔案的時候
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@iZ2ze5wj5w33v3gyd9kv1bZ bin]# rm -rf dump.rdb 
[root@iZ2ze5wj5w33v3gyd9kv1bZ bin]# redis-server phzconfig/redis.conf 
[root@iZ2ze5wj5w33v3gyd9kv1bZ bin]# redis-cli -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> get key1
(nil)

當我們執行了flush命令,save命令,退出redis,都會自動生成一個dump.rdb檔案

如何恢復rdb檔案!

只需要將rdb檔案放在我們redis啟動目錄就可以,redis啟動的時候會自動檢查dump.rdb恢復其中的資料!

#獲取存放路徑
config get dir

優點:
1、適合大規模的資料恢復!

2、對資料的完整性要不高!

缺點:

1、需要一定的時間間隔程序操作!如果redis意外宕機了,這個最後一次修改資料就沒有的了!

2、fork程序的時候,會佔用一定的內容空間!!

AOF(append only file)

將我們的所有命令都記錄下來,類似history,恢復的時候就把這個檔案全部在執行一遍

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-tJHLpLZH-1609205739511)(redis.assets/image-20201129100919556.png)]

以日誌的形式來記錄每個寫操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許追加檔案但不可以改寫檔案,redis啟動之初會讀取該檔案重新構建資料,換言之,redis重啟的話就根據日誌檔案的內容將寫指令從前到後執行一次以完成資料的恢復工作
Aof儲存的是appendonly.aof 檔案

重寫規則

aof預設就是檔案的無限追加,檔案會越來越大

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-UpOS8DVp-1609205739512)(redis.assets/image-20201129102810941.png)]

如果aof檔案大於64m,太大了! fork一個新的程序來將我們的檔案進行重寫!

預設是不開啟的,需要開啟就需要在配置檔案中的這個no改為yes

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HhQv9xx5-1609205739513)(redis.assets/image-20201129101130467.png)]

#重新啟動,使配置檔案生效
shutdown

我們就發現在同級目錄出現了這個appendonly.aof檔案

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-eaUUVoud-1609205739513)(redis.assets/image-20201129101724791.png)]

寫入一個命令,再次開啟該檔案

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wlfPWAvd-1609205739514)(redis.assets/image-20201129101826428.png)]

當我們在關機後,人為修改這個aof檔案,或者rdb檔案,我們的資料就會被破壞,我們redis就會啟動不起來,我們就需要修復這個檔案,我們就可以使用redis給我們提供的修復工具redis-check-aof或者redis-check-rdb

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-N0yfn4C1-1609205739515)(redis.assets/image-20201129102228512.png)]

#執行修復
redis-check-aof --fix appendonly.aof

擴充套件

1、RDB持久化方式能夠在指定的時間間隔內對你的資料進行快照儲存

2、AOF持久化方式記錄每次對伺服器寫的操作,當伺服器重啟的時候會重新執行這些命令來恢復原始的資料,AOF命令以Redis協議追加儲存每次寫的操作到檔案

末尾,Redis還能對AOF檔案進行後臺重寫,使得AOF檔案的體積不至於過大。

3、只做快取,如果你只希望你的資料在伺服器執行的時候存在,你也可以不使用任何持久化

4、同時開啟兩種持久化方式

  • 在這種情況下,當redis重啟的時候會優先載入AOF檔案來恢復原始的資料,因為在通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集要完整。
  • RDB的資料不實時,同時使用兩者時伺服器重啟也只會找AOF檔案,那要不要只使用AOF呢?作者建議不要,因為RDB更適合用於備份資料庫(AOF在不斷變化不好備份),快速重啟,而且不會有AOF可能潛在的Bug,留著作為一個萬一的手段。

5、效能建議

  • 因為RDB檔案只用作後備用途,建議只在Slave上持久化RDB檔案,而且只要15分鐘備份一次就夠了,只保留save 9001這條規則。

  • 如果Enable AOF,好處是在最惡劣情況下也只會丟失不超過兩秒資料,啟動指令碼較簡單隻load自己的AOF檔案就可以了,代價一是帶來了持續的IO,二是AOF rewrite 的最後將rewrite過程中產生的新資料寫到新檔案造成的阻塞幾乎是不可避免的。只要硬碟許可,應該儘量減少AOF rewrite 的頻率,AOF重寫的基礎大小預設值64M太小了,可以設到5G以上,預設超過原大小100%大小重寫可以改到適當的數值。

  • 如果不Enable AOF,僅靠Master-Slave Replcation實現高可用性也可以,能省掉一大筆IO,也減少了rewrite時帶來的系統皮動。代價是如果Master/Slave同時倒掉,會丟失十幾分鐘的資料,啟動指令碼也要比較兩個MasterlSlave 中的RDB檔案,載入較新的那個,微博就是這種架構。

Redis釋出訂閱

Redis 釋出訂閱(pub/sub)是一種訊息通訊模式︰傳送者(pub)傳送訊息,訂閱者(sub)接收訊息。微信,微博、關注系統
Redis客戶端可以訂閱任意數量的頻道。
訂閱/釋出訊息圖︰
第一個:訊息傳送者,第二個:頻道第三個:訊息訂閱者!

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-f8kV9vtS-1609205739515)(redis.assets/image-20201129105307938.png)]

下圖展示了頻道channel1,以及訂閱這個頻道的三個客戶端―—client2、 client5和client1之間的關係∶

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-y3llilWA-1609205739516)(redis.assets/image-20201129105348191.png)]

當有新訊息通過PUBLISH 命令傳送給頻道channel1時,這個訊息就會被髮送給訂閱它的三個客戶端

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-esN1jqiq-1609205739517)(redis.assets/image-20201129105404060.png)]

命令

  • 監聽某個頻道
SUBSCRIBE phz

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-VxupaVuQ-1609205739517)(redis.assets/image-20201129105740547.png)]

  • 向某個頻道傳送資訊
PUBLISH phz "hello phz"

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-n5DI2pio-1609205739518)(redis.assets/image-20201129105957655.png)]

原理

  • Redis是使用C實現的,通過SUBSCRIBE 命令訂閱某頻道後,redis-server裡維護了一個字典,字典的鍵就是一個個channel,而字典的值則是一個連結串列,連結串列中儲存了所有訂閱這個channel 的客戶端。SUBSCRIBE命令的關鍵,就是將客戶端新增到給定channel的訂閱連結串列中。通過PUBLISH命令向訂閱者傳送訊息,redis-server會使用給定的頻道作為鍵,在它所維護的channe字典中查詢記錄了訂閱者這個頻道的所有客戶端的連結串列,遍歷這個連結串列,將訊息釋出給所有訂閱者。
  • Pub/Sub 從字面上理解就是釋出( Publish )與訂閱( Subscribe ),在Redis中,你可以設定對某一個key值進行訊息釋出及訊息訂閱,當一個key值上進行了訊息釋出後,所有訂閱它的客戶端都會收到相應的訊息。這一功能最明顯的用法就是用作實時訊息系統,比如普通的即時聊天,群聊等功能。

Redis主從複製

概念

主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(masterlk/leader),後者稱為從節點(slave/follower);資料的複製是單向

的,只能由主節點到從節點。Master以寫為主,Slave以讀為主。

預設情況下,每臺Redis伺服器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。主從複製的作用主要包括:

1、資料冗餘∶主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘方式。

2、故障恢復∶當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗餘。3、負載均衡∶在主從複製的基礎上,配合讀寫分

離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連線主節點,讀Redis資料時應用連線從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量。

4、高可用基石︰除了上述作用以外,主從複製還是哨兵和叢集能夠實施的基礎,因此說主從複製是Redis高可用的基礎。

一般來說,要將Redis運用於工程專案中,只使用一臺Redis是萬萬不能的,原因如下︰

1、從結構上,單個Redis伺服器會發生單點故障,並且一臺伺服器需要處理所有的請求負載,壓力較大﹔

2、從容量上,單個Redis伺服器記憶體容量有限,就算一臺Redis伺服器記憶體容量為256G,也不能將所有記憶體用作Redis儲存記憶體,一般來說,單臺Redis最大使用記憶體不應該超過20G。

電商網站上的商品,一般都是一次上傳,無數次瀏覽的,說專業點也就是"多讀少寫"。對於這種場景,我們可以使如下這種架構∶

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CQwdnIds-1609205739519)(redis.assets/image-20201129111033155.png)]

主從複製,讀寫分離。80%的情況下都是在進行讀操作,減緩伺服器的壓力!架構中經常使用,一主二從

環境配置

只配置從庫,不配置主庫,因為每臺redis服務本身預設就是一個主庫

首先開啟四個終端,在其中一個終端開啟一個redis服務,檢視該服務的一些資訊

info replication
127.0.0.1:6379> info replication
# Replication
role:master#角色
connected_slaves:0#從機個數

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-jsEogkJn-1609205739519)(redis.assets/image-20201129111916659.png)]

關閉redis服務,將redis配置檔案複製三個

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-wCcIWPRq-1609205739520)(redis.assets/image-20201129112426960.png)]

修改每一個配置檔案,拿redis-79為例,其他的80,81都要進行對應的修改

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8ZkXM3Iz-1609205739521)(redis.assets/image-20201129113008608.png)]

對應開啟三個服務

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-NK0Xst11-1609205739521)(redis.assets/image-20201129113618249.png)]

如果開啟成功就會看到三個log檔案,和三個程序資訊

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bQxusEwb-1609205739523)(redis.assets/image-20201129113710081.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zRltDDLe-1609205739523)(redis.assets/image-20201129113759909.png)]

一主二從

開啟的三臺redis服務預設都是主機

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-31j7eZrc-1609205739524)(redis.assets/image-20201129114302353.png)]

配置一主(79)二從(80,81)

#引數為主機的資訊
SLAVEOF 127.0.0.1 6379

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-l9kRjAsO-1609205739525)(redis.assets/image-20201129114510038.png)]

如果主機設定了密碼,那麼從機的配置檔案中需要將主機的密碼配置好,被坑了。。。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YHVRKVkl-1609205739525)(redis.assets/image-20201129115056380.png)]

兩者都修改完成後,shutdown重啟以下,然後再重新配置一主二從,然後就會在主機上看見我們的兩個從機了

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fn0Gyytu-1609205739526)(redis.assets/image-20201129115420914.png)]

正式的主從配置應該從配置檔案修改,那樣就是永久的,這裡使用命令只是暫時的

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-lvJvMcqb-1609205739527)(redis.assets/image-20201129115848177.png)]

配置完成後,主機可以寫,從機就不能寫了,只能讀 ,主機中所有的資料都會被複制到從機中去

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bSgLVxim-1609205739528)(redis.assets/image-20201129120155661.png)]

如果嘗試在從機中寫:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-h2sxwD7Q-1609205739528)(redis.assets/image-20201129120235717.png)]

  • 如果主機突然斷電,宕機,從機不受影響,沒有配置哨兵,兩個從機還是從機,配置資訊依然生效,只是沒有寫操作了,當主機恢復,進行操作仍然還是可以共享到主機的資料
  • 如果從機突然斷電,宕機,主機在從機斷線以後中寫入的資料,從機就拿不到了,而且通過命令列配置的主從複製,重連後配置就失效了,不能夠重新獲取到主機的資料,需要重新配置,如果是配置檔案配置的主從複製,則不需要再手動配置,重連後依然生效,同樣的。從機只要連線上了主機,之前是否共享過的資料都可以直接複製給從機

複製的原理

Slave啟動成功連線到master後會傳送一個sync命令

Master接到命令,啟動後臺的存檔程序,同時收集所有接收到的用於修改資料集命令,在後臺程序執行完畢之後,master將傳送整個資料檔案到slave,並完成一次完全同步。

全量複製:slave服務在接收到資料庫檔案資料後,將其存檔並載入到記憶體中。

增量複製:Master繼續將新的所有收集到的修改命令依次傳給slave,完成同步

只要是重新連線master,一次完全同步(全量複製)將被自動執行

層層鏈路

將從機80作為81的主機,79寫入的資料,81依然能收到,但是這個80依然無法寫入

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-nymCpE3J-1609205739529)(redis.assets/image-20201129125736798.png)]

當79這個主機斷開連線以後,就會造成群龍無首的局面,這是作為從機,可以手動配置自己當主機,這個時候79回來了,自己卻沒有了從機,變成了光桿司令

SLAVEOF no one

哨兵模式

以下內容均是最基礎的配置,雲伺服器配置不高,開不了更多的哨兵執行緒,就只開啟了一個

自動選取老大

以上配置實際生產環境幾乎不用

主從切換技術的方法是︰當主伺服器宕機後,需要手動把一臺從伺服器切換為主伺服器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。這不是

一種推薦的方式,更多時候,我們優先考慮哨兵模式。Redis從2.8開始正式提供了Sentinel (哨兵)架構來解決這個問題。

謀朝篡位的自動版,能夠後臺監控主機是否故障,如果故障了根據投票數自動將從庫轉換為主庫。

哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的程序,作為程序,它會獨立執行。其原理是哨兵通過傳送命令,等待Redis伺服器響

應,從而監控執行的多個Redis例項。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-dr2fzByK-1609205739530)(redis.assets/image-20201129130621396.png)]

這裡的哨兵有兩個作用

  • 通過傳送命令,讓Redis伺服器返回監控其執行狀態,包括主伺服器和從伺服器。
  • 當哨兵監測到master宕機,會自動將slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機。

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

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-H7xMg6qk-1609205739531)(redis.assets/image-20201129130748907.png)]

假設主伺服器宕機,哨兵1先檢測到這個結果,系統並不會馬上進行failover(重新選舉)過程,僅僅是哨兵1主觀的認為主伺服器不可用,這個現象成為主觀下

。當後面的哨兵也檢測到主伺服器不可用,並且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行failover[故障轉移]操

作。切換成功後,就會通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器實現切換主機,這個過程稱為客觀下線

測試:目前狀態仍然是一主二從,79主,80,81從

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-5tgcYY3p-1609205739531)(redis.assets/image-20201129131219283.png)]

在配置資料夾下,即配置檔案同級目錄新建sentinel.conf檔案

#sentinel monitor 被監控名稱 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

後面的這個數字1,代表主機掛了,slave從機投票看讓誰接替成為主機,票數最多的,就會成為主機

啟動哨兵

redis-sentinel phzconfig/sentinel.conf

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-biCvh7wl-1609205739532)(redis.assets/image-20201129132210793.png)]

這個時候發現沒有從機的相關資訊,又被坑了,主機有密碼,配置檔案還得加上引數

#myredis與配置哨兵起的名字一致
sentinel auth-pass myredis 123456

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-cWZ16agG-1609205739533)(redis.assets/image-20201129133223334.png)]

重新開啟哨兵

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ixGoKNM9-1609205739534)(redis.assets/image-20201129133447912.png)]

這個時候我們關閉6379

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1Kx0MzS6-1609205739534)(redis.assets/image-20201129133523332.png)]

稍等片刻,重新選舉主機

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-mI8Hcl7F-1609205739535)(redis.assets/image-20201129133551003.png)]

重新登入原來的主機,發現被謀反了,只能作為從機了

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-IiZhMYey-1609205739536)(redis.assets/image-20201129133743468.png)]

所以哨兵模式就是如果Master節點斷開了,這個時候就會從從機中隨機選擇一個伺服器!(這裡面有一個投票演算法)

優點:

1、哨兵叢集,基於主從複製模式,所有的主從配置優點,它全有2、主從可以切換,故障可以轉移,系統的可用性就會更好

3、哨兵模式就是主從模式的升級,手動到自動,更加健壯!

缺點︰

1、Redis不好啊線上擴容的,叢集容量一旦到達上限,線上擴容就十分麻煩!

2、實現哨兵模式的配置其實是很麻煩的,裡面有很多選擇!

官方完整配置

# Example sentinel.conf
# port <sentinel-port>
# The port that this sentinel instance will run on
# sentinel例項執行的埠
port 26379
# sentinel announce-ip <ip>
# sentinel announce-port <port>
#
# The above two configuration directives are useful in environments where,
# because of NAT, Sentinel is reachable from outside via a non-local address.
#
# When announce-ip is provided, the Sentinel will claim the specified IP address
# in HELLO messages used to gossip its presence, instead of auto-detecting the
# local address as it usually does.
#
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
# will announce the specified TCP port.
#
# The two options don't need to be used together, if only announce-ip is
# provided, the Sentinel will announce the specified IP and the server port
# as specified by the "port" option. If only announce-port is provided, the
# Sentinel will announce the auto-detected local IP and the specified port.
#
# Example:
#
# sentinel announce-ip 1.2.3.4
# dir <working-directory>
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
# for the process to don't interferer with administrative tasks such as
# unmounting filesystems.
dir /tmp
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name : master Redis Server名稱
# ip : master Redis Server的IP地址
# redis-port : master Redis Server的埠號
# quorum : 主例項判斷為失效至少需要 quorum 個 Sentinel 程序的同意,只要同意 Sentinel 的數量不達標,自動failover就不會執行
#
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Slaves are auto-discovered, so you don't need to specify slaves in
# any way. Sentinel itself will rewrite this configuration file adding
# the slaves using additional configuration options.
# Also note that the configuration file is rewritten when a
# slave is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
# 
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass <master-name> <password>
#
# Set the password to use to authenticate with the master and slaves.
# Useful if there is a password set in the Redis instances to monitor.
#
# Note that the master password is also used for slaves, so it is not
# possible to set a different password in masters and slaves instances
# if you want to be able to monitor these instances with Sentinel.
#
# However you can have Redis instances without the authentication enabled
# mixed with Redis instances requiring the authentication (as long as the
# password set is the same for all the instances requiring the password) as
# the AUTH command will have no effect in Redis instances with authentication
# switched off.
#
# Example:
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached slave or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
# 選項指定了 Sentinel 認為Redis例項已經失效所需的毫秒數。當例項超過該時間沒有返回PING,或者直接返回錯誤, 那麼 Sentinel 將這個例項標記為主觀下線(subjectively down,簡稱 SDOWN )
#
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
# sentinel parallel-syncs <master-name> <numslaves>
#
# How many slaves we can reconfigure to point to the new slave simultaneously
# during the failover. Use a low number if you use the slaves to serve query
# to avoid that all the slaves will be unreachable at about the same
# time while performing the synchronization with the master.
# 選項指定了在執行故障轉移時, 最多可以有多少個從Redis例項在同步新的主例項, 在從Redis例項較多的情況下這個數字越小,同步的時間越長,完成故障轉移所需的時間就越長。
sentinel parallel-syncs mymaster 1
# sentinel failover-timeout <master-name> <milliseconds>
#
# Specifies the failover timeout in milliseconds. It is used in many ways:
#
# - The time needed to re-start a failover after a previous failover was
#   already tried against the same master by a given Sentinel, is two
#   times the failover timeout.
#
# - The time needed for a slave replicating to a wrong master according
#   to a Sentinel current configuration, to be forced to replicate
#   with the right master, is exactly the failover timeout (counting since
#   the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
#   did not produced any configuration change (SLAVEOF NO ONE yet not
#   acknowledged by the promoted slave).
#
# - The maximum time a failover in progress waits for all the slaves to be
#   reconfigured as slaves of the new master. However even after this time
#   the slaves will be reconfigured by the Sentinels anyway, but not with
#   the exact parallel-syncs progression as specified.
# 如果在該時間(ms)內未能完成failover操作,則認為該failover失敗
#
# Default is 3 minutes.
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#
# sentinel notification-script and sentinel reconfig-script are used in order
# to configure scripts that are called to notify the system administrator
# or to reconfigure clients after a failover. The scripts are executed
# with the following rules for error handling:
#
# If script exits with "1" the execution is retried later (up to a maximum
# number of times currently set to 10).
#
# If script exits with "2" (or an higher value) the script execution is
# not retried.
#
# If script terminates because it receives a signal the behavior is the same
# as exit code 1.
#
# A script has a maximum running time of 60 seconds. After this limit is
# reached the script is terminated with a SIGKILL and the execution retried.
# NOTIFICATION SCRIPT
#
# sentinel notification-script <master-name> <script-path>
# 
# Call the specified notification script for any sentinel event that is
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
# This script should notify the system administrator via email, SMS, or any
# other messaging system, that there is something wrong with the monitored
# Redis systems.
#
# The script is called with just two arguments: the first is the event type
# and the second the event description.
#
# The script must exist and be executable in order for sentinel to start if
# this option is provided.
# 指定sentinel檢測到該監控的redis例項指向的例項異常時,呼叫的報警指令碼。該配置項可選,但是很常用。
#
# Example:
#
# sentinel notification-script mymaster /var/redis/notify.sh
# CLIENTS RECONFIGURATION SCRIPT
#
# sentinel client-reconfig-script <master-name> <script-path>
#
# When the master changed because of a failover a script can be called in
# order to perform application-specific tasks to notify the clients that the
# configuration has changed and the master is at a different address.
# 
# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is currently always "failover"
# <role> is either "leader" or "observer"
# 
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave
# (now a master).
#
# This script should be resistant to multiple invocations.
#
# Example:
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

常用配置

#Example sentine1.conf

#哨兵sentine1例項執行的埠預設26379
port 26379

#哨兵sentine1的工作目錄
dir /tmpl

#哨兵sentine1監控的redis主節點的 ip port
# master-name可以自己命名的主節點名字只能由字母A-z、數字0-9、這三個字元".-_"組成。
# quorum配置多少個sentine1哨兵統一認為master主節點失聯那麼這時客觀上認為主節點失聯了
# sentine7 monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

#當在Redis例項中開啟了requirepass foobared 授權密碼這樣所有連線Redis例項的客戶端都要提供密碼
#設定哨兵sentine1連線主從的密碼注意必須為主從設定一樣的驗證密碼
# sentine7 auth-pass <master-name> <password>
sentine1 auth-pass mymaster MySUPER--secret-O123password

#指定多少毫秒之後主節點沒有應答哨兵sentine1此時哨兵主觀上認為主節點下線預設30秒
# sentine7 down-after-mi77iseconds <master-name> <mi77iseconds>
sentine7 down-after-mi77iseconds mymaster 30000

# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行同步,這個數字越小,完成failover所需的時間就越長,但是如果這個數字越大,就意味著越多的slave因為rep7ication而不可用。可以通過將這個值設為1來保證每次只有一個slave處於不能處理命令請求的狀態。
#sentinel para7le1-syncs <master-name> <nums1aves>
sentine1 para1le7-syncs mymaster 1

#故障轉移的超時時間failover-timeout可以用在以下這些方面:
#1.同一個sentine1對同一個master兩次fai1over之間的間隔時間。
#2.當一個slave從一個錯誤的master那裡同步資料開始計算時間。直到s7ave被糾正為向正確的master那裡同步資料時。
#3.當想要取消一個正在進行的failover所需要的時間。
#4.當進行failoveri時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置為指向master,但是就不para7le1-syncs所配置的規則來了
#預設三分鐘
#sentine7 failover-timeout <master-name> <mi77iseconds>
sentine1 failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#配置當某一事件發生時所需要執行的指令碼,可以通過指令碼來通知管理員,例如當系統執行不正常時發郵件通知相關人員。
#對於指令碼的執行結果有以下規則;
#若指令碼執行後返回1,那麼該指令碼稍後將會被再次執行,重複次數目前預設為10#若指令碼執行後返回2,或者比2更高的一個返回值,指令碼將不會重複執行。
#如果指令碼在執行過程中由於收到系統中斷訊號被終止了,則同返回值為1時的行為相同。
#一個指令碼的最大執行時間為60s,如果超過這個時間,指令碼將會被一個SIGKILL訊號終止,之後重新執行。
#通知型指令碼:當sentine7有任何警告級別的事件發生時(比如說redis例項的主觀失效和客觀失效等等),將會去呼叫這個指令碼,這時這個指令碼應該通過郵件,SNS等方式去通知系統管理員關於系統不正常執行的資訊。呼叫該指令碼時,將傳給指令碼兩個引數,一個是事件的型別,一個是事件的描述。如果sentine1.conf配置檔案中配置了這個指令碼路徑,那麼必須保證這個指令碼存在於這個路徑,並且是可執行的,否則sentine1無法正常啟動成功。
#通知指令碼
# sentine7 notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh


#客戶端重新配置主節點引數指令碼
#當一個master由於failover而發生改變時,這個指令碼將會被呼叫,通知相關的客戶端關於master地址已經發生改變的資訊。
#以下引數將會在呼叫指令碼時傳給指令碼:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#目前<state>總是“fai1over",
# <role>是“leader”或者"observer”中的一個。
#引數 from-ip,from-port,to-ip,to-port是用來和舊的master和新的master(即舊的s1ave)通訊的
#這個指令碼應該是通用的,能被多次呼叫,不是針對性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

Redis快取穿透和雪崩

Redis快取的使用,極大的提升了應用程式的效能和效率,特別是資料查詢方面。但同時,它也帶來了一些問題。其中,最要害的問題,就是資料的一致性問題,

從嚴格意義上講,這個問題無解。如果對資料的一致性要求很高,那麼就不能使用快取。

另外的一些典型問題就是,快取穿透、快取雪崩和快取擊穿。目前,業界也都有比較流行的解決方案。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-vmzh8piN-1609205739536)(redis.assets/image-20201129135406872.png)]

快取穿透

(查不到)

快取穿透的概念很簡單,使用者想要查詢一個數據,發現redis記憶體資料庫沒有,也就是快取沒有命中,於是向持久層資料庫查詢。發現也沒有,於是本次查詢失

敗。當用戶很多的時候,快取都沒有命中(秒殺!),於是都去請求了持久層資料庫。這會給持久層資料庫造成很大的壓力,這時候就相當於出現了快取穿透。

布隆過濾器

布隆過濾器是一種資料結構,對所有可能查詢的引數以hash形式儲存,在控制層先進行校驗,不符合則丟棄,從而避免了對底層儲存系統的查詢壓力;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9p7oXIA0-1609205739537)(redis.assets/image-20201129135707986.png)]

快取空物件

當儲存層不命中後,即使返回的空物件也將其快取起來,同時會設定一個過期時間,之後再訪問這個資料將會從快取中獲取,保護了後端資料來源;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Ffs5ICX9-1609205739538)(redis.assets/image-20201129135752215.png)]

但是這種方法會存在兩個問題︰

1、如果空值能夠被快取起來,這就意味著快取需要更多的空間儲存更多的鍵,因為這當中可能會有很多的空值的鍵;

2、即使對空值設定了過期時間,還是會存在快取層和儲存層的資料會有一段時間視窗的不一致,這對於需要保持一致性的業務會有影響。

快取擊穿

(查太多,量太大,快取過期)

概述

這裡需要注意和快取擊穿的區別,快取擊穿,是指一個key非常熱點,在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大

併發就穿破快取,直接請求資料庫,就像在一個屏障上鑿開了一個洞

當某個key在過期的瞬間,有大量的請求併發訪問,這類資料一般是熱點資料,由於快取過期,會同時訪問資料庫來查詢最新資料,並且回寫快取,會導使資料庫

瞬間壓力過大。

解決方案

設定熱點資料永不過期

從快取層面來看,沒有設定過期時間,所以不會出現熱點 key過期後產生的問題。加互斥鎖

分散式鎖

使用分散式鎖,保證對於每個key同時只有一個執行緒去查詢後端服務,其他執行緒沒有獲得分散式鎖的許可權,因此只需要等待即可。這種方式將高併發的壓力轉移到

了分散式鎖,因此對分散式鎖的考驗很大。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zYz3RrLU-1609205739539)(redis.assets/image-20201129140324482.png)]

快取雪崩

概念

快取雪崩,是指在某一個時間段,快取集中過期失效。Redis宕機

產生雪崩的原因之一,比如在寫本文的時候,馬上就要到雙十二零點,很快就會迎來一波搶購,這波商品時間比較集中的放入了快取,假設快取一個小時。那麼到

了凌晨一點鐘的時候,這批商品的快取就都過期了。而對這批商品的訪問查詢,都落到了資料庫上,對於資料庫而言,就會產生週期性的壓力波峰。於是所有的請

求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會掛掉的情況。

解決方案

redis高可用

這個思想的含義是,既然redis有可能掛掉,那我多增設幾臺redis ,這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建的叢集。(異地多活)

限流降級

這個解決方案的思想是,在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。
資料預熱

資料加熱的含義就是在正式部署之前,我先把可能的資料先預先訪問一遍,這樣部分可能大量訪問的資料就會載入到快取中。在即將發生大併發訪問前手動觸發加

載快取不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻。