微服务弹性框架hystrix-javanica详解(下)

时间:2022-05-06
本文章向大家介绍微服务弹性框架hystrix-javanica详解(下),主要内容包括续上集。我们之所以向你介绍有关javanica的内容,很大一部分原因,是因为spring cloud中集成了javanica,我们都知道spring cloud使用了大量的注解,javanica基于注解的风格和spring cloud不谋而合,在最新的spring cloud中集成了1.5.6的hystrix-javanica。 本集主要介绍请求缓存以及配置和批处理。、微服务弹性框架hystrix-javanica详解(上)、Request Cache 请求缓存、Configuration 配置、DefaultProperties 默认属性、Hystrix collapser、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

续上集。我们之所以向你介绍有关javanica的内容,很大一部分原因,是因为spring cloud中集成了javanica,我们都知道spring cloud使用了大量的注解,javanica基于注解的风格和spring cloud不谋而合,在最新的spring cloud中集成了1.5.6的hystrix-javanica。 本集主要介绍请求缓存以及配置和批处理。

微服务弹性框架hystrix-javanica详解(上)

Request Cache 请求缓存

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。 实现是线程安全的。 注释方法的参数由以下规则选择:

  • 如果没有用@CacheKey标记参数,则包括所有参数
  • 如果一个或多个@CacheKey注释存在,那么只包括带有@CacheKey注解的那些参数

注意:如果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>

Configuration 配置

Command Properties 命令属性

@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 默认属性

@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;
    }
}

Hystrix collapser

假设你有一些命令调用需要折叠在一个后端调用中批量处理,那么你可以使用@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或另一个存根。 方法签名要遵循以下的原则。

  1. Collapser方法必须有一个任何类型的参数,须是一个原始类型的包装器,如Integer,Long,String等。
  2. 批处理方法(batch method)必须有一个参数,类型为java.util.List,参数化为相应的类型,即如果collapser参数的类型为Integer,则批处理方法参数的类型必须为List <Integer>。
  3. 批处理方法的返回类型必须是java.util.List参数化为相应的类型,也就是如果collapser方法的返回类型是User然后返回类型的批处理命令必须是List <User>。

批处理方法行为约定

响应集合的大小必须等于请求集合的大小。

  @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,那么这些全都为你做好了。