From e44494f6b38845fca4833dcfc79a3d819bcc21a3 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 28 Mar 2015 16:02:15 +0300 Subject: [PATCH 1/3] Update README.md #689: updated wiki: collapser section --- hystrix-contrib/hystrix-javanica/README.md | 150 ++++++++++++++------- 1 file changed, 101 insertions(+), 49 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index d5bea981b..8cb6fe022 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -380,73 +380,125 @@ ThreadPoolProperties can be set using @HystrixCommand's 'threadPoolProperties' l Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use ```@HystrixCollapser``` annotation. -**Asynchronous call** +Example: ```java + @HystrixCollapser(batchMethod = "getUserByIds") + public Future getUserById(String id) { + return null; + } + @HystrixCommand - @HystrixCollapser - public Future getUserAsync(final String id, final String name) { - return new AsyncResult() { - @Override - public User invoke() { - return new User(id, name + id); // there should be a network call - } - }; + public List getUserByIds(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; } + + +Future f1 = userService.getUserById("1"); +Future f2 = userService.getUserById("2"); +Future f3 = userService.getUserById("3"); +Future f4 = userService.getUserById("4"); +Future f5 = userService.getUserById("5"); +``` +A method annotated with {@link HystrixCollapser} annotation can return any value with compatible type, it does not affect the result of collapser execution, collapser method can even return {@code null} or another stub. +There are several rules applied for methods signatures. +1. Collapser method must have one argument of any type, desired a wrapper of a primitive type like Integer, Long, String and etc. +2. A batch method must have one argument with type java.util.List parameterized with corresponding type, that's if a type of collapser argument is Integer then type of batch method argument must be List. +3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be List + +**Convention for batch method behavior** +1. The size of response collection must be equal to the size of request collection. +```java + @HystrixCommand + public List getUserByIds(List ids); // batch method + + List ids = List("1", "2", "3"); + getUserByIds(ids).size() == ids.size(); + ``` +2. Order of elements in response collection must be same to order of elements in request collection. -**Synchronous call** -_Note_ : Request collapsing on a single thread makes no sense, because if a single thread is invoking ```execute()``` or ```queue().get()``` synchronously it will block and wait for a response and thus not submit any further requests that will be collapsed inside the window. Doing requests on a separate threads (such as user requests with collapsing scope set to GLOBAL) would make sense: +``` + @HystrixCommand + public List getUserByIds(List ids); // batch method + + List ids = List("1", "2", "3"); + List users = getUserByIds(ids); + System.out.println(users); + // output + User: id=1 + User: id=2 + User: id=3 +``` + +Why order of elements of request and response collections is important. The reason of this is in reducing logic, basically request elements are mapped one-to-one to response elements. Thus if order of elements of request collection is different then the result of execution can be unpredictable. + +**Deduplication batch command request parameters**. +In some cases your batch method can depend on behavior of third-party service or library that skips duplicates in a request. It can be a rest service that expects unique values. In this case the size of elements in request collection can be different from size of elements in response collection. It violates one of the behavior principle. To fix it you need to manually map request to response collection, for example: ```java - @HystrixCommand - @HystrixCollapser(scope = GLOBAL) - public User getUserSync(String id, String name) { - return new User(id, name + id); // there should be a network call - } +// hava 8 +@HystrixCommand +List batchMethod(List ids){ +// ids = [1, 2, 2, 3] +List users = restClient.getUsersByIds(ids); +// users = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +List response = ids.stream().map(it -> users.stream() + .filter(u -> u.getId().equals(it)).findFirst().get()) + .collect(Collectors.toList()); +// response = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +return response; ``` +Same case if you want to remove duplicate elements from request collection before a service call. +Example: +```java +// hava 8 +@HystrixCommand +List batchMethod(List ids){ +// ids = [1, 2, 2, 3] +List uniqueIds = ids.stream().distinct().collect(Collectors.toList()); +// uniqueIds = [1, 2, 3] +List users = restClient.getUsersByIds(uniqueIds); +// users = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +List response = ids.stream().map(it -> users.stream() + .filter(u -> u.getId().equals(it)).findFirst().get()) + .collect(Collectors.toList()); +// response = [User{id='1', name='user1'}, User{id='2', name='user2'}, User{id='2', name='user2'}, User{id='3', name='user3'}] +return response; +``` To set collapser [properties](https://github.com/Netflix/Hystrix/wiki/Configuration#Collapser) use `@HystrixCollapser#collapserProperties` Read more about Hystrix request collapsing [here] (https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-RequestCollapsing) **Collapser error processing** +Bath command can have a fallback method. +Example: -All commands are collapsed are instances of ```BatchHystrixCommand```. In BatchHystrixCommand the ```getFallback()``` isn't implemented, even if a collapsed command has fallback ``` @HystrixCommand(fallbackMethod = "fallbackCommand")``` then it never be processed in ```getFallback()```, below this moment is consecrated in detail. - -If the processing of a request fails then other requests will not be processed within this collapser and ```setException``` method will be automatically called on requests instances. Generally this is an all or nothing affair. For example, a timeout or rejection of the HystrixCommand would cause failure on all of the batched calls. Thus existence of fallback method does not eliminate the break of all requests and the collapser as a whole. Thus there are two scenarios that you can change using ```@HystrixCollapser#fallbackEnabled``` annotation property. - -When one of the requests has failed: - -**Scenario #1** (```@HystrixCollapser(fallbackEnabled = false)```): - -All requests that were processed successfully will be returned to an user and will be available from Future, other requests including one that failed will not. The fallback of collapsed command will not be executed in a case of any errors. -Short example: ```java + @HystrixCollapser(batchMethod = "getUserByIdsWithFallback") + public Future getUserByIdWithFallback(String id) { + return null; + } + + @HystrixCommand(fallbackMethod = "getUserByIdsFallback") + public List getUserByIdsWithFallback(List ids) { + throw new RuntimeException("not found"); + } - @HystrixCommand(fallbackMethod = "fallback") - @HystrixCollapser(fallbackEnabled = false) - public Future getUserAsync(final String id, final String name) { - // some logic here - }; - - // ....... - - - Future f1 = userService.getUserAsync("1", "name: "); - Future f2 = userService.getUserAsync("2", "name: "); - Future f3 = userService.getUserAsync("not found", "name"); // not found, exception here - Future f4 = userService.getUserAsync("4", "name: "); // will not be processed - Future f5 = userService.getUserAsync("5", "name: "); // will not be processed - System.out.println(f1.get().getName()); // this line will be executed - System.out.println(f2.get().getName()); // this line will be executed - System.out.println(f3.get().getName()); // this line will not be executed - System.out.println(f4.get().getName()); // this line will not be executed - System.out.println(f5.get().getName()); // this line will not be executed -``` - -**Scenario #2** (```@HystrixCollapser(fallbackEnabled = true)```): -All requests that were processed successfully or failed will be returned to an user and will be available from a Future instance. For failed requests the collapsed command's fallback method will be executed, of course if the name of fallback method was defined in ```@HystrixCommand```. The fallback of collapsed command will be used to process any exceptions during batch processing. If the fallback method hasn't ```@HystrixCommand``` annotation then this method will be processed in the a single thread with the BatchHystrixCommand in method _run()_, otherwise fallback will be process as Hystrix command in separate thread if fallback method was annotated with ```@HystrixCommand```. This is important point that you need to pay attention if you want to use fallback for your collapsed command. Thus fallback method will be processed in ```BatchHystrixCommand#run()``` not in ```BatchHystrixCommand#getFallback()```, but fallback can be process as Hystrix command therefore set the ```fallbackEnabled``` as true can be useful in some situations to avoid crash of whole collapser and continue to process requests, but I recommend to use ```@HystrixCollapser(fallbackEnabled = false)``` and give preference to the first scenario as this is the correct behavior in most cases. + @HystrixCommand + private List getUserByIdsFallback(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); + } + return users; + } +``` #Development Status and Future From eecfdc059a0378b637fe3b6adae39ef86b121965 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 28 Mar 2015 19:51:00 +0300 Subject: [PATCH 2/3] Update README.md --- hystrix-contrib/hystrix-javanica/README.md | 85 ++++++++++++---------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 8cb6fe022..222a23638 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -382,44 +382,46 @@ Suppose you have some command which calls should be collapsed in one backend cal Example: ```java - @HystrixCollapser(batchMethod = "getUserByIds") - public Future getUserById(String id) { - return null; - } + @HystrixCollapser(batchMethod = "getUserByIds") + public Future getUserById(String id) { + return null; + } - @HystrixCommand - public List getUserByIds(List ids) { - List users = new ArrayList(); - for (String id : ids) { - users.add(new User(id, "name: " + id)); - } - return users; + @HystrixCommand + public List getUserByIds(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); } + return users; + } -Future f1 = userService.getUserById("1"); -Future f2 = userService.getUserById("2"); -Future f3 = userService.getUserById("3"); -Future f4 = userService.getUserById("4"); -Future f5 = userService.getUserById("5"); + Future f1 = userService.getUserById("1"); + Future f2 = userService.getUserById("2"); + Future f3 = userService.getUserById("3"); + Future f4 = userService.getUserById("4"); + Future f5 = userService.getUserById("5"); ``` -A method annotated with {@link HystrixCollapser} annotation can return any value with compatible type, it does not affect the result of collapser execution, collapser method can even return {@code null} or another stub. +A method annotated with ```@HystrixCollapser``` annotation can return any value with compatible type, it does not affect the result of collapser execution, collapser method can even return ```null``` or another stub. There are several rules applied for methods signatures. + 1. Collapser method must have one argument of any type, desired a wrapper of a primitive type like Integer, Long, String and etc. 2. A batch method must have one argument with type java.util.List parameterized with corresponding type, that's if a type of collapser argument is Integer then type of batch method argument must be List. -3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be List +3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be List. **Convention for batch method behavior** -1. The size of response collection must be equal to the size of request collection. + +The size of response collection must be equal to the size of request collection. + ```java @HystrixCommand public List getUserByIds(List ids); // batch method List ids = List("1", "2", "3"); getUserByIds(ids).size() == ids.size(); - ``` -2. Order of elements in response collection must be same to order of elements in request collection. +Order of elements in response collection must be same as in request collection. ``` @HystrixCommand @@ -434,10 +436,13 @@ There are several rules applied for methods signatures. User: id=3 ``` -Why order of elements of request and response collections is important. The reason of this is in reducing logic, basically request elements are mapped one-to-one to response elements. Thus if order of elements of request collection is different then the result of execution can be unpredictable. +**Why order of elements of request and response collections is important?** + +The reason of this is in reducing logic, basically request elements are mapped one-to-one to response elements. Thus if order of elements of request collection is different then the result of execution can be unpredictable. **Deduplication batch command request parameters**. -In some cases your batch method can depend on behavior of third-party service or library that skips duplicates in a request. It can be a rest service that expects unique values. In this case the size of elements in request collection can be different from size of elements in response collection. It violates one of the behavior principle. To fix it you need to manually map request to response collection, for example: + +In some cases your batch method can depend on behavior of third-party service or library that skips duplicates in a request. It can be a rest service that expects unique values and ignores duplicates. In this case the size of elements in request collection can be different from size of elements in response collection. It violates one of the behavior principle. To fix it you need manually map request to response, for example: ```java // hava 8 @@ -479,29 +484,31 @@ Bath command can have a fallback method. Example: ```java - @HystrixCollapser(batchMethod = "getUserByIdsWithFallback") - public Future getUserByIdWithFallback(String id) { - return null; - } + @HystrixCollapser(batchMethod = "getUserByIdsWithFallback") + public Future getUserByIdWithFallback(String id) { + return null; + } - @HystrixCommand(fallbackMethod = "getUserByIdsFallback") - public List getUserByIdsWithFallback(List ids) { - throw new RuntimeException("not found"); - } + @HystrixCommand(fallbackMethod = "getUserByIdsFallback") + public List getUserByIdsWithFallback(List ids) { + throw new RuntimeException("not found"); + } - @HystrixCommand - private List getUserByIdsFallback(List ids) { - List users = new ArrayList(); - for (String id : ids) { - users.add(new User(id, "name: " + id)); - } - return users; + @HystrixCommand + private List getUserByIdsFallback(List ids) { + List users = new ArrayList(); + for (String id : ids) { + users.add(new User(id, "name: " + id)); } + return users; + } ``` #Development Status and Future Please create an issue if you need a feature or you detected some bugs. Thanks -**Note**: Javaniva 1.4.+ is more stable than 1.3.+ All fixes initially are added in 1.4.+ and after if it's not much efforts then merged to 1.3. **It's recommended to use Javaniva 1.4.+** +**Note**: Javaniva 1.4.+ is updated more frequently than 1.3.+ hence 1.4+ is more stable. + +**It's recommended to use Javaniva 1.4.+** From 3f1b9078e07dc06ddf4c3a961570b8f6b88bbd24 Mon Sep 17 00:00:00 2001 From: Roman Pleshkov Date: Sat, 28 Mar 2015 20:05:09 +0300 Subject: [PATCH 3/3] Update README.md #689: fix wiki format --- hystrix-contrib/hystrix-javanica/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hystrix-contrib/hystrix-javanica/README.md b/hystrix-contrib/hystrix-javanica/README.md index 222a23638..c6d5b10ae 100644 --- a/hystrix-contrib/hystrix-javanica/README.md +++ b/hystrix-contrib/hystrix-javanica/README.md @@ -407,8 +407,8 @@ A method annotated with ```@HystrixCollapser``` annotation can return any value There are several rules applied for methods signatures. 1. Collapser method must have one argument of any type, desired a wrapper of a primitive type like Integer, Long, String and etc. -2. A batch method must have one argument with type java.util.List parameterized with corresponding type, that's if a type of collapser argument is Integer then type of batch method argument must be List. -3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be List. +2. A batch method must have one argument with type java.util.List parameterized with corresponding type, that's if a type of collapser argument is ```Integer``` then type of batch method argument must be ```List```. +3. Return type of batch method must be java.util.List parameterized with corresponding type, that's if a return type of collapser method is ```User``` then a return type of batch command must be ```List```. **Convention for batch method behavior**