【資料庫】Redis叢集篇

xbmchina發表於2019-05-12

歡迎關注公眾號:【愛編碼】 如果有需要後臺回覆2019贈送1T的學習資料哦!!

【資料庫】Redis叢集篇

哨兵模式

背景

當主伺服器當機後,需要手動把一臺從伺服器切換為主伺服器,這就需要人工干預,費事費力,還會造成一段時間內服務不可用。這不是一種推薦的方式,更多時候,我們優先考慮哨兵模式。

定義

Sentinel(哨兵)是Redis 的高可用性解決方案:由一個或多個Sentinel 例項 組成的Sentinel 系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器。

哨兵監聽

server1故障

server1下線,server2升級為新的主伺服器

實戰配置

1.首先配置Redis的主從伺服器,修改redis.conf檔案如下

# 使得Redis伺服器可以跨網路訪問
bind 0.0.0.0
# 設定密碼
requirepass "123456"
# 指定主伺服器,注意:有關slaveof的配置只是配置從伺服器,主伺服器不需要配置
slaveof 192.168.11.128 6379
# 主伺服器密碼,注意:有關slaveof的配置只是配置從伺服器,主伺服器不需要配置
masterauth 123456
複製程式碼

上述內容主要是配置Redis伺服器,從伺服器比主伺服器多一個slaveof的配置和密碼。

  1. 配置3個哨兵,每個哨兵的配置都是一樣的。在Redis安裝目錄下有一個sentinel.conf檔案,copy一份進行修改
# 禁止保護模式
protected-mode no
# 配置監聽的主伺服器,這裡sentinel monitor代表監控,mymaster代表伺服器的名稱,可以自定義,192.168.11.128代表監控的主伺服器,6379代表埠,2代表只有兩個或兩個以上的哨兵認為主伺服器不可用的時候,才會進行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定義服務的密碼,mymaster是服務名稱,123456是Redis伺服器密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456
複製程式碼
  1. 有了上述的修改,我們可以進入Redis的安裝目錄的src目錄,通過下面的命令啟動伺服器和哨兵
# 啟動Redis伺服器程式
./redis-server ../redis.conf
# 啟動哨兵程式
./redis-sentinel ../sentinel.conf
複製程式碼

注意啟動的順序。首先是主機(192.168.11.128)的Redis服務程式,然後啟動從機的服務程式,最後啟動3個哨兵的服務程式。

參考文章: www.jianshu.com/p/06ab9daf9…

叢集

搭建叢集工作需要以下三個步驟:

1.準備節點

Redis叢集一般由多個節點組成,節點數量至少為6個才能保證組成完整高可用的叢集。每個節點需要開啟配置cluster-enabled yes,讓Redis執行在叢集模式下。建議為叢集內所有節點統一目錄,一般劃分三個目錄:conf、data、log,分別存放配置、資料和日誌相關檔案。把6個節點配置統一放在conf目錄下

#節點埠
port 6379
# 開啟叢集模式
cluster-enabled yes
# 節點超時時間,單位毫秒
cluster-node-timeout 15000
# 叢集內部配置檔案
cluster-config-file "nodes-6379.conf"
複製程式碼

其他配置和單機模式一致即可,配置檔案命名規則redis-{port}.conf,準備好配置後啟動所有節點,命令如下

redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf
redis-server conf/redis-6382.conf
redis-server conf/redis-6383.conf
redis-server conf/redis-6384.conf
複製程式碼

2.節點握手

節點握手是指一批執行在叢集模式下的節點通過Gossip協議彼此通訊,達到感知對方的過程。節點握手是叢集彼此通訊的第一步,由客戶端發起命令:cluster meet{ip}{port}

【資料庫】Redis叢集篇

圖中執行的命令是:cluster meet127.0.0.16380讓節點6379和6380節點進 行握手通訊。cluster meet命令是一個非同步命令,執行之後立刻返回。內部發起與目標節點進行握手通訊。

127.0.0.1:6379>cluster meet 127.0.0.1 6381
127.0.0.1:6379>cluster meet 127.0.0.1 6382
127.0.0.1:6379>cluster meet 127.0.0.1 6383
127.0.0.1:6379>cluster meet 127.0.0.1 6384
複製程式碼

最後執行cluster nodes命令確認6個節點都彼此感知並組成叢集

127.0.0.1:6379> cluster nodes
4fa7eac4080f0b667ffeab9b87841da49b84a6e4 127.0.0.1:6384 master - 0 1468073975551
5 connected
cfb28ef1deee4e0fa78da86abe5d24566744411e 127.0.0.1:6379 myself,master - 0 0 0 connected
be9485a6a729fc98c5151374bc30277e89a461d8 127.0.0.1:6383 master - 0 1468073978579
4 connected
40622f9e7adc8ebd77fca0de9edfe691cb8a74fb 127.0.0.1:6382 master - 0 1468073980598
3 connected
8e41673d59c9568aa9d29fb174ce733345b3e8f1 127.0.0.1:6380 master - 0 1468073974541
1 connected
40b8d09d44294d2e23c7c768efc8fcd153446746 127.0.0.1:6381 master - 0 1468073979589
2 connected
複製程式碼

節點建立握手之後叢集還不能正常工作,這時叢集處於下線狀態,所有的資料讀寫都被禁止。

3.分配槽

Redis叢集把所有的資料對映到16384個槽中。每個key會對映為一個固定的槽,只有當節點分配了槽,才能響應和這些槽關聯的鍵命令。通過cluster addslots命令為節點分配槽。這裡利用bash特性批量設定槽(slots)

redis-cli -h 127.0.0.1 -p 6379 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 6380 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 6381 cluster addslots {10923...16383}
複製程式碼

關於叢集伸縮、故障轉移、節點通訊等知識。 可參考《Redis開發與運維》

快取設計

穿透優化

快取穿透是指查詢一個根本不存在的資料,快取層和儲存層都不會命中,通常出於容錯的考慮,如果從儲存層查不到資料則不寫入快取層。 整個過程分為如下3步

1.快取層不命中。 2.儲存層不命中,不將空結果寫回快取。 3.返回空結果

解決辦法

1.快取空物件 儲存層不命中後,仍然將空物件保留到快取層中,之後再訪問這個資料將會從快取中獲取,這樣就保護了後端資料來源。

快取空值應對穿透問題

快取空物件會有兩個問題: 第一,空值做了快取,意味著快取層中存了更多的鍵,需要更多的記憶體空間(如果是攻擊,問題更嚴重),比較有效的方法是針對這類資料設定一個較短的過期時間,讓其自動剔除。

第二,快取層和儲存層的資料會有一段時間視窗的不一致,可能會對業務有一定影響。例如過期時間設定為5分鐘,如果此時儲存層新增了這個資料,那此段時間就會出現快取層和儲存層資料的不一致,此時可以利用訊息系統或者其他方式清除掉快取層中的空物件。

類似程式碼實現如下:

String get(String key) {
// 從快取中獲取資料
String cacheValue = cache.get(key);
// 快取為空
if (StringUtils.isBlank(cacheValue)) {
    // 從儲存中獲取
    String storageValue = storage.get(key);
    cache.set(key, storageValue);
    // 如果儲存資料為空,需要設定一個過期時間(300秒)
    if (storageValue == null) {
      cache.expire(key, 60 * 5);
    }
    return storageValue;
} else {
    // 快取非空
    return cacheValue;
  }
}
複製程式碼

2.布隆過濾器攔截

bloomfilter就類似於一個hash set,用於快速判某個元素是否存在於集合中,其典型的應用場景就是快速判斷一個key是否存在於某容器,不存在就直接返回。布隆過濾器的關鍵就在於hash演算法和容器大小,下面先來簡單的實現下看看效果,我這裡用guava實現的布隆過濾器:

<dependencies>  
     <dependency>  
         <groupId>com.google.guava</groupId>  
         <artifactId>guava</artifactId>  
         <version>23.0</version>  
     </dependency>  
</dependencies>  
複製程式碼
public class BloomFilterTest {
 
    private static final int capacity = 1000000;
    private static final int key = 999998;
 
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), capacity);
 
    static {
        for (int i = 0; i < capacity; i++) {
            bloomFilter.put(i);
        }
    }
 
    public static void main(String[] args) {
        /*返回計算機最精確的時間,單位微妙*/
        long start = System.nanoTime();
 
        if (bloomFilter.mightContain(key)) {
            System.out.println("成功過濾到" + key);
        }
        long end = System.nanoTime();
        System.out.println("布隆過濾器消耗時間:" + (end - start));
        int sum = 0;
        for (int i = capacity + 20000; i < capacity + 30000; i++) {
            if (bloomFilter.mightContain(i)) {
                sum = sum + 1;
            }
        }
        System.out.println("錯判率為:" + sum);
    }
}
複製程式碼
成功過濾到999998
布隆過濾器消耗時間:215518
錯判率為:318
複製程式碼

可以看到,100w個資料中只消耗了約0.2毫秒就匹配到了key,速度足夠快。然後模擬了1w個不存在於布隆過濾器中的key,匹配錯誤率為318/10000,也就是說,出錯率大概為3%,跟蹤下BloomFilter的原始碼發現預設的容錯率就是0.03:

public String getByKey(String key) {
    // 通過key獲取value
    String value = redisService.get(key);
    if (StringUtil.isEmpty(value)) {
        if (bloomFilter.mightContain(key)) {
            value = userService.getById(key);
            redisService.set(key, value);
            return value;
        } else {
            return null;
        }
    }
    return value;
}
複製程式碼

更多知識可參考文章: blog.csdn.net/fanrenxiang…

雪崩優化

由於快取層承載著大量請求,有效地保護了儲存層,但是如果快取層由於某些原因不能提供服務,於是所有的請求都會達到儲存層,儲存層的呼叫量會暴增,造成儲存層也會級聯當機的情況。快取雪崩的英文原意是stampeding herd(奔逃的野牛),指的是快取層宕掉後,流量會像奔逃的野牛一樣,打向後端儲存。

【資料庫】Redis叢集篇

預防和解決快取雪崩問題,可以從以下三個方面進行著手

1.保證快取層服務高可用性。 和飛機都有多個引擎一樣,如果快取層設計成高可用的,即使個別節點、個別機器、甚至是機房宕掉,依然可以提供服務,例如前面介紹過的Redis Sentinel和Redis Cluster都實現了高可用

2.依賴隔離元件為後端限流並降級。

無論是快取層還是儲存層都會有出錯的概率,可以將它們視同為資源。作為併發量較大的系統,假如有一個資源不可用,可能會造成執行緒全部阻塞(hang)在這個資源上,造成整個系統不可用。降級機制在高併發系統中是非常普遍的:比如推薦服務中,如果個性化推薦服務不可用,可以降級補充熱點資料,不至於造成前端頁面是開天窗。 推薦一個Java依賴隔離工具Hystrix

3. 提前演練。 在專案上線前,演練快取層宕掉後,應用以及後端的負載情況以及可能出現的問題,在此基礎上做一些預案設定。

總結

Redis的學習到此為止。以後會總結一下Redis的面試篇。 接下來會學習netty相關知識,敬請期待。

最後

如果對 Java、大資料感興趣請長按二維碼關注一波,我會努力帶給你們價值。覺得對你哪怕有一丁點幫助的請幫忙點個贊或者轉發哦。 關注公眾號**【愛編碼】,回覆2019**有相關資料哦。

【資料庫】Redis叢集篇

相關文章