首先講一下開關(guān)的由來,例如東京在6月18日做店慶促銷活動,在交易下單環(huán)節(jié),可能需要調(diào)用A、B、C三個接口來完成,但是其實A和B是必須的,C只是附加的功能(例如在下單的時候做一下推薦),可有可無,在平時系統(tǒng)沒有壓力,容量充足的情況下,調(diào)用下沒問題,但是在類似店慶之類的大促環(huán)節(jié),系統(tǒng)已經(jīng)滿負荷了,這時候其實完全可以不去調(diào)用C接口,怎么實現(xiàn)這個呢?改代碼?no,no,no,這樣太不敏捷,此時開關(guān)誕生了,開發(fā)人員只要簡單執(zhí)行一下命令或者點一下頁面,就可以關(guān)掉對于C接口的調(diào)用,在大促過去之后,再把開關(guān)恢復(fù)回去即可。
??????問題一:在單個java系統(tǒng)中如何實現(xiàn)開關(guān)功能?
??????其實對于開關(guān)來說,對應(yīng)Java中的類型,很好映射,就是一個boolean值,在需要做開關(guān)操作的地方,調(diào)用這個屬性,判斷狀態(tài),然后走相應(yīng)的邏輯即可。這個類是一個單例,保證全局唯一(代碼就不寫了,單例模式一般是學(xué)習(xí)設(shè)計模式中最開始接觸的呵呵)。
??????問題二:單個java系統(tǒng)中,如何實現(xiàn)開關(guān)值變更的操作呢?
??????在單機系統(tǒng)中,改變開關(guān)的狀態(tài)很簡單(留一個口子,外部可以改變屬性的值,例如改為true或者false),這時候,可以是頁面來維護開關(guān),通過頁面的點擊類改變這個全局唯一的屬性,從而實現(xiàn)開關(guān)動作的觸發(fā)。
??????問題三:多個同構(gòu)java系統(tǒng),如何實現(xiàn)開關(guān)狀態(tài)的同步呢?
??????通過一和二的介紹,在單機情況下,開關(guān)的變更可以了,但是在多個同構(gòu)(這里的同構(gòu),值得是部署的同一套代碼,邏輯完全相同,類似Master和Slaver的模式)系統(tǒng)中,如何保持一致呢?單例模式,開關(guān)屬性是被加載到本地緩存,就是說java一直持有的對象,在FullGC的時候回收不走的那種。這個時候,如果要保持各個系統(tǒng)中開關(guān)屬性狀態(tài)的一致,就需要從第三方外部系統(tǒng)中加載這個數(shù)據(jù)。
??????什么系統(tǒng)能充當(dāng)?shù)谌酵獠肯到y(tǒng)呢?可以是一個數(shù)據(jù)庫訪問系統(tǒng),我們暫且稱之為MetaServer,開關(guān)的屬性防止在DB中,然后MetaServer提供頁面來修改數(shù)據(jù),同時提供接口讀取開關(guān)的數(shù)據(jù),在應(yīng)用啟動的時候,通過MetaServer來讀取數(shù)據(jù),加載到本地緩存中。這時候就有個問題,就是我通過MetaServer的頁面改變了值,各個應(yīng)用如何知道我改變了屬性呢?這個時候就需要通過一些辦法(辦法很多,可以是消息系統(tǒng),可以是zookeeper,可以是頁面觸發(fā))來清理一下開關(guān)屬性的緩存,讓緩存重新加載一下,從而實現(xiàn)最新的狀態(tài)獲取。
??????總體思路就是:metaServer維護開關(guān)數(shù)據(jù)–應(yīng)用讀取DB中的數(shù)據(jù)到本地緩存–DB中數(shù)據(jù)變更–觸發(fā)開關(guān)屬性緩存重新加載。
??????這個是不是有點復(fù)雜,有沒有更加簡單的辦法?當(dāng)然有了,之前淘寶開源了一個系統(tǒng)diamond(持久化配置管理系統(tǒng),http://code.taobao.org/p/diamond/wiki/index/),其實可以理解為“配置信息的偽推送服務(wù)”,例如我變更了一個開關(guān)的屬性,不再需要做清理緩存的事情,diamond幫你做掉了(原理很簡單,例如系統(tǒng)A訂閱了在diamond中的開關(guān)信息,這時候A會啟動一個線程,每隔一段時間來輪循diamond的服務(wù)端,看看開關(guān)屬性的數(shù)據(jù)有沒有變更,如果有變更,在diamond服務(wù)端來加載最新的數(shù)據(jù))。
??????總體思路是:在diamond中維護配置信息–系統(tǒng)訂閱開關(guān)屬性–系統(tǒng)輪循配置是否有變更,有變更直接就變掉了。
??????????問題四:開關(guān)設(shè)計的幾個坑
??????有時候,我們?yōu)榱朔奖?,沒有借助問題三種的MetaServer或者diamond的方式,就是留了一個HTTP的接口來觸發(fā)修改開關(guān)(多臺機器的話,可以寫批量腳本),這時候其實需要我們在apache或者nginx中,把這個URL的訪問禁止掉,防止惡意用戶在外部拼湊鏈接來進行開關(guān)的變動,這時候只能在服務(wù)器上通過linux的curl來觸發(fā)操作了。
??????還有一個,就是如果通過HTTP的形式來修改開關(guān)的屬性,有個是需要注意的,就是開關(guān)的執(zhí)行要冪等操作,這樣方便操作,避免出現(xiàn)集群中數(shù)據(jù)不一致的狀態(tài)(就是執(zhí)行開,開關(guān)就是開,不能第一次執(zhí)行是開,第二次執(zhí)行是關(guān))。
??????????問題五:開關(guān)組合情況下怎么搞?
??????上面的幾種情況,僅僅是執(zhí)行單個開關(guān),應(yīng)該比較簡單。但是我同時又A、B、C三個開關(guān),在不同的業(yè)務(wù)場景下,可能需要關(guān)閉A和B開關(guān),在另外一個場景下,可能需要關(guān)閉A和C開關(guān),這時候認為操作有可能會有遺漏或者疏忽,怎么搞呢?在單獨屬性開關(guān)的基礎(chǔ)上做封裝,例如A和B上面增加一層屬性,暫且叫“AB”,修改AB的值,對應(yīng)的系統(tǒng)修改A和B的值,這樣就避免人肉記住一些組合。
??????????問題六:如何實現(xiàn)自動升降級?
??????上面的情況,都是在提起可以預(yù)知的情況下,我們做一些人為的操作,這個能不能自動化?當(dāng)然可以,就是這一小節(jié)討論的自動升降級。
??????舉例子,現(xiàn)在東京和作的外部物流公司有多家,會調(diào)用它們的系統(tǒng)或者物流節(jié)點的狀態(tài),這個時候,物流公司系統(tǒng)是不穩(wěn)定的,如果掛了或者響應(yīng)時間慢了,對于自身的系統(tǒng)會影響比較大,比較理想的辦法是,在物流公司系統(tǒng)出現(xiàn)問題的時候,這塊邏輯自動降級處理,然后等物流公司系統(tǒng)好了之后,再把這部分邏輯自動升級,整個過程沒有人為參與,自動保持系統(tǒng)穩(wěn)定性。這里說一下總體思路:
??????第一步:搞一個計數(shù)器,記錄接口,暫定A的調(diào)用成功次數(shù)、失敗次數(shù)以及響應(yīng)時間;
??????第二步:將這些信息放入隊列中,同時設(shè)置閥值(例如RT超過5秒就降級,1秒就升級)以及閥值觸發(fā)改動的開關(guān);
??????第三部:異步啟動一個線程,掃描隊列,達到我們的條件,就觸發(fā)做變更(有個問題,就是加入業(yè)務(wù)降級了,這時候就沒有調(diào)用量,也就沒有了自動升級的條件了,怎么搞呢?這時候業(yè)務(wù)降級,并不是完全100%的停掉,可以預(yù)留一部分流量繼續(xù)調(diào)用A,把A調(diào)用的信息放入隊列中,根據(jù)這些信息,就能實現(xiàn)升級了);
??????總結(jié):
??????上面這些是在陸續(xù)的系統(tǒng)維護中嘗試或者看到的處理辦法,通過開關(guān)的方式,實現(xiàn)系統(tǒng)的升降級,從而更好的保護系統(tǒng)。這篇文章只是闡述了大體的思路,沒有涉及到具體的代碼,希望能夠達到拋磚引玉的作用。

轉(zhuǎn)載請注明原文出處:Harries Blog????服務(wù)升降級之開關(guān)功能控制