天天看點

redis單機&叢集原理詳解

redis基礎知識

  • 什麼是redis

    是一種基于記憶體的key-value資料庫,用作資料庫和緩存。基礎的資料類型隻有5種;string,list,hash,set和SortedSet(有序集合)。額外還支援HypeLogLog(統計redis的基數,使用很小的記憶體就可以實作很大資料的統計,會有誤差。實際是一種算法的支援),Geo(Redis的地理資料的存儲,可以兩地的經緯度計算距離和搜尋一點範圍的其他地名,也是一種算法支援)等。使用場景,緩存,抽獎,釋出式session,排行榜,計數,附近地理等。

  • redis的安裝(線上安裝)
  1. redis是c語言編寫的是以需要c環境
  1. 線上下載下傳redis的tar包(進入你要儲存我檔案的目錄)
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
           
  1. 解壓(解壓到你需要的目錄,這裡是目前目前)
tar -zxvf redis-5.0.3.tar.gz
           
  1. 編譯安裝(進入解壓後的目前,PREFIX是你要安裝的目前錄自定義)
make install PREFIX=/usr/softOPT/redis
           
  1. 啟動redis 使用bin目錄下的 (在解壓目錄下)
bin/redis-server ../redis.conf
           
  • redis的資料結構

    redis中的字元串是二進制安全的。使用的是c語言的char類似但是這個char會吧 ‘\0’認為是結束符,是以不能存儲比如圖形嗎,視訊等。但是redis使用的是sds(simple dynamic string)。不會将 ‘\0’讀作結束符号。

雪崩,擊穿和穿透

  • 雪崩

    在系統流量高峰期的時候大面積的key同時失效,使得大量的流量直接打在DB上,瞬間将DB打崩。此時問題比較嚴重,重新開機資料庫也會被瞬間打崩。此時最主要的是卷鋪蓋了哈哈哈。

    解決方式也比較簡單:所有的key失效時間随機

  • 擊穿

    與雪崩有一點像,就是有一個熱點key正在被大量的請求,此時哈,這個熱點key突然地失效掉。哦豁,大量的流量又直接打在DB上,(額,為什麼我要說又?)。

    解決方法也比較粗暴:設定熱點key用不過期,或者加互斥鎖

  • 穿透

    這個穿透有點惡意通路的味道,就是會一種請求一個不可能存在資料,每次都可以透過redis直接打在DB上。

    解決方法:

    1. 第一次通路不存在後也将這個key設定進redis中value為null,但是過期時間短一些比如30秒,不然正常的請求也過不來了哈哈哈。

    2. 背景直接攔截不合理的key。

    3. 使用布隆過濾器(BoolmFilter)【使用一種算法和資料結構快速的判斷這個key是否存在】

redis的持久化

持久化就是将記憶體中的資料寫入磁盤,如果有aof方式,優先使用aof恢複。
           
  1. RDB方式(适合做冷備)

    快照方式,将某一運作時刻redis的記憶體資料持久化到磁盤,具有周期性。生成快照的方式有兩種:save和gbsave。預設是bgsave。

save:是同步執行,會阻塞其他用戶端指令,但不會消耗記憶體資源。bgsave是異步執行,會生成fork子程序器執行持久化,不會阻塞用戶端其他指令,但是會消耗記憶體資源。

根據配置redis.conf的政策進行持久化;如

redis單機&叢集原理詳解
優點:
	1. 檔案比較小
	2. 對redis的性能影響相對小
	3. 恢複快
缺點:
	1. 資料不全面
	2. 如果生成的快照比較大,會使得用戶端稍微有卡頓。因為有fork子程序去做持久化
           
  1. AOF方式(适合熱備)

    即append-only的縮寫,使用日志追加的形式将redis用戶端的指令寫入到aof檔案中。

有三種追加方式

appendfsync always 每次有新指令就追加到aof中,最慢但是最安全。

appendfsync everysec 将在一秒内執行的指令追加到aof檔案中。和RDB速度差不多,但是在發生故障後隻丢失一秒的資料(預設)

appendfsync no 從不進行fync,交給作業系統,更快但是也更不安全

預設是第二種方式:

redis單機&叢集原理詳解
優點:
  	1. 資料比較全,最多也就丢失1秒的資料
  	2. 追加的方式去寫,省去磁盤尋址,寫入性能高
  	3. 使用非常可讀的形式追加資料,比較适合性資料誤删的緊急恢複了
  缺點:
  	1. 檔案大
  	2. 開啟aof方式後,redis的QPS要比隻RBD支援時要低,因為每秒都要去異步重新整理一次日志
           

redis主從複制(master-slave)【垂直擴充】

由于redis實作的pub/sub模式,是以任何一台從伺服器可以訂閱任何主伺服器的頻道,實作同步複制。而且從伺服器可以訂閱任何從伺服器的主伺服器是以redis可以實作單層樹結構的複制。

原理

  • 全量複制(第一次建立socket連接配接的時候進行)
    1. slave跟master建立socket長連接配接,并發送psync【見下面的注意點】指令同步資料
      2. master收到psync指令後,執行bgsave指令,生産最新的RBD檔案,并發送給slave。随之發送replid_id和offset
      3. master将生成RDB檔案期間的指令寫入複制積壓緩沖區(replication backlog buffer)并發送給slave
      4. slave将master發送過來的buffer和RDB生成新的RDB檔案并加載到記憶體中
      5. slave初始化完成
               

其中 replid是這個 master 的複制 ID,對于master來說和4.0以前的run_id是一樣的意義。但對于slave來說,它儲存的複制 ID(即 replid) 表示目前正在同步的 master 的複制 ID 。master_replid 2 則表示前一個 master 的複制 ID(如果它之前沒複制過其他的 master,那這個字段沒用),這個在主從角色發生改變的時候會用到 。而 offset 則是 master 目前的複制偏移量,slave 會将這個值作為自己的初始化偏移量

示意圖:

redis單機&叢集原理詳解

部分複制

部分複制是4.0版本後出現的為了優化slave由于網絡等原因斷開重新連接配接之後減輕master複制壓力的方案。

在slave重新連接配接到master之後,将offset和run_id也發送給master,master對比。如果run_id一緻說明斷開重新連接配接的slaver,如果offset一緻,則進行進行部分複制。隻要其中兩個一個不一緻則進行全量複制。

1. slave跟master建立socket長連接配接,并發送psync【見下面的注意點】指令同步資料,并發送replid_id和offset
  2. master對比replid_id和offset
  3. 一緻則将rb_buffer中的指令發送給slave。不一緻将進行一次全量複制
           

示意圖:參考上圖

  • 增量複制(socket穩定後,主伺服器将寫的指令持續發送到從伺服器)

    增量複制是slave初始化後開始正常工作時主伺服器發生的寫操作同步到從伺服器的過程,增量複制的過程主要是主伺服器每執行一個寫指令就會向從伺服器發送相同的寫指令,從伺服器接收并執行收到的寫指令。

搭建主從

master的配置檔案不需要修改

修改slave的配置檔案(4.0以前好像是slaveof)

replicaof 192.168.160.146 6379 #master的ip port

replica-serve-stale-data yes #yes跟master斷開後繼續響應,no是除去指定指令外傳回一個錯誤資訊

replica-read-only yes #設定slave是否隻讀

直接啟動slave和master就可以了

注意點

2.8 版本之前 Redis 複制采用 sync 指令,無論是第一次主從複制還是斷線重連後再進行複制都采用全量同步,成本高

2.8 ~ 4.0 之間複制采用 psync 指令,這一特性主要添加了 Redis 在斷線重連時候可通過 offset 資訊使用部分同步

4.0 版本之後也采用 psync,相比于 2.8 版本的 psync 優化了增量複制,這裡我們稱為 psync2,2.8 版本的 psync 可以稱為 psync1

redis多master叢集【水準擴充】

redis叢集是由多個master-slave組成的,具有高可用,複制和分片(資料沒有采用一緻的hash,而是使用hash slot)等特性。

示意圖:

redis單機&叢集原理詳解

原理:

redisCluster可以挂載n多個master節點,并且每個master節點上可以挂載n多個slave節點的叢集模式。
           
  1. redis執行個體的分派方法

    redis沒有采用一緻性的hash,而是使用了hash slot的方式。hash slot是有限的在0-16383之間。在一個redis叢集中,所有的slot都會完全分派給所有的master叢集。比如有3個mastermaster,則每個master節點管理5461個slot。【也可以使用 cluster addslots 0 将0slot分派給目前的執行個體】。

  2. redis的key映射方式

    當一個要被存儲在redis叢集的時候,先将其發送給任意的master節點,然後使用 HASH_SLOT = CRC16(key) mod 16384算法得到這個slot如果這個master正好在維護這個slot則就地處理這個key,否則傳回一個重定向的指令讓去正确的執行個體去操作。

    一般情況下,在本地會緩存一個slot的維護清單,直接在本地進行計算然後直接去正确的redis執行個體操
    作。如果還是錯誤會重新計算并且重新整理本地緩存。
               
  3. 資料分片

    redis會将所有的資料分派不同的redis上,即隻保留一份資料。

  4. 高可用

    使用主從複制模型保證master節點不可用的時候,可以快速将從節點更新為主節點,保證系統的高可用。換句話說,redisCluster內建了主從複制和哨兵模式

Slot機制還有一個很明顯的優勢,就是在處理并發的場景,因為它将資料集進行了分割,實際上減小了鎖的粒度,進而擴大了并發度。Java中的ConcurrentHashMap容器是應用這種機制來實作并發的典型的例子

搭建叢集(三主三從)

  1. 複制bin/redis-server 和redis.conf 六份。兩兩放在三個檔案夾中
    redis單機&叢集原理詳解
  2. 修改主節點的redis.conf

修改下面那個配置内容

cluster-enabled yes

cluster-node-timeout 15000 #叢集逾時時間

cluster-config-file nodes-port.conf 【port是這個執行個體對應的端口号。如果配置檔案中沒有這個選項可以不加,可以自己生成好像】

  1. 修改從節點的配置(和主從複制一樣,指定是哪一個的slave即可)
  2. 啟動所有的redis執行個體
  3. 建立cluster
使用任意執行個體的bin/redis-cli指令執行: redis-cli --cluster create 192.168.43.129:6380 192.168.43.129:6381 192.168.43.129:6382 192.168.43.129:63800 192.168.43.129:63801 192.168.43.129:6382 --cluster-replicas 1

具體的redis-cli的指令參考文章redis-cli指令參考

6. 驗證cluster

進入任意一個redis執行個體

cluster info(檢視叢集資訊)

cluster nodes(檢視節點清單)

(3)進行資料操作驗證

redisCluseter01:0>set name 小J

OK

redisCluseter02:0>get name

小J

(4)關閉叢集則需要逐個進行關閉,使用指令: /usr/local/redis-5.0.2/bin/redis-cli -c -h 192.168.43.129 -p 638* shutdown

  1. 坑點
在建立叢集的時候不要使用127.0.0.1這個ip否則在外部通路不了

哨兵模式【保證redis主從模式的高可用】

  1. 什麼是redis-sentinel

    Redis-Sentinel是官方推薦的高可用解決方案,當redis在做master-slave的高可用方案時,假如master當機了,redis本身(以及其很多用戶端)都沒有實作自動進行主備切換,而redis-sentinel本身也是獨立運作的程序,可以部署在其他與redis叢集可通訊的機器中監控redis叢集。

很顯然,單個哨兵會存在自己挂掉而無法監控整個叢集的問題,是以哨兵也是支援叢集的,我們通常用三台哨兵機器來監控一組redis叢集。
  1. sentinel的元件功能

    叢集監控:負責監控 Redis master 和 slave 程序是否正常工作。

    消息通知:如果某個 Redis 執行個體有故障,那麼哨兵負責發送消息作為報警通知給管理者。

    故障轉移:如果 master node 挂掉了,會自動轉移到 slave node 上。

    配置中心:如果故障轉移發生了,通知 client 用戶端新的 master 位址。

  2. 搭建redis-sentinel(經典的三個哨兵)
  • 依然拷貝三份bin 和redis.conf
  • 修改配置,三份一樣

#sentinel監控的IP 端口号 sentinel通過投票後認為mater當機的數量,此處為至少2個

sentinel monitor mymaster 192.168.132.128 7000 2

#20秒ping不通主節點的資訊,主觀認為master當機

sentinel down-after-milliseconds mymaster 30000

#故障轉移後重新主從複制,1表示串行,>1并行

sentinel parallel-syncs mymaster 1

#故障轉移開始,60秒内沒有完成,則認為轉移失敗

sentinel failover-timeout mymaster 600000

  • 分别啟動三個redis-sentinel使用下面的指令
redis-sentinel redis.conf

springboot2.X整合redis【單機版和Cluster版】

在springboot2.x中內建了jedis,不需要大量的配置。對jedis進行了大量的封裝形成RedisTemplate類,使我們更加分别的操作redis。

加入pom依賴

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
           

單機版

  1. 配置檔案
spring.redis.host=192.168.43.129
spring.redis.port=6379
spring.redis.client-name=testRedis
spring.redis.password=''"
spring.redis.timeout=5000
spring.redis.jedis.pool.max-wait=0
spring.redis.jedis.pool.max-active=8
spring.redis.database=1
           
  1. 使用的時候直接注入就可以
@Autowired
    private RedisTemplate<String, Object> redisTemplate;
           

Cluster版

  1. 配置檔案(有三種方式,推薦使用lettuce連接配接池的方式)
spring:
  redis:
    timeout: 6000ms
    password:
    cluster:
      max-redirects: 3  # 擷取失敗 最大重定向次數
      nodes: 192.168.43.129:6380,192.168.43.129:6381, 192.168.43.129:6382
    lettuce:
      pool:
        max-active: 1000  #連接配接池最大連接配接數(使用負值表示沒有限制)
        max-idle: 10 # 連接配接池中的最大空閑連接配接
        min-idle: 5 # 連接配接池中的最小空閑連接配接
        max-wait: -1 # 連接配接池最大阻塞等待時間(使用負值表示沒有限制)
           
  1. 配置類(RedisTemplate預設使用的是Jedis連接配接池)
package com.wdg.redis;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author wdg
 * @date 2020/5/18 16:43
 * @Description
 */
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

           
  1. 使用的時候直接注入就可以
@Autowired
    private RedisTemplate<String, Object> redisTemplate;
           

最後

redis由于直接在記憶體中運作,是以其處理速度是特别快的,和memcached相比,redis豐富的資料類型和可持久化的模式都大大的增加了redis的實用性,作為傳統關系型資料庫補充很好的解決了現代網際網路的巨大流量的對系統的沖擊, 理論上導緻redis性能瓶頸不在是硬體而是網絡IO。

在然後選擇叢集還是主從複制的模式的時候,一般如果資料量隻有幾十G的時候,搭建master就可以了,因為一般伺服器的記憶體大概在32G。如果資料量超出32G則考慮搭建Cluster,如有100G的資料量可以搭建5台master。

未完待續。。。