1. 程式人生 > 其它 >Redis基礎篇

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。這就是快取穿透,根本原因是因為查詢不存在的資料。

危害
如果存在海量請求訪問不存在的資料,那麼就會有海量請求訪問資料庫,最終會導致資料壓力倍增或者系統崩潰。

為什麼發生快取穿透

  1. 惡意攻擊,故意營造大量不存在的資料請求伺服器,由於快取中並不存在這些資料,導致大量的請求均落在資料庫中,從而導致資料庫崩潰。
  2. 程式碼邏輯錯誤,開發時需注意!!!

解決方案

  1. 快取空資料
    將資料庫查詢為空的key也儲存在快取中(集合返回為{},而非null),設定一個較短的過期時間,讓其自動剔除。=》解決快取中存在大量的空物件,記憶體佔用問題
    如果在快取中空資料未過期時,插入一條記錄,則會導致快取層與儲存層資料不一致的現象 ==》此時可以刪除快取中的空物件
  2. 布隆過濾器(bitmap)
    在業務層與快取層之間加一層,布隆過濾器
    業務層 ==》 布隆過濾器 ==》快取 ==》資料庫
    當業務傳送請求時,首先在布隆過濾器中判斷key是否存在。若不存在,則說明資料庫中也不存在該資料,因此快取都不用查詢,直接返回null。若存在,則繼續執行後面的流程。

使用場景
第一種:適應於空資料的key數量有限,key重複請求概率較高的場景。
第二種:適用於空資料的key各不相同、key重複請求概率低的場景。

2. 快取雪崩

問題描述
快取的存在可以對資料庫進行保護,抵擋大量的查詢請求,從而避免資料庫壓力劇增或崩潰。如果快取因某種原因發生宕機,那麼原來大量可以被快取處理的請求就會全部被資料庫處理,此時會導致資料庫處理不了,導致系統崩潰。這就是快取雪崩。

解決方案

  1. 使用快取叢集,保證快取高可用
  2. 使用Hystrix
    防雪崩工具,通過服務熔斷、降級、限流三個手段來降低雪崩發生後的損失。
    Hystrix就是一個Java類庫,它採用命令模式,每一項服務處理請求都有各自的處理器。所有的請求都要經過各自的處理器。處理器會記錄當前服務的請求失敗率。一旦發現當前服務的請求失敗率達到預設的值,Hystrix將會拒絕隨後該服務的所有請求,直接返回一個預設的結果。這就是所謂的“熔斷”。當經過一段時間後,Hystrix會放行該服務的一部分請求,再次統計它的請求失敗率。如果此時請求失敗率符合預設值,則完全開啟限流開關;如果請求失敗率仍然很高,那麼繼續拒絕該服務的所有請求。這就是所謂的“限流”。而Hystrix向那些被拒絕的請求直接返回一個預設結果,被稱為“降級”。

3. 快取擊穿

問題描述
熱點資料集中失效,導致海量請求進入資料庫重建快取,這個過程可能資料庫處理不了那麼多請求,導致系統崩壞。
解決方案

  1. 互斥鎖
    當第一個資料庫查詢請求發起後,就將快取中該資料上鎖;此時到達快取的其他查詢請求將無法查詢該欄位,從而被阻塞等待;當第一個請求完成資料庫查詢,並將資料更新值快取後,釋放鎖;此時其他被阻塞的查詢請求將可以直接從快取中查到該資料。
    互斥鎖具有兩個問題
    第一個問題:降低系統的吞吐量
    第二個問題:互斥鎖可以避免某一個熱點資料失效導致資料庫崩潰的問題,而往往有一批熱點資料同時失效的場景,此時如何防止資料庫過載???===》將熱點資料的過期時間錯開(在基礎時間上+隨機數)
  2. 永不過期
    存在資料一致性問題,程式碼複雜度會增大