文章內容均爲轉載,本人記錄學習使用。前端
https://blog.csdn.net/z69183787/article/details/54601777 (原貼地址,感謝做者 OkidoGreen。)java
首先講一下開關的由來,例如東京在6月18日作店慶促銷活動,在交易下單環節,可能須要調用A、B、C三個接口來完成,可是其實A和B是必須的,C只是附加的功能(例如在下單的時候作一下推薦),無關緊要,在平時系統沒有壓力,容量充足的狀況下,調用下沒問題,可是在相似店慶之類的大促環節,系統已經滿負荷了,這時候其實徹底能夠不去調用C接口,怎麼實現這個呢?改代碼?no,no,no,這樣太不敏捷,此時開關誕生了,開發人員只要簡單執行一下命令或者點一下頁面,就能夠關掉對於C接口的調用,在大促過去以後,再把開關恢復回去便可。linux
什麼是服務降級
服務降級,當服務器壓力劇增的狀況下,根據當前業務狀況及流量對一些服務和頁面有策略的降級,以此釋放服務器資源以保證核心任務的正常運行。
服務降級方式:
服務接口拒絕服務:無用戶特定信息,頁面能訪問,可是添加刪除提示服務器繁忙。頁面內容也可在Varnish或CDN內獲取。
頁面拒絕服務:頁面提示因爲服務繁忙此服務暫停。跳轉到varnish或nginx的一個靜態頁面。
延遲持久化:頁面訪問照常,可是涉及記錄變動,會提示稍晚能看到結果,將數據記錄到異步隊列或log,服務恢復後執行。
隨機拒絕服務:服務接口隨機拒絕服務,讓用戶重試,目前較少有人採用。由於用戶體驗不佳。
服務降級埋點的地方:
消息中間件:全部API調用可使用消息中間件進行控制
前端頁面:指定網址不可訪問(NGINX+LUA)
底層數據驅動:拒絕全部增刪改動做,只容許查詢nginx
問題一:在單個java系統中如何實現開關功能?數據庫
其實對於開關來講,對應Java中的類型,很好映射,就是一個boolean值,在須要作開關操做的地方,調用這個屬性,判斷狀態,而後走相應的邏輯便可。這個類是一個單例,保證全局惟一(代碼就不寫了,單例模式通常是學習設計模式中最開始接觸的呵呵)。apache
問題二:單個java系統中,如何實現開關值變動的操做呢?設計模式
在單機系統中,改變開關的狀態很簡單(留一個口子,外部能夠改變屬性的值,例如改成true或者false),這時候,能夠是頁面來維護開關,經過頁面的點擊類改變這個全局惟一的屬性,從而實現開關動做的觸發。緩存
問題三:多個同構java系統,如何實現開關狀態的同步呢?服務器
經過一和二的介紹,在單機狀況下,開關的變動能夠了,可是在多個同構(這裏的同構,值得是部署的同一套代碼,邏輯徹底相同,相似Master和Slaver的模式)系統中,如何保持一致呢?單例模式,開關屬性是被加載到本地緩存,就是說java一直持有的對象,在FullGC的時候回收不走的那種。這個時候,若是要保持各個系統中開關屬性狀態的一致,就須要從第三方外部系統中加載這個數據。curl
什麼系統能充當第三方外部系統呢?能夠是一個數據庫訪問系統,咱們暫且稱之爲MetaServer,開關的屬性防止在DB中,而後MetaServer提供頁面來修改數據,同時提供接口讀取開關的數據,在應用啓動的時候,經過MetaServer來讀取數據,加載到本地緩存中。這時候就有個問題,就是我經過MetaServer的頁面改變了值,各個應用如何知道我改變了屬性呢?這個時候就須要經過一些辦法(辦法不少,能夠是消息系統,能夠是zookeeper,能夠是頁面觸發)來清理一下開關屬性的緩存,讓緩存從新加載一下,從而實現最新的狀態獲取。
整體思路就是:metaServer維護開關數據--應用讀取DB中的數據到本地緩存--DB中數據變動--觸發開關屬性緩存從新加載。
這個是否是有點複雜,有沒有更加簡單的辦法?固然有了,以前淘寶開源了一個系統diamond(持久化配置管理系統,http://code.taobao.org/p/diamond/wiki/index/),其實能夠理解爲「配置信息的僞推送服務」,例如我變動了一個開關的屬性,再也不須要作清理緩存的事情,diamond幫你作掉了(原理很簡單,例如系統A訂閱了在diamond中的開關信息,這時候A會啓動一個線程,每隔一段時間來輪循diamond的服務端,看看開關屬性的數據有沒有變動,若是有變動,在diamond服務端來加載最新的數據)。
整體思路是:在diamond中維護配置信息--系統訂閱開關屬性--系統輪循配置是否有變動,有變動直接就變掉了。
問題四:開關設計的幾個坑
有時候,咱們爲了方便,沒有藉助問題三種的MetaServer或者diamond的方式,就是留了一個HTTP的接口來觸發修改開關(多臺機器的話,能夠寫批量腳本),這時候其實須要咱們在apache或者nginx中,把這個URL的訪問禁止掉,防止惡意用戶在外部拼湊連接來進行開關的變更,這時候只能在服務器上經過linux的curl來觸發操做了。
還有一個,就是若是經過HTTP的形式來修改開關的屬性,有個是須要注意的,就是開關的執行要冪等操做,這樣方便操做,避免出現集羣中數據不一致的狀態(就是執行開,開關就是開,不能第一次執行是開,第二次執行是關)。
問題五:開關組合狀況下怎麼搞?
上面的幾種狀況,僅僅是執行單個開關,應該比較簡單。可是我同時又A、B、C三個開關,在不一樣的業務場景下,可能須要關閉A和B開關,在另一個場景下,可能須要關閉A和C開關,這時候認爲操做有可能會有遺漏或者疏忽,怎麼搞呢?在單獨屬性開關的基礎上作封裝,例如A和B上面增長一層屬性,暫且叫「AB」,修改AB的值,對應的系統修改A和B的值,這樣就避免人肉記住一些組合。
問題六:如何實現自動升降級?
上面的狀況,都是在提起能夠預知的狀況下,咱們作一些人爲的操做,這個能不能自動化?固然能夠,就是這一小節討論的自動升降級。
舉例子,如今東京和做的外部物流公司有多家,會調用它們的系統或者物流節點的狀態,這個時候,物流公司系統是不穩定的,若是掛了或者響應時間慢了,對於自身的系統會影響比較大,比較理想的辦法是,在物流公司系統出現問題的時候,這塊邏輯自動降級處理,而後等物流公司系統好了以後,再把這部分邏輯自動升級,整個過程沒有人爲參與,自動保持系統穩定性。這裏說一下整體思路:
第一步:搞一個計數器,記錄接口,暫定A的調用成功次數、失敗次數以及響應時間;
第二步:將這些信息放入隊列中,同時設置閥值(例如RT超過5秒就降級,1秒就升級)以及閥值觸發改動的開關;
第三部:異步啓動一個線程,掃描隊列,達到咱們的條件,就觸發作變動(有個問題,就是加入業務降級了,這時候就沒有調用量,也就沒有了自動升級的條件了,怎麼搞呢?這時候業務降級,並非徹底100%的停掉,能夠預留一部分流量繼續調用A,把A調用的信息放入隊列中,根據這些信息,就能實現升級了);
總結:
上面這些是在陸續的系統維護中嘗試或者看到的處理辦法,經過開關的方式,實現系統的升降級,從而更好的保護系統。這篇文章只是闡述了大致的思路,沒有涉及到具體的代碼,但願可以達到拋磚引玉的做用。