Redis學習筆記(理論+面試)

2020-10-12 11:00:20

Redis

1 簡介

  • 是開源免費、遵守BSD協定、高效能的NOSQLkey-value 資料庫。
  • 是簡單的、高效的、分散式的、基於記憶體單執行緒快取工具。
  • 簡單是Redis的突出特色,簡單可以保證核心功能的穩定和優異。
  • 支援網路、可以持久化、支援資料備份、叢集、訊息佇列等高可用功能。
  • 在企業開發中,Redis常被作為分散式快取
    • 熱點資料:常被查詢、不常被修改、刪除的資料
    • 臨時資料:不必持久化、有失效時間的資料(驗證碼等)

2 特點

2.1 關係型資料庫 VS 非關係型資料庫

(1)關係型

  • 優點
    • 容易理解,二維表比較符合邏輯世界的展示
    • 單一關係模型,可以進行結構化儲存,有完整的約束
    • 正是因為是結構化儲存的,所以可以很方便的使用結構化語言SQL,實現複雜的操作(表關聯JOIN等)
  • 缺點
    • SQL需要解析,效率低
    • 要保證資料一致性,就需要加鎖,並行效能低
    • 結構化儲存雖然方便,但是不靈活,擴充套件困難。

(2)非關係型

  • 優點
    • 並行效能高,可以處理大量的資料
    • 效率高、速度快
  • 缺點
    • 不能支援複雜的操作(比如JOIN),每次只能執行簡單的命令
    • 沒有完整的約束,事務處理能力差(比如Redis的事務就是偽事務)

2.2 Redis優點

  • 效能高速度快,簡單高效,可以處理萬級的QPS,使用叢集後可達百萬級,遠超MySQL(幾千算好的)。
  • 資料型別豐富(5個基本資料型別,還有其他的擴充套件型別,下面介紹)
  • 因為是單執行緒的,所以操作都是原子性的,天生執行緒安全。
  • 雖然是基於記憶體的,但是提供持久化功能。(兩種持久化方式,下面介紹)

2.3 Redis缺點

  • 基於記憶體,所以對記憶體的要求高

3 安裝

還是去搜詳細教學吧,這部分之後有時間再整理。

  • 使用docker安裝
docker pull redis
docker run -d --name redis -p 6379:6379 redis
docker exec -it redis redis-cli
  • 直接壓縮包安裝
1、確保Linux已經安裝gcc
    若沒安裝,則需要在root登入下、可連線外網狀態下執行下面命令
    yum -y install gcc automake autoconf libtool make
     
2、下載Redis
    wget http://download.redis.io/releases/redis-5.0.5.tar.gz
    
3、解壓
    tar -zxvf redis-5.0.5.tar.gz -C /opt (解壓到此目錄)
    
4、進入到解壓的目錄後編譯
    cd redis-5.0.5
    make MALLOC=libc
    
5、同樣在解壓後的目錄內進行安裝(安裝後會有一個bin目錄,目錄內就有redis-server檔案)
    make PREFIX=/usr/local/redis install
    
6、啟動redis服務(預設不是後臺程序,所以當前視窗就會被佔用,不能進行其他操作)
    cd /usr/local/redis/bin
    ./redis-server
   
7、啟動redis使用者端(可以在xshell重開一個對話,啟動使用者端)
    cd /usr/local/redis/bin
    ./redis-cli
        啟動使用者端後檢測redis服務是否開啟:
        redis 127.0.0.1:6379> PING(返回PONG則表示ping通即服務已開啟)

4 設定

1、進入解壓的Redis目錄
    cd /opt/redis-5.0.5

2、將redis.conf複製到安裝檔案的目錄下(與bin同級)
    cp redis.conf /usr/local/redis

3、注意:要想讓組態檔生效,需要在啟動redis時帶上組態檔!!!
    cd /usr/local/redis/bin
    ./bin/redis-server ./redis.conf

部分設定項

