導航:首頁 > 源碼編譯 > sentinel源碼修改

sentinel源碼修改

發布時間:2023-10-02 17:52:44

❶ [redis 源碼走讀] sentinel 哨兵 - 節點發現流程

承接上一章 《 [redis 源碼走讀] sentinel 哨兵 - 原理 》,本章通過 strace 命令從底層抓取 sentinel 工作日誌,熟悉節點通信流程,閱讀相關源碼。

節點之間通過 TCP 建立聯系,下圖展示了 sentinel A 節點與其它節點的關系。

通過 strace 命令查看 socket 的發送和接收數據日誌內容,我們基本可以掌握 sentinel/master/slave 這三個角色是怎麼聯系起來的。

這樣 sentinel 只需要配置 master 的信息,通過 INFO 命令和訂閱頻道 __sentinel__:hello 就能將集群中所有角色的節點緊密聯系在一起。

根據 strace 日誌參考上述對應連接關系圖。

通過上述分析,我們基本了解了節點之間的通信流程時序,下面來分析一下源碼。

sentinel 進程對 sentinel / master / slave 三個角色用數據結構 sentinelRedisInstance 進行管理。

sentinel 進程啟動,載入配置,創建對應節點的管理實例 sentinelRedisInstance 。

定時器定期對其它節點進行監控管理。sentinel 利用 hiredis 作為 redis client,鏈接其它節點進行相互通信。

sentinel 非同步重連其它節點。

sentinel 定期發送命令:PING / INFO / PUBLISH。每種命令發送的時間間隔不一樣;不同場景下,同一個命令發送時間間隔可能會改變。

命令發送對象

sentinel 通過 master / slave 的 INFO 回復,主要下面幾件事:

如果文章不錯,給個點贊唄 ~ 謝謝。👍

❷ 阿里sentinel源碼解析

sentinel是阿里巴巴開源的流量整形(限流、熔斷)框架,目前在github擁有15k+的star,sentinel以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。

我們以sentinel的主流程入手,分析sentinel是怎麼搜集流量指標,完成流量整形的。

首先我們先看一個sentinel的簡單使用demo,只需要調用SphU.entry獲取到entry,然後在完成業務方法之後調用entry.exit即可。

SphU.entry會調用Env.sph.entry,將name和流量流向封裝成StringResourceWrapper,然後繼續調用entry處理。

進入CtSph的entry方法,最終來到entryWithPriority,調用InternalContextUtil.internalEnter初始化ThreadLocal的Context,然後調用lookProcessChain初始化責任鏈,最終調用chain.entry進入責任鏈進行處理。

InternalContextUtil.internalEnter會調用trueEnter方法,主要是生成DefaultNode到contextNameNodeMap,然後生成Context設置到contextHolder的過程。

lookProcessChain已經做過優化,支持spi載入自定義的責任鏈bulider,如果沒有定義則使用默認的DefaultSlotChainBuilder進行載入。默認載入的slot和順序可見鎮樓圖,不再細說。

最後來到重頭戲chain.entry進入責任鏈進行處理,下面會按照順序分別對每個處理器進行分析。
首先來到NodeSelectorSlot,主要是獲取到name對應的DefaultNode並緩存起來,設置為context的當前節點,然後通知下一個節點。

下一個節點是ClusterBuilderSlot,繼續對DefaultNode設置ClusterNode與OriginNode,然後通知下一節點。

下一個節點是LogSlot,只是單純的列印日誌,不再細說。

下一個節點是StatisticSlot,是一個後置節點,先通知下一個節點處理完後,
1.如果沒有報錯,則對node、clusterNode、originNode、ENTRY_NODE的線程數、通過請求數進行增加。
2.如果報錯是PriorityWaitException,則只對線程數進行增加。
3.如果報錯是BlockException,設置報錯到node,然後對阻擋請求數進行增加。
4.如果是其他報錯,設置報錯到node即可。

下一個節點是FlowSlot,這個節點就是重要的限流處理節點,進入此節點是調用checker.checkFlow進行限流處理。

來到FlowRuleChecker的checkFlow方法,調用ruleProvider.apply獲取到資源對應的FlowRule列表,然後遍歷FlowRule調用canPassCheck校驗限流規則。

canPassCheck會根據rule的限流模式,選擇集群限流或者本地限流,這里分別作出分析。

passLocalCheck是本地限流的入口,首先會調用選出限流的node,然後調用canPass進行校驗。

