一、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 時指定過期時間(推薦);