設定項說明
bind指定可以存取Redis的主機IP。預設只能本機存取Redis。如果要遠端存取Redis,則需要將 bind 其註釋掉,或者繫結地址==# bind 127.0.0.1==
daemonize指定是否為守護行程(後臺執行)。 預設為no,即不是後臺執行,一執行就佔用視窗,無法啟動其他程式。一般修改為 yes
port指定埠號。預設為 6379
requirepass設定密碼。預設沒有密碼
pidfile指定piffile檔案位置。當Redis以守護行程執行時,會產生pid檔案預設把pid寫入到==/var/run/redis-6379.pid==檔案中。
timeout使用者端閒置多少(秒)後關閉連線。 預設為0,表示關閉此功能
loglevel指定紀錄檔記錄級別。共四級:debug、verbose、notice、warning 。預設為notice
database設定資料庫數量。預設是16個。編號 0-15
save 指定多少時間、有多少次更新操作,就將資料持久化到dump.rdb檔案。這是RDB持久化方式的設定。redis預設設定有三個,滿足其中一個就會進行持久化: save 900 1 (900秒有1個更改) save 300 10 (300秒有10個更改) save 60 10000 (60秒有10000更改)
rdbcompression指定持久化是否壓縮資料。預設是yes。
dbfilename指定持久化後生成的檔案。預設是dump.rdb
dir指定上面檔案存放的路徑。預設是 ./ (當前目錄,與bin同級)
slaveof 當本機設定為slave服務時,設定相應master服務的IP地址和埠號 。redis啟動時,自動從master進行資料同步
masterauth 當master服務設定了密碼保護時,slave服務連線master的密碼
maxclients 設定同一時間最大使用者端連線數。 預設不限制。
maxmemory 指定最大記憶體限制。(建議不要超過1G,在256-512M) 預設不限制。 單位是M

5 啟動和關閉

5.1 啟動

(一)伺服器端啟動

[root@localhost ~]# cd /usr/local/redis
[root@localhost redis]# ./bin/redis-server ./redis.conf

(二)使用者端啟動

①本地存取使用者端

[root@localhost redis]# ./bin/redis-cli -a [密碼(123456)]
預設: -h 127.0.0.1 -p 6379

②遠端存取使用者端

[root@localhost redis]# ./bin/redis-cli -h [伺服器端IP] -p [埠號] -a [密碼]

5.2 關閉

  • 正常關閉(通過使用者端,關閉伺服器端)
127.0.0.1:6379> shutdown [NOSAVE|SAVE]
  • 非正常關閉(直接殺掉redis伺服器端程序)
檢視當前與redis有關的程序:				 ps –ef | grep –i redis  

得到redis-server的PID後,殺死相應的程序:	kill -9 PID

6 基本資料型別和常用命令

key的命名建議

​ 使用統一的命名模式,用冒號隔開。通過統一模式,來體現資料之間的關係

​ 如:user:123:name 不用我解釋,也能看懂大概是什麼

注意:

​ 1、key區分大小寫,命令不區分大小寫

​ 2、基本資料型別針對的是value,key的儲存都是通過字串形式儲存的

6.1 公用命令

​ 對所有型別資料都可以操作,或者說是資料庫層面的命令。

  • SELECT numb:切換資料庫(預設是0)

  • DEL key:刪除key(返回受影響的key數量)

  • DUMP key:序列化給定key(返回被序列化的值)

  • EXISTS key:檢查key是否存在(存在返回1,不存在返回0)

  • EXPIRE key seconds:為key設定過期時間(秒)

    • 沒有設定則預設為永久存在,除非delete掉
    • 驗證碼等有時效性的資料中常用
    • PEXPIRE key milliseconds:功能相同,毫秒為單位
  • TTL key:返回key剩餘時間(秒)

    • -1 表示永久有效
    • -2表示已無效 / 不存在
    • PTTL key:功能相同,毫秒為單位
  • PERSIST key:移除key的過期時間,key將持久儲存

  • KEYS pattern:查詢所有符號給定模式的key

    • pattern表示萬用字元:* 代表任意數量任意字元。? 代表任意一個字元
      • KEYS * : 查詢所有的key
      • KEYS *a: 查詢以a為結尾的key
      • KEYS ?a*:查詢以任意一個字元拼接上a作為開頭的key
      • KEYS ??: 查詢出兩個字元的key
  • RANDOMKEY:隨機返回一個key

  • RANAME key newkey:修改key的名稱

  • MOVE key numb:移動key至numb序號所指定資料庫中(第一個庫為0)

  • FLUSHDB:清除當前資料庫的所有key

  • FLUSHALL:清除整個Redis所有資料庫的所有key

  • DBSIZE:檢視當前資料庫的key的數量

  • TYPE key:返回key所儲存的值的型別

6.2 基本資料型別

​ Redis有5中基本資料型別:StringHashListSetZSet

6.2.1 String

​ String型別是二進位制安全的,redis的String可以包含任何資料,如字串、影象音訊視訊的序列化物件(二進位制),但是一個value最多能儲存512MB

二進位制安全是指,在傳輸資料的時候,能保證二進位制資料的不會被篡改、破譯;如果被攻擊,能夠及時檢測出來

注意:String可以覆蓋其他型別的資料,但是其他型別不能覆蓋String型別的資料

​ 如果一個key已經存在且是String型別,那麼就不可以有其他型別使用賦值語句覆蓋原來的key

​ 如果一個key是其他型別的,可以用String型別的賦值語句將其覆蓋成String型別的資料

使用場景

  • 通常用於儲存單個資料(字串或者數值)
  • 也可用於保密要求高的圖片檔案,內容作為字串(二進位制序列化)來儲存

