Javanica提供特定的注解,以便启用和管理请求缓存。 这些注解看起来非常类似于JSR107,但是没不那么广泛,另一方面Hystrix不提供独立和复杂的缓存系统,因此没有必要像JSR107一样有这样的注释多样性。 Javanica只有三个专用于请求缓存的注解。
Annotation | 描述 | Properties |
---|---|---|
@CacheResult | 标记一个方法的返回结果应被缓存。此注释必须与HystrixCommand配合使用。 | cacheKeyMethod |
@CacheRemove | 标记用于使命令的高速缓存无效的方法。 生成的缓存密钥必须与在链接CacheResult上下文中生成的密钥相同 | commandKey, cacheKeyMethod |
@CacheKey | 将方法的参数标记为缓存的key。 如果没有标记参数,则使用所有参数。 如果@CacheResult或@CacheRemove annotation指定了cacheKeyMethod,那么方法参数不会用于构建缓存的key,即使它们用@CacheKey注解 | value |
cacheKeyMethod - 用于获取请求缓存的键的方法名称。 命令和缓存键方法应放在同一个类中,并且具有相同的方法签名,但缓存键方法返回类型应为String。 cacheKeyMethod具有比方法的参数更高的优先级,这意味着使用@CacheResult注释的方法的实际参数不会用于生成高速缓存密钥,而是改为指定cacheKeyMethodly分配给自己负责缓存密钥生成。 默认情况下,这返回空字符串,这意味着“不使用缓存方法。可以考虑使用cacheKeyMethod作为公共密钥生成器的替代(例如JSR170-CacheKeyGenerator),但是使用cacheKeyMethod缓存密钥生成变得更方便和简单。 比较两种方法:JSR107
@CacheRemove(
cacheName = "getUserById",
cacheKeyGenerator = UserCacheKeyGenerator.class)
@HystrixCommand
public void update(@CacheKey User user) {
storage.put(user.getId(), user);
}
public static class UserCacheKeyGenerator implements HystrixCacheKeyGenerator {
@Override
public HystrixGeneratedCacheKey generateCacheKey(CacheKeyInvocationContext<? extends Annotation> cacheKeyInvocationContext) {
CacheInvocationParameter cacheInvocationParameter =
cacheKeyInvocationContext.getKeyParameters()[0];
User user =
(User) cacheInvocationParameter.getValue();
return new DefaultHystrixGeneratedCacheKey(user.getId());
}
}
Javanica cacheKeyMethod
@CacheRemove(commandKey = "getUserById", cacheKeyMethod=)
@HystrixCommand
public void update(User user) {
storage.put(user.getId(), user);
}
private String cacheKeyMethod(User user) {
return user.getId();
}
或者直接写成这样:
@CacheRemove(commandKey = "getUserById")
@HystrixCommand
public void update(@CacheKey("id") User user) {
storage.put(user.getId(), user);
}
您不需要创建新类,如果您将为缓存键方法提供正确的名称,也可以使用cacheKeyMethod帮助重构。 建议将前缀“cacheKeyMethod”附加到实际方法名称,例如:
public User getUserById(@CacheKey String id);
private User getUserByIdCacheKeyMethod(String id);
Cache key generator
Jacanica只有一个缓存密钥生成器HystrixCacheKeyGenerator,它根据CacheInvocationContext生成一个HystrixGeneratedCacheKey。 实现是线程安全的。 注释方法的参数由以下规则选择:
注意:如果CacheResult或CacheRemove注释指定了cacheKeyMethod,那么方法参数不会用于构建缓存键,即使它们用CacheKey注释。
@CacheKey和value属性此注释默认有一个属性,允许指定某个参数属性的名称。 例如:@CacheKey(“id”)用户用户或在组合属性的情况下:@CacheKey(“profile.name”)用户用户。 空属性被忽略,即如果profile为空,则@CacheKey(“profile.name”)的结果用户用户将是空字符串。
例:
@CacheResult
@HystrixCommand
public User getUserById(@CacheKey String id) {
return storage.get(id);
}
// --------------------------------------------------
@CacheResult(cacheKeyMethod = "getUserByNameCacheKey")
@HystrixCommand
public User getUserByName(String name) {
return storage.getByName(name);
}
private Long getUserByNameCacheKey(String name) {
return name;
}
// --------------------------------------------------
@CacheResult
@HystrixCommand
public void getUserByProfileName(@CacheKey("profile.email") User user) {
storage.getUserByProfileName(user.getProfile().getName());
}
Get-Set-Get模式要了解有关此模式的更多信息,您可以阅读本章示例:
public class UserService {
@CacheResult
@HystrixCommand
public User getUserById(@CacheKey String id) {
// GET
return storage.get(id);
}
@CacheRemove(commandKey = "getUserById")
@HystrixCommand
public void update(@CacheKey("id") User user) {
// SET
storage.put(user.getId(), user);
}
}
// test app
public void test(){
User user = userService.getUserById("1");
HystrixInvokableInfo<?> getUserByIdCommand = getLastExecutedCommand();
// this is the first time we've executed this command with
// the value of "1" so it should not be from cache
assertFalse(getUserByIdCommand.isResponseFromCache());
user = userService.getUserById("1");
getUserByIdCommand = getLastExecutedCommand(); // this is the second time we've executed this command with
// the same value so it should return from cache
assertTrue(getUserByIdCommand.isResponseFromCache());
user = new User("1", "new_name");
userService.update(user); // update the user
user = userService.getUserById("1");
getUserByIdCommand = getLastExecutedCommand(); // this is the first time we've executed this command after "update"
// method was invoked and a cache for "getUserById" command was flushed
// so the response shouldn't be from cache
assertFalse(getUserByIdCommand.isResponseFromCache());
}
Note:您可以将@CacheRemove注释与@HystrixCommand结合使用或不使用。 如果你想用@CacheRemove注释注释not命令方法,那么你需要添加HystrixCacheAspect方面到你的配置:
<aspects>
...
<aspect name="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect"/>
...
</aspects><!-- or Spring conf -->
<aop:aspectj-autoproxy/>
<bean id="hystrixAspect" class="com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCacheAspect"></bean>
@HystrixCommand的属性应该使用commandProperties,像下面这样:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
})
public User getUserById(String id) {
return userResource.getUserById(id);
}
Javanica使用Hystrix ConfigurationManager动态设置属性。 对于上面的例子,Javanica在幕后执行了以下动作:
ConfigurationManager
.getConfigInstance()
.setProperty("hystrix.command.getUserById.execution.isolation.thread.timeoutInMilliseconds", "500");
ThreadPoolProperties,有关线程池的属性设置,你可以使用@HystrixCommand的threadPoolProperties,像下面这样:
@HystrixCommand(commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500")
},
threadPoolProperties = {
@HystrixProperty(name = "coreSize", value = "30"),
@HystrixProperty(name = "maxQueueSize", value = "101"),
@HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
@HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "1440")
})
public User getUserById(String id) {
return userResource.getUserById(id);
}
@DefaultProperties是类(类型)级别注解,允许缺省命令属性,如groupKey,threadPoolKey,commandProperties,threadPoolProperties,ignoreExceptions和raiseHystrixExceptions。 使用此注解指定的属性将默认用于在注释类中定义的每个hystrix命令,除非命令使用相应的@HystrixCommand参数显式指定这些属性。 例:
@DefaultProperties(groupKey = "DefaultGroupKey")
class Service {
@HystrixCommand
// hystrix command group key is 'DefaultGroupKey'
public Object commandInheritsDefaultProperties() {
return null;
}
@HystrixCommand(groupKey = "SpecificGroupKey")
// command overrides default group key
public Object commandOverridesGroupKey() {
return null;
}
}
假设你有一些命令调用需要折叠在一个后端调用中批量处理,那么你可以使用@HystrixCollapser这个注解来完成。
例:
/** Asynchronous Execution */
@HystrixCollapser(batchMethod = "getUserByIds")
public Future<User> getUserByIdAsync(String id) {
return null;
}
/** Reactive Execution */
@HystrixCollapser(batchMethod = "getUserByIds")
public Observable<User> getUserByIdReact(String id) {
return null;
}
@HystrixCommand
public List<User> getUserByIds(List<String> ids) {
List<User> users = new ArrayList<User>();
for (String id : ids) {
users.add(new User(id, "name: " + id));
}
return users;
}
// Async
Future<User> f1 = userService.getUserByIdAsync("1");
Future<User> f2 = userService.getUserByIdAsync("2");
Future<User> f3 = userService.getUserByIdAsync("3");
Future<User> f4 = userService.getUserByIdAsync("4");
Future<User> f5 = userService.getUserByIdAsync("5");
// Reactive
Observable<User> u1 = getUserByIdReact("1");
Observable<User> u2 = getUserByIdReact("2");
Observable<User> u3 = getUserByIdReact("3");
Observable<User> u4 = getUserByIdReact("4");
Observable<User> u5 = getUserByIdReact("5");
// Materialize reactive commands
Iterable<User> users = Observables
.merge(u1, u2, u3, u4, u5)
.toBlocking()
.toIterable();
使用@HystrixCollapser注解标记的方法可以返回具有兼容类型的任何值,它不会影响collapser方法执行的结果,collapser方法甚至可以返回null或另一个存根。 方法签名要遵循以下的原则。
批处理方法行为约定
响应集合的大小必须等于请求集合的大小。
@HystrixCommand
public List<User> getUserByIds(List<String> ids); // batch method
List<String> ids = List("1", "2", "3");
getUserByIds(ids).size() == ids.size();
响应集合中的元素顺序必须与请求集合中的元素顺序相同。
@HystrixCommand
public List<User> getUserByIds(List<String> ids); // batch method
List<String> ids = List("1", "2", "3");
List<User> users = getUserByIds(ids);
System.out.println(users);
// output
User: id=1
User: id=2
User: id=3
为什么要保证请求的元素顺序和响应的元素顺序一致?
原因是在减少逻辑,基本上请求元素是一对一映射到响应元素。 因此,如果请求收集的元素的顺序不同,那么执行的结果可能是不可预测的。
Deduplication batch command request parameters.
在某些情况下,您的批处理方法可能取决于在请求中跳过重复的第三方服务或库的行为。 它可以是一个休息服务,它期望唯一的值,并忽略重复。 在这种情况下,请求集合中的元素的大小可以不同于响应集合中的元素的大小。 它违反了行为原则之一。 要修复它,您需要手动将请求映射到响应,例如:
// hava 8
@HystrixCommand
List<User> batchMethod(List<String> ids){
// ids = [1, 2, 2, 3]
List<User> users = restClient.getUsersByIds(ids);
// users = [
// User{id='1', name='user1'},
// User{id='2', name='user2'},
// User{id='3', name='user3'}
// ]
List<User> 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;
同样的情况,如果您想在服务调用之前从请求集合中删除重复的元素,则也要处理一下,例:
// hava 8
@HystrixCommand
List<User> batchMethod(List<String> ids){
// ids = [1, 2, 2, 3]
List<String> uniqueIds = ids
.stream()
.distinct()
.collect(Collectors.toList());
// uniqueIds = [1, 2, 3]
List<User> users = restClient.getUsersByIds(uniqueIds);
// users = [
// User{id='1', name='user1'},
// User{id='2', name='user2'},
// User{id='3', name='user3'}
// ]
List<User> 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;
要设置collapser属性,请使用@ HystrixCollapser#collapserProperties
Collapser错误处理Bath命令可以有fallback方法。 例:
@HystrixCollapser(batchMethod = "getUserByIdsWithFallback")
public Future<User> getUserByIdWithFallback(String id) {
return null;
}
@HystrixCommand(fallbackMethod = "getUserByIdsFallback")
public List<User> getUserByIdsWithFallback(List<String> ids) {
throw new RuntimeException("not found");
}
@HystrixCommand
private List<User> getUserByIdsFallback(List<String> ids) {
List<User> users = new ArrayList<User>();
for (String id : ids) {
users.add(new User(id, "name: " + id));
}
return users;
}
最后说一句,我们推荐你使用Javanica 1.4.+以上。
当然如果你使用spring cloud,那么这些全都为你做好了。
本文分享自 ImportSource 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!