Redis基礎篇
一、安裝Redis
首先,通過DockerHub搜尋redis,找到相應的版本號。並通過以下命令進行安裝啟動。
[root@shang ~]# systemctl status docker # 檢視docker是否啟動 [root@shang ~]# systemctl start docker # 啟動docker [root@shang ~]# docker pull redis:6.2.4 # 安裝redis 6.2.4版本的映象 [root@shang ~]# docker images # 檢視映象 REPOSITORY TAG IMAGE ID CREATED SIZE redis 6.2.4 fad0ee7e917a 9 days ago 105MB [root@shang ~]# mkdir -p /opt/docker/redis/data/ # 建立資料目錄 [root@shang ~]# chmod 777 /opt/docker/redis/data # 修改資料夾許可權 # 複製一個redis.conf到 /opt/docker/redis/目錄下,修改配置,使其它客戶端可以訪問 # 通過外部配置檔案啟動redis [root@shang ~]# docker run -d --name redis6379 -p 6379:6379 -v /opt/docker/redis/data:/data -v /opt/docker/redis/redis.conf:/etc/redis/redis.conf redis:6.2.4 redis-server /etc/redis/redis.conf --appendonly yes e765c1e08c8926ff8de647bf30f2fe5eed2f8a1f1d35a9f4067279857f3b0fb7 [root@shang ~]# docker ps # 檢視容器是否啟動成功 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e765c1e08c89 redis:6.2.4 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp redis6379 # 啟動redis6379的客戶端進行連線 [root@shang ~]# docker exec -it redis6379 redis-cli 127.0.0.1:6379>
/opt/docker/redis/data:/data 將redis容器資料目錄掛載到/opt/docker/redis/data
/opt/docker/redis/redis.conf:/etc/redis/redis.conf 將redis容器配置檔案掛載到/opt/docker/redis/redis.conf
redis-server /etc/redis/redis.conf 通過配置檔案/etc/redis/redis.conf啟動redis-server,因上面配置檔案被掛載,所以最終使用的使我們主機目錄的配置檔案 /opt/docker/redis/redis.conf
--appendonly yes 開啟資料持久化
二、基本資料型別
1. redis-key
127.0.0.1:6379> set name shang OK 127.0.0.1:6379> set age 18 OK 127.0.0.1:6379> type name # 檢視key的型別 string 127.0.0.1:6379> keys * # 檢視所有的key 1) "age" 2) "name" 127.0.0.1:6379> exists age s # 返回存在key的數量 (integer) 1 127.0.0.1:6379> exists age name # 返回存在key的數量 (integer) 2 127.0.0.1:6379> exists a # 返回存在key的數量 (integer) 0 127.0.0.1:6379> move name 1 # 從當前庫移除key (integer) 1 127.0.0.1:6379> keys * 1) "age" 127.0.0.1:6379> expire age 10 # 給key設定過期時間 (integer) 1 127.0.0.1:6379> ttl age # 檢視key的剩餘時間 (integer) 6 127.0.0.1:6379> ttl age (integer) 4 127.0.0.1:6379> ttl age # key失效,返回-2 (integer) -2 127.0.0.1:6379> get age (nil)
2. String
127.0.0.1:6379> set name zhang # 設定 key
OK
127.0.0.1:6379> get name # 獲取key值
"zhang"
127.0.0.1:6379> append name "san" # 拼接字串,返回字串長度
(integer) 8
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> strlen name # 獲取字串長度
(integer) 8
127.0.0.1:6379> set view 0
OK
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> incr view # 自增1
(integer) 1
127.0.0.1:6379> incr view # 自增1
(integer) 2
127.0.0.1:6379> get view
"2"
127.0.0.1:6379> decr view # 自減1
(integer) 1
127.0.0.1:6379> decr view # 自減1
(integer) 0
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> incrby view 5 # 增量,步長5
(integer) 5
127.0.0.1:6379> incrby view 10 # 增量,步長10
(integer) 15
127.0.0.1:6379> get view
"15"
127.0.0.1:6379> decrby view 15 # 減量,步長15
(integer) 0
127.0.0.1:6379> get view
"0"
127.0.0.1:6379> getrange name 0 -1 # 擷取字串,獲取全部字串
"zhangsan"
127.0.0.1:6379> getrange name 0 4 # 擷取字串
"zhang"
127.0.0.1:6379> setrange name 6 ui # 字串替換
(integer) 8
127.0.0.1:6379> get name
"zhangsui"
127.0.0.1:6379> setex food 5 fish # 設定key的同時設定過期時間
OK
127.0.0.1:6379> ttl food
(integer) -2
127.0.0.1:6379> setnx name lisi # 不存在時才設定key,存在時則不設定
(integer) 0
127.0.0.1:6379> get name
"zhangsui"
127.0.0.1:6379> setnx age 18 # 不存在時才設定key
(integer) 1
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> mset k1 v1 k2 v2 # 批量插入key
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> mget k1 k3 k2 # 批量獲取key
1) "v1"
2) (nil)
3) "v2"
127.0.0.1:6379> getset k1 vv # 存在,則返回舊值,更新新值
"v1"
127.0.0.1:6379> get k1
"vv"
127.0.0.1:6379> getset k3 v3 # 不存在,則返回(nil),設定新值
(nil)
127.0.0.1:6379> get k3
"v3"
3. List
lpush rpush # 頭部或尾部插入元素
lpop rpop # 頭部或尾部彈出元素
lrange # 檢視列表元素範圍
llen # 檢視元素列表個數
ltrim # 通過下標擷取指定的長度,這個list被改變了,只剩下被擷取的元素
rpoplpush # 移除列表的最後一個元素,將他移動到新的列表中
lset # 將列表中指定下標的值替換為另外一個值,更新操作
linsert # 將某個具體的value 插入到list指定value的前邊或者後邊
lindex # 通過下標獲取列表中的值
127.0.0.1:6379> lpush k1 one two # 頭部插入兩個元素
(integer) 2
127.0.0.1:6379> lpush k1 three # 頭部插入一個元素
(integer) 3
127.0.0.1:6379> lrange k1 0 -1 # 0 -1 檢視全部元素
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange k1 1 1 # 檢視從索引為1的位置開始的一個元素
1) "two"
127.0.0.1:6379> rpush k1 four # 尾部插入一個元素
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "one"
4) "four"
127.0.0.1:6379> lpop k1 2 # 頭部彈出兩個元素
1) "three"
2) "two"
127.0.0.1:6379> lrange k1 0 -1
1) "one"
2) "four"
127.0.0.1:6379> rpop k1 1
1) "four"
127.0.0.1:6379> lrange k1 0 -1
1) "one"
127.0.0.1:6379> llen k1
(integer) 1
127.0.0.1:6379> lpush k1 one one two two three
(integer) 6
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
5) "one"
6) "one"
127.0.0.1:6379> lrem k1 2 one
(integer) 2
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
127.0.0.1:6379> ltrim k1 0 2
OK
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
127.0.0.1:6379> lpush k2 one
(integer) 1
127.0.0.1:6379> rpoplpush k1 k2
"two"
127.0.0.1:6379> lrange k2 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lset k3 1 hello
(error) ERR no such key
127.0.0.1:6379> lset k1 1 one
OK
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "one"
127.0.0.1:6379> lset k1 3 three
(error) ERR index out of range
127.0.0.1:6379> linsert k1 before one two
(integer) 3
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> linsert k1 after two two
(integer) 4
127.0.0.1:6379> lrange k1 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
4. Set
sadd # 新增元素
smembers # 獲取set集合中的所有元素
sismember # 判斷一個值是否存在set中
scard # 獲取set集合中元素的個數
srem # 移除set集合中指定元素
srandmember # 隨機抽取元素
spop # 隨機刪除set集合中的元素
smove #將一個指定的set集合(存在)元素移動到另一個set集合(存在)中
sdiff # 求集合差集
sinter # 求集合交集
sunion # 求集合並集
127.0.0.1:6379> sadd k1 v1 v2
(integer) 2
127.0.0.1:6379> smembers k1
1) "v2"
2) "v1"
127.0.0.1:6379> sadd k1 v3
(integer) 1
127.0.0.1:6379> smembers k1
1) "v2"
2) "v1"
3) "v3"
127.0.0.1:6379> sismember k1 v4
(integer) 0
127.0.0.1:6379> sismember k1 v1
(integer) 1
127.0.0.1:6379> scard k1
(integer) 3
127.0.0.1:6379> srem k1 v2
(integer) 1
127.0.0.1:6379> smembers k1
1) "v1"
2) "v3"
127.0.0.1:6379> sadd k1 v1 v2 v4 v5 v6
(integer) 4
127.0.0.1:6379> smembers k1
1) "v1"
2) "v4"
3) "v3"
4) "v6"
5) "v2"
6) "v5"
127.0.0.1:6379> srandmember k1 2
1) "v1"
2) "v5"
127.0.0.1:6379> srandmember k1
"v3"
127.0.0.1:6379> spop k1
"v1"
127.0.0.1:6379> sadd k2 one two four
(integer) 3
127.0.0.1:6379> sadd k3 one three four five
(integer) 4
127.0.0.1:6379> sdiff k2 k3
1) "two"
127.0.0.1:6379> sinter k2 k3
1) "four"
2) "one"
127.0.0.1:6379> sunion k2 k3
1) "one"
2) "four"
3) "five"
4) "two"
5) "three"
5. Hash
6. Zset
三、主從複製
在Slave啟動並連線到Master之後,它將主動傳送一個SYNC命令。此後Master將啟動後臺存檔程序,同時收集所有接收到的用於修改資料集的命令,在後臺程序執行完畢後,Master將傳送整個資料庫檔案到Slave,以完成一次完全同步。而Slave伺服器在接收到資料庫檔案資料之後將其存檔並載入到記憶體中。此後,Master繼續將所有已經收集到的修改命令,和新的修改命令依次傳送給Slaves,Slave將在本次執行這些資料修改命令,從而達到最終的資料同步。如果Master和Slave之間的連結出現斷連現象,Slave可以自動重連Master,但是在連線成功之後,一次完全同步將被自動執行。
哨兵模式
四、持久化機制
五、三大問題
正常順序:業務層 ==》快取 ==》資料庫
1. 快取穿透
問題描述
核心:查詢快取和資料庫,二者皆不存在的資料。
業務邏輯發鬆查詢請求,首先從快取中查詢,由於快取不存在,然後再前往資料庫中查詢。發現數據庫中也沒有該條記錄,然後返回 null。這就是快取穿透,根本原因是因為查詢不存在的資料。
危害
如果存在海量請求訪問不存在的資料,那麼就會有海量請求訪問資料庫,最終會導致資料壓力倍增或者系統崩潰。
為什麼發生快取穿透
- 惡意攻擊,故意營造大量不存在的資料請求伺服器,由於快取中並不存在這些資料,導致大量的請求均落在資料庫中,從而導致資料庫崩潰。
- 程式碼邏輯錯誤,開發時需注意!!!
解決方案
- 快取空資料
將資料庫查詢為空的key也儲存在快取中(集合返回為{},而非null),設定一個較短的過期時間,讓其自動剔除。=》解決快取中存在大量的空物件,記憶體佔用問題
如果在快取中空資料未過期時,插入一條記錄,則會導致快取層與儲存層資料不一致的現象 ==》此時可以刪除快取中的空物件 - 布隆過濾器(bitmap)
在業務層與快取層之間加一層,布隆過濾器
業務層 ==》 布隆過濾器 ==》快取 ==》資料庫
當業務傳送請求時,首先在布隆過濾器中判斷key是否存在。若不存在,則說明資料庫中也不存在該資料,因此快取都不用查詢,直接返回null。若存在,則繼續執行後面的流程。
使用場景
第一種:適應於空資料的key數量有限,key重複請求概率較高的場景。
第二種:適用於空資料的key各不相同、key重複請求概率低的場景。
2. 快取雪崩
問題描述
快取的存在可以對資料庫進行保護,抵擋大量的查詢請求,從而避免資料庫壓力劇增或崩潰。如果快取因某種原因發生宕機,那麼原來大量可以被快取處理的請求就會全部被資料庫處理,此時會導致資料庫處理不了,導致系統崩潰。這就是快取雪崩。
解決方案
- 使用快取叢集,保證快取高可用
- 使用Hystrix
防雪崩工具,通過服務熔斷、降級、限流三個手段來降低雪崩發生後的損失。
Hystrix就是一個Java類庫,它採用命令模式,每一項服務處理請求都有各自的處理器。所有的請求都要經過各自的處理器。處理器會記錄當前服務的請求失敗率。一旦發現當前服務的請求失敗率達到預設的值,Hystrix將會拒絕隨後該服務的所有請求,直接返回一個預設的結果。這就是所謂的“熔斷”。當經過一段時間後,Hystrix會放行該服務的一部分請求,再次統計它的請求失敗率。如果此時請求失敗率符合預設值,則完全開啟限流開關;如果請求失敗率仍然很高,那麼繼續拒絕該服務的所有請求。這就是所謂的“限流”。而Hystrix向那些被拒絕的請求直接返回一個預設結果,被稱為“降級”。
3. 快取擊穿
問題描述
熱點資料集中失效,導致海量請求進入資料庫重建快取,這個過程可能資料庫處理不了那麼多請求,導致系統崩壞。
解決方案
- 互斥鎖
當第一個資料庫查詢請求發起後,就將快取中該資料上鎖;此時到達快取的其他查詢請求將無法查詢該欄位,從而被阻塞等待;當第一個請求完成資料庫查詢,並將資料更新值快取後,釋放鎖;此時其他被阻塞的查詢請求將可以直接從快取中查到該資料。
互斥鎖具有兩個問題
第一個問題:降低系統的吞吐量
第二個問題:互斥鎖可以避免某一個熱點資料失效導致資料庫崩潰的問題,而往往有一批熱點資料同時失效的場景,此時如何防止資料庫過載???===》將熱點資料的過期時間錯開(在基礎時間上+隨機數) - 永不過期
存在資料一致性問題,程式碼複雜度會增大