逅弈 轉載請註明原創出處,謝謝!
系列文章
規則持久化的5種方式
規則丟失
無論是通過硬編碼的方式來更新規則,還是通過接入 Sentinel Dashboard 後,在頁面上操作來更新規則,都無法避免一個問題,那就是服務重新後,規則就丟失了,因為預設情況下規則是儲存在記憶體中的。
Dashboard 是通過 transport 模組來獲取每個 Sentinel 客戶端中的規則的,獲取到的規則通過 RuleRepository 介面儲存在 Dashboard 的記憶體中,如果在 Dashboard 頁面中更改了某個規則,也會呼叫 transport 模組提供的介面將規則更新到客戶端中去。
試想這樣一種情況,客戶端連線上 Dashboard 之後,我們在 Dashboard 上為客戶端配置好了規則,並推送給了客戶端。這時由於一些因素客戶端出現異常,服務不可用了,當客戶端恢復正常再次連線上 Dashboard 後,這時所有的規則都丟失了,我們還需要重新配置一遍規則,這肯定不是我們想要的。
如上圖所示,當 Sentinel 的客戶端掛掉之後,儲存在各個 RuleManager 中的規則都會付之一炬,所以在生產中是絕對不能這麼做的。
規則持久化原理
那我們有什麼辦法能解決這個問題呢,其實很簡單,那就是把原本儲存在 RuleManager 記憶體中的規則,持久化一份副本出去。這樣下次客戶端重啟後,可以從持久化的副本中把資料 load 進記憶體中,這樣就不會丟失規則了,如下圖所示:
Sentinel 為我們提供了兩個介面來實現規則的持久化,他們分別是:ReadableDataSource 和 WritableDataSource。
其中 WritableDataSource 不是我們本次關心的重點,或者說 WritableDataSource 並沒有那麼重要,因為通常各種持久化的資料來源已經提供了具體的將資料持久化的方法了,我們只需要把資料從持久化的資料來源中獲取出來,轉成我們需要的格式就可以了。
下面我們來看一下 ReadableDataSource 介面的具體的定義:
public interface ReadableDataSource<S, T> {
// 從資料來源中讀取原始的資料
S readSource() throws Exception;
// 將原始資料轉換成我們所需的格式
T loadConfig() throws Exception;
// 獲取該種資料來源的SentinelProperty物件
SentinelProperty<T> getProperty();
}
複製程式碼
介面很簡單,最重要的就是這三個方法,另外 Sentinel 還為我們提供了一個抽象類:AbstractDataSource,該抽象類中實現了兩個方法,具體的資料來源實現類只需要實現一個 readSource 方法即可,具體的程式碼如下:
public abstract class AbstractDataSource<S, T>
implements ReadableDataSource<S, T> {
// Converter介面負責轉換資料
protected final Converter<S, T> parser;
// SentinelProperty介面負責觸發PropertyListener
// 的configUpdate方法的回撥
protected final SentinelProperty<T> property;
public AbstractDataSource(Converter<S, T> parser) {
if (parser == null) {
throw new IllegalArgumentException("parser can't be null");
}
this.parser = parser;
this.property = new DynamicSentinelProperty<T>();
}
@Override
public T loadConfig() throws Exception {
return loadConfig(readSource());
}
public T loadConfig(S conf) throws Exception {
return parser.convert(conf);
}
@Override
public SentinelProperty<T> getProperty() {
return property;
}
}
複製程式碼
實際上每個具體的 DataSource 實現類需要做三件事:
- 實現 readSource 方法將資料來源中的原始資料轉換成我們可以處理的資料S
- 提供一個 Converter 來將資料S轉換成最終的資料T
- 將最終的資料T更新到具體的 RuleManager 中去
我把規則是如何從資料來源載入進 RuleManager 中去的完整流程濃縮成了下面這張圖:
大家可以就著這張圖對照著原始碼來看,可以很容易的弄明白這個過程,這裡我就不再展開具體的原始碼講了,有幾點需要注意的是:
- 規則的持久化配置中心可以是redis、nacos、zk、file等等任何可以持久化的資料來源,只要能保證更新規則時,客戶端能得到通知即可
- 規則的更新可以通過 Sentinel Dashboard 也可以通過各個配置中心自己的更新介面來操作
- AbstractDataSource 中的 SentinelProperty 持有了一個 PropertyListener 介面,最終更新 RuleManager 中的規則是 PropertyListener 去做的
規則持久化
好了,知道了具體的原理了,下面我們就來講解下如何來接入規則的持久化。
目前 Sentinel 中預設實現了5種規則持久化的方式,分別是:file、redis、nacos、zk和apollo。
下面我們對這5種方式一一進行了解,以持久化限流的規則為例。
File
檔案持久化有一個問題就是檔案不像其他的配置中心,資料發生變更後會發出通知,使用檔案來持久化的話就需要我們自己定時去掃描檔案,來確定檔案是否發現了變更。
檔案資料來源是通過 FileRefreshableDataSource 類來實現的,他是通過檔案的最後更新時間來判斷規則是否發生變更的。
首先需要引入依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
<version>x.y.z</version>
</dependency>
複製程式碼
接入的方法如下:
private void init() throws Exception {
// 儲存了限流規則的檔案的地址
String flowRuleName = yourFlowRuleFileName();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
// 建立檔案規則資料來源
FileRefreshableDataSource<List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRuleName, parser);
// 將Property註冊到 RuleManager 中去
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
複製程式碼
PS:需要注意的是,我們需要在系統啟動的時候呼叫該資料來源註冊的方法,否則不會生效的。具體的方式有很多,可以藉助 Spring 來初始化該方法,也可以自定義一個類來實現 Sentinel 中的 InitFunc 介面來完成初始化。
Sentinel 會在系統啟動的時候通過 spi 來掃描 InitFunc 的實現類,並執行 InitFunc 的 init 方法,所以這也是一種可行的方法,如果我們的系統沒有使用 Spring 的話,可以嘗試這種方式。
Redis
Redis 資料來源的實現類是 RedisDataSource。
首先引入依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-redis</artifactId>
<version>x.y.z</version>
</dependency>
複製程式碼
接入方法如下:
private void init() throws Exception {
String redisHost = yourRedisHost();
String redisPort = yourRedisPort();
String ruleKey = yourRuleKey();
String channel = yourChannel();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
RedisConnectionConfig config = RedisConnectionConfig.builder()
.withHost(redisHost)
.withPort(redisPort)
.build();
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, ruleKey, channel, parser);
FlowRuleManager.register2Property(redisDataSource.getProperty());
}
複製程式碼
Nacos
Nacos 資料來源的實現類是 NacosDataSource。
首先引入依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>x.y.z</version>
</dependency>
複製程式碼
接入方法如下:
private void init() throws Exception {
String remoteAddress = yourRemoteAddress();
String groupId = yourGroupId();
String dataId = yourDataId();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
ReadableDataSource<String, List<FlowRule>> nacosDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId, parser);
FlowRuleManager.register2Property(nacosDataSource.getProperty());
}
複製程式碼
Zk
Zk 資料來源的實現類是 ZookeeperDataSource。
首先引入依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
<version>x.y.z</version>
</dependency>
複製程式碼
接入方法如下:
private void init() throws Exception {
String remoteAddress = yourRemoteAddress();
String path = yourPath();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
ReadableDataSource<String, List<FlowRule>> zookeeperDataSource = new ZookeeperDataSource<>(remoteAddress, path, parser);
FlowRuleManager.register2Property(zookeeperDataSource.getProperty());
}
複製程式碼
Apollo
Apollo 資料來源的實現類是 ApolloDataSource。
首先引入依賴:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-apollo</artifactId>
<version>x.y.z</version>
</dependency>
複製程式碼
接入方法如下:
private void init() throws Exception {
String namespaceName = yourNamespaceName();
String ruleKey = yourRuleKey();
String defaultRules = yourDefaultRules();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
ReadableDataSource<String, List<FlowRule>> apolloDataSource = new ApolloDataSource<>(namespaceName, ruleKey, path, defaultRules);
FlowRuleManager.register2Property(apolloDataSource.getProperty());
}
複製程式碼
可以看到5中持久化的方式基本上大同小異,主要還是對接每種配置中心,實現資料的轉換,並且監聽配置中心的資料變化,當接收到資料變化後能夠及時的將最新的規則更新到 RuleManager 中去就可以了。
更多原創好文,請關注公眾號「逅弈逐碼」