逅弈 轉載請註明原創出處,謝謝!java
Sentinel 原理-全解析redis
Sentinel 原理-滑動窗口post
Sentinel 實戰-限流篇this
不管是經過硬編碼的方式來更新規則,仍是經過接入 Sentinel Dashboard 後,在頁面上操做來更新規則,都沒法避免一個問題,那就是服務從新後,規則就丟失了,由於默認狀況下規則是保存在內存中的。spa
Dashboard 是經過 transport 模塊來獲取每一個 Sentinel 客戶端中的規則的,獲取到的規則經過 RuleRepository 接口保存在 Dashboard 的內存中,若是在 Dashboard 頁面中更改了某個規則,也會調用 transport 模塊提供的接口將規則更新到客戶端中去。3d
試想這樣一種狀況,客戶端鏈接上 Dashboard 以後,咱們在 Dashboard 上爲客戶端配置好了規則,並推送給了客戶端。這時因爲一些因素客戶端出現異常,服務不可用了,當客戶端恢復正常再次鏈接上 Dashboard 後,這時全部的規則都丟失了,咱們還須要從新配置一遍規則,這確定不是咱們想要的。code
如上圖所示,當 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 實現類須要作三件事:
我把規則是如何從數據源加載進 RuleManager 中去的完整流程濃縮成了下面這張圖:
你們能夠就着這張圖對照着源碼來看,能夠很容易的弄明白這個過程,這裏我就再也不展開具體的源碼講了,有幾點須要注意的是:
好了,知道了具體的原理了,下面咱們就來說解下如何來接入規則的持久化。
目前 Sentinel 中默認實現了5種規則持久化的方式,分別是:file、redis、nacos、zk和apollo。
下面咱們對這5種方式一一進行了解,以持久化限流的規則爲例。
文件持久化有一個問題就是文件不像其餘的配置中心,數據發生變動後會發出通知,使用文件來持久化的話就須要咱們本身定時去掃描文件,來肯定文件是否發現了變動。
文件數據源是經過 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 數據源的實現類是 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 數據源的實現類是 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 數據源的實現類是 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 數據源的實現類是 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 中去就能夠了。
更多原創好文,請關注公衆號「逅弈逐碼」