In the microservice architecture, we split a project into many independent modules, which work together through remote calls. However, in the case of high concurrency, the increase in the number of communications will lead to an increase in the total communication time. At the same time, the resources of the thread pool are also limited. A high concurrency environment will cause a large number of threads to be in a waiting state, which will lead to response delays. In order to solve these problems, we need to understand Hystrix's request merging.
Request merging in Hystrix is to use a merge processor to merge consecutive requests initiated by the same service into one request for processing (the time window for these continuous requests is 10ms by default). One of the core classes involved in this process is HystrixCollapser, OK. Next, let’s take a look at how to implement Hystrix request merging.
Service Provider Interface
I need to provide two interfaces in the service provider for service consumers to call, as follows:
@RequestMapping("/getbook6")public List<Book> book6(String ids) { System.out.println("ids>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 33, "Hu Shi", "None")); books.add(new Book("ids", 22, "helloworld", "haha")); return books;}@RequestMapping("/getbook6/{id}")public Book book61(@PathVariable Integer id) { Book book = new Book("Li Zicheng 2", 55, "Yao Xueying 2", "People's Literature Publishing House 2"); return book;}The first interface is a batch interface and the second interface is an interface that handles a single request. In the batch interface, the ids parameter format sent by the service consumer is 1, 2, 3, 4... This format. Under normal circumstances, we need to query the corresponding data based on the ids, and then assemble it into a set to return. For the sake of easy processing, I will return the same data set no matter what kind of request; the interface for processing a single request is relatively simple, so I won't repeat it.
Serving consumers
OK, after the service provider has handled it, let’s take a look at how the service consumers should handle it.
BookService
First, add two methods to the BookService to call the interface provided by the service provider, as follows:
public Book test8(Long id) { return restTemplate.getForObject("http://HELLO-SERVICE/getbook6/{1}", Book.class, id);}public List<Book> test9(List<Long> ids) { System.out.println("test9------------"+ids+"Thread.currentThread().getName():" + Thread.currentThread().getName()); Book[] books = restTemplate.getForObject("http://HELLO-SERVICE/getbook6?ids={1}", Book[].class, StringUtils.join(ids, ",")); return Arrays.asList(books);}test8 is used to call the interface that provides a single id, test9 is used to call the interface that batch processing. In test9, I print out the thread where test9 is executed to facilitate us to observe the execution results. In addition, in RestTemplate, if the return value is a collection, we have to receive it first with an array, and then convert it to a collection (maybe there are other methods, friends have better suggestions to make).
BookBatchCommand
OK, after the method in BookService is ready, we can create a BookBatchCommand, which is a batch command, as follows:
public class BookBatchCommand extends HystrixCommand<List<Book>> { private List<Long> ids; private BookService bookService; public BookBatchCommand(List<Long> ids, BookService bookService) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("CollapsingGroup"))) .andCommandKey(HystrixCommandKey.Factory.asKey("CollapsingKey"))); this.ids = ids; this.bookService = bookService; } @Override protected List<Book> run() throws Exception { return bookService.test9(ids); }}This class is actually similar to the class we introduced in the previous blog. They are both inherited from HystrixCommand and are used to handle merged requests and call the test9 method in BookService in the run method.
BookCollapseCommand
Next we need to create BookCollapseCommand inherited from HystrixCollapser to implement request merge. as follows:
public class BookCollapseCommand extends HystrixCollapseer<List<Book>, Book, Long> { private BookService bookService; private Long id; public BookCollapseCommand(BookService bookService, Long id) { super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("bookCollapseCommand")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100))); this.bookService = bookService; this.id = id; } @Override public Long getRequestArgument() { return id; } @Override protected HystrixCommand<List<Book>> createCommand(Collection<CollapsedRequest<Book, Long>> collapsedRequests) { List<Long> ids = new ArrayList<>(collapsedRequests.size()); ids.addAll(collapsedRequests.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList())); BookBatchCommand bookBatchCommand = new BookBatchCommand(ids, bookService); return bookBatchCommand; } @Override protected void mapResponseToRequests(List<Book> batchResponse, Collection<CollapsedRequest<Book, Long>> collapsedRequests) { System.out.println("mapResponseToRequests"); int count = 0; for (CollapsedRequest<Book, Long> collapsedRequest : collapsedRequests) { Book book = batchResponse.get(count++); collapsedRequest.setResponse(book); } }}Regarding this class, I will say the following points:
1. First of all, in the construction method, we set the request time window to 100ms, that is, the requests with the request time interval within 100ms will be merged into one request.
2. The createCommand method is mainly used to merge requests, obtain the ids of each single request here, put these single ids into a collection, and then create a BookBatchCommand object, and use this object to initiate a batch request.
3. The mapResponseToRequests method is mainly used to set the request result for each request. The first parameter of this method represents the result of the batch request, and the second parameter collapsedRequests represents each merged request. Then we set the request result for collapsedRequests by traversing batchResponse.
OK, after all these operations are completed, we can come and test it.
test
We create an access interface on the service consumer side to test the merge request. The test interface is as follows:
@RequestMapping("/test7")@ResponseBodypublic void test7() throws ExecutionException, InterruptedException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); BookCollapseCommand bc1 = new BookCollapseCommand(bookService, 1l); BookCollapseCommand bc2 = new BookCollapseCommand(bookService, 2l); BookCollapseCommand bc3 = new BookCollapseCommand(bookService, 3l); BookCollapseCommand bc4 = new BookCollapseCommand(bookService, 4l); Future<Book> q1 = bc1.queue(); Future<Book> q2 = bc2.queue(); Future<Book> q3 = bc3.queue(); Book book1 = q1.get(); Book book2 = q2.get(); Book book3 = q3.get(); Thread.sleep(3000); Future<Book> q4 = bc4.queue(); Book book4 = q4.get(); System.out.println("book1>>>"+book1); System.out.println("book2>>>"+book2); System.out.println("book3>>>"+book3); System.out.println("book4>>>"+book4); context.close();}Regarding this test interface, I said the following two points:
1. First, you need to initialize the HystrixRequestContext
2. Create an instance of BookCollapseCommand class to initiate a request, send 3 requests first, then sleep for 3 seconds, and then initiate a request. In this way, the first 3 requests will be merged into one request. The fourth request will not be merged because the interval between them is relatively long, but will create a thread to process it separately.
OK, let's take a look at the execution results, as follows:
Request merge is implemented through annotation
OK, the above request merging method is a little troublesome to write, we can use annotations to implement this function more elegantly. First, add two methods in BookService, as follows:
@HystrixCollapser(batchMethod = "test11",collapserProperties = {@HystrixProperty(name ="timerDelayInMilliseconds",value = "100")})public Future<Book> test10(Long id) { return null;}@HystrixCommandpublic List<Book> test11(List<Long> ids) { System.out.println("test9----------"+ids+"Thread.currentThread().getName():" + Thread.currentThread().getName()); Book[] books = restTemplate.getForObject("http://HELLO-SERVICE/getbook6?ids={1}", Book[].class, StringUtils.join(ids, ",")); return Arrays.asList(books);}Add the @HystrixCollapser annotation to implement request merge on the test10 method, use the batchMethod attribute to indicate the processing method after request merge, and the collapserProperties attribute to specify other attributes.
OK, after writing it in BookService, just call it directly, as follows:
@RequestMapping("/test8")@ResponseBodypublic void test8() throws ExecutionException, InterruptedException { HystrixRequestContext context = HystrixRequestContext.initializeContext(); Future<Book> f1 = bookService.test10(1l); Future<Book> f2 = bookService.test10(2l); Future<Book> f3 = bookService.test10(3l); Book b1 = f1.get(); Book b2 = f2.get(); Book b3 = f3.get(); Thread.sleep(3000); Future<Book> f4 = bookService.test10(4l); Book b4 = f4.get(); System.out.println("b1>>>"+b1); System.out.println("b2>>>"+b2); System.out.println("b3>>>"+b3); System.out.println("b4>>>"+b4); context.close();}Like the previous one, the first three requests will be merged, and the fourth request will be executed separately. OK, and the execution result is as follows:
Summarize
Advantages of request merger, friends have seen that multiple requests are merged into a request for one-time processing, which can effectively save network bandwidth and thread pool resources. However, there are advantages and disadvantages. After setting up the request merge, a request might have been completed in 5ms, but now you have to wait for another 10ms to see if there are any other requests together. In this way, the time consumption of a request will be increased from 5ms to 15ms. However, if the command we are going to initiate is a high-delay command, then you can use the request merge at this time, because the time consumption of the time window is insignificant at this time. In addition, high concurrency is also a very important scenario for request mergers.
Ok, that’s all for our request to merge. If you have any questions, please leave a message and discuss. I hope it will be helpful to everyone's learning, and I hope everyone will support Wulin.com more.