[學習]sentinel中的DatatSource(一) ReadableDataSource

sentinel是今年阿里開源的高可用防禦的流量管理框架java

git地址:https://github.com/alibaba/Sentinelgit

wiki:https://github.com/alibaba/Sentinel/wikigithub

FAQ:https://github.com/alibaba/Sentinel/wiki/FAQapache

--------------------------------------------------------------------------------------------------------------------------------------------------------api

本文對其中的DataSource擴展進行學習,此爲第一部分ReadableDataSource。數組

sentinel-datasource-extension模塊,在sentinel-extension模塊下,見下圖:緩存

 sentinel-datasource-extension模塊主要定義了datasource擴展相關的接口,見下圖:框架

其中,FileRefreshableDataSource、FileWritableDataSource是基於文件的DataSource的實現;函數

此外官方還提供了zookeeper、nacos、apolo的3個DataSource實現,參見上圖的sentinel-datasource-zookeeper、sentinel-datasource-nacos、sentinel-datasource-apollo的3個模塊;學習

 

首先,咱們來看下sentinel-datasource-extension模塊吧,先看下接口和類圖結構;

在IDEA裏,選中com.alibaba.csp.sentinel.datasource包下全部類和接口,右鍵->Diagrams->Show Diagram(Ctrl+Alt+Shift+U):

能夠看到頂層有3個接口,兩個DataSource,一個Converter;

顧名思義,ReadableDataSource表示讀的數據源,WriteableDataSource表示寫的數據源,Converter表示轉換器接口;

一個個看:)

ReadableDataSource<S, T>:這是個泛型接口,其實註釋很清楚了,S、T分別代碼來源和結果,通常是把某個數據來源對象轉換成Sentinel的Rule類,好比String->List<FlowRule>;

接口定義了4個方法:

1.T loadConfig() 從數據源中讀取數據,返回T類型數據;

   後面會看到,其實該方法實現一般是2步

   S readSource(); // 調下面的readSource接口讀取

   T convert(S source); // 經過Converter<S, T>接口轉換

2.S readSource() 從數據源中讀取數據,返回S類型數據;

   讀取數據跟具體的數據源有關,須要數據源相關的api包或client;

   好比從文件中讀數據,經過java.io、java.nio包;

         從zookeeper讀取,經過org.apache.curator client api;

      

    讀數據又分爲拉模式、推模式,參考sentinel官方的wiki,動態規則擴展:https://github.com/alibaba/Sentinel/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%99%E6%89%A9%E5%B1%95

    拉模式:繼承AutoRefreshDataSource抽象類,在readSource()中實現從數據源讀取數據;

    推模式:繼承AbstractDataSource抽象類,在構造函數中添加數據源變更的監聽器,在readSource()中實現從數據源讀取數據;

    注:繼承這兩個抽象類是推薦的一種簡單方式,但不是強制;

3.SentinelProperty<T> getProperty() 返回SentielProperty,注意是<T>類型

   SentinelProperty<T>是一個泛型接口,在sentinel-core模塊的property包下

 

4.void close() 關閉數據源,通常是指釋放資源,好比文件類型數據源關閉文件

 

既然關鍵接口其中一個方法返回SentinelProperty<T>,那咱們先去sentinel-core看看該接口和相關的實現類。

先查看接口和類圖結構。一樣方法,選中property包下全部類和接口,右鍵->Diagrams->Show Diagram(Ctrl+Alt+Shift+U):

頂層有2個接口,看名稱PropertyListener可能跟SentinelProperty的監聽有關。

先看SentinelProperty接口

有3個方法,看名稱感受有點像觀察者模式中的監聽器。

接着,咱們看下它的2個實現類

NoOpSentinelProperty類方法實現爲空,從名稱也看得出,它不做任何操做;

對着NoOpSentinelProperty類名按住Crtl+鼠標左鍵,

發現只有EmptyDataSource類用到了NoOpSentinelProperty,看註釋和方法,瞭解到這是一個空的DataSource實現,用於設置默認配置。

接着,咱們重點關注SentinelProperty<T>接口的另外一個實現DynamicSentinelProperty<T>

能夠看到,DynamicSentinelProperty<T>定義了2個屬性,

Set<PropertyListener<T>> listeners用於保存屬性監聽器,注意PropertyListener<T>是接口類型;

T value 用於存值;

觀察addListener、removeListener方法,分別是往Set<PropertyListener<T>> listeners添加和刪除監聽器;

只是addListener方法添加了監聽器後,調用了添加的那個監聽器的listener.configLoad(value)回調方法,用於監聽器處理第一次加載

在實現的void updateValue(T newValue)方法中,首先經過isEqual(value, newValue)方法,判斷了新值和舊值是否相同,

若是相同,不做處理直接返回;

若是不一樣,首先用RecordLog.info方法記1條日誌信息;

將新值賦給value,遍歷監聽器集合,調用回調方法listener.configUpdate(newValue);// 看到這裏就很清楚了,這是典型的觀察者模式

此外,DynamicSentinelProperty還提供一個public方法close用戶清空監聽器集合;

 

咱們接着看PropertyListener<T>接口:

該接口提供了兩個回調方法,1個是value更新回調,1個是第一次加載value回調

在看實現了PropertyListener<T>的SimplePropertyListener類:

該類是一個抽象類,重寫了configLoad方法,調用configUpdate(value)方法,意思就是第一次加載回調複用更新回調的邏輯。

到這裏,咱們並無看到PropertyListener<T>接口的具體實現類,

選中PropertyListener文件,Ctrl+H查看類繼承結構:

能夠看到除SimplePropertyListener抽象類外,有6個類直接或間接實現了PropertyListener<T>,經過靜態內部類或匿名內部類;

限於篇幅,本文就不展開了。

對sentinel-core下的property包簡單總結下:定義了屬性和監聽器接口,其中DynamicSentinelProperty經過觀察者模式,實現了屬性的第一次加載和變更的回調通知

 

咱們回到datasource包,順着ReadableDataSource的繼承結構看:

先看AbstractDataSource<S,T>:

實現了ReadableDataSource<S,T>的兩個接口,T loadConfig()和 SentinelProperty<T> getProperty()

定義了兩個屬性,一個轉換器Converter<S, T> parser,一個前文提到的SentinelProperty<T> property;

能夠看到getProperty()直接返回propery便可;

而loadConfig()方法,首先調用接口中的readSource()方法獲得S數據,再調用parser.convert轉換成T數據// ps:這裏變量名是parser,因Converter接口重構過,之前版本叫ConfigParser接口,後來抽象出了WritableDataSource接口,

S->T、T->S都有轉換,所以ConfigParser改成Converter

構造函數中,須要傳入一個Converter<S,T>對象,而且驗證不能爲null;而SentinelProperty<T> property使用了以前提到的DynamicSentinelProperty<T>實現;

 

再來看AutoRefreshDataSource<S, T>抽象類:

該類擴展了AbstractDataSource<S, T>,在基礎上經過ScheduledExecutorService使用後臺線程,定時刷新數據(即輪詢方式的拉模式),

抽象了一個protected boolean isModified()方法,用於子類實現,判斷數據源是否發生了數據變化;

T newValue = loadConfig(); // 還記得AbstractDataSource<S, T>實現了loadConfig嗎,經過readSource()、parser.convert()兩步實現
getProperty().updateValue(newValue);// 這兩句很關鍵,自定義實現也會用到,讀取數據,更新SentinelProperty。

實現了close()方法,用ScheduledExecutorService的shutdownNow()方法,進行了線程的關閉;

注意:AutoRefreshDataSource<S, T>仍然是個抽象類,由於它沒有實現具體S readSource()方法,留給具體數據源的子類實現;

 

接下來,咱們下一個類FileRefreshableDataSource,終於該類不是抽象類了:)  能夠new使用。

該類代碼較多,就不貼完了,首先繼承AutoRefreshDataSource<String, T>,注意:泛型S已經沒有了,而是具體的String,說明T readSource方法返回的是String,Converter<S, T>中的轉換來源也是String;

靜態屬性文件讀取的固定參數,好比文件緩衝區最大限制,字符類型等;

類屬性定義了緩存區字節數組、文件等;

來看關鍵的兩個方法:

readSource是抽象類中沒有實現的方法了,這裏有了具體實現,從文件中讀取數據,並返回String;

isModifed方法根據文件修改時間判斷是否數據源(即文件內容)發生了變化;

 

咱們看看FileRefreshableDataSource具體哪裏用到的;按住Ctrl鍵,對着類名點左鍵:

 找到了它的demo示例,在sentinel-demo模塊的子模塊sentinel-demo-dynamic-file-rule下:

 

能夠看到,使用FileFreashDataSource就4步:

1.定義文件路徑

2.定義String到List<XxxRule>轉換器

3.構造FileFreashDataSource

4.用XxxRuleManager.register2Property加載

--------------------------------------------------------------------------------------------------------------------------------------------------------

最後,咱們對ReadableDataSource作個簡單的總結:

層次結構:ReadableDataSource->AbstractDataSource->AutoRefreshDataSource->FileRefreshDataSource

T loadConfig() 讀取數據源並轉換爲T類型,調用readSource()獲得S,調用Converter<S,T> 獲得T

S readSource()  讀取數據源,獲得S

SentinelProperty<T> getProperty()  獲取SentinelProperty<T>接口,通常是DynamicSentinelProperty<T>類型

一次從讀取數據源到更新數據過程:經過loadConfig()獲得T,getProperty().updateValue(T)

 

拉模式:繼承AutoRefreshDataSource抽象類,在readSource()中實現從數據源讀取數據;

              例:FileRefreshDataSource

推模式:繼承AbstractDataSource抽象類,在構造函數中添加數據源變更的監聽器,在readSource()中實現從數據源讀取數據;

              例:ZookeeperDataSource、NacosDataSource、ApolloDataSource

// 再回過頭去看最開始的類圖,能夠發現sentinel的datasource這樣的泛型接口和抽象類結構定義是比較優雅的,讓自定義DataSource實現變得更加簡單:)

--------------------------------------------------------------------------------------------------------------------------------------------------------

思考:

SentinelProperty<T>接口沒有提供getValue方法,不能獲取T value;

只能經過給SentinelProperty添加監聽器PropertyListener<T>,並在回調方法void configUpdate(T value)、void configLoad(T value)時,可以訪問value;

// 我的理解可能這是一種對property中value的保護

--------------------------------------------------------------------------------------------------------------------------------------------------------

參考:

sentinel官方wiki-動態規則擴展 https://github.com/alibaba/Sentinel/wiki/動態規則擴展

聊聊sentinel的DataSource https://my.oschina.net/go4it/blog/1929547

相關文章
相關標籤/搜索