Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Request Cache and Reactive Execution #229

Merged
merged 1 commit into from
Apr 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 169 additions & 50 deletions hystrix-contrib/hystrix-javanica/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,83 @@ More about Spring AOP + AspectJ read [here] (http://docs.spring.io/spring/docs/3
# How to use

## Hystrix command
### Synchronous Execution

To run method as Hystrix command synchronously you need to annotate method with `@HystrixCommand` annotation, for example
```java
public class UserService {
...
@HystrixCommand
public User getUserById(String id) {
return userResource.getUserById(id);
}
}
...
```
In example above the `getUserById` method will be processed [synchronously](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution) within new Hystrix command.
By default the name of **command key** is command method name: `getUserById`, default **group key** name is class name of annotated method: `UserService`. You can change it using necessary `@HystrixCommand` properties:

```java
@HystrixCommand(groupKey="UserGroup", commandKey = "GetUserByIdCommand")
public User getUserById(String id) {
return userResource.getUserById(id);
}
```
To set threadPoolKey use ```@HystrixCommand#threadPoolKey()```

### Asynchronous Execution

To process Hystrix command asynchronously you should return an instance of `AsyncResult` in your command method as in the exapmple below:
```java
@HystrixCommand
public Future<User> getUserByIdAsync(final String id) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return userResource.getUserById(id);
}
};
}
```

The return type of command method should be Future that indicates that a command should be executed [asynchronously] (https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Asynchronous-Execution).

## Reactive Execution

To performe "Reactive Execution" you should return an instance of `ObservableResult` in your command method as in the exapmple below:

```java
@HystrixCommand
public Observable<User> getUserById(final String id) {
return new ObservableResult<User>() {
@Override
public User invoke() {
return userResource.getUserById(id);
}
};
}
```

The return type of command method should be `Observable`.

## Fallback

Graceful degradation can be achieved by declaring name of fallback method in `@HystrixCommand` like below:

To run method as Hystrix command you need to annotate method with @HystrixCommand annotation, for example
```java
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
return userResource.getUserById(id);
}

private User defaultUser(String id) {
return new User();
return new User("def", "def");
}
```
In example above the 'getUserById' method will be processed [synchronously](https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Synchronous-Execution) as Hystrix command. Fallback method can be private or public. Method 'defaultUser' will be used to process fallback logic in a case of any errors. If you need to run 'defaultUser' as command then you can annotate it with HystrixCommand annotation as below:

**_Its important to remember that Hystrix command and fallback should be placed in the same class and have same method signature_**.

Fallback method can have any access modifier. Method `defaultUser` will be used to process fallback logic in a case of any errors. If you need to run fallback method `defaultUser` as separate Hystrix command then you need to annotate it with `HystrixCommand` annotation as below:
```java
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
Expand All @@ -65,7 +129,7 @@ In example above the 'getUserById' method will be processed [synchronously](http
}
```

If fallback method was marked with @HystrixCommand then this method also can has own fallback method, as in the example below:
If fallback method was marked with `@HystrixCommand` then this fallback method (_defaultUser_) also can has own fallback method, as in the example below:
```java
@HystrixCommand(fallbackMethod = "defaultUser")
public User getUserById(String id) {
Expand All @@ -83,87 +147,142 @@ If fallback method was marked with @HystrixCommand then this method also can has
}
```

Its important to remember that Hystrix command and fallback should be placed in the same class.
## Error Propagation
Based on [this](https://github.com/Netflix/Hystrix/wiki/How-To-Use#ErrorPropagation) description, `@HystrixCommand` has an ability to specify exceptions types which should be ignored and wrapped to throw in `HystrixBadRequestException`.

To process Hystrix command asynchronously you should return an instance of AsyncCommand in your command method as in the exapmple below:
```java
@HystrixCommand
public Future<User> getUserByIdAsync(final String id) {
return new AsyncCommand<User>() {
@Override
public User invoke() {
return userResource.getUserById(id);
}
};
@HystrixCommand(ignoreExceptions = {BadRequestException.class})
public User getUserById(String id) {
return userResource.getUserById(id);
}
```

The return type of command method should be Future that indicates that a command should be executed [asynchronously] (https://github.com/Netflix/Hystrix/wiki/How-To-Use#wiki-Asynchronous-Execution).
If `userResource.getUserById(id);` throws an exception which type is _BadRequestException_ then this exception will be wrapped in `HystrixBadRequestException` and will not affect metrics and will not trigger fallback logic.

Command properties can be set using commandProperties field like below:
## Request Cache

Request caching is enabled by defining the _get cache key_ method like in example below:
```java
@HystrixCommand(cacheKeyMethod = "getUserIdCacheKey")
public User getUserById(String id) {
return userResource.getUserById(id);
}

private String getUserIdCacheKey(String id){
return id;
}
```

If "getUserIdCacheKey" returns `null` then the cache key will be "null" (string).

**_Its important to remember that Hystrix command and "get cache key" method should be placed in the same class and have same method signature_**.

@HystrixCommand(fallbackMethod = "defaultUser",
commandProperties = {
## Configuration
### Command Properties

Command properties can be set using @HystrixCommand's 'commandProperties' like below:

```java
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
})
public User getUserById(String id) {
return userResource.getUserById(id);
}
```


Javanica sets command properties using Hystrix ConfigurationManager.
Javanica dynamically sets properties using Hystrix ConfigurationManager.
For the example above Javanica behind the scenes performs next action:
```java
ConfigurationManager.getConfigInstance().setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500");
```

More about Hystrix command properties [command](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandExecution) and [fallback](https://github.com/Netflix/Hystrix/wiki/Configuration#wiki-CommandFallback)

## Hystrix collapser

Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use HystrixCollapser annotation.
Suppose you have some command which calls should be collapsed in one backend call. For this goal you can use ```@HystrixCollapser``` annotation.

Hystrix command:
**Asynchronous call**
```java
@HystrixCommand
public User getUserById(String id) {
return userResource.getUserById(id);
}
@HystrixCommand
@HystrixCollapser
public Future<User> getUserAsync(final String id, final String name) {
return new AsyncResult<User>() {
@Override
public User invoke() {
return new User(id, name + id); // there should be a network call
}
};
}
```

Asynchronous call:
```java
@HystrixCollapser(commandMethod = "getUserById",
collapserProperties = {@HystrixProperty(name = "timerDelayInMilliseconds", value = "200")})
public Future<User> getUserByIdCollapserAsync(String id) {
return CollapserResult.async();
}
```
**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:

Synchronous call:
```java
@HystrixCollapser(commandMethod = "getUserById",
collapserProperties = {@HystrixProperty(name = "maxRequestsInBatch", value = "3")})
@Override
public User getUserByIdCollapser(String id) {
return CollapserResult.sync();
}
@HystrixCommand
@HystrixCollapser(scope = GLOBAL)
public User getUserSync(String id, String name) {
return new User(id, name + id); // there should be a network call
}
```

Lines:
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**

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
return CollapserResult.async();
return CollapserResult.sync();

@HystrixCommand(fallbackMethod = "fallback")
@HystrixCollapser(fallbackEnabled = false)
public Future<User> getUserAsync(final String id, final String name) {
// some logic here
};

// .......


Future<User> f1 = userService.getUserAsync("1", "name: ");
Future<User> f2 = userService.getUserAsync("2", "name: ");
Future<User> f3 = userService.getUserAsync("not found", "name"); // not found, exception here
Future<User> f4 = userService.getUserAsync("4", "name: "); // will not be processed
Future<User> 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
```
In examples above don't affect the result of a collapser call. This just to avoid ```return null;``` statement in code.
It's important to remember that Hystrix command and сollapser should be placed in the same class.

Read more about Hystrix request collapsing [here] (https://github.com/Netflix/Hystrix/wiki/How-it-Works#wiki-RequestCollapsing)
**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.


#Development Status and Future
Todo list:

1. Add new annotation that can be applied for types in order to define default command properties for all commands that are specified in a class. For example specify default group key or default properties for all commands.
2.Add Cache annotation to mark some parameters which should participate in building cache key, for example:

```java
todo
```
@HystrixCommand
User getUserByParams(@Cache String id, String name, @Cache Integer age)
```
`toString()` method will be invoked for parameters: id and age to build cache key:
Pseudo code
```
String cacheKey = id.toString()+age.toString()
```
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
import java.lang.annotation.Target;

/**
* This annotation is used to mark some methods to collapse some commands into a single backend dependency call.
* This annotation is used to collapse some commands into a single backend dependency call.
* This annotation should be used together with {@link HystrixCommand} annotation.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
Expand All @@ -50,17 +51,19 @@
Scope scope() default Scope.REQUEST;

/**
* Specifies name of a method which has @HystrixCommand annotation and should be collapsed.
* Specifies collapser properties.
*
* @return method name.
* @return collapser properties
*/
String commandMethod();
HystrixProperty[] collapserProperties() default {};

/**
* Specifies collapser properties.
* Used to enabled fallback logic for a batch hystrix command.
* <p/>
* default => the false
*
* @return collapser properties
* @return true if processing of a fallback is allowed
*/
HystrixProperty[] collapserProperties() default {};
boolean fallbackEnabled() default false;

}
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,28 @@
*/
String fallbackMethod() default "";

/**
* Method name to be used to get a key for request caching.
* The command and get cache key method should be placed in the same class.
* <p/>
* By default this returns empty string which means "do not cache".
*
* @return method name or empty string
*/
String cacheKeyMethod() default "";

/**
* Specifies command properties.
*
* @return command properties
*/
HystrixProperty[] commandProperties() default {};

/**
* Defines exceptions which should be ignored and wrapped to throw in HystrixBadRequestException.
*
* @return exceptions to ignore
*/
Class<? extends Throwable>[] ignoreExceptions() default {};
}

Loading