一、NoSQL 資料庫概述
1.1、定義、特點
NoSQL,(Not Only SQL),泛指,非關係型資料庫。不依賴業務邏輯的儲存方式,是以 key-value 的形式儲存資料的,大大增加了資料庫的擴充套件能力!他的排名也算是比較靠前的(資料庫排名);
- 它不遵循 SQL 標準;
- 不支援 ACID (即四個特性:原子性(atomicity,或稱不可分割性)、一致性(consistency)、隔離性(isolation,又稱獨立性)、永續性(durability));
- 效能遠超 SQL;
1.2、NoSQL適用場景
- 適用於海量資料的讀寫
- 對資料高併發的讀寫
- 對資料的高可擴充套件性
二、Redis 的概述、安裝教程
2.1、概述
- Redis 是一個開源的 key-value 儲存系統;
- 和 Memcached 類似,它支援儲存的 value 型別相對更多,包括 string(字串)、list(連結串列)、Set(集合)、zset(sorted set --有序集合)和 hash(雜湊型別);
- 這些資料型別都支援 push/pop、add/remove 及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的;
- 在此基礎上,Redis 支援各種不同方式的排序;
- 與 memcached 一樣,為了保證效率,資料都是快取在記憶體中;
- 區別的是 Redis 會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案;
- 並且在此基礎上實現了 master-slave(主從)同步;
2.2、安裝教程
2.2.1、下載 Linux 版本的 Redis
2.2.2、安裝 gcc 執行環境
先檢視 linux 中是否存在 gcc,是否需要更新;
# 檢視命令
gcc --version
安裝命令(通過yum來進行安裝);
yum install gcc
2.2.3、解壓下載下來的安裝包(這裡的版本是 redis-6.2.6.tar.gz )
執行解壓命令;
tar -zxvf redis-6.2.6.tar.gz
解壓好之後進入其目錄進行編譯操作;
cd redis-6.2.6
make
2.2.4、編譯之後,執行 install 命令
make install
2.2.5、測試啟動
1)啟動(終端前臺啟動,不推薦。啟動了之後不能關閉終端,終端關閉,服務停止)
安裝完成後來到 /use/local/bin
,進入到主目錄檢視;
cd /usr/local/bin
redis-servere
2)後臺啟動
後臺啟動需要一點點配置,首先來到剛解壓安裝編譯的位置(我自己的路勁,根據需求修改即可);
cd /home/zyd/redis/redis-6.2.6
該路徑下會有一個 redis.conf
的檔案,將其賦值一份到其他路勁下(我這裡就暫且拷貝當這個位置 /etc
路徑下);
cp /home/zyd/redis/redis-6.2.6/redis.conf /etc/redis.conf
拷貝完成後,修改redis.conf
中的選項,將 daemonize
改為 yes 即可;
回到解壓的根位置(帶檔案)啟動
redis-server /etc/redis.conf
Redis 出現了一些問題,啟動後無法正常的實現存到磁碟任務的問題,解決方案如下:
將配置項 stop-writes-on-bgsave-error
設定為 no
,可以在 Redis 命令列裡配置,也可以在 redis.conf
配置檔案裡改;
啟動就到這裡就可以跟安裝啟動部分告一段落 了!
三、Redis 的五大常用資料型別
3.1常用、通用的命令
檢視當前庫所有 key;
keys *
判斷某個 key 是否存在;
exists key
檢視你的 key 是什麼型別;
type key
刪除指定的 key 資料;
del key
根據 value 選擇非阻塞刪除(僅將 keys從 keyspace 後設資料中刪除,真正的刪除會在後續非同步操作);
unlink key
10 秒鐘:為給定的 key 設定過期時間;
expire key 10
檢視還有多少秒過期,-1 表示永不過期,-2 表示已過期;
ttl key
命令切換資料庫 例如 select 0
代表切換到0庫;
select
檢視當前資料庫的key的數量;
dbsize
清空當前庫;
flushdb
通殺全部庫;
flushall
3.2、Redis 字串 String
String 是 Redis 的最基本資料型別,就是一個典型的 key-value 對兒,String 是二進位制安全的,這就意味著你可以存放任何資料到 Redis 的 string 中,包括序列化的物件,以及圖片等等。一個字串的最大佔用記憶體是 512M
3.2.1、常用命令
新增一個鍵值對;
set key value
查詢對應的鍵的值;
get key
追加對應 key 的值;
append key value...
獲得值得長度;
strlen key
只有在 key 不在的時候設定值;
setnx key values
將key中存放的數字進行自增1操作,如果為空,新增值為 -1;
incr key
decr key
自增自減可以自定義步長;
incrby key 2
decrby key 1
設定/獲取一對或者多個鍵值對的值;
mset k2 value2 k3 value3 k4 value4 k5 3
mget k1 k2 k3 k4 k5
只有在 key 不在的時候設定值(基於他的原子性,有一個失敗,就都失敗);
msetnx k2 value2 k3 value3 k4 value4 k5 3
根據給定的範圍獲取值(值得範圍是左閉右閉),類似於 Java 中的 substring( ) 方法;
getrange k2 0 -1 獲取全部
getrange k2 0 3 0-3一共四個字元
根據給定的位置開始覆蓋寫值;
get k2 -> value2
setrange k2 0 test
get k2 -> teste2
設定鍵值的同時,設定過期時間(單位為秒);
setex key 5 value
設定新值的同時獲得舊值;
getset key value
3.3、Redis 列表 List
Redis 的 list 是單鍵多值,即一個鍵可以對應一個或者多個值。Redis 是一個簡單的字串列表,按照插入順序排序。他的底層是一個雙向連結串列,所以你可以在連結串列的頭部和尾部插入元素,下面有簡單示例;
3.3.1、常用命令
從左邊或者右邊插入一個或多個值(並非鍵值對);
lpush/rpush k1 v1 v2 v3
從左邊或者右邊彈出一個值;特點:值在,鍵在,值無,鍵亡;
lpop/rpop key
從k1右邊彈出一個值插入到左邊的k2中(右彈左壓);
rpoplpush k1(右彈) k2(左壓)
按照索引下標獲得元素;
lrange key start end
lrange k5 0 3 共四位
lrange k5 0 -1 代表全部
按照索引下標從左向右開始查詢對應下標的值;
lindex k5 0
在指定值得前面插入一個新值;
linsert key before lilei newvalue
從左邊開始刪除 n 個 value;
lrem key n vakue
將列表 key 下標為 index 的值換為 value;
lset key index value
3.3.2、資料結構
List 的資料結構quickList
;首先,在元素比較少的時候,會使用一塊連續的記憶體儲存,這個結構是 ziplist
,稱之為壓縮列表。他講所有的元素緊緊挨著一起儲存,分配的是一塊連續對的儲存。當資料量比較多的時候,才會改成 quicklist
;
因為普通的連結串列需要附加指標的空間很大,會比較浪費空間。Redis 將連結串列個 ziplist
結合起來組成了 quicklist
,也就是使用雙向指標將 ziplist
穿起來使用,解決了快速插入刪除的效能問題,又不會出現太大的資料冗餘!
3.4、Redis 集合 Set
Redis Set對外提供的功能是與 list 是類似的,特殊之處在於 Set 是可以自動排重的,也就是說,當你需要一個列表資料,又不希望出現重複資料,就可以選擇使用 Set,Set 提供了判斷某個成員是否在一個 set 集合內的重要介面,這個也是 list 所不能夠提供的
Redis 的 Set 是 string 型別的無序集合,它底層其實是一個 value 為 null 的 hash 表,所以新增、查詢、刪除的複雜度都是 o(1);
3.4.1、常用命令
新增一個或多個值到 Set 集合中;
sadd key value1 value2...
返回該鍵的所有值(並不是刪除);
smembers key
返回該集合的元素個數;
scard key
刪除結合中的一個或多個元素;
srem key value1 value2...
從該集合中隨機 彈出一個值;
spop key
從該集合中隨意取出 n 個值,但是不會從集合中刪除;
srandmember key n
把集合中的一個值從 source 集合移動到另一個集合destination;
smove source destination value
返回兩個或多個集合的交集;
sinter key1 key2
返回兩個集合或多個集合的並集;
sunion key1 key2
返回兩個集合的差集元素(key1 有但是 key2 沒有,也就是 key1 - key2);
sdiff key1 key2
3.4.2、資料結構
Set 資料結構對應的是 dict 字典,字典是用雜湊表實現的;而 Java 中的 HashSet 的內部實現使用的是 HashMap,只不過所有的 value 都只想同一個物件,Redis 的 Set 結構也是一樣的,它的內部也使用 hash 結構,所有的 value 都指向同一個內部值;
3.5、Redis 雜湊 Hash
Redis Hash 是一個鍵值對集合,他是一個 string 型別的 field 和 value 的對映表。類似於 Java 裡的 Map<String,Object>
,所以 Hash 特別適合用於儲存物件;如下圖所示
如上圖,key (使用者 ID) + field (屬性標籤) 就可以操作對應的屬性的資料了,既不需要重複儲存資料,也不會帶來序列化和併發修改控制的問題;
3.5.1、常用命令
給 hash 集合中新增值(經過測試,是可以同時新增很多個屬性的);
hset user1: id 1
hset user1: id 1 name zhangsan age 18
從集合中取出來值 value (記得加上冒號);
hget user1: name
批量設定值;
hmset key: field value field value...
hmset user2: id 2 name lisa age 26
檢視 Hash 表 key 中,給定的域是否是存在的;
hexists key: field
檢視該 key 的所有的 field (記得加上冒號) ;
hkeys key:
檢視該結合的所有的 filed 的值;
hvals key:
為雜湊表 key 中的域 field 的值增加量(1,0,-1);
hincrby user1: age 2
為 key 中的域 field 的值設定為 value (當且僅當域 field 不存在時才可以設定成功);
hsetnx user1: firstName lisi
3.5.2、資料結構
Hash 型別對應的資料結構是兩種:ziplist (壓縮列表),hashtable (雜湊表) 。當 field-value 長度較短且個數較少,使用 ziplist,否則使用 hashtable,我認為和之前的那個 List 的是類似的;
3.6、Redis 集合 Zset
他是有序集合 Zset(sorted set),與普通的 Set 集合非常的相似,他是一個沒有重複元素的字串集合,區別在於他多了一個 “評分” ,這個用來實現了排序的功能,集合裡的元素是唯一的,但是評分是可以重複的;
注意:為了方便理解和做筆記,這裡將引用 set 集合中的 field 和 value 的概念,即 score = value
3.6.1、常用命令
將一個或多個 member 元素及其 score 值加入到有序集 key 中;
zadd key score1 field score2 field...
返回有序集合中,下標在 start 和 stop 之間的元素;
zrange key start stop
# WITHSCORES 攜帶評分一起返回
zrange key start stop withscores
返回有序集合中,評分在 min 和 max 之間的結果(從小到大就是 min,max);
zrangebyscore key min max
zrangebyscore key min max withscores
從大到小就是 max,min;
zrangebyscore key max min
zrangebyscore key max min withscores
給指定的集合中的元素加上一個指定的數(這裡只得是加上評分);
zincrby key value field
刪除指定的元素,不是根據評分刪除,是根據值;
zrem key field
統計該集合中,評分割槽間內的元素的個數
zcount key 0 500
根據已存在的 field(不是評分),來返回對應的排位(從 0 開始)
zrank key field
3.6.2、資料結構
SortedSet (zset) 是 Redis 提供的一個非常特別的資料結構,在結構方面和 Set 非常的相似,即 Map<String,Double>
,可以給每一個元素 value (field) 賦予一個權重(score);另外,他在內部又類似於 TreeSet ,內部元素會根據權重(score)進行排序,可以得到每個元素的名次,也可以通過 score 的範圍來獲取元素的列表;
zset 的底層還用了兩個資料結構,hash 和 跳躍表;
hash ,hash 的作用就是關聯元素的 value (field) 和權重 score,保障內部元素 value (field) 的唯一性,可以通過元素 value (score) 來找到對應的 score 的值;
跳躍表,跳躍表的目的在於給元素 value (field) 排序,根據 score 的範圍來獲取元素列表;什麼是跳躍表?
四、配置檔案
首先到我們自己定義的配置檔案的位置去,我這裡是 /etc/redis.conf
4.1、Units 單位
第一個部分是 Units 單位,我們在 Redis 中是隻支援 byte(位元組) ,而不支援 bit(位),大小寫不敏感;
4.2、INCLUDES 包含
4.3、網路相關配置
4.3.1、bind
預設情況下,他為 127.0.0.1 ,即只允許本地進行訪問,在不寫的情況下,允許任何地址進行訪問,我這裡就註釋掉了;
4.3.2、protected-mode
在下面一點的位置,有一個 protected-mode ,這裡我們將他設定為 no 將他關掉;這邊是有點說道的,假如你開啟了保護模式,且沒有設定 bind ip 和 密碼,那麼 Redis 是隻允許本機進行訪問的;
4.3.3、port
以及我們可以看到他的埠設定 6379 ,那麼這個就不在這裡贅述啦;
4.3.4、tcp-backlog
通過設定 tcp 的 backlog,backlog 其實是一個連線佇列,連線總和包括未完成三次握手佇列和已經完成了三次握手的佇列;在高併發環境下,可以設定一個高的 backlog 值來避免客戶端連線問題;
linux 核心會將這個值減小到 /proc/sys/net/core/somaxconn 的值(128),所以需要確認增大 /proc/sys/net/core/somaxconn 和 /proc/net.ipv4/tcp_max_syn_backlog(128)兩個值,來達到想要的結果;
4.3.5、timeout
超時,一個客戶端維持多少秒會關閉,0 表示永遠不關閉;
4.3.6、daemonize
是否啟用後臺程式,通常設定為 yes,守護程式,後臺啟動;
4.3.7、pidfile
存放 pid 檔案的位置,每個例項都會產生一個不同的 pid 檔案;
4.3.8、loglevel
指定日誌級別,Redis 總共分為四個級別,分別為:debug、verbose、notice、warning,預設為 notice;
4.3.9、logfile
日誌檔名稱
4.3.10、database 16
設定預設的庫的數量為 16,預設的資料庫為 0,可以使用 select 命令來指定連線某個庫;
4.3.11、設定密碼
在命令列裡面設定的密碼是臨時的,在 Redis 伺服器重啟之後,密碼就還原了。需要永久的設定密碼還得是在配置檔案裡面設定;
4.3.12、LIMITS 限制
1)maxclients
設定 Redis 同時可以連線多少個客戶端進行連線,預設情況下為 10000 個客戶端;如果達到了該限制, Redis 則會拒接新的連線請求,並反饋為 “max number of clients reached”;
2)maxmemory
建議設定,否則記憶體滿的時候,伺服器會直接當機;
設定 Redis 的可使用記憶體量之後,當到達記憶體的使用上限的時候,Redis 將檢視移除內部的資料,移除的規則可以通過 maxmemory-policy 來指定;
-
volatile-lru:使用LRU演算法移除key,只對設定了過期時間的鍵;(最近最少使用);
-
allkeys-lru:在所有集合key中,使用LRU演算法移除key;
-
volatile-random:在過期集合中移除隨機的key,只對設定了過期時間的鍵;
-
allkeys-random:在所有集合key中,移除隨機的key;
-
volatile-ttl:移除那些TTL值最小的key,即那些最近要過期的key;
-
noeviction:不進行移除。針對寫操作,只是返回錯誤資訊;
也可以設定不允許移除,那麼 Redis 就會返回一些錯誤資訊,比如 SET、LPUSH 等;
但是對於無記憶體申請的指令,仍然會正常響應,比如 GET 等。當然,如果你有從 Redis 的情況下,那麼在設定記憶體使用上限時,需要在系統中留出一些記憶體空間給同步佇列快取,只有在你設定的是不移除的情況下,就可以不考慮這個因素;
3)maxmemory-samples
設定樣本數量
五、 訂閱和釋出
訂閱和釋出是 Rerdis 的一種訊息通訊模式;Redis 客戶端可以訂閱任意數量的頻道;
客戶端進行訂閱;
當在頻道中釋出訊息的時候,訊息就會發給訂閱的客戶端;
5.1、訂閱
進入客戶端之後,先進行訂閱,等待訊息釋出即可;
subscribe channel
5.2、釋出
進入另一臺客戶端之後,開始釋出,那麼訂閱了該頻道的客戶端就會收到該釋出的訊息;
publish channel helloworld
釋出端,返回的數字即為訂閱的客戶端的數量;
六、Redis 新資料型別
6.1、Bitmaps
6.1.1、概念
Redis 提供了 Bitmaps 這個 “資料型別” 可以實現對位的操作;Bitmaps 並不是一個資料型別,實際上他只是一個字串(key-value),但是他可以對字串的位進行操作;Bitmaps 單獨提供了一套命令,所以在 Redis 中使用 Bitmaps 和使用字串的方法不太相同,可以把 Bitmaps 想象成一個以位為單位的陣列,陣列的每個陣列只能儲存 0 和 1,陣列的下標在 Bitmaps 中叫做偏移量;
6.1.2、常用命令
給 key 中指定偏移量的位置上賦值 0 或 1,在第一次初始化 Bitmaps 時,加入偏移量比較大時,那麼整個初始化過程會比較慢,而且可能會造成 Redis 阻塞;
setbit key offset value
獲取 key 中的某個偏移量的值
getbit key offset
操作如下圖所示
統計集合中 1 的個數
bitcount key
將多個集合進行交集、並集等操作
bitop and/or/not result k1 k2...
6.1.3、Bitmap 與 Set 集合的對比
我們要辯證的看待這個問題!
Bitmap 佔用的空間小,Set 的佔用空間較大;所以當資料量很大的時候,那麼我們此時此刻來使用 Set 是不利的,會消耗很大的記憶體空間;但是也並不意味這 Bitmap 就是比 Set 要好,在資料量很大的情況下,有用的資料很少的時候,那麼用 Bitmap 就是十分不划算的,因為其大部分的資料都是 0 ,造成了很大的資料冗餘,空間浪費;
6.2、HyperLogLog
6.2.1、概念
有一個場景,我們在某些情況下需要這種資料型別,比如在統計網站 PV(PageView 頁面訪問量),可以利用 Redis 的 incr、incrby 來進行實現;但是存在的問題,像 UV(UniqueVisitor 獨立訪客)、獨立的 IP 數、搜尋記錄數等需要去重的問題,這類問題統稱為基數問題;
6.2.2、常用命令
新增指定的元素到 HyperLogLog 返回 1,則說明新增成功,返回 0,則說明新增失敗;
pfadd key value
計算 HLL 的近似數;
pfcount key
合併兩個 HLL 資料型別的資料集;
pfmerge
演示如下圖所示
6.3、Geospatial
6.3.1、概念
Redis 3.2 中增加了對 GEO 型別的支援。GEO,Geographic,地理資訊的縮寫。該型別,就是元素的二維座標,在地圖上就是經緯度。Redis 基於該型別,提供了經緯度設定,查詢,範圍查詢,距離查詢,經緯度 Hash 等常見操作;
6.3.2、常用命令
加地理位置(經度、緯度、名稱);
geoadd key longitude latitude member longitude latitude member ...
獲得指定地區的座標值;
geopos key member
geopos china:city chongqing
獲得兩個地區的直線距離;
geodist key member1 member2 [m/km/ft/mi]
獲得指定經緯度的某一半徑內的元素;
georadius key longitude latitude radius [m/km/ft/mi]
georadius china:city 110 30 1000 km
七、Jedis
7.1、概念
Jedis 是用於使用 Java 程式碼來操作 Redis 的一個軟體(可以理解為 Redis 專用的 JDBC),我們建立一個簡單的 Maven 工程測試一下相關功能。
7.2、環境搭建
- 建立好 Maven 工程之後,直接在 pom.xml 裡面寫入依賴,這裡是用的是 jedis 3.2.0
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
- 等待下載完成後,我這裡建了一個測試類準備進行測試,如下
public class JedisDemo1{
public static void main(String[] args){
// 建立Jedis物件(使用有參構造)
Jedis jedis = new Jedis("192.168.182.128",6379);
String s = jedis.ping();
System.out.println(s);
}
}
- 現在去立馬執行程式,大概率會發生拒絕連線等問題,返回異常,大概率那是因為以下兩點
- 配置檔案中的選項沒有設定好;
- Linux 中的 firewall 沒有開啟對應的埠 6379;
解決上述問題 1:配置檔案中將 bind 選項直接註釋掉,和 protected-mode 設定為 no,具體看上邊的部分,這裡不做贅述;
解決上述問題 2:在 Linux 中開啟 6379 埠;
# 檢視當前防火牆狀態
systemctl status firewalld.service
# 檢視當前需要開啟的埠6379的狀態
firewall-cmd --zone=docker --query-port=6379/tcp
firewall-cmd --query-port=6379/tcp
# 如果沒有開啟,那麼就開啟,並再次檢視是否開啟
# firewall-cmd --add-port=6379/tcp
firewall-cmd --add-port=6379/tcp --permanent
# 開啟後,重啟服務
systemctl stop firewalld.service
systemctl start firewalld.service
# 2021.11.26 補充(今天看到了新的開啟埠的方法,這種方法更加簡潔有效,補充如下)
# 1.檢視防火牆狀態
firewall-cmd --state
# 2.開啟埠
firewall-cmd --permanent --zone=public --add-port=22/tcp
# 3.重啟防火牆
firewall-cmd --reload
# 4.檢視防火牆看起的埠
在這之後,就可以進行成功連線了;我在這裡搞了一個小、shell指令碼,用來開啟指定埠,建議放在 root 的許可權下!
#!/bin/bash
command=`firewall-cmd --state`
if [ $command == "running" ];then
firewall-cmd --permanent --zone=public --add-port=$1/tcp
firewall-cmd --reload
firewall-cmd --list-ports
else
echo "firewall has been closed"
fi
執行指令碼(開啟 8888 埠號)
./addport.sh 8888
具體的程式碼操作這裡不做詳細描述;
八、SpringBoot 整合 Redis
8.1、匯入依賴
<!-- 本來是用的2.6.0,後改為 2.2.1.RELEASE -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- web的啟動器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 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>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
8.2、配置環境
這裡遇到一個坑,也是我長期沒有看 SpringBoot 的結果,他的包掃描範圍由他的啟動類來決定,也就是說他的包掃描路徑是“他的父目錄下的所有子目錄”,否則會因為無法掃描到包而導致無法啟動
8.2.1、Redis 通用配置類
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport{
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解決查詢快取轉換異常的問題
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解決亂碼的問題),過期時間600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
8.2.2、測試類
@RestController
@RequestMapping("/redisTest")
public class RedisTestController{
@Autowired
RedisTemplate redisTemplate;
@GetMapping
public String testRedis(){
redisTemplate.opsForValue().set("name","張三");
String name = (String) redisTemplate.opsForValue().get("name");
return name;
}
}
九、Redis 事務和鎖機制
首先他將開始進行組隊,按順序組隊,組隊的過程是為了防止執行過程中有其他命令進行插隊操作,導致我們沒有達到想要的結果!
基本的操作流程如下所示:
9.1、命令列操作事務
# 開啟事務操作
multi
# 開始向事務佇列中進行新增語句,這裡稱之為組隊階段
set key value
...
# 如果需要中途打斷組隊,放棄事務操作,那麼就立即使用 discard 命令進行操作。
discard
# 執行階段,開始執行佇列裡的語句
exec
9.2、事務出錯
9.2.1、情況一
組隊失敗
當我們在組隊時發生錯誤時,那我們後面執行的時候,也是會直接報錯的,整個事務是失敗的,相當於是一個回滾的操作;
9.2.2、情況二
組隊成功,執行出錯;
當我們發生組隊成功,但是執行失敗的情況時,報錯的那一條執行失敗,其他的都是會執行成功的,沒有類似於回滾操作;
9.3、事務衝突的問題
我們都知道,在學習資料庫的時候會有一個非常經典的案例,就是銀行取錢結賬的這個場景,這裡就不在這裡贅述這個場景具體是怎麼樣的了;有兩種鎖可以很好的結局這種問題;
9.3.1、悲觀鎖
當我們在運算元據的時候,會對資料直接進行上鎖,以防止其他人對資料進行操作。等操作結束後,釋放鎖。等一下個人過來進行運算元據的時候,首先會判斷是否滿足條件,那麼這就會解決我們上面闡述的事務衝突的問題;悲觀鎖,可以理解為是悲觀的,永遠認為有人會操作他的資料,所以直接上鎖,這種態度被稱為是一種悲觀的態度,所以稱之為悲觀鎖;
9.3.2、樂觀鎖
樂觀鎖,顧名思義,是樂觀的,認為不會有人更改他的資料,所以叫做樂觀鎖,實際上是給資料加了一個版本號,用來做資料校驗;當我們進行運算元據的時候,會拿著資料的版本號和資料庫的資料的版本號進行對比,如果是一樣的版本號且資料滿足操作條件,就進行直接運算元據,否則不操作;
樂觀鎖的操作
終端一:
# 用 watch 關鍵字來監視一個 key
set blance 100
watch blance
# 開啟事務
multi
# 在事務下進行對 blane 的操作
incrby blance 10
exec
blance = 110
終端二:
# 用 watch 關鍵字來監視一個 key
set blance 100
watch blance
# 開啟事務
multi
# 在事務下進行對 blance 的操作
incrby blance 10
exec
操作失敗返回 nil
9.3.3、秒殺案例
秒殺案例,這裡不做筆記和演示,只做一些原理說明;
版本一
秒殺案例的實現,靠 Redis 的樂觀鎖和事務進行實現,先用 Watch 來對庫存進行監視,然後建立一個事務 multi 進行事務操作,再進行操作,但是這樣的方法可以解決超賣的問題,但是會出現連線超時的問題;
版本二
通過資料連線池來解決了版本一中的連線超時的問題,但是同時又發現了,有庫存遺留的問題;
版本三
Lua 指令碼是一個小巧的指令碼語言,Lua 指令碼可以輕鬆的被 C 語言、C ++ 呼叫,也可以輕鬆的呼叫 C 語言等函式,Lua 沒有提供強大的類庫,所以只能充當一個嵌入式指令碼語言,不適合作為開發獨立應用程式的語言
利用 Lua 指令碼完成了 Redis 實現的秒殺任務,並且解決了遺留庫存的問題;
十、Redis 持久化
10.1、Redis 持久化之 RDB
在指定的時間間隔內將記憶體中的資料集快照寫入磁碟,也就是 Snapshot 快照中,它恢復時是將快照檔案直接讀到記憶體中;
10.1.1、備份是如何進行的
Redis 會單獨建立(fork)一個程式用來進行持久化,會先將資料寫入到一個臨時檔案中,待持久化過程都結束了,在用這個臨時檔案替換上次持久化好的檔案,在整個過程中,主執行緒是不會進行 IO 操作的,這樣就會極大的提高了 Redis 的效能;如果需要大規模的進行資料恢復,並且對於大資料恢復的完整性不是非常的敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB 的缺點是最後一次持久化後的資料可能丟失(伺服器當機之前的一個備份將會丟失,因為它會存在一個持久化週期,例如 save 20 3
表示在 20 秒內如果有 3 個資料發生改變,就會觸發持久化操作);
Fork 子程式的作用就是複製一個和當前程式一樣的程式,新程式的所有資料(變數、環境變數、程式計數器等)數值和原程式一致,但是他是一個全新的程式,並作為原程式的子程式;
10.1.2、RDB 的持久化流程圖
10.1.3、dump.rdb 檔案
在 redis.conf 中配置檔名稱,預設為 dump.rdb;該檔案的預設儲存路徑,預設為 Redis 啟動命令列所在的目錄下為 dir "/myredis/";
快照中預設的快照配置
表示的是在 3600 秒內有一個資料發生變化就會觸發備份操作,其他的類同;
命令 save 和 bgsave
save:save 只管儲存,其他不管,全部阻塞手動儲存,不建議使用這種方法;
bgsave:Redis 會在後臺一步進行快照操作,快照的同時還可以響應客戶端的請求;
可以通過 lastsave 命令獲取最後一次成功執行快照的時間;
flushall 命令
執行 flushall 命令,也會產生 dump.rdb 檔案,但是裡面是空的,將是毫無意義的;
配置檔案中的選項
stop-writes-on-bgsave-error
當 Redis 無法寫入磁碟的時候,直接關掉 Redis 的寫操作,推薦 yes;
rdbcompression 壓縮檔案
對於已經儲存到磁碟中的快照,可以設定是否進行壓縮儲存,如果是的話,Redis 會使用 LZF 演算法進行壓縮儲存;
如果你不想消耗 CPU 來進行壓縮的話,可以設定為關閉此功能;
rdbchecksum 檢查完整性
在儲存快照後,還可以讓 Redis 使用 CRC64 演算法進行資料校驗,但是這樣做會增大 10% 的效能消耗,如果希望獲取到更大的效能提升,可以關閉此功能;
rdb 的備份
先通過 config get dir 查詢 rdb 檔案的目錄,將 *.rdb 的檔案拷貝到別的地方;
rdb 的恢復:
- 關閉 Redis;
- 先把備份的檔案拷貝到工作目錄下
cp dump2.rdb dump.rdb;
- 啟動 Redis,備份資料會直接載入;
優勢
- 適合大規模的資料恢復;
- 對資料完整性和一致性要求不高更合適使用;
- 節省磁碟空間;
- 恢復速度快;
劣勢
- Fork 的時候,記憶體中的資料被克隆了一份,大致 2 倍的膨脹性需要考慮;
- 雖然 Redis 在 Fork 時使用了寫時拷貝技術,但是如果資料量龐大時還是比消耗記憶體的;
- 在備份週期內(在一定時間間隔內做一次備份),所以如果 Redis 在備份的時候發生當機,就會丟失最後一次快照後的所有修改;
如何停止
動態停止 RDB:redis-cli config set save"" save 後給空值,表示禁用儲存策略;
10.2、Redis 持久化之 AOF
AOF 是什麼?Append Only File,他是以日誌的形式來記錄每個寫操作(增量儲存),將 Redis 執行過的所有指令記錄寫下來(讀操作不記錄),只是追加檔案不會改寫檔案,Redis 啟動之初會讀取改檔案重新構建資料,換言之,Redis 重啟的話就根據日誌檔案的內容將寫指令從前到後執行一次,從而達到恢復資料的目的;
10.2.1、AOF 持久化流程
- 客戶端的請求寫命令會被 append 追加到 AOF 緩衝區內;
- AOF 緩衝區根據 AOF 持久化策略【always、everysec、no】將操作 sync 同步到磁碟 AOF 檔案中;
- AOF 檔案大小超過重寫策略或手動重寫時,會對 AOF 檔案 rewrite 重寫,壓縮 AOF 檔案容量;
- Redis 服務重啟時,會重新載入 AOF 檔案中的寫操作達到資料恢復的目的;
10.2.2、AOF 預設不開啟
AOF 預設不開啟,可以在 redis.conf 中配置檔名稱,預設為 appendonly.aof ,AOF 檔案的儲存路徑,同 RDB 的路徑一致;
10.2.3、RDB 和 AOF 同時開啟
當他們同時開啟的時候,系統會預設讀取 AOF 的資料(資料不會存在丟失);
10.2.4、AOF 啟動、修復、恢復
- AOF 的備份機制和效能雖然和 RDB 不通,但是備份和恢復的操作同 RDB 一樣,都是拷貝備份檔案,需要恢復時,再拷貝到 Redis 工作目錄下,啟動系統即載入;
- 正常恢復
- 修改預設的 appendonly no 改為 yes;
- 將有資料的 aof 檔案複製一份儲存到對應的目錄(檢視目錄:config get dir)
- 恢復:重啟 redis 然後重新載入
- 異常恢復
- 修改預設的 appendonly no 改為 yes;
- 如果遇到 AOF 檔案損壞,通過
/usr/local/bin/redis-check-aof --fix appendonly.aof
來進行恢復; - 備份被寫壞的 AOF 檔案;
- 恢復:重啟 Redis,然後重新載入;
10.2.5、AOF 同步頻率設定
appendfsync always
始終同步,每次 Redis 的寫入都會立刻記入日誌;效能較差,但是資料完整性較好;
appendfsync everysec
每秒同步,每秒記入日誌一次,如果當機,本地的資料可能丟失;
appendfsync no
Redis 不主動進行同步,把同步時機交給作業系統;
10.2.6、Rewrite 壓縮
AOF採用檔案追加方式,檔案會越來越大為避免出現此種情況,新增了重寫機制, 當AOF檔案的大小超過所設定的閾值時,Redis就會啟動AOF檔案的內容壓縮, 只保留可以恢復資料的最小指令集.可以使用命令 bgrewriteaof
10.2.7、優勢
- 備份機制更加穩健,丟失資料概率更低;
- 可讀的日誌文字,通過操作 AOF 穩健,可以處理誤操作;
10.2.8、劣勢
- 比起 RDB 佔用更多的磁碟空間;
- 恢復備份速度要慢;
- 每次讀寫都同步的話,有一定的效能壓力;
- 存在個別 Bug,造成不能恢復的後果;
10.3、總結
- 官方推薦兩個都啟用;
- 如果對資料不敏感,可以單獨啟用 RDB;
- 不建議單獨使用 AOF,因為可能會出現 Bug;
- 如果只是做純記憶體快取,可以都不啟用;
十一、Redis 主從複製
10.1、搭建 Redis 主從伺服器
10.1.1、配置檔案
首先複製幾個一樣的配置檔案出來;
cp /home/zyd/redis/redis-6.2.6/redis.conf /home/zyd/myredis/redis.conf
設定配置檔案中的一些設定;
vim redis.conf
daemonize yes
vim redis6379.conf
# 以下就是配置檔案中的內容
include /home/zyd/myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
拷貝另外的兩臺 Redis 伺服器的配置,並且設定好其中的配置埠;
cp redis6379.conf redis6380.conf
cp redis6379.conf redis6381.conf
10.1.2、啟動伺服器
redis-server /home/zyd/myredis/redis6379.conf
redis-server /home/zyd/myredis/redis6380.conf
redis-server /home/zyd/myredis/redis6381.conf
這樣一來,三臺伺服器就此就啟動成功了;我們可以通過命令來檢視伺服器的狀態;
# 連線埠號為 6379 的 redis 伺服器
reids-cli -p 6379
# 檢視伺服器資訊、狀態
> info replication
10.1.3、讓從機連線主機
redis-cli -p 6380
> slaveof 127.0.0.1 6379
redis-cli -p 6381
> slaveof 127.0.0.1 6379
下圖可能會存在一些問題,他們的 IP 地址可能存在一些問題,應該是用 127.0.0.1 才行;
10.1.4、情況說明
- 當我們 Redis 的主機伺服器掛掉的話,那麼兩臺從機是不會發生“叛變”的,會繼續等待主機的恢復;
- 當我們的 Redis 的有一臺從機掛掉的時候,那麼他再啟動起來的時候,就會變為 master 主機狀態,需要重新關聯主機,但是再一次連上 Redis 主機的時候,就會將資料同步過來;
10.2、常用的三種模式
10.2.1、一主二僕
該模式就是我們常見的,一臺主伺服器下面掛著很多臺從伺服器;主機掛了,等主機重啟後,地位不變還是主機,從機還是從機;
10.2.2、薪火相傳
該模式就是主機下面只直接掛了一臺從機伺服器,其他的從機伺服器就掛在第一臺從機上,達到“薪火相傳”的目的;
- 主機掛了,從機還是從機,無法進行寫資料;
- 中途變更轉向,會清除之前的資料,重新建立最新的拷貝;
- 某一個從機掛了後面的從機都無法進行備份;
10.2.3、反客為主
當一個 master 當機之後,後面的 slave 可以立刻升為 master,其後面的 slave 不用做任何修改;
slaveof no one
10.3、複製原理
- Slave 啟動成功連線到 master 後會傳送一個 sync 命令;
- master 接到命令啟動後臺的存檔程式,同時手機所有接收到的用於修改資料集命令,在後臺程式執行完畢之後,master 將傳送整個資料檔案到 slave ,完成一次同步複製;
- 全量複製:slave 伺服器在接收到資料庫檔案資料後,將其存檔並載入到記憶體中;
- 增量複製:master 繼續將新的所有收集到的修改命令一次傳給 slave,完成同步;
- 只要重新連線 master,一次完全同步(全量複製)將被自動執行;
10.4、哨兵模式(sentinel)
反客為主的自動版,能夠後臺監控主機是否故障,如果故障了會根據投票數自動將從庫換為主庫;
10.4.1、使用步驟
-
將三臺伺服器,調整為一主二僕模式,在這裡就是 6379 帶著 6380、6381;
-
在自定義的目錄下新建 sentinel.conf 檔案,這個名字絕對是不能錯的;
-
在上述一步的配置檔案中寫好配置內容為 sentinel monitor mymaster 127.0.0.1 6379 1
- 其中 mymaster 為監控物件起得伺服器的名稱,1 為至少有多個哨兵統一遷移的標識;
-
啟動哨兵
vim /home/zyd/myredis/sentinel.conf # 編輯內容 sentinel monitor mymaster 127.0.0.1 1
啟動哨兵
redis-sentinel /home/zyd/myredis/sentinel.conf
啟動成功,如上圖所示,看的到主機是 127.0.0.1 6379 ,兩臺從機分別又是 6380,6381;
10.4.2、若干情況
主機當機情況
當我們的主機當機了,那麼我們的哨兵就會根據一些規則在從機中來選出來一個新的主機當伺服器;而當我們的主機重啟成功的時候,他只能作為從機來使用;
複製延時
由於所有的寫操作都是在主機 master 上進行的操作的,然後同步更新到 slave 上,所以從 master 同步到 slave 機器上會有一定的延遲,當系統很繁忙的時候,延遲的問題會更加嚴重,slave 機器數量的增加也會使這個問題更加嚴重;
故障恢復
- 優先順序在其配置檔案 redis.conf 中:
replica-priority 100
; - 偏移量是指的是,獲得原主機資料最全的;
- 每個 Redis 例項啟動後都會隨機生成一個 40 位的 runid;
主從複製的 Java 程式碼實現
private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
Set<String> sentinelSet=new HashSet<>();
sentinelSet.add("192.168.11.103:26379");
JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10); //最大可用連線數
jedisPoolConfig.setMaxIdle(5); //最大閒置連線數
jedisPoolConfig.setMinIdle(5); //最小閒置連線數
jedisPoolConfig.setBlockWhenExhausted(true); //連線耗盡是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待時間
jedisPoolConfig.setTestOnBorrow(true); //取連線的時候進行一下測試 ping pong
jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
}else{
return jedisSentinelPool.getResource();
}
}
十二、Redis 叢集
12.1、叢集的搭建
Redis 叢集實現了對 Redis 的水平擴容,即啟動 N 個 Redis 節點,將整個資料庫分佈儲存在這 N 個節點中,每個節點儲存總資料的 1/N;
我們這裡搭建6臺 Redis 伺服器組成叢集,由於條件限制我們選擇在不同的埠來啟動這些伺服器;
12.1.1、刪除相關檔案
刪除持久化相關的備份檔案:rdb、aof;
12.1.2、製作 6 個例項
製作 6 個例項,對映 6 個埠,分別是 6379 、6380 、6381、6389、6390、6391
12.1.3、配置基本資訊
在 redis.conf 中 開啟 daemonize yes
cluster-enable yes 開啟叢集模式
cluster-config-file nodes-6379.conf 設定節點配置檔名
cluster-node-timeout 15000 設定節點失聯時間
總的配置資訊如下:
在 vim 編輯器中的命令列模式使用 /%s/6379/6380 命令來替換埠資訊,快速配置;
啟動這 6 個 Redis 伺服器;
redis-server redis6379.conf
使這 6 個節點合成一個叢集,在執行以下組合命令前。要保證 nodes-xxxx.conf 檔案都正常的生成了;
cd /home/zyd/redis/redis-6.x.x/src/
此處應該寫上 198.x.x.x 的真實的 ip 地址,不應該寫 127.0.0.1
redis-cli --cluster create --cluster-replicas 1 192.168.182.128:6379 192.168.182.128:6380 192.168.182.128:6381 192.168.182.128:6389 192.168.182.128:6390 192.168.182.128:6391
12.2、登入客戶端
以叢集方式登入!
-c 採用叢集策略連線,設定資料會自動切換到相應的寫主機上
redis-cli -c -p 6379
12.2.1、cluster nodes
在 Redis 客戶端內,通過 cluster nodes 命令檢視叢集資訊
> cluster nodes
12.2.2、分配節點
-
redis cluster 如何分配 6 個節點,一個叢集至少要有三個主力節點;
-
叢集啟動選項中 --cluster-replicas 1 表示我們希望為叢集中的每個主力節點建立一個從節點;要實現主節點掛了從節點頂上;
-
分配原則儘量保證每個主資料庫執行在不同的 IP 地址,每個從庫和主庫不在一個 IP 地址上;
12.3、slots
什麼是 slots,一個 Redis 叢集包含 16384 個插槽(hash slot),資料庫中的每個鍵都屬於這 16384 個插槽的其中一個;
叢集會使用 CRC16(key)% 16384 來計算鍵 key 屬於哪個槽,其中 CRC16(key)語句用於計算 key 的 CRC16 校驗和;
12.4、在叢集中錄入值
在 redis-cli 每次錄入、查詢鍵值,Redis 都會計算出該 key 應該送往哪個插槽(slots),如果不是客戶端對應的伺服器的插槽,Redis 會報錯,並告知應前往的 Redis 例項地址和埠;
redis-cli 客戶端提供了 -c 引數實現自動重定向;
如 redis-cli -c -p 6379 登入後,再錄入、查詢鍵值對可以自動重定向;
不在一個 slot 下的鍵值,是不能使用 mget,mset 等多鍵操作。可以通過 { } 來定義組的概念,從而使 key 中 { } 內相同的鍵值對放到一個 slot 中去;
12.5、查詢叢集中的值
CLUSTER GETKEYSINSLOTS < slot > < count > 返回 count 個 slot 槽中的鍵;
12.6、故障恢復
如果主節點下線,從節點會自動升為主節點,其中會有 15 秒超時;主節點恢復後,主節點會變為從機;
如果某一段插槽的主機從機全部掛掉了,而 cluster-require-full-coverage 為 yes,那麼整個叢集都會掛掉,如果該配置項為 no,那麼,該插槽資料全部不能使用,也無法儲存;
redis.conf 中的引數 cluster-require-full-coverage
12.7、叢集的優缺點
優點:
- 實現擴容、分攤壓力、無中心配置相對簡單
缺點:
- 多鍵操作是不被支援的;
- 多鍵的 Redis 事務是不背支援的,lua 指令碼不被支援;
十三、分散式鎖
13.1、問題描述
隨著業務發展的需要,遠單體單機部署的系統被演化成分散式叢集系統後,由於分散式系統多執行緒、多程式並且分部在不同的機器上,這將使原單機部署情況下併發控制所策略失效,單純的 Java API 並不能提供分散式鎖的能力。為了解決這個問題就需要一種跨 JVM 的互斥機制來控制共享資源的訪問,這就是分散式鎖要解決的問題;
分散式鎖的主流實現方案:
- 基於資料庫實現分散式鎖;
- 基於快取 Redis 等;
- 基於 Zookeeper
每一種分散式鎖解決方案都有各自的優缺點:
- 效能:Redis 最高;
- 可靠性:Zookeeper 最高;
這裡,我們就基於 Redis 實現分散式鎖;
13.2、解決方案
使用 Redis 實現分散式鎖
Redis 命令
set key value nx px 10000
EX second :設定鍵的過期時間為 second 秒,SET key value EX second 效果等同於 SETEX key second value;
PX millisecond:設定鍵的過期時間為 millisecond 毫秒。SET key value PX millisecond 效果等同於 PSETEX key millisecond value;
NX:只在鍵不存在是,才對鍵進行設定操作。SET key value NX 效果等同於 SETNX key value;
XX:只在鍵已經存在時,才對鍵進行設定操作;
- 多個客戶端同時獲得鎖(setnx);
- 獲取成功,執行業務邏輯{從 db 獲取資料,放入快取},執行完成釋放鎖(del);
- 其他客戶端等待重試;
13.3、優化之設定鎖的過期時間
設定過期時間有兩種方式
- 首先想到的是通過 expire 設定過期時間(缺乏原子性;如果在 setnx 和 expire 之間出現異常,鎖也無法釋放);
- 在 set 時指定過期時間(推薦);