架構師系列文:通過Spring Cloud元件Hystrix合併請求
在前文裡,我們講述了通過Hystrix進行容錯處理的方式,這裡我們將講述通過Hystrix合併請求的方式
哪怕一個URL請求呼叫的功能再簡單,Web應用服務都至少會開啟一個執行緒來提供服務,換句話說,有效降低URL請求數能很大程度上降低系統的負載。通過Hystrix提供的“合併請求”機制,我們能有效地降低請求數量。
在如下的HystrixMergeDemo.java裡,我們將收集2秒內到達的所有“查詢訂單”的請求,並把它們合併到一個物件中傳輸給後臺,後臺則是根據多個請求引數統一返回查詢結果,這種基於合併的做法將比每次只處理一個請求的方式要高效得多,程式碼比較長,我們按類來說明。
1//省略必要的package和import的程式碼 2class OrderDetail{ //訂單業務類,其中包含2個屬性 3private String orderId; 4private String orderOwner; 5//省略針對orderId和orderOwner的get和set方法 6//重寫toString方法,方便輸出 7public String toString() { 8return "orderId: " + orderId + ", orderOwner: " + orderOwner ; 9} 10} 11//合併訂單請求的處理器 12class OrderHystrixCollapser extendsHystrixCollapser<Map<String, OrderDetail>, OrderDetail, String> 13{ 14String orderId; 15//在建構函式裡傳入請求引數 16public OrderHystrixCollapser(String orderId) 17{ this.orderId = orderId;} 18//指定根據orderId去請求OrderDetail 19public String getRequestArgument() 20{ return orderId;} 21//建立請求命令 22protected HystrixCommand<Map<String, OrderDetail>> createCommand( 23Collection<CollapsedRequest<OrderDetail, String>> requests) 24{ return new MergerCommand(requests); } 25//把請求得到的結果和請求關聯到一起 26protected void mapResponseToRequests(Map<String, OrderDetail> batchResponse, 27Collection<CollapsedRequest<OrderDetail, String>> requests) { 28for (CollapsedRequest<OrderDetail, String> request : requests) 29{ 30// 請注意這裡是得到單個請求的結果 31OrderDetail oneOrderDetail = batchResponse.get(request.getArgument()); 32// 把結果關聯到請求中 33request.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物件就能關聯到合併後的查詢結果。
37class MergerCommand extends HystrixCommand<Map<String, OrderDetail>> { 38//用orderDB模擬資料庫中的資料 39static HashMap<String,String> orderDB = new HashMap<String,String> (); 40static{ 41orderDB.put("1","Peter"); 42orderDB.put("2","Tom"); 43orderDB.put("3","Mike"); 44} 45Collection<CollapsedRequest<OrderDetail, String>> requests; 46public MergerCommand(Collection<CollapsedRequest<OrderDetail, String>> requests) { 47super(Setter.withGroupKey(HystrixCommandGroupKey.Factory 48.asKey("mergeDemo"))); 49this.requests = requests; 50} 51//在run方法里根據請求引數返回結果 52protected Map<String, OrderDetail> run() throws Exception { 53List<String> orderIds = new ArrayList<String>(); 54//通過for迴圈,整合引數 55for(CollapsedRequest<OrderDetail, String> request : requests) 56{ orderIds.add(request.getArgument());} 57// 呼叫服務,根據多個訂單Id獲得多個訂單物件 58Map<String, OrderDetail> ordersHM = getOrdersFromDB(orderIds); 59return ordersHM; 60} 61//用HashMap模擬資料庫,從資料庫中獲得物件 62private Map<String, OrderDetail> getOrdersFromDB(List<String> orderIds) { 63Map<String, OrderDetail> result = new HashMap<String, OrderDetail>(); 64for(String orderId : orderIds) { 65OrderDetail order = new OrderDetail(); 66//這個本該是從資料庫裡得到,但為了模擬,僅從HashMap裡取資料 67order.setOrderId(orderId); 68order.setOrderOwner(orderDB.get(orderId) ); 69result.put(orderId, order); 70} 71return result; 72} 73}
在MergerCommand類的第38到44行裡,我們用了orderDB物件來模擬資料庫裡儲存的訂單資料。在第46行的建構函式裡,我們用傳入的requests物件來構建本類裡的同名物件,在這個傳入的requests物件裡,已經包含了合併後的請求。
在第52行的run方法裡,我們通過第55行的for迴圈,依次遍歷requests物件,並組裝包含請求引數集合的orderIds物件,隨後在第58行裡,通過getOrdersFromDB方法,根據List型別的orderIds引數,模擬地從資料庫裡讀取資料。
74public class HystrixMergeDemo{ 75public static void main(String[] args){ 76// 收集2秒內發生的請求,合併為一個命令執行 77ConfigurationManager.getConfigInstance().setProperty("hystrix.collapser.default.timerDelayInMilliseconds", 2000); 78// 初始化請求上下文 79HystrixRequestContext context = HystrixRequestContext.initializeContext(); 80// 建立3個請求合併處理器 81OrderHystrixCollapser collapser1 = new OrderHystrixCollapser("1"); 82OrderHystrixCollapser collapser2 = new OrderHystrixCollapser("2"); 83OrderHystrixCollapser collapser3 = new OrderHystrixCollapser("3"); 84// 非同步執行 85Future<OrderDetail> future1 = collapser1.queue(); 86Future<OrderDetail> future2 = collapser2.queue(); 87Future<OrderDetail> future3 = collapser3.queue(); 88try { 89System.out.println(future1.get()); 90System.out.println(future2.get()); 91System.out.println(future3.get()); 92} catch (InterruptedException e) { 93e.printStackTrace(); 94} catch (ExecutionException e) { 95e.printStackTrace(); 96} 97/關閉請求上下文 98context.shutdown(); 99} 100}
第74行定義的HystrixMergeDemo類裡包含著main方法,在第77行裡,我們設定了合併請求的視窗時間是2秒,在第81到83行,建立了3個合併處理器物件,從第85到87行,我們是通過queue 方法,以非同步的方式啟動了三個處理器,並在第89到91行裡,輸出了三個處理器返回的結果。這個程式的執行結果如下。
1orderId: 1, orderOwner: Peter 2orderId: 2, orderOwner: Tom 3orderId: 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其它相關文章。