會根據以下規則選中node。
1.strategy是STRATEGY_DIRECT。
1.1.limitApp不是other和default,並且等於orgin時,選擇originNode。
1.2.limitApp是other,選擇originNode。
1.3.limitApp是default,選擇clusterNode。
2.strategy是STRATEGY_RELATE,選擇clusterNode。
3.strategy是STRATEGY_CHAIN,選擇node。

選擇好對應的node後就是調用canPass校驗限流規則,目前sentinel有三種本地限流規則:普通限流、勻速限流、冷啟動限流。

普通限流的實現是DefaultController,就是統計當前的線程數或者qps加上需要通過的數量有沒有大於限定值,小於等於則直接通過,否則阻擋。

勻速限流的實現是RateLimiterController,使用了AtomicLong保證了latestPassedTime的原子增長,因此停頓的時間是根據latestPassedTime-currentTime計算出來,得到一個勻速的睡眠時間。

冷啟動限流的實現是WarmUpController,是sentinel中最難懂的限流方式,其實不太需要關注這些復雜公式的計算,也可以得出冷啟動的限流思路:
1.當qps已經達到溫熱狀態時,按照正常的添加令牌消耗令牌即可。
2.當qps處於過冷狀態時,會添加令牌使得演算法繼續降溫。
3.當qps逐漸回升,大於過冷的邊界qps值時,不再添加令牌,慢慢消耗令牌使得逐漸增大單位時間可通過的請求數,讓演算法繼續回溫。
總結出一點,可通過的請求數跟令牌桶剩餘令牌數量成反比,以達到冷啟動的作用。

接下來是集群限流,passClusterCheck是集群限流的入口,會根據flowId調用clusterSerivce獲取指定數量的token,然後根據其結果判斷是否通過、睡眠、降級到本地限流、阻擋。

接下來看一下ClusterService的處理,會根據ruleId獲取到對應的FlowRule,然後調用ClusterFlowChecker.acquireClusterToken獲取結果返回。ClusterFlowChecker.acquireClusterToken的處理方式跟普通限流是一樣的,只是會將集群的請求都集中在一個service中處理,來達到集群限流的效果,不再細說。

FlowSlot的下一個節點是DegradeSlot,是熔斷處理器,進入時會調用performChecking,進而獲取到CircuitBreaker列表,然後調用其tryPass校驗是否熔斷。

來到AbstractCircuitBreaker的tryPass方法,主要是判斷熔斷器狀態,如果是close直接放行,如果是open則會校驗是否到達開啟halfopen的時間,如果成功將狀態cas成halfopen則繼續放行,其他情況都是阻攔。

那怎麼將熔斷器的狀態從close變成open呢?怎麼將halfopen變成close或者open呢?sentinel由兩種熔斷器:錯誤數熔斷器ExceptionCircuitBreaker、響應時間熔斷器ResponseTimeCircuitBreaker,都分析一遍。
當業務方法報錯時會調用Tracer.traceEntry將報錯設置到entry上。

當調用entry.exit時,會隨著責任鏈來到DegradeSlot的exit方法,會遍歷熔斷器列表調用其onRequestComplete方法。

ExceptionCircuitBreaker的onRequestComplete會記錄錯誤數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果沒有報錯,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較錯誤數/錯誤比例是否大於限定值,如果大於則直接轉換成open。

ExceptionCircuitBreaker的onRequestComplete會記錄慢響應數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果當前響應時間小於限定值,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較慢請求數/慢請求比例是否大於限定值,如果大於則直接轉換成open。

下一個節點是AuthoritySlot,許可權控制器,這個控制器就是看當前origin是否被允許進入請求,不允許則報錯,不再細說。

終於來到最後一個節點SystemSlot了,此節點是自適應處理器,主要是根據系統自身負載(qps、最大線程數、最高響應時間、cpu使用率、系統bbr)來判斷請求是否能夠通過,保證系統處於一個能穩定處理請求的安全狀態。

尤其值得一提的是bbr演算法,作者參考了tcp bbr的設計,通過最大的qps和最小的響應時間動態計算出可進入的線程數,而不是一個粗暴的固定可進入的線程數,為什麼能通過這兩個值就能計算出可進入的線程數?可以網上搜索一下tcp bbr演算法的解析,十分巧妙,不再細說。

❸ Python中的10條冷門知識

下面時Python中一些不常見的冷門知識,感興趣的小夥伴不妨來學習一下。
1、省略號也是對象
… 這是省略號,在Python中,一切皆對象。它也不例外。在 Python 中,它叫做 Ellipsis 。在 Python 3 中你可以直接寫…來得到這玩意。
>>> ...
Ellipsis
>>> type(...)
<class 'ellipsis'>
而在 Python2 中沒有…這個語法,只能直接寫Ellipsis來獲取。
>>> Ellipsis
Ellipsis
>>> type(Ellipsis)
<type 'ellipsis'>
它轉為布爾值時為真
>>> bool(...)
True
最後,這東西是一個單例。
>>> id(...)
4362672336
>>> id(...)
4362672336
這東西有啥用呢?據說它是Numpy的語法糖,不玩 Numpy 的人,可以說是沒啥用的。
在網上只看到這個 用 … 代替 pass ,稍微有點用,但又不是必須使用的。
try:
1/0
except ZeroDivisionError:
...
2、增量賦值的性能更好
諸如 += 和 *= 這些運算符,叫做 增量賦值運算符。這里使用用 += 舉例,以下兩種寫法,在效果上是等價的。
# 第一種
a = 1 ; a += 1
# 第二種
a = 1; a = a + 1
+= 其背後使用的魔法方法是 iadd,如果沒有實現這個方法則會退而求其次,使用 add 。
這兩種寫法有什麼區別呢?
用列表舉例 a += b,使用 add 的話就像是使用了a.extend(b),如果使用 add 的話,則是 a = a+b,前者是直接在原列表上進行擴展,而後者是先從原列表中取出值,在一個新的列表中進行擴展,然後再將新的列表對象返回給變數,顯然後者的消耗要大些。
所以在能使用增量賦值的時候盡量使用它。
3、and 和or 的取值順序
and 和 or 是我們再熟悉不過的兩個邏輯運算符。而我們通常只用它來做判斷,很少用它來取值。
如果一個or表達式中所有值都為真,Python會選擇第一個值,而and表達式則會選擇第二個。
>>>(2 or 3) * (5 and 7)
14 # 2*7
4、修改解釋器提示符
>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>>
>>> sys.ps2 = '---------------- '
>>> sys.ps1 = 'Python編程時光>>>'
Python編程時光>>>for i in range(2):
---------------- print (i)
----------------
5、默認參數最好不為可變對象
函數的參數分三種
可變參數
默認參數
關鍵字參數
今天要說的是,傳遞默認參數時,新手很容易踩雷的一個坑。
先來看一個示例:
def func(item, item_list=[]):
item_list.append(item)
print(item_list)
func('iphone')
func('xiaomi', item_list=['oppo','vivo'])
func('huawei')
在這里,你可以暫停一下,思考一下會輸出什麼?
思考過後,你的答案是否和下面的一致呢
['iphone']
['oppo', 'vivo', 'xiaomi']
['iphone', 'huawei']
如果是,那你可以跳過這部分內容,如果不是,請接著往下看,這里來分析一下。
Python 中的 def 語句在每次執行的時候都初始化一個函數對象,這個函數對象就是我們要調用的函數,可以把它當成一個一般的對象,只不過這個對象擁有一個可執行的方法和部分屬性。
對於參數中提供了初始值的參數,由於 Python 中的函數參數傳遞的是對象,也可以認為是傳地址,在第一次初始化 def 的時候,會先生成這個可變對象的內存地址,然後將這個默認參數 item_list 會與這個內存地址綁定。在後面的函數調用中,如果調用方指定了新的默認值,就會將原來的默認值覆蓋。如果調用方沒有指定新的默認值,那就會使用原來的默認值。
在這里插入圖片描述
6、訪問類中的私有方法
大家都知道,類中可供直接調用的方法,只有公有方法(protected類型的方法也可以,但是不建議)。也就是說,類的私有方法是無法直接調用的。
這里先看一下例子
class Kls():
def public(self):
print('Hello public world!')
def __private(self):
print('Hello private world!')
def call_private(self):
self.__private()
ins = Kls()
# 調用公有方法,沒問題
ins.public()
# 直接調用私有方法,不行
ins.__private()
# 但你可以通過內部公有方法,進行代理
ins.call_private()
既然都是方法,那我們真的沒有方法可以直接調用嗎?
當然有啦,只是建議你千萬不要這樣弄,這里只是普及,讓你了解一下。
# 調用私有方法,以下兩種等價
ins._Kls__private()
ins.call_private()
7、時有時無的切片異常
這是個簡單例子
my_list = [1, 2, 3, 4, 5]
print(my_list[5])
Traceback (most recent call last):
File "F:/Python Script/test.py", line 2, in <mole>
print(my_list[5])
IndexError: list index out of range
來看看,如下這種寫法就不會報索引異常,執行my_list[5:],會返回一個新list:[]。
my_list = [1, 2, 3]
print(my_list[5:])
8、for 死循環
for 循環可以說是 基礎得不能再基礎的知識點了。但是如果讓你用 for 寫一個死循環,你會寫嗎?(問題來自群友 陳**)
這是個開放性的問題,在往下看之前,建議你先嘗試自己思考,你會如何解答。
好了,如果你還沒有思路,那就來看一下 一個海外 MIT 群友的回答:
for i in iter(int, 1):pass
是不是懵逼了。iter 還有這種用法?這為啥是個死循環?
這真的是個冷知識,關於這個知識點,你如果看中文網站,可能找不到相關資料。
還好你可以通過 IDE 看py源碼里的注釋內容,介紹了很詳細的使用方法。
原來iter有兩種使用方法,通常我們的認知是第一種,將一個列表轉化為一個迭代器。
而第二種方法,他接收一個 callable對象,和一個sentinel 參數。第一個對象會一直運行,直到它返回 sentinel 值才結束。
在這里插入圖片描述
那int 呢,這又是一個知識點,int 是一個內建方法。通過看注釋,可以看出它是有默認值0的。你可以在終端上輸入 int() 看看是不是返回0。
在這里插入圖片描述
由於int() 永遠返回0,永遠返回不了1,所以這個 for 循環會沒有終點。一直運行下去。
9、奇怪的字元串
字元串類型作為 Python 中最常用的數據類型之一,Python解釋器為了提高字元串使用的效率和使用性能,做了很多優化。
例如:Python 解釋器中使用了 intern(字元串駐留)的技術來提高字元串效率。
什麼是 intern 機制?就是同樣的字元串對象僅僅會保存一份,放在一個字元串儲蓄池中,是共用的,當然,肯定不能改變,這也決定了字元串必須是不可變對象。
示例一
# Python2.7
>>> a = "Hello_Python"
>>> id(a)
32045616
>>> id("Hello" + "_" + "Python")
32045616
# Python3.7
>>> a = "Hello_Python"
>>> id(a)
38764272
>>> id("Hello" + "_" + "Python")
32045616
示例二
>>> a = "MING"
>>> b = "MING"
>>> a is b
True
# Python2.7
>>> a, b = "MING!", "MING!"
>>> a is b
True
# Python3.7
>>> a, b = "MING!", "MING!"
>>> a is b
False
示例三
# Python2.7
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
# Python3.7
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
True
示例四
>>> s1="hello"
>>> s2="hello"
>>> s1 is s2
True
# 如果有空格,默認不啟用intern機制
>>> s1="hell o"
>>> s2="hell o"
>>> s1 is s2
False
# 如果一個字元串長度超過20個字元,不啟動intern機制
>>> s1 = "a" * 20
>>> s2 = "a" * 20
>>> s1 is s2
True
>>> s1 = "a" * 21
>>> s2 = "a" * 21
>>> s1 is s2
False
>>> s1 = "ab" * 10
>>> s2 = "ab" * 10
>>> s1 is s2
True
>>> s1 = "ab" * 11
>>> s2 = "ab" * 11
>>> s1 is s2
False
10、兩次return
我們都知道,try…finally… 語句的用法,不管 try 裡面是正常執行還是報異常,最終都能保證finally能夠執行。
同時,我們又知道,一個函數里只要遇到 return 函數就會立馬結束。
基於以上這兩點,我們來看看這個例子,到底運行過程是怎麼樣的?
>>> def func():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> func()
'finally'
驚奇的發現,在try里的return居然不起作用。
原因是,在try…finally…語句中,try中的return會被直接忽視,因為要保證finally能夠執行。

閱讀全文

與sentinel源碼修改相關的資料

熱點內容
程序員那麼可愛陸漓和姜逸城吻戲 瀏覽:802
android獲取窗口大小 瀏覽:180
程序員為世界帶來的貢獻 瀏覽:214
程序員招聘自薦信 瀏覽:693
魔獸鍵位設置命令宏 瀏覽:645
程序員沒有目標了 瀏覽:828
搶答器c程序編程 瀏覽:703
什麼app可以自己玩 瀏覽:76
刨客app是什麼 瀏覽:963
cad輸入命令欄不見了 瀏覽:834
做故事集可以用什麼app 瀏覽:692
qq郵箱發送壓縮包 瀏覽:672
程序員桌面機器人 瀏覽:589
xjr快速開發平台源碼 瀏覽:159
java介面runnable 瀏覽:31
python怎麼運行web伺服器 瀏覽:349
notepad編程代碼 瀏覽:740
什麼安卓的毛病最少 瀏覽:611
hp的pjl設備訪問命令 瀏覽:635
googlewebp圖片壓縮技術 瀏覽:215