命令

  • SET key value [EX seconds|PX milliseconds] [ NX |XX ]:設定key-value
    • EX表示過期時間設為幾秒,PX則是毫秒級
    • NX → key不存在時才設定成功
    • XX → key存在時才能設定成功(覆蓋)
    • 如果都不申明:不過期、key存不存在都可以設定
  • SETNX key value:設定key-value
    • 如果key已經存在,則不會執行任何操作(也就是只有不存在才設定
  • GET key:獲得key對應的value
  • GETRANGE key start end:獲取key中字串的子字串。
    • 下標從start開始,end結束(左閉右閉)(第一個字元的下標位0
  • SETRANGE key offset value:**置換(不是拼接)**指定的字串
    • 從 key 對應的 value 中 第offset位 開始置換
  • APPEND key value:字串拼接
    • 追加至末尾,如果不存在,為其賦值
  • MGET key1 [key2 …]:獲取多個key
    • 每個key之間用空格隔開
  • GETSET key value:設定key的值,並返回key的舊值
    • 當key不存在,返回nil
    • 當key存在時,返回舊值
  • STRLEN key:返回key所儲存的字串的長度
  • INCR key: key對應的value(整數)的數位 +1
    • 如果不存在key,則key中的值話先被初始化為 0 再加 1
  • INCRBY key 增量:key 對應的value(整數)增加增量的大小
    • 增量只能是整數
  • INCRBYFLOAT key 增量:key 對應的value增加增量的大小
    • value可以是整數,也可以是小數,增量同理
  • DECR key: key中的值自 -1
  • DECRBY key 減量:key 對應的value減少指定量的大小
  • DECRBYFLOAT key 減量:同理

6.2.2 Hash

​ 是一個String型別的 field-value 的對映表,hash特別適用於儲存物件(類似於Java中的bean,field為屬性,value為屬性值)。每個Hash可以儲存 (2^32 - 1) 鍵值對。可以看成存放key-value對的HashMap容器。相比於JSON,hash佔用很少的磁碟空間。

使用場景

​ 常用來儲存一個物件的資料。Hash是最接近關聯式資料庫的資料型別,可以將資料庫的一條記錄或程式中的一個物件轉換成HashMap存放在Redis中。

為什麼不用String儲存一個物件?/ 為什麼Hash更適合儲存多屬性的物件?

如果使用String來儲存,主要有下面兩種方式:

1、將物件的主鍵作為key,其他的所有屬性封裝成JSON串,儲存成value。

​ 這種方式有下面幾個缺點:JSON串修改單個屬性需要將整個值取出來;增加了序列化/反序列化的開銷

​ 2、物件的所有屬性都儲存成一個個key-value,在key中進行物件的唯一性標識不同物件的不同屬性

​ (物件型別:範例物件主鍵:屬性名) 這種方式的缺點很明顯:浪費記憶體

常用命令

  • HSET key field value:為設定的key設定 field 及其 value

  • HSETNX key field value:只有field不存在,才設定此欄位

  • HMSET key field value [field2 value2]:批次設定(空格隔開即可)

  • HGET key field:獲取Hash型別的key中對應的field的value

  • HMGET key field [field2]:批次獲取

  • HGETALL key:返回hash表中key的所有field和value

  • HKEYS key:獲取hash表中key的所有field

  • HLEN key:獲取hash表中key對應的field的數量

  • HDEL key field [field2]:刪除一個或多個hash表中key的field

    • 注意:刪除時,如果key中所有的field都被刪除,redis會自動清除這個key (redis不能存在沒有value的key,對於hash型別,其value就是field)
  • HESISTS key field:檢視field是否存在

  • HINCRBY key field 增量:指定field(整數)增加增量

  • HINCRBYFLOAT key field 增量:指定field(整數或小數)增加增量

    • 上面計數器的功能和效果,同String型別

6.2.3 List

​ 類似於Java中LlinkedList,是簡單的字串列表,按照插入的順序排序。列表中的每個字串稱為元素(element),一個列表最多可以儲存2^32 -1個元素。

使用場景

  • 儲存 資料量大、型別一致的基本資料型別的集合
    • 粉絲列表、留言評價、熱點新聞等
  • Redis的 lpush + brpop 命令組合即可實現阻塞佇列(不過一般不用redis作為訊息佇列)

常用命令

  • LPUSH key value1 [value2 …]:按順序從左側插入值

  • RPUSH key value1 [value2 …]:按順序從右側插入值

  • LPUSHX key value:從左側插入值,如果list不存在,則不操作

  • RPUSHX key value:從右側插入值,如果list不存在,則不操作

  • LLEN key:獲取列表長度(元素的個數)

  • LINDEX key index:獲取指定索引的元素

  • LRANGE key start stop:獲取列表指定範圍的元素

    • LRANGE key 0 -1:表示獲取list的全部元素
    • LRANGE key 1 1:表示獲取下標為1的一個元素
    • -1 表示最後一個元素 -2 表示倒數第二個元素 以此類推
  • LPOP key:從左側移除一個元素

  • RPOP key:從右側移除一個元素

    • 同樣的,list內一定要有元素,如果所有元素都移除,則redis也會自動刪除這個key
  • BLPOP key [key2 …] timeout:從左側移除並獲取key列表一個元素

    • 可以多個列表一起
    • 如果列表不存在,會阻塞到等待超時(秒)或發現可彈出元素為止
  • BRPOP key [key2 …] timeout:功能同上,只不過是從右側

  • LTRIM key start stop :對列表進行修剪,保留指定區間的元素

  • LSET key index value : 修改指定索引的值

  • LINSERT key before|after element value:在列表key的element元素前或則後插入元素value

6.2.4 Set

​ 是String型別的無序、不可重複的集合。(唯一性、無序性是與List型別的區別)

使用場景

​ 常用於兩個集合之間資料進行交、差、並運算

  • 方便實現如共同關注、共同喜好等功能
  • 利用唯一性,可以統計存取網站的所有獨立 IP等

常見命令

  • SADD key value1 [value2]:向key新增元素
  • SCARD key:返回集合元素數
  • SMEMBERS key:返回集合中所有元素
  • SISMEMBER key element:判斷element元素是否在集合 key 中
  • SRANDMEMBER key [count]:返回集合中一個或多個(count)亂數
  • SREM key element1 [element2]:移除集合中一個或多個元素
  • SPOP key:移除並返回集合中隨機一個元素
  • SMOVE source destination element:將element元素從source集合移動到destination集合
  • SINTER key [key …] : 返回多個集合求交集
  • SUNION key [key …] :返回多個集合求並集(不會出現重複資料)
  • SDIFF key [key …]: 返回多個集合的差集
  • SINTERSTORE destination key [key …]: 返回給定所有key集合的交集並儲存在destination中
  • SUNIONSTORE destination key [key …]: 返回給定所有key集合的並集並儲存在destination中
  • SDIFFSTORE destination key [key …]: 返回給定所有key集合的差集並儲存在destination中

6.2.5 ZSet

​ 不能重複、可以排序的String型別元素的集合。每個元素都會關聯一個double型別分數(score),Redis通過分數進行從小到大的排序。(元素唯一,但是分數可以重複)

使用場景

​ 排行榜

常用命令

  • ZADD key score1 memeber1 [score2 member2 ]:設定分數和元素

  • ZCARD key :獲取集合中的元素數量

  • ZCOUNT key min max: 計算指定區間分數的元素數量

  • ZRANK key member:返回指定元素的索引(下標值)

  • ZSCORE key element:獲得指定元素的分數

  • ZINCRBY key 增量 element:為指定元素的分數增加增量

  • ZRANGE key start stop:返回索引區間內的元素(分數從低到高排序)

  • ZREVRANGE key start stop :返回索引區間內的元素(分數從高到低排序)

  • ZRANGEBYSCORE key min max:返回分數區間內的元素

  • ZREM key member [member …]: 移除有序集合中的指定元素

  • ZREMRANGEBYRANK key start stop: 移除索引區間的所有元素

  • ZREMRANGEBYSCORE key min max: 移除分數區間的所有元素

7 持久化

​ Redis有兩種持久化的機制:RDBAOF。預設是RDB
​ 若兩種方式同時開啟,則重新啟動時以AOF的檔案恢復原始的Redis資料庫。(建議兩種都開啟)

7.1 RDB

​ 這是Redis預設的持久化機制,是 定時快照 的方式,儲存的是一種狀態。這種方式就是將記憶體中的資料以快照的方式寫入到二進位制檔案中,預設檔名為dump.rdb。(幾十G的資料 → 幾KB的快照檔案)

優點

  • 儲存速度、還原速度極快,適合大規模的資料恢復

  • 適用於災難備份

    缺點

  • 小記憶體的機器不符合使用(快照的時候,redis消耗增加了一倍的記憶體)

  • 有可能丟失最後一份修改

    設定

    設定是在組態檔中的save部分進行相關的設定(何時進行快照)

#redis預設設定有三個,滿足其中一個即可 
save 900 1 #900秒有1個更改
save 300 10 #300秒有10個更改
save 60 10000 #60秒有10000更改

# 如果資料很重要,又不能等到時間到讓redis自動持久化,則可以使用命令來儲存
save:只管儲存,其他不管,全部阻塞
bgsave:後臺非同步儲存,快照的同時還可以相應使用者端請求

# (可以通過lastsave檢視最後一次成功執行快照的時間)	

7.2 AOF

​ 由於RDB方式是在一定時間間隔做的,如果在間隔期間,修改了資料而redis斷電等意外導致伺服器端shutdown,就會丟失最後一次修改。如果要求不能丟失任何修改的話,就可以採用AOF的方式。

​ 使用AOF,Redis會將每一個收到的寫命令都通過write函數 追加到檔案 中,預設是appendonly.aof。當redis重新啟動時會通過重新執行檔案中命令,重建整個資料庫內容。(儲存的不是狀態,而是命令)

優點

  • 可以保證修改資料不丟失

    缺點

  • 持久化檔案會越來越大,且檔案中可能存在許多多餘的命令

  • 比如不斷對統一資料的修改命令都會加入到檔案中,所有命令都會執行,才能得到最後真正有用的一個資料。

  • aof檔案遠大於rdb檔案,恢復速度較慢

設定

# 首先要開啟AOF:
appendonly yes

# 可以指定檔案:
appendfilename "appendonly.aof"

# 有三種AOF方式:
appendfsync always   # 收到寫命令就立即寫入磁碟
appendfsync everysec # 每秒寫入磁碟一次(預設)
appendfsync no		 # 完全依賴OS,redis不主動進行AOF

重寫機制(REWRITE)

​ 由於AOF採取檔案追加的方式,檔案會越來越大,為避免檔案過大的情況,新增了重寫機制:當檔案大小超過一定閾值的時候,Redis會啟動AOF檔案的內容壓縮,只保留可以恢復資料的最小指令集

​ 重寫原理和快照相似,是 fork一個新程序將整個記憶體中的資料用命令的方式重寫了一個新的AOF檔案,並刪除舊的AOF檔案,注意沒有讀取舊的AOF檔案

​ 觸發情景:Redis會記錄上次重寫時AOF的大小,預設設定是當AOF檔案大小是上次rewrite後大小的一倍且檔案大於64M時觸發。

auto-aof-rewrite-percenttage 100  # 重寫資料所佔的百分比
auto-aof-rewrite-min-size 64mb	  # 重寫檔案最小大小
no-appendfsync-on-rewrite no      # 重寫時是否可以運用Appendfsync,預設為no,保證資料安全

8 事務

​ Redis事務可以一次執行多個命令,且有下面兩個保證:

  • Redis會將一個事務中的所有命令序列化,然後按順序執行(序列執行)
  • 執行中不會被其他命令插入,即不允許加塞行為

相關命令

  • MULTI:標記一個事務塊的開始
  • EXEC:執行上面事務塊內的命令
  • DISCARD:取消事務,放棄事務塊中的命令
  • WATCH key1 [ key2 ]:監視key,如果在事務執行前,這些key被其他命令改動,則打斷此事務。
    • 有點像樂觀鎖機制,watch相當於確定版本號,只要前後兩次所監視的key不同,則打斷
    • 一旦執行了EXEC或DISCARD或UNWATCH,之前所加的watch監控鎖都會取消掉
  • UNWATCH:取消對所有key的監視

正常事務命令塊例子

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> GET account:a
QUEUED
127.0.0.1:6379> GET account:b
QUEUED
127.0.0.1:6379> DECRBY account:a 50
QUEUED
127.0.0.1:6379> INCRBY account:b 50
QUEUED
127.0.0.1:6379> EXEC
1) "80"
2) "10"
3) (integer) 30
4) (integer) 60

