在前文裏,咱們講述了經過Hystrix進行容錯處理的方式,這裏咱們將講述經過Hystrix合併請求的方式html
哪怕一個URL請求調用的功能再簡單,Web應用服務都至少會開啓一個線程來提供服務,換句話說,有效下降URL請求數能很大程度上下降系統的負載。經過Hystrix提供的「合併請求」機制,咱們能有效地下降請求數量。java
在以下的HystrixMergeDemo.java裏,咱們將收集2秒內到達的全部「查詢訂單」的請求,並把它們合併到一個對象中傳輸給後臺,後臺則是根據多個請求參數統一返回查詢結果,這種基於合併的作法將比每次只處理一個請求的方式要高效得多,代碼比較長,咱們按類來講明。 數據庫
1 //省略必要的package和import的代碼 2 class OrderDetail{ //訂單業務類,其中包含2個屬性 3 private String orderId; 4 private String orderOwner; 5 //省略針對orderId和orderOwner的get和set方法 6 //重寫toString方法,方便輸出 7 public String toString() { 8 return "orderId: " + orderId + ", orderOwner: " + orderOwner ; 9 } 10 } 11 //合併訂單請求的處理器 12 class OrderHystrixCollapser extends HystrixCollapser<Map<String, OrderDetail>, OrderDetail, String> 13 { 14 String orderId; 15 //在構造函數裏傳入請求參數 16 public OrderHystrixCollapser(String orderId) 17 { this.orderId = orderId;} 18 //指定根據orderId去請求OrderDetail 19 public String getRequestArgument() 20 { return orderId; } 21 //建立請求命令 22 protected HystrixCommand<Map<String, OrderDetail>> createCommand( 23 Collection<CollapsedRequest<OrderDetail, String>> requests) 24 { return new MergerCommand(requests); } 25 //把請求獲得的結果和請求關聯到一塊兒 26 protected void mapResponseToRequests(Map<String, OrderDetail> batchResponse, 27 Collection<CollapsedRequest<OrderDetail, String>> requests) { 28 for (CollapsedRequest<OrderDetail, String> request : requests) 29 { 30 // 請注意這裏是獲得單個請求的結果 31 OrderDetail oneOrderDetail = batchResponse.get(request.getArgument()); 32 // 把結果關聯到請求中 33 request.setResponse(oneOrderDetail); 34 } 35 } 36 }
在第2行,咱們定義了OrderDetail類,這裏,咱們將合併針對該類對象的請求。緩存
在第12行,咱們定義了合併訂單的處理器OrderHystrixCollapser類, 它繼承(extends)了HystrixCollapser<Map<String, OrderDetail>, OrderDetail, String>類,而HystrixCollapser泛型中包含了3個參數,其中第一個參數Map<String, OrderDetail>表示該合併處理器合併請求後返回的結果類型,第二個參數表示是合併OrderDetail類型的對象,第三個參數則表示是根據String類型的請求參數來合併對象。架構
在第19行裏,咱們指定了是根據String類型的OrderId參數來請求OrderDetail對象,在第22行的createCommand方法裏,咱們指定了是調用MergerCommand方法來請求多個OrderDetail,在第26行的mapResponseToRequests方法裏,咱們是用第28行的for循環,依次把batchResponse對象中包含的多個的查詢結果設置到request對象裏,因爲request是參數requests裏的元素,因此執行完第28行的for循環後,requests對象就能關聯到合併後的查詢結果。 負載均衡
37 class MergerCommand extends HystrixCommand<Map<String, OrderDetail>> { 38 //用orderDB模擬數據庫中的數據 39 static HashMap<String,String> orderDB = new HashMap<String,String> (); 40 static { 41 orderDB.put("1","Peter"); 42 orderDB.put("2","Tom"); 43 orderDB.put("3","Mike"); 44 } 45 Collection<CollapsedRequest<OrderDetail, String>> requests; 46 public MergerCommand(Collection<CollapsedRequest<OrderDetail, String>> requests) { 47 super(Setter.withGroupKey(HystrixCommandGroupKey.Factory 48 .asKey("mergeDemo"))); 49 this.requests = requests; 50 } 51 //在run方法里根據請求參數返回結果 52 protected Map<String, OrderDetail> run() throws Exception { 53 List<String> orderIds = new ArrayList<String>(); 54 //經過for循環,整合參數 55 for(CollapsedRequest<OrderDetail, String> request : requests) 56 { orderIds.add(request.getArgument()); } 57 // 調用服務,根據多個訂單Id得到多個訂單對象 58 Map<String, OrderDetail> ordersHM = getOrdersFromDB(orderIds); 59 return ordersHM; 60 } 61 //用HashMap模擬數據庫,從數據庫中得到對象 62 private Map<String, OrderDetail> getOrdersFromDB(List<String> orderIds) { 63 Map<String, OrderDetail> result = new HashMap<String, OrderDetail>(); 64 for(String orderId : orderIds) { 65 OrderDetail order = new OrderDetail(); 66 //這個本該是從數據庫裏獲得,但爲了模擬,僅從HashMap裏取數據 67 order.setOrderId(orderId); 68 order.setOrderOwner(orderDB.get(orderId) ); 69 result.put(orderId, order); 70 } 71 return result; 72 } 73 }
在MergerCommand類的第38到44行裏,咱們用了orderDB對象來模擬數據庫裏存儲的訂單數據。在第46行的構造函數裏,咱們用傳入的requests對象來構建本類裏的同名對象,在這個傳入的requests對象裏,已經包含了合併後的請求。異步
在第52行的run方法裏,咱們經過第55行的for循環,依次遍歷requests對象,並組裝包含請求參數集合的orderIds對象,隨後在第58行裏,經過getOrdersFromDB方法,根據List類型的orderIds參數,模擬地從數據庫裏讀取數據。 函數
74 public class HystrixMergeDemo{ 75 public static void main(String[] args){ 76 // 收集2秒內發生的請求,合併爲一個命令執行 77 ConfigurationManager.getConfigInstance().setProperty( "hystrix.collapser.default.timerDelayInMilliseconds", 2000); 78 // 初始化請求上下文 79 HystrixRequestContext context = HystrixRequestContext .initializeContext(); 80 // 建立3個請求合併處理器 81 OrderHystrixCollapser collapser1 = new OrderHystrixCollapser("1"); 82 OrderHystrixCollapser collapser2 = new OrderHystrixCollapser("2"); 83 OrderHystrixCollapser collapser3 = new OrderHystrixCollapser("3"); 84 // 異步執行 85 Future<OrderDetail> future1 = collapser1.queue(); 86 Future<OrderDetail> future2 = collapser2.queue(); 87 Future<OrderDetail> future3 = collapser3.queue(); 88 try { 89 System.out.println(future1.get()); 90 System.out.println(future2.get()); 91 System.out.println(future3.get()); 92 } catch (InterruptedException e) { 93 e.printStackTrace(); 94 } catch (ExecutionException e) { 95 e.printStackTrace(); 96 } 97 /關閉請求上下文 98 context.shutdown(); 99 } 100 }
第74行定義的HystrixMergeDemo類裏包含着main方法,在第77行裏,咱們設置了合併請求的窗口時間是2秒,在第81到83行,建立了3個合併處理器對象,從第85到87行,咱們是經過queue 方法,以異步的方式啓動了三個處理器,並在第89到91行裏,輸出了三個處理器返回的結果。這個程序的運行結果以下。 post
1 orderId: 1, orderOwner: Peter 2 orderId: 2, orderOwner: Tom 3 orderId: 3, orderOwner: Mike
雖然在main方法裏,咱們發起了3次調用,但因爲這些調用是發生在2秒內的,因此會被合併處理,下面咱們結合上述針對類和方法的說明,概括下合併處理3個請求的流程。測試
步驟一,在代碼的81到83行裏,咱們是經過OrderHystrixCollapser類型的collapser1等三個對象來傳入待合併處理的請求,OrderHystrixCollapser類會經過第16行的構造函數,分別接收三個對象傳入的orderId參數,並經過第22行的createCommand方法,調用MergerCommand類的方法執行「根據訂單Id查訂單」的業務。
這裏說明下,因爲在OrderHystrixCollapser內第16行的getRequestArgument方法裏,咱們指定了查詢參數名是orderId,因此createCommand方法的requests參數,會用orderId來設置查詢請求,同時,MergerCommand類中的相關方法也會用該對象來查詢OrderDetail信息。
步驟二,因爲在createCommand方法裏,調用了MergerCommand類的構造函數,因此會觸發該類第52行的run方法,在這個方法裏,經過第55行和第56行的for循環,把request請求中包含的多個Argument(也就是OrderId)放入到orderIds這個List類型的對象中,隨後經過第58行的getOrdersFromDB方法,根據這些orderIds去找對應的OrderDetail對象。
步驟三,在getORdersFromDB方法裏,找到對應的多個OrderDetail對象,並組裝成Map<String, OrderDetail>類型的result對象返回,而後按調用鏈的關係,層層返回給OrderHystrixCollapser類。
步驟四,在OrderHystrixCollapser類的mapResponseToRequests方法裏,經過for循環,把屢次請求的結果組裝到requests對象中。因爲requests對象是Collection<CollapsedRequest<OrderDetail, String>>類型的,其中用String類型的OrderId關聯到了一個OrderDetail對象,因此這裏會把合併查詢的結果再拆散給3次請求,具體而言,會把3個OrderDetail對象對應地返回給第85行到第87行經過queue調用的3個請求。
這裏請注意,雖然經過合併請求的處理方法能下降URL請求的數量,但若是合併後的URL請求數過多,會撐爆掉合併處理器(這裏是OrderHystrixCollapser類)的緩存。好比在某項 目裏,雖然只設置了合併5秒內的請求,但正好遇上秒殺活動,在這個窗口期內的請求數過萬,那麼就有可能出問題。
因此通常會在上線前,先經過測試肯定合併處理器的緩存容量,隨後再預估下平均每秒的可能訪問數,而後再據此設置合併的窗口時間。
本人以前寫的和本文有關的Spring Cloud其它相關文章。