防火牆(9)——禁止某個時間段內訪問我們的web
Redis學習
Redis是什麼?有什麼特點?
是什麼:
Redis是一個開源的,基於記憶體亦可持久化的日誌型、高效能Key-Value資料庫,並提供多種語言的API
幹什麼:
效能極高 –redis讀寫效能測試redis官網測試讀寫能到10萬左右,redis讀寫能力為2W/s,mysql讀能力5K/s、寫能力為3K/s,資料上看redis效能碾壓mysql
豐富的資料型別 – Redis支援二進位制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
原子 – Redis的所有操作都是原子性的,意思就是要麼成功執行要麼失敗完全不執行。單個操作是原子性的。多個操作也支援事務,即原子性,通過8和EXEC指令包起來。 但是Redis 事務的執行並不是原子性的。
redis事務可以理解為一個打包的批量執行指令碼,但批量指令並非原子化的操作,中間某條指令的失敗不會導致前面已做指令的回滾,也不會造成後續的指令不做。
什麼是Nosql
NoSQL = Not Only SQL(不僅僅是SQL)
Not Only Structured Query Language
關係型資料庫:列+行,同一個表下資料的結構是一樣的。
非關係型資料庫:資料儲存沒有固定的格式,並且可以進行橫向擴充套件。
NoSQL泛指非關係型資料庫,隨著web2.0網際網路的誕生,傳統的關係型資料庫很難對付web2.0時代!尤其是超大規模的高併發的社群,暴露出來很多難以克服的問題,NoSQL在當今大資料環境下發展的十分迅速,Redis是發展最快的。
Nosql特點
-
方便擴充套件(資料之間沒有關係,很好擴充套件!)
-
大資料量高效能(Redis一秒可以寫8萬次,讀11萬次,NoSQL的快取記錄級,是一種細粒度的快取,效能會比較高!)
-
資料型別是多樣型的!(不需要事先設計資料庫,隨取隨用)
-
傳統的 RDBMS 和 NoSQL
傳統的 RDBMS(關係型資料庫) - 結構化組織 - SQL - 資料和關係都存在單獨的表中 row col - 操作,資料定義語言 - 嚴格的一致性 - 基礎的事務 - ...
Nosql - 不僅僅是資料 - 沒有固定的查詢語言 - 鍵值對儲存,列儲存,文件儲存,圖形資料庫(社交關係) - 最終一致性 - CAP定理和BASE - 高效能,高可用,高擴充套件 - ...
一.Redis安裝
1.linux安裝Redis
第一步:下載redis安裝包
wget http://download.redis.io/releases/redis-4.0.6.tar.gz
第二步:解壓壓縮包
tar -zxvf redis-4.0.6.tar.gz
第三步:yum安裝gcc依賴
yum install gcc
第四步:跳轉到redis解壓目錄下
cd redis-4.0.6
第五步:編譯安裝
make MALLOC=libc
將/usr/local/redis-4.0.6/src目錄下的檔案加到/usr/local/bin目錄
cd src && make install
2.window安裝
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-VUEgwWCn-1611141871860)(C:\Users\dong\AppData\Roaming\Typora\typora-user-images\image-20210110134354488.png)]
第一步:啟動服務端
第二步:啟動客戶端進行連線測試
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-CNFN4R0G-1611141871863)(C:\Users\dong\AppData\Roaming\Typora\typora-user-images\image-20210110134554492.png)]
連線成功
3.效能測試
redis-benchmark:**Redis官方提供的效能測試工具,引數選項如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-bdPFkb7a-1611141871864)(C:\Users\dong\AppData\Roaming\Typora\typora-user-images\image-20210110135124569.png)]
依次是
10萬個請求進行寫入測試
50個併發客戶端
每次寫入3個位元組
只有一臺伺服器處理,單機效能
所有請求1毫秒內完成
每秒處理93129次請求
二.配置檔案
port 6379 --埠號
bind 127.0.0.1 --這個配置是隻允許本地客戶端訪問
protected-mode yes --是否開啟保護模式。預設開啟,如果沒有設定bind項的ip和redis密碼的話,服務將只允許本地訪 問
loglevel notice – 配置日誌級別。選項有debug, verbose, notice, warning
logfile “” --日誌名稱。空字串表示標準輸出。注意如果redis配置為後臺程序,標準輸出中資訊會發送到/dev/null
databases 16 --設定資料庫個數。預設資料庫是 DB 0,可以通過SELECT where dbid is a number between 0 and ‘databases’-1為每個連線使用不同的資料庫。
**save 900 1 # 持久化設定:
save 300 10 # 下面的例子將會進行把資料寫入磁碟的操作:
save 60 10000 # 900秒(15分鐘)之後,且至少1次變更
\ # 300秒(5分鐘)之後,且至少10次變更
\ # 60秒之後,且至少10000次變更
\ # 不寫磁碟的話就把所有 “save” 設定註釋掉就行了。
\ # 通過新增一條帶空字串引數的save指令也能移除之前所有配置的save指令,如: save “”
配置檔案詳解:https://www.jianshu.com/p/41f393f594e8
三.基本資料型別
在redis中無論什麼資料型別,在資料庫中都是以key-value形式儲存,通過進行對Redis-key的操作,來完成對資料庫中資料的操作。
常用命令
exists key
:判斷鍵是否存在del key
:刪除鍵值對move key db
:將鍵值對移動到指定資料庫expire key second
:設定鍵值對的過期時間type key
:檢視value的資料型別
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-ngR0Wpd8-1611141871866)(C:\Users\dong\AppData\Roaming\Typora\typora-user-images\image-20210110142137970.png)]
關於TTL
命令
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8gJn9zVT-1611141871868)(C:\Users\dong\AppData\Roaming\Typora\typora-user-images\image-20210110142718592.png)]
Redis的key,通過TTL命令返回key的過期時間,一般來說有3種:
- 當前key沒有設定過期時間,所以會返回-1.
- 當前key有設定過期時間,而且key已經過期,所以會返回-2.
- 當前key有設定過期時間,且key還沒有過期,故會返回key的正常剩餘時間.
關於重新命名RENAME
和RENAMENX
RENAME key newkey
修改 key 的名稱RENAMENX key newkey
僅當 newkey 不存在時,將 key 改名為 newkey 。
String(字串)
命令 | 描述 | 示例 |
---|---|---|
APPEND key value | 向指定的key的value後追加字串 | 127.0.0.1:6379> set msg hello OK 127.0.0.1:6379> append msg " world" (integer) 11 127.0.0.1:6379> get msg “hello world” |
DECR/INCR key | 將指定key的value數值進行+1/-1(僅對於數字) | 127.0.0.1:6379> set age 20 OK 127.0.0.1:6379> incr age (integer) 21 127.0.0.1:6379> decr age (integer) 20 |
INCRBY/DECRBY key n | 按指定的步長對數值進行加減 | 127.0.0.1:6379> INCRBY age 5 (integer) 25 127.0.0.1:6379> DECRBY age 10 (integer) 15 |
INCRBYFLOAT key n | 為數值加上浮點型數值 | 127.0.0.1:6379> INCRBYFLOAT age 5.2 “20.2” |
STRLEN key | 獲取key儲存值的字串長度 | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> STRLEN msg (integer) 11 |
GETRANGE key start end | 按起止位置獲取字串(閉區間,起止位置都取) | 127.0.0.1:6379> get msg “hello world” 127.0.0.1:6379> GETRANGE msg 3 9 “lo worl” |
SETRANGE key offset value | 用指定的value 替換key中 offset開始的值 | 127.0.0.1:6379> SETRANGE msg 2 hello (integer) 7 127.0.0.1:6379> get msg “tehello” |
GETSET key value | 將給定 key 的值設為 value ,並返回 key 的舊值(old value)。 | 127.0.0.1:6379> GETSET msg test “hello world” |
SETNX key value | 僅當key不存在時進行set | 127.0.0.1:6379> SETNX msg test (integer) 0 127.0.0.1:6379> SETNX name sakura (integer) 1 |
SETEX key seconds value | set 鍵值對並設定過期時間 | 127.0.0.1:6379> setex name 10 root OK 127.0.0.1:6379> get name (nil) |
MSET key1 value1 [key2 value2…] | 批量set鍵值對 | 127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 OK |
MSETNX key1 value1 [key2 value2…] | 批量設定鍵值對,僅當引數中所有的key都不存在時執行 | 127.0.0.1:6379> MSETNX k1 v1 k4 v4 (integer) 0 |
MGET key1 [key2…] | 批量獲取多個key儲存的值 | 127.0.0.1:6379> MGET k1 k2 k3 1) “v1” 2) “v2” 3) “v3” |
PSETEX key milliseconds value | 和 SETEX 命令相似,但它以毫秒為單位設定 key 的生存時間, | |
getset key value | 如果不存在值,則返回nil,如果存在值,獲取原來的值,並設定新的值 |
String類似的使用場景:value除了是字串還可以是數字,用途舉例:
- 計數器
- 統計多單位的數量:uid:123666:follow 0
- 物件儲存快取
List(列表)
Redis列表是簡單的字串列表,按照插入順序排序。你可以新增一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多可以包含 232 - 1 個元素 (4294967295, 每個列表超過40億個元素)。
首先我們列表,可以經過規則定義將其變為佇列、棧、雙端佇列等
正如圖Redis中List是可以進行雙端操作的,所以命令也就分為了LXXX和RLLL兩類,有時候L也表示List例如LLEN
命令 | 描述 |
---|---|
LPUSH/RPUSH key value1[value2..] | 從左邊/右邊向列表中PUSH值(一個或者多個)。 |
LRANGE key start end | 獲取list 起止元素==(索引從左往右 遞增)== |
LPUSHX/RPUSHX key value | 向已存在的列名中push值(一個或者多個) |
LINSERT key BEFORE|AFTER pivot value | 在指定列表元素的前/後 插入value |
LLEN key | 檢視列表長度 |
LINDEX key index | 通過索引獲取列表元素 |
LSET key index value | 通過索引為元素設值 |
LPOP/RPOP key | 從最左邊/最右邊移除值 並返回 |
RPOPLPUSH source destination | 將列表的尾部(右)最後一個值彈出,並返回,然後加到另一個列表的頭部 |
LTRIM key start end | 通過下標擷取指定範圍內的列表 |
LREM key count value | List中是允許value重複的 count > 0 :從頭部開始搜尋 然後刪除指定的value 至多刪除count個 count < 0 :從尾部開始搜尋… count = 0 :刪除列表中所有的指定value。 |
BLPOP/BRPOP key1[key2] timout | 移出並獲取列表的第一個/最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。 |
BRPOPLPUSH source destination timeout | 和RPOPLPUSH 功能相同,如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。 |
---------------------------LPUSH---RPUSH---LRANGE--------------------------------
127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是無法獲取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 獲取起止位置範圍內的元素
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 2
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 1
1) "k2"
2) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 獲取全部元素
1) "k2"
2) "k1"
3) "k3"
---------------------------LPUSHX---RPUSHX-----------------------------------
127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失敗
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左邊 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k1"
5) "k3"
---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------
127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素後 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "ins_key1"
5) "k1"
6) "k3"
127.0.0.1:6379> LLEN mylist # 檢視mylist的長度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 獲取下標為3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 將下標3的元素 set值為k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k6"
5) "k1"
6) "k3"
---------------------------LPOP--RPOP--------------------------
127.0.0.1:6379> LPOP mylist # 左側(頭部)彈出
"k5"
127.0.0.1:6379> RPOP mylist # 右側(尾部)彈出
"k3"
---------------------------RPOPLPUSH--------------------------
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"
4) "k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 將mylist的最後一個值(k1)彈出,加入到newlist的頭部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
1) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"
---------------------------LTRIM--------------------------
127.0.0.1:6379> LTRIM mylist 0 1 # 擷取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
# 初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------
127.0.0.1:6379> LREM mylist 3 k2 # 從頭部開始搜尋 至多刪除3個 k2
(integer) 3
# 刪除後:mylist: k2,k2,k2,k4,k2,k2,k2,k2
127.0.0.1:6379> LREM mylist -2 k2 #從尾部開始搜尋 至多刪除2個 k2
(integer) 2
# 刪除後:mylist: k2,k2,k2,k4,k2,k2
---------------------------BLPOP--BRPOP--------------------------
mylist: k2,k2,k2,k4,k2,k2
newlist: k1
127.0.0.1:6379> BLPOP newlist mylist 30 # 從newlist中彈出第一個值,mylist作為候選
1) "newlist" # 彈出
2) "k1"
127.0.0.1:6379> BLPOP newlist mylist 30
1) "mylist" # 由於newlist空了 從mylist中彈出
2) "k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超時了
127.0.0.1:6379> BLPOP newlist 30 # 我們連線另一個客戶端向newlist中push了test, 阻塞被解決。
1) "newlist"
2) "test"
(12.54s)
小結
- list實際上是一個連結串列,before Node after , left, right 都可以插入值
- 如果key不存在,則建立新的連結串列
- 如果key存在,新增內容
- 如果移除了所有值,空連結串列,也代表不存在
- 在兩邊插入或者改動值,效率最高!修改中間元素,效率相對較低
應用:
訊息排隊!訊息佇列(Lpush Rpop),棧(Lpush Lpop)
Set(集合)
Redis的Set是string型別的無序集合。集合成員是唯一的,這就意味著集合中不能出現重複的資料。
Redis 中 集合是通過雜湊表實現的,所以新增,刪除,查詢的複雜度都是O(1)。
集合中最大的成員數為 232 - 1 (4294967295, 每個集合可儲存40多億個成員)。
命令 | 描述 |
---|---|
SADD key member1[member2..] | 向集合中無序增加一個/多個成員 |
SCARD key | 獲取集合的成員數 |
SMEMBERS key | 返回集合中所有的成員 |
SISMEMBER key member | 查詢member元素是否是集合的成員,結果是無序的 |
SRANDMEMBER key [count] | 隨機返回集合中count個成員,count預設值為1 |
SPOP key [count] | 隨機移除並返回集合中count個成員,count預設值為1 |
SMOVE source destination member | 將source集合的成員member移動到destination集合 |
SREM key member1[member2..] | 移除集合中一個/多個成員 |
SDIFF key1[key2..] | 返回所有集合的差集 key1- key2 - … |
SDIFFSTORE destination key1[key2..] | 在SDIFF的基礎上,將結果儲存到集合中==(覆蓋)==。不能儲存到其他型別key噢! |
SINTER key1 [key2..] | 返回所有集合的交集 |
SINTERSTORE destination key1[key2..] | 在SINTER的基礎上,儲存結果到集合中。覆蓋 |
SUNION key1 [key2..] | 返回所有集合的並集 |
SUNIONSTORE destination key1 [key2..] | 在SUNION的基礎上,儲存結果到及和張。覆蓋 |
SSCAN KEY [MATCH pattern] [COUNT count] | 在大量資料環境下,使用此命令遍歷集合中元素,每次遍歷部分 |
---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------
127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成員 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 獲取集合的成員數目
(integer) 4
127.0.0.1:6379> smembers myset # 獲取集合中所有成員
1) "m4"
2) "m3"
3) "m2"
4) "m1"
127.0.0.1:6379> SISMEMBER myset m5 # 查詢m5是否是myset的成員
(integer) 0 # 不是,返回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,返回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1
---------------------SRANDMEMBER--SPOP----------------------------------
127.0.0.1:6379> SRANDMEMBER myset 3 # 隨機返回3個成員
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset # 隨機返回1個成員
"m3"
127.0.0.1:6379> SPOP myset 2 # 隨機移除並返回2個成員
1) "m1"
2) "m4"
# 將set還原到{m1,m2,m3,m4}
---------------------SMOVE--SREM----------------------------------------
127.0.0.1:6379> SMOVE myset newset m3 # 將myset中m3成員移動到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "m4"
2) "m2"
3) "m1"
127.0.0.1:6379> SMEMBERS newset
1) "m3"
127.0.0.1:6379> SREM newset m3 # 從newset中移除m3元素
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)
# 下面開始是多集合操作,多集合操作中若只有一個引數預設和自身進行運算
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
-----------------------------SDIFF------------------------------------
127.0.0.1:6379> SDIFF setx sety setz # 等價於setx-sety-setz
1) "m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
1) "m5"
-------------------------SINTER---------------------------------------
# 共同關注(交集)
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
1) "m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
1) "m2"
2) "m6"
-------------------------SUNION---------------------------------------
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的並集
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"
127.0.0.1:6379> SUNION setx sety # setx sety 並集
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"
Hash(雜湊)
Redis hash 是一個string型別的field和value的對映表,hash特別適合用於儲存物件。
Set就是一種簡化的Hash,只變動key,而value使用預設值填充。可以將一個Hash表作為一個物件進行儲存,表中存放物件的資訊。
命令 | 描述 |
---|---|
HSET key field value | 將雜湊表 key 中的欄位 field 的值設為 value 。重複設定同一個field會覆蓋,返回0 |
HMSET key field1 value1 [field2 value2..] | 同時將多個 field-value (域-值)對設定到雜湊表 key 中。 |
HSETNX key field value | 只有在欄位 field 不存在時,設定雜湊表字段的值。 |
HEXISTS key field | 檢視雜湊表 key 中,指定的欄位是否存在。 |
HGET key field value | 獲取儲存在雜湊表中指定欄位的值 |
HMGET key field1 [field2..] | 獲取所有給定欄位的值 |
HGETALL key | 獲取在雜湊表key 的所有欄位和值 |
HKEYS key | 獲取雜湊表key中所有的欄位 |
HLEN key | 獲取雜湊表中欄位的數量 |
HVALS key | 獲取雜湊表中所有值 |
HDEL key field1 [field2..] | 刪除雜湊表key中一個/多個field欄位 |
HINCRBY key field n | 為雜湊表 key 中的指定欄位的整數值加上增量n,並返回增量後結果 一樣只適用於整數型欄位 |
HINCRBYFLOAT key field n | 為雜湊表 key 中的指定欄位的浮點數值加上增量 n。 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代雜湊表中的鍵值對。 |
------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 將studentx雜湊表作為一個物件,設定name為sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重複設定field進行覆蓋,並返回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 設定studentx的age為20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 設定sex為1,tel為15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 設定已存在的field
(integer) 0 # 失敗
127.0.0.1:6379> HSETNX studentx email [email protected]
(integer) 1 # 成功
----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name欄位在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在
-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 獲取studentx中name欄位的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 獲取studentx中name、age、tel欄位的value
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx # 獲取studentx中所有的field及其value
1) "name"
2) "gyc"
3) "age"
4) "20"
5) "sex"
6) "1"
7) "tel"
8) "15623667886"
9) "email"
10) "[email protected]"
--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 檢視studentx中所有的field
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx # 檢視studentx中的欄位數量
(integer) 5
127.0.0.1:6379> HVALS studentx # 檢視studentx中所有的value
1) "gyc"
2) "20"
3) "1"
4) "15623667886"
5) "[email protected]"
-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 刪除studentx 中的sex、tel欄位
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"
-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age欄位數值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整數字型欄位不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight欄位增加0.6
"90.8"
Hash變更的資料user name age,尤其是使用者資訊之類的,經常變動的資訊!Hash更適合於物件的儲存,Sring更加適合字串儲存!
Zset(有序集合)
不同的是每個元素都會關聯一個double型別的分數(score)。redis正是通過分數來為集合中的成員進行從小到大的排序。
score相同:按字典順序排序
有序集合的成員是唯一的,但分數(score)卻可以重複。
命令 | 描述 |
---|---|
ZADD key score member1 [score2 member2] | 向有序集合新增一個或多個成員,或者更新已存在成員的分數 |
ZCARD key | 獲取有序集合的成員數 |
ZCOUNT key min max | 計算在有序集合中指定區間score的成員數 |
ZINCRBY key n member | 有序集合中對指定成員的分數加上增量 n |
ZSCORE key member | 返回有序集中,成員的分數值 |
ZRANK key member | 返回有序集合中指定成員的索引 |
ZRANGE key start end | 通過索引區間返回有序集合成指定區間內的成員 |
ZRANGEBYLEX key min max | 通過字典區間返回有序集合的成員 |
ZRANGEBYSCORE key min max | 通過分數返回有序集合指定區間內的成員==-inf 和 +inf分別表示最小最大值,只支援開區間()== |
ZLEXCOUNT key min max | 在有序集合中計算指定字典區間內成員數量 |
ZREM key member1 [member2..] | 移除有序集合中一個/多個成員 |
ZREMRANGEBYLEX key min max | 移除有序集合中給定的字典區間的所有成員 |
ZREMRANGEBYRANK key start stop | 移除有序集合中給定的排名區間的所有成員 |
ZREMRANGEBYSCORE key min max | 移除有序集合中給定的分數區間的所有成員 |
ZREVRANGE key start end | 返回有序集中指定區間內的成員,通過索引,分數從高到底 |
ZREVRANGEBYSCORRE key max min | 返回有序集中指定分數區間內的成員,分數從高到低排序 |
ZREVRANGEBYLEX key max min | 返回有序集中指定字典區間內的成員,按字典順序倒序 |
ZREVRANK key member | 返回有序集合中指定成員的排名,有序整合員按分數值遞減(從大到小)排序 |
ZINTERSTORE destination numkeys key1 [key2 ..] | 計算給定的一個或多個有序集的交集並將結果集儲存在新的有序集合 key 中,numkeys:表示參與運算的集合數,將score相加作為結果的score |
ZUNIONSTORE destination numkeys key1 [key2..] | 計算給定的一個或多個有序集的交集並將結果集儲存在新的有序集合 key 中 |
ZSCAN key cursor [MATCH pattern\] [COUNT count] | 迭代有序集合中的元素(包括元素成員和元素分值) |
-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中新增成員m1 score=1 以及成員m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 獲取有序集合的成員數
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 獲取score在 [0,1]區間的成員數量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2
----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 將成員m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 獲取成員m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 獲取成員m1的索引,索引按照score排序,score相同索引值按字典順序順序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 獲取索引在 0~1的成員
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 獲取全部成員
1) "m1"
2) "m3"
3) "m2"
#testset=>{abc,add,amaze,apple,back,java,redis} score均為0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成員
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分頁 按索引顯示查詢結果的 0,1,2條記錄
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 顯示 3,4,5條記錄
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 顯示 (-,apple] 區間內的成員
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 顯示 [apple,java]字典區間的成員
1) "apple"
2) "back"
3) "java"
-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之間的的成員
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
1) "m1"
2) "m3"
--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成員abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典區間[apple,java]中的所有成員
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成員
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成員
(integer) 2
# testset=> {abc,add,apple,amaze,back,java,redis} score均為0
# myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score遞減排序,然後按索引,返回結果的 0~3
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序結果的 索引的2~4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score遞減順序 返回集合中分數在[2,6]之間的成員
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典區間的成員
1) "java"
2) "back"
3) "apple"
4) "amaze"
-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score遞減順序,返回成員m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4
# mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小紅、小剛的數學成績
# enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小紅、小剛的英語成績
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 將mathscore enscore進行合併 結果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合併後的score是之前集合中所有score的和
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "188"
127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取兩個集合的成員score最小值作為結果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"
應用案例:
- set排序 儲存班級成績表 工資表排序!
- 普通訊息,1.重要訊息 2.帶權重進行判斷
- 排行榜應用實現,取Top N測試
四.事務
- 開啟事務(
multi
) - 命令入隊
- 執行事務(
exec
)
127.0.0.1:6379> multi # 開啟事務
OK
127.0.0.1:6379> set k1 v1 # 命令入隊
QUEUED
127.0.0.1:6379> set k2 v2 # ..
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 事務執行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
2) "k2"
3) "k1"
取消事務
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD # 放棄事務
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI # 當前未開啟事務
127.0.0.1:6379> get k1 # 被放棄事務中命令並未執行
(nil)
事務錯誤
程式碼語法錯誤(編譯時異常)所有的命令都不執行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 這是一條語法錯誤命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 會報錯但是不影響後續命令入隊
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 執行報錯
127.0.0.1:6379> get k1
(nil) # 其他命令並沒有被執行
程式碼邏輯錯誤 (執行時異常) **其他命令可以正常執行 ** >>> 所以不保證事務原子性
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> INCR k1 # 這條命令邏輯錯誤(對字串進行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 執行時報錯
4) "v2" # 其他命令正常執行
# 雖然中間有一條命令報錯了,但是後面的指令依舊正常執行成功了。
# 所以說Redis單條指令保證原子性,但是Redis事務不能保證原子性。
五.持久化
由於Redis是基於記憶體的資料庫,需要將資料由記憶體持久化到檔案中
持久化方式:
- RDB
- AOF
什麼是RDB
在指定時間間隔後,將記憶體中的資料集快照寫入資料庫 ;在恢復時候,直接讀取快照檔案,進行資料的恢復 ;
預設情況下, Redis 將資料庫快照儲存在名字為 dump.rdb的二進位制檔案中。檔名可以在配置檔案中進行自定義。
工作原理
在進行 RDB
的時候,redis
的主執行緒是不會做 io
操作的,主執行緒會 fork
一個子執行緒來完成該操作;
- Redis 呼叫forks。同時擁有父程序和子程序。
- 子程序將資料集寫入到一個臨時 RDB 檔案中。
- 當子程序完成對新 RDB 檔案的寫入時,Redis 用新 RDB 檔案替換原來的 RDB 檔案,並刪除舊的 RDB 檔案。
這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益(因為是使用子程序進行寫操作,而父程序依然可以接收來自客戶端的請求。)
觸發機制
- save的規則滿足的情況下,會自動觸發rdb原則
- 執行flushall命令,也會觸發我們的rdb原則
- 退出redis,也會自動產生rdb檔案
save
使用 save
命令,會立刻對當前記憶體中的資料進行持久化 ,但是會阻塞,也就是不接受其他操作了;
由於
save
命令是同步命令,會佔用Redis的主程序。若Redis資料非常多時,save
命令執行速度會非常慢,阻塞所有客戶端的請求。
觸發持久化規則
滿足配置條件中的觸發條件 ;
可以通過配置檔案對 Redis 進行設定, 讓它在“ N 秒內資料集至少有 M 個改動”這一條件被滿足時, 自動進行資料集儲存操作。
bgsave
bgsave
是非同步進行,進行持久化的時候,redis
還可以將繼續響應客戶端請求 ;
bgsave和save對比
命令 | save | bgsave |
---|---|---|
IO型別 | 同步 | 非同步 |
阻塞? | 是 | 是(阻塞發生在fock(),通常非常快) |
複雜度 | O(n) | O(n) |
優點 | 不會消耗額外的記憶體 | 不阻塞客戶端命令 |
缺點 | 阻塞客戶端命令 | 需要fock子程序,消耗記憶體 |
優缺點
優點:
- 適合大規模的資料恢復
- 對資料的完整性要求不高
缺點:
- 需要一定的時間間隔進行操作,如果redis意外宕機了,這個最後一次修改的資料就沒有了。
- fork程序的時候,會佔用一定的內容空間。
持久化AOF
Append Only File
將我們所有的命令都記錄下來,history,恢復的時候就把這個檔案全部再執行一遍
以日誌的形式來記錄每個寫的操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許追加檔案但不可以改寫檔案,redis啟動之初會讀取該檔案重新構建資料,換言之,redis重啟的話就根據日誌檔案的內容將寫指令從前到後執行一次以完成資料的恢復工作。
什麼是AOF
快照功能(RDB)並不是非常耐久(durable): 如果 Redis 因為某些原因而造成故障停機, 那麼伺服器將丟失最近寫入、以及未儲存到快照中的那些資料。 從 1.1 版本開始, Redis 增加了一種完全耐久的持久化方式: AOF 持久化。
如果要使用AOF,需要修改配置檔案:
appendonly no yes
則表示啟用AOF
預設是不開啟的,我們需要手動配置,然後重啟redis,就可以生效了!
如果這個aof檔案有錯位,這時候redis是啟動不起來的,我需要修改這個aof檔案
redis給我們提供了一個工具redis-check-aof --fix
優點和缺點
appendonly yes # 預設是不開啟aof模式的,預設是使用rdb方式持久化的,在大部分的情況下,rdb完全夠用
appendfilename "appendonly.aof"
# appendfsync always # 每次修改都會sync 消耗效能
appendfsync everysec # 每秒執行一次 sync 可能會丟失這一秒的資料
# appendfsync no # 不執行 sync ,這時候作業系統自己同步資料,速度最快
優點
- 每一次修改都會同步,檔案的完整性會更加好
- 每秒同步一次,可能會丟失一秒的資料
- 從不同步,效率最高
缺點
- 相對於資料檔案來說,aof遠遠大於rdb,修復速度比rdb慢!
- Aof執行效率也要比rdb慢,所以我們redis預設的配置就是rdb持久化
RDB和AOP選擇
RDB 和 AOF 對比
RDB | AOF | |
---|---|---|
啟動優先順序 | 低 | 高 |
體積 | 小 | 大 |
恢復速度 | 快 | 慢 |
資料安全性 | 丟資料 | 根據策略決定 |
如何選擇使用哪種持久化方式?
一般來說, 如果想達到足以媲美 PostgreSQL 的資料安全性, 你應該同時使用兩種持久化功能。
如果你非常關心你的資料, 但仍然可以承受數分鐘以內的資料丟失, 那麼你可以只使用 RDB 持久化。
有很多使用者都只使用 AOF 持久化, 但並不推薦這種方式: 因為定時生成 RDB 快照(snapshot)非常便於進行資料庫備份, 並且 RDB 恢復資料集的速度也要比 AOF 恢復的速度要快。
六.主從複製
概念
主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(Master/Leader),後者稱為從節點(Slave/Follower), 資料的複製是單向的!只能由主節點複製到從節點(主節點以寫為主、從節點以讀為主)。
預設情況下,每臺Redis伺服器都是主節點,一個主節點可以有0個或者多個從節點,但每個從節點只能由一個主節點。
作用
- 資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘的方式。
- 故障恢復:當主節點故障時,從節點可以暫時替代主節點提供服務,是一種服務冗餘的方式
- 負載均衡:在主從複製的基礎上,配合讀寫分離,由主節點進行寫操作,從節點進行讀操作,分擔伺服器的負載;尤其是在多讀少寫的場景下,通過多個從節點分擔負載,提高併發量。
- 高可用基石:主從複製還是哨兵和叢集能夠實施的基礎。
為什麼使用叢集
- 單臺伺服器難以負載大量的請求
- 單臺伺服器故障率高,系統崩壞概率大
- 單臺伺服器記憶體容量有限。
環境配置
我們在講解配置檔案的時候,注意到有一個replication
模組
檢視當前庫的資訊:info replication
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 從機數量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
既然需要啟動多個服務,就需要多個配置檔案。每個配置檔案對應修改以下資訊:
- 埠號
- pid檔名
- 日誌檔名
- rdb檔名
啟動單機多服務叢集:
一主二從配置
==預設情況下,每臺Redis伺服器都是主節點;==我們一般情況下只用配置從機就好了!
認老大!一主(阿里雲)一從(localhost)
使用SLAVEOF host port
就可以為從機配置主機了。
然後主機上也能看到從機的狀態:
我們這裡是使用命令搭建,是暫時的,==真實開發中應該在從機的配置檔案中進行配置,==這樣的話是永久的。
使用規則
-
從機只能讀,不能寫,主機可讀可寫但是多用於寫。
127.0.0.1:6381> set name sakura # 從機6381寫入失敗 (error) READONLY You can't write against a read only replica. 127.0.0.1:6380> set name sakura # 從機6380寫入失敗 (error) READONLY You can't write against a read only replica. 127.0.0.1:6379> set name sakura OK 127.0.0.1:6379> get name "sakura" 12345678910
-
當主機斷電宕機後,預設情況下從機的角色不會發生變化 ,叢集中只是失去了寫操作,當主機恢復以後,又會連線上從機恢復原狀。
-
當從機斷電宕機後,若不是使用配置檔案配置的從機,再次啟動後作為主機是無法獲取之前主機的資料的,若此時重新配置稱為從機,又可以獲取到主機的所有資料。這裡就要提到一個同步原理。
-
第二條中提到,預設情況下,主機故障後,不會出現新的主機,有兩種方式可以產生新的主機:
- 從機手動執行命令
slaveof no one
,這樣執行以後從機會獨立出來成為一個主機 - 使用哨兵模式(自動選舉)
- 從機手動執行命令
如果沒有老大了,這個時候能不能選擇出來一個老大呢?手動!
如果主機斷開了連線,我們可以使用SLAVEOF no one
讓自己變成主機!其他的節點就可以手動連線到最新的主節點(手動)!如果這個時候老大修復了,那麼久重新連線!
七.java整合
jedis整合
<!--匯入jredis的包-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
String response = jedis.ping();
System.out.println(response); // PONG
}
}
public class TestPing {
public static void main(String[] args) {
// 1. new 一個物件
Jedis jedis = new Jedis("123.57.56.219",6379);
jedis.auth("123456");//redis密碼
//所有的命令就是之前學習的指令
System.out.println(jedis.ping());
JSONObject jsonObject = new JSONObject();
jedis.flushDB();
jsonObject.put("hello", "world");
jsonObject.put("name", "dongxuehai");
// 開啟事務
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result)
try {
multi.set("user1", result);
multi.set("user2", result);
// 執行事務
int i = 1/0;
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整合
pom檔案
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.study</groupId>
<artifactId>radis-02-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>radis-02-springboot</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
springboot 2.x後 ,原來使用的 Jedis 被 lettuce 替換。
jedis:採用的直連,多個執行緒操作的話,是不安全的。如果要避免不安全,使用jedis pool連線池!更像BIO模式
lettuce:採用netty,例項可以在多個執行緒中共享,不存線上程不安全的情況!可以減少執行緒資料了,更像NIO模式
自定義Redis工具類
使用RedisTemplate需要頻繁呼叫.opForxxx
然後才能進行對應的操作,這樣使用起來程式碼效率低下,工作中一般不會這樣使用,而是將這些常用的公共API抽取出來封裝成為一個工具類,然後直接使用工具類來間接操作Redis,不但效率高並且易用。
工具類參考部落格:
https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html
https://www.cnblogs.com/zhzhlong/p/11434284.html
八.快取方案
快取穿透
快取沒有,資料庫也沒有,業務系統訪問壓根就不存在的資料,導致每次訪問都將壓力掛到了資料庫伺服器上導致服務崩潰,一般來說都是惡意訪問導致
解決方案:
1、快取空資料
第一,空值做了快取,意味著快取層中存了更多的鍵,需要更多的記憶體空間 ( 如果是攻擊,問題更嚴重 ),比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。
第二,快取層和儲存層的資料會有一段時間視窗的不一致,可能會對業務有一定影響。例如過期時間設定為 5 分鐘,如果此時儲存層添加了這個資料,那此段時間就會出現快取層和儲存層資料的不一致,此時可以利用訊息系統或者其他方式清除掉快取層中的空物件。
2、布隆控制器
https://www.cnblogs.com/ysocean/p/12594982.html
快取擊穿
主要體現在:熱點資料過了有效時間,此刻有大量請求會落在資料庫上,從而可能會導致資料庫崩潰
解決方案:
1、互斥鎖
只允許一個執行緒重建快取,其他執行緒等待重建快取的執行緒執行完,重新從快取獲取資料—》可能存在死鎖
@Test
public void testLock() {
for (int i = 0; i < 100; i++) {
get("001");
}
}
private String get(String key) {
JedisPool jedisPool = (JedisPool) context.getBean("jedisPool");
Jedis jedis = jedisPool.getResource();
String value = jedis.get(key);
System.out.println("redis的值" + value);
if (value == null) { //代表快取值過期
//設定一個臨時key_mutex,用於阻塞相同的請求!設定10S的超時,防止del操作失敗的時候,下次快取過期一直不能load db
if (jedis.setnx(key_mutex + key, key_mutex + key) == 1) { //代表設定成功
jedis.expire(key_mutex + key, 10);
value = "去資料庫查詢出來的值";
jedis.set(key, value);
jedis.expire(key, 5 * 60);
jedis.del(key_mutex);
} else { //這個時候代表同時候的其他執行緒已經load ,並且第一個大爺db並回設到快取了,這時候重試獲取快取值即可
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
get(key); //重試
}
} else {
return value;
}
return "";
}
無非是使用setNx阻塞相同的請求!
2、熱點資料永不過期
業界主流的做法又分兩種:
無非就是對該資料不設定過期時間,但是一定要注意該資料更新的同時,必須對快取資料進行更新,這種問題在於熱點資料過多的話會一致佔據記憶體
快取雪崩
因某種原因發生了宕機或者資料在同一時間批量失效,那麼原本被快取抵擋的海量查詢請求就會像瘋狗一樣湧向資料庫。此時資料庫如果抵擋不了這巨大的壓力,它就會崩潰。
解決方案:
1、如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同得快取資料庫中—Redis Cluster
2、儘可能使快取資料不在同一時間過期,比如使用隨機時間
3、熱點資料永不過期
4、最後沒辦法的情況下,使用服務熔斷降級、隔離限流等手段,比如採用netflix的hystrix
Jedis jedis = jedisPool.getResource();
String value = jedis.get(key);
System.out.println("redis的值" + value);
if (value == null) { //代表快取值過期
//設定一個臨時key_mutex,用於阻塞相同的請求!設定10S的超時,防止del操作失敗的時候,下次快取過期一直不能load db
if (jedis.setnx(key_mutex + key, key_mutex + key) == 1) { //代表設定成功
jedis.expire(key_mutex + key, 10);
value = "去資料庫查詢出來的值";
jedis.set(key, value);
jedis.expire(key, 5 * 60);
jedis.del(key_mutex);
} else { //這個時候代表同時候的其他執行緒已經load ,並且第一個大爺db並回設到快取了,這時候重試獲取快取值即可
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
get(key); //重試
}
} else {
return value;
}
return "";
}
無非是使用setNx阻塞相同的請求!
**2、熱點資料永不過期**
業界主流的做法又分兩種:
無非就是對該資料不設定過期時間,但是一定要注意該資料更新的同時,必須對快取資料進行更新,這種問題在於熱點資料過多的話會一致佔據記憶體
## 快取雪崩
因某種原因發生了宕機或者資料在同一時間批量失效,那麼原本被快取抵擋的海量查詢請求就會像瘋狗一樣湧向資料庫。此時資料庫如果抵擋不了這巨大的壓力,它就會崩潰。
解決方案:
**1、如果快取資料庫是分散式部署,將熱點資料均勻分佈在不同得快取資料庫中**---Redis Cluster
**2、儘可能使快取資料不在同一時間過期,比如使用隨機時間**
**3、熱點資料永不過期**
**4、最後沒辦法的情況下,使用服務熔斷降級、隔離限流等手段,比如採用netflix的hystrix**
最後:無論是快取穿透,快取擊穿還是快取雪崩,都建議使用佇列來排隊、拒絕大量請求湧入和分散式互斥鎖來避免後端資料服務被衝擊,防止已有的資料出現問題 。