服务降级及dubbo中的实现示例
經歷過12306搶票的人應該經常會遇到這個問題:在搶票高峰的時候,明明票還有,但是查詢出來的列表卻是為空的(如果沒票列表也應該會呈現);等高峰過后再查詢,列表又恢復正常。個人猜測應該是查詢過程中出現了問題,要么超時,要么網絡問題導致查詢失敗采用的服務降級處理。所以,最終呈現給用戶的并不是內部系統出錯之類的提示,而是一個空的列表。
服務降級,當服務器壓力劇增時,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此緩解了服務器資源壓力,以保證核心任務的正常運行,同時也保證了部分甚至大部分客戶得到正確響應。
服務降級實施策略
- 頁面拒絕服務:頁面提示由于服務繁忙此服務暫停。跳轉到varnish或nginx的一個靜態頁面。
- 服務接口拒絕服務:無用戶特定信息的頁面能訪問,提示服務器繁忙。頁面內容也可在緩存(Varnish)或CDN內獲取。
- 延遲持久化:頁面訪問照常,但是涉及記錄變更,會提示稍晚能看到結果,將數據記錄到異步隊列或log,服務恢復后執行。
- 隨機拒絕服務:服務接口隨機拒絕服務,讓用戶重試,目前較少采用。因為用戶體驗不佳。
服務降級埋點
- 消息中間件
所有API調用請求可以使用消息中間件進行控制,這可以有效地處理請求流量的增長,避免服務壓力過大,導致服務雪崩效應。缺點是,增加了服務處理和業務處理的復雜性。 - 前端頁面
指定網址不可訪問(NGINX等load balance來處理),處理簡單。缺點是用戶體驗非常不好。 - 底層數據層
拒絕所有增刪改動作,只允許查詢。此時用戶可以查看所有的查詢處理頁面和業務,但無法進行有更新修改的操作。
管理方式
直覺管理方式:運維人員可以指定哪些模塊降級。
當服務器檢測到壓力增大,服務器監測自動發送通知給運維人員,運維人員根據自己或相關人員判斷后通過配置平臺設置當前運行等級來降級。降級首先可以對非核心業務進行接口降級。如果效果不顯著,開始對一些頁面進行降級,以此保證核心功能的正常運行。
分級管理方式:運維人員無需關心業務細節,直接按級別降低即可。
業務確定好對應業務的優先級別,指定好分級降級方案。當服務器檢測到壓力增大,服務檢測自動發送通知給運維人員。運維人員根據情況選擇運行等級.
而各個應用還可以根據自己的級別自動判斷是否工作,如何拒絕
在dubbo平臺上實現服務降級
最后,在dubbo中想實現服務降級,需要怎么樣做可以實現?
dubbo中的處理
dubbo開發中,可能由于服務沒有啟動或者網絡不通,調用中會出現RpcException,也就是遠程調用失敗。如果是服務啟動順序的問題,可能加工check=”false”的配置可以得到很好的解決。但是,如果是服務宕掉或者并發數太高導致的RpcException該如何處理?
查看dubbo的官方文檔,可以發現有個mock的配置,mock只在出現非業務異常(比如超時,網絡異常等)時執行。mock的配置支持兩種,一種為boolean值,默認的為false。如果配置為true,則缺省使用mock類名,即類名+Mock后綴;另外一種則是配置”return null”,可以很簡單的忽略掉異常。
一個示例
服務接口
/**接口定義*/ public interface IUser {public void addUser(User u);public User getUserById(int id);}/**實現類*/ public class UserImpl implements IUser {private static List<User> USER_LIST = new ArrayList<User>();static{for(int i=0;i<10;i++){User u = new User();u.setAddress("address"+i);u.setId(i);u.setName("name"+i);USER_LIST.add(u);}}public void addUser(User u) {USER_LIST.add(u);System.out.println("total:"+USER_LIST.size());}public User getUserById(int id) {for(int i=0;i<USER_LIST.size();i++){if(USER_LIST.get(i).getId() == id){return USER_LIST.get(i);}}return null;} }服務提供方的配置:
dubbo-provider.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"><!-- 提供方應用信息,用于計算依賴關系 --><dubbo:application name="hello-world-app" /><!-- 使用multicast廣播注冊中心暴露服務地址 --><dubbo:registry address="zookeeper://127.0.0.1:2181" /><!-- 用dubbo協議在20880端口暴露服務 --><dubbo:protocol name="dubbo" port="20880" /><!-- 聲明需要暴露的服務接口 --><dubbo:service interface="com.dubbosample.iface.IUser" ref="userImpl" timeout="10000" /><!-- 和本地bean一樣實現服務 --><bean id="userImpl" class="com.dubbosample.ifaceimpl.UserImpl" /></beans>服務調用方的配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"><!-- 消費方應用名,用于計算依賴關系,不是匹配條件,不要與提供方一樣 --><dubbo:application name="dubbo-consumer" /><dubbo:registry address="zookeeper://127.0.0.1:2181" /><!-- 生成遠程服務代理,可以和本地bean一樣使用demoService --><dubbo:reference id="iUser" interface="com.dubbosample.iface.IUser" timeout="10000" check="false" mock="return null"></dubbo:reference></beans>服務調用的測試代碼:
public static void main(String[] args) throws Exception{ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"classpath:dubbo-consumer.xml"});context.start();IUser iUser = (IUser)context.getBean("iUser");User u = new User();u.setAddress("aaa");u.setId(311);u.setName("n3");iUser.addUser(u);System.out.println(iUser.getUserById(1));}測試時,如果服務啟動,則程序按照預期的運行正常;如果服務沒啟動,則此時運行程序,程序并未報錯,輸出數據為null。
說明
通過以上的例子可以知道,通過mock的配置,可以很好的實現dubbo服務降級。但是,仔細查看上面的例子會發現,IUser本身定義了兩個接口,一個是新增用戶,一個是根據id查詢用戶信息。對于根據id查詢用戶信息,在調用失敗的時候返回null很好理解,可能是由于驗證失敗或者記錄刪除了,但是對于新增用戶,可能就需要拋出具體的業務信息,否則程序無法處理后續的業務,包括頁面彈出”添加成功“或者列表刷新的時候無法查看到最新的記錄,這樣體驗將會非常不好。所以,如果要有較好的區分,可以通過以下的方式,可以更好的實現降級:
(1)將接口進行歸類,分成查詢操作類、變更操作類:對于查詢的操作分為一個接口類,變更的歸類為其他的接口類,這樣對于查詢的可以使用mock=”return null”進行降級操作;對于變更類的操作接口,可以仍舊使用try……catch進行異常捕獲處理;
(2)配置mock=”true”,同時mock實現接口,接口名要注意命名規范:接口名+Mock后綴。此時如果調用失敗會調用Mock實現。mock實現需要保證有無參的構造方法。
配置mock=”true”的情況,對于上面的例子即在IUser的同個路徑下,添加類IUserMock,實現如下:
public class IUserMock implements IUser {@Overridepublic void addUser(User u) {throw new RuntimeException("add user fail!");}@Overridepublic User getUserById(int id) {return null;} }總結
以上是生活随笔為你收集整理的服务降级及dubbo中的实现示例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos8 系统分区规划
- 下一篇: dubbo中对服务多版本的支持