架構師系列文:經過Spring Cloud組件Hystrix合併請求 架構師入門:Spring Cloud系列,Hystrix與Eureka的整合

    在前文裏,咱們講述了經過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其它相關文章。

 架構師入門:Spring Cloud系列,Hystrix與Eureka的整合

  Hystrix針對不可用服務的保護機制以及引入緩存

 經過案例瞭解Hystrix的各類基本使用方式 

相關文章
相關標籤/搜索