事務的錯誤處理

  • 如果是邏輯上的錯誤(比如:對一個字串「hello」執行加一命令),那麼此事務塊中,只有這個報錯的命令不執行,其他的命令,無論在其前或後,只要是邏輯正確的,都會執行。

  • 如果是語法上的錯誤(即命令語法不正確,也稱報告錯誤),那麼此事務塊到最後執行exec時,會自動取消此事務。

    • 在事務塊中,提交了一個錯誤的命令,會立即列印出報告錯誤,但是如果無視,依舊可以繼續在事務塊中新增其他命令,但是到最後執行exec也會取消此事務,所以一旦出現報告錯誤,則可以使用discard中斷事務。

所以說:Redis的事務,並不是真正的事務,是偽事務

9 主從複製 & 哨兵模式 & 叢集

單臺Redis服務機有下面幾個缺點:

  • 容易發生單點故障,不具備容錯性
  • 一臺伺服器負責所有的請求負載,壓力較大
  • 且單臺伺服器容量有限,一臺Redis伺服器記憶體最好不要超過20G

如果使用多臺機器來儲存Redis資料,不就可以解決上面問題,保證系統的高可用性了嗎?而這種方式,就需要進行機器之間的資料同步問題,這就是接下來要討論的。

9.1 主從複製

​ 一個Redis服務可以有多個該服務的複製,這個Redis服務稱為Master,其他複製稱為Slave。因為大部分的應用都是「讀多寫少」,故:

  • Master負責處理寫請求可寫可讀
  • Slave負責處理讀請求唯讀不可寫

主機(Master)資料更新後,根據設定和策略,自動同步到備機(Slave)中

  • Slave啟動成功連線到Master後,會傳送一個sync命令,進行全量複製(Master所有的資料進行同步)
  • 之後的Master寫資料,slave進行的是增量複製(新增的部分進行同步)

​ 要點:

  • 一個Slave只能有一個Master
  • 一個Redis可以既是Master,也是Slave
  • 主從複製的時候不會阻塞Master,是非同步的

設定實現

配從不配主,有下面三種方式可以變成從機

  • 在伺服器端登入時,帶上 -slaveof-p 兩個屬性
注意是伺服器端登入帶上上面兩個屬性,使用者端登入只需帶上6380埠
(因為這裡假設6379為主機的埠,從機的埠為6380)

//伺服器端登入
./bin/redis-server ./redis6380-conf -p 6380 -slaveof MasterIP Master埠

//使用者端登入
./bin/redis-cli -p 6380 -a 123456
  • 在使用者端已經登入的情況下,使用slaveof命令,可以變成從機
127.0.0.1:6380> slaveof MasterIP Master埠

上面兩種在與Master斷開連線(即從機shutdown等)時,需要重新進行salve連線,如果想要永久連線,則需要下面這種方式,將相關屬性設定在組態檔中

  • 複製一份conf檔案作為從機的組態檔,從機設定開啟下面設定
# 在redis-6380.conf中設定
slaveof 127.0.0.1 6379
port 6380
# 還有其他類似 log檔名、pid檔名、dump檔名 都進行修改

# 注意:這裡修改是為了在一臺機器上模擬成多臺機器執行,實際上都是在不同機器上的redis中實現主從的,所以一般只需要設定slaveof即可。

# 伺服器端啟動
./bin/redis-server ./redis6380-conf

​ 若要將從機變為獨立的主機Master,則執行命令slaveof on one即可。

9.2 哨兵模式(Sentinel)

​ 主從複製弊端就是不具備高可用性,一旦Master掛了,Redis就不能提供寫操作處理,所以就出現了哨兵模式(若Master掛了自動從slave機中選出新的Master機)

​ 哨兵模式是Redis高可用的一種解決方案,當Master主機遇到異常中斷服務後,Redis能夠在後臺自動檢測到主機是否故障,如果故障了就根據投票數自動將一個Slave從機轉換成新Master機,並行送訊息通知管理員,無需人工操作,實現高可用性。

​ 每秒每個哨兵會向整個叢集:Master主庫、Slave從庫、其他Sentinel(哨兵)程序,傳送一次ping命令做一次心跳檢測。這個就是哨兵用來判斷節點是否正常的重要依據,涉及兩概念:

  • 主觀下線:一個哨兵節點判定主機掛掉了
  • 客觀下線:多個哨兵交換主觀判定結果,只有半數以上的哨兵都認為主機掛了,才會判定主機下線

​ 基本上哪個哨兵節點最先判斷出這個主機客觀下線,就會在各個哨兵節點中發起投票機制Raft演演算法(選舉演演算法),最終被投為新Master機的哨兵節點完成主從自動化切換。

注意

​ 如果原來掛了的Master在哨兵決定了新Master後,又恢復正常回來了,則這個舊Master機則會作為Slave機,掛在新的Master後

​ 和主從複製中手動修改新Master機不同,主從複製中手動修改,舊Master回來後,和其他的主從關係機無關係,是一個新的獨立的Master主機。

9.3 叢集(Redis Cluster)

​ 哨兵模式基本可以滿足一般生產的需求,具備高可用性。但是當資料量過大到一臺伺服器存放不下的情況時,主從模式或sentinel模式就不能滿足需求了,這個時候需要對儲存的資料進行分片,將資料儲存到多個Redis範例中。

​ 叢集可以說是哨兵和主從模式的結合體,叢集模式就是為了解決單機Redis容量有限的問題,將Redis的資料根據一定的規則分配到多臺機器

cluster叢集特點

  • 所有redis節點網路互聯,資料共用

  • 都是 一主N從,其中從庫不提供服務,僅作為備用

  • 叢集至少要三個主庫才能正常執行,即一個叢集最少需要有6個Redis節點(三組,每組一主一從)

  • 不支援同時處理多個key,redis把key均勻分佈在各個節點上,分開儲存

  • 使用者端可以連線任何一個主節點進行讀寫,支援線上增加、刪除節點

  • 叢集是去中心化的,每個Master節點都是平等的,都可以獲取和設定資料,而slave節點不提供服務,只是對應Master的一個備份。

  • 在其中一個Master伺服器上登入的使用者端,寫入的資料會根據一定的演演算法,將其存入到計算得到的 槽(slot) 中,由於槽平均分配給不同的Master,所以有可能在AMaster設定的資料,會儲存在其他的BMaster伺服器中,同時BSlave伺服器也會同步備份。(這也是去中心化的部分體現)

真叢集:每個Redis伺服器端都在不同的機器上(IP不同,port可能同)

假叢集:在同一個機器上,通過不同的埠,模擬多個Redis伺服器端(IP同,port不同)

開啟叢集(這部分留坑,之後深入使用後再填坑)

  • 需要將redis組態檔中的cluster-enable設定開啟,設定為yes
  • 在redis沒有設定密碼並且bind IP註釋掉的情況下,redis會處於安全保護模式,禁止公網存取redis cache,為了正常存取需要取消保護模式:protected-mode no

10 快取常見問題 & 解決

10.1 快取穿透

​ 當查詢一個資料庫不存在的資料,這個資料預設也不會寫入到快取Redis中,所以在高並行下,大量的使用者查詢此不存在的資料,會導致每次查詢都是直接存取資料庫,穿透快取中介軟體,導致資料庫壓力極大,這就是快取穿透。

解決

  • 當查詢一個不存在的資料時,在Redis快取中介軟體中寫於相對應key的空字串,表示空結果,這樣子之後繼續查詢此資料時,就會在快取中找到對應的key返回空字串,避免再次穿透到資料庫。
  • 進行引數過濾(對於大區間可以進行過濾,但是對於區間內的部分小無效的間隙,無法細緻地過濾)
    • 比如引數在1-100中有效,其中的3、8號無效。區間過濾只能過濾掉<1 、>100部分無效引數。
  • 布隆過濾器(判斷所查資料是否存在資料庫中)

10.2 快取擊穿

在高並行下,對一個特定的值進行查詢,但是這個時候快取正好過期了,導致大量請求直接落到資料庫上

解決

  • 重要的快取資料不設定過期,若資料需要更新,就再同步更新快取。
  • 或者限流,減低直接查詢資料庫的請求數量,查詢到後會快取在redis中,之後的請求就不會直接查詢資料庫了,因為在redis的時候就命中了。
  • 甚至直接使用鎖(簡單利用redis的setnx以及Java的sleep即可),鎖住一小段時間,保證只有一個請求達到資料庫拿到資料後快取到redis中。sleep時間過後,大量的請求再去redis中拿,就不會打到資料庫上了。(如果請求量很多,個人感覺比限流好)

區分:

​ 快取穿透:查詢的資料是資料庫不存在的資料

​ 快取擊穿:查詢的資料是資料庫存在的資料,只是在快取中剛好過期了

10.3 快取雪崩

​ 大量快取在同一時間內失效,導致大量的查詢直接存取資料庫,即發生大量的快取擊穿,造成快取雪崩。

解決

  • 讓快取的失效時間分佈均勻(加上個隨機值,不要同一時間失效),然後就是處理快取擊穿的操作了。

10.4 快取預熱

​ 在專案產品上線前,快取暫時為空,如果直接上線,同樣會導致大量的查詢直接存取資料庫。所以在上線之前,可以先進行主要的查詢,將熱點資料加入到快取中在上線,這就是快取預熱。

11 其他面試題

部分參考敖丙大佬的:https://blog.csdn.net/qq_35190492/article/details/103041932

1、快取型別

  • 本地快取:儲存在本地,只有本地可以存取,服務重新啟動就沒了。

  • 分散式快取:儲存在快取中介軟體,各服務都可以存取,可以持久化。

2、redis的淘汰策略

  • noeviction: 不刪除策略, 達到最大記憶體限制時, 直接返回錯誤資訊。
  • allkeys-lru: 所有key通用。 優先刪除最近最少使用的 key。
  • volatile-lru: 只限於設定了 expire 的部分。 優先刪除最近最少使用的 key。
  • allkeys-random: 所有key通用;。隨機刪除一部分 key。
  • volatile-random: 只限於設定了 expire 的部分。隨機刪除一部分 key。
  • volatile-ttl: 只限於設定了 expire 的部分。優先刪除剩餘時間短的key。

3、Redis為什麼是單執行緒的?單執行緒還那麼快?

​ Redis 核心就是 如果我的資料全都在記憶體裡,我單執行緒的去操作 就是效率最高的,為什麼呢,因為多執行緒的本質就是 CPU 模擬出來多個執行緒的情況,這種模擬出來的情況就有一個代價,就是上下文的切換,對於一個記憶體的系統來說,它沒有上下文的切換就是效率最高的。**redis 用 單個CPU 繫結一塊記憶體的資料,然後次讀寫的時候針對這塊記憶體的資料進行多,都是在一個CPU上完成的,所以它是單執行緒處理這個事。**在記憶體的情況下,這個方案就是最佳方案 —— 阿里 沈詢

4、Redis為什麼很快

  • 是基於記憶體的單執行緒
  • IO多路複用
  • 底層的設計結構

5、RDB的原理是什麼?

​ 首先redis通過fork子程序來進行RDB檔案的寫入操作,然後進行copy on write。子程序建立後,父子程序共用資料段,父程序繼續提供讀寫服務子程序負責寫RDB,隨者子程序的RDB寫入,父子程序共用的資料段會不斷減少,即寫髒的頁面資料會逐漸和子程序分離開來。(和拉鍊一樣,還沒拉開的就共用,拉開了的就分離了,如果父程序在已分離的區域有寫了資料,是不會寫入到本次RDB檔案中的)

6、除了5種基本資料型別,還有沒有其他的?

​ pipeline、pub/sub、HyperLogLog等

Redis的同步機制瞭解麼?

​ slave 剛啟動時,會傳送一個psync命令給master ,如果是這個slave第一次連線到master,他會觸發一個**全量複製:master就會啟動一個執行緒,生成RDB快照,還會把新的寫請求都快取在記憶體中,RDB檔案生成後,master會將這個RDB傳送給slave的,slave拿到之後做的第一件事情就是寫進原生的磁碟,然後載入進記憶體。而在RDB快照之後新增的資料,則是通過AOF進行增量複製**保證同步的。

7、多個系統同時操作(並行)Redis帶來的資料問題?

​ Redis是單執行緒的,並沒有鎖機制,各個使用者端之間的事務並不隔離,所以需要同步的話,要麼就利用外部的分散式鎖來保證,要麼就採取非鎖的方式來保證。

  • 加上分散式鎖,可以利用zookeeper實現分散式鎖。
  • 非鎖方式,那就是CAS。成本低、非阻塞、並行效能高。這個也是Redis提供的一個機制:watch。在redis事務中可以監聽某些key,如果key在這個事務未提交期間被其他使用者端修改過了,則本事務不會提交,會執行回滾。

8、如何保證快取與資料庫的雙寫一致性?

​ 如果是嚴格要求資料一致性的,那麼就是隻能將讀請求和寫請求序列化了,資料絕對一致性,但是並行性嚴重拉跨。

​ 如果不是嚴格要求資料一致性的,那麼最經典的快取+資料庫讀寫的模式,就是 Cache Aside Pattern。

  • 讀的時候,先讀快取,快取沒有的話,就讀資料庫,然後取出資料後放入快取,同時返回響應。

  • 更新的時候,先更新資料庫,然後再刪除快取/更新快取

    • 刪除快取還是更新快取,主要看是不是熱點資料,如果不是熱點資料,那麼直接刪除掉,等到之後有人存取再存入快取即可,不然直接更新快取的話,這個資料又是經常修改但是不被經常用到,也就白白更新了。
    • 注意這樣同樣也會出現資料不一致的情況,就比如更新資料庫後刪除快取失敗,那麼資料庫是新資料,快取是舊資料,不一致了。一般可以採用延時雙刪,也就是多次刪除確保刪除成功。

9、為什麼要用redis做快取

​ 首先,為什麼要快取?

  • 如果沒有快取,請求會頻繁地達到資料庫上,對資料庫的壓力較大。使用了快取,相當於在資料庫前加了一層屏障,不僅可以降低資料庫的壓力,還可以較快地返回擊中快取的資料。

​ 其次,為什麼要用redis做快取?

  • redis是分散式快取,可以獨立部署,可以持久化。
  • redis是基於記憶體、單執行緒、多路複用的快取,速度快,效率高。

10、redis實現分散式鎖

​ 先拿setnx來爭搶鎖,搶到之後,再用expire給鎖加一個過期時間防止鎖忘記了釋放。最後如果是手動釋放鎖的話,使用delete

​ 而為了避免出現setnx之後執行expire之前程序意外crash或者要重新啟動維護了,導致鎖釋放不遼,可以直接在setnx命名後面帶上過期時間(即將setnx和expire結合成一條命令

11、如果redis正在給線上的業務提供服務,那使用keys指令模糊查詢會有什麼問題?

​ Redis的單執行緒的。keys指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。

​ 這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在使用者端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。不過在scan過程中,鍵可能會被修改, 所以增量式迭代命令只能對被返回的元素提供有限的保證 。

12、使用過Redis做非同步佇列麼,你是怎麼用的?

一般使用list結構作為佇列,rpush生產訊息,lpop消費訊息(或者相反也可以)。當lpop沒有訊息的時候,要適當sleep一會再重試。

如果不用sleep,可以使用的是blpop。在沒有訊息的時候,它會阻塞住直到訊息到來。