学习笔记--Hystrix服务容错学习笔记【2】

1 定义服务降级

在Hystrix中通过重载getFallback()方法可以实现服务降级逻辑。Hystrix会在run()执行过程中出现错误、超时、线程池拒绝、断路器熔断等情况时,执行getFallback()方法内的逻辑。

@Override
protected String getFallback() {
    return "服务调用出错!";
}

在继承HystrixObservableCommand实现的Hystrix命令中可以重载resumeWithFallback方法实现服务降级逻辑. 该方法返回Observable对象, 当命令执行失败的时候, Hystrix会将Observable中的结果通知给所有订阅者.

@Override
protected Observable<String> resumeWithFallback() {
    return Observable.error(new Throwable("服务调用出错!"));
}

通过注解实现:
只需要使用fallbackMethod参数来指定具体的服务降级实现方法.

  // 使用的注解实现的服务降级
  @HystrixCommand(fallbackMethod = "fallback")
  ......
  // fallback也可以定义服务降级逻辑
  @HystrixCommand(fallbackMethod = "fallback1")
  public String fallback(){
      return "访问服务出错!";
  }

2 异常处理

2.1 异常传播

在HystrixCommand实现的run()方法中抛出异常时,除了使用HystrixBadRequestException之外,其他异常均会被Hystrix认为命令执行失败并触发服务降级逻辑, 所以当需要在命令执行中抛出不触发服务降级的异常的时候需要使用它.
使用注册配置实现Hystrix命令时, 还支持忽略指定异常类型功能, 只需要通过设置@HystrixCommand注解的ignoreExceptions参数:

@HystrixCommand(fallbackMethod = "fallback"
        , ignoreExceptions = {BadRequestException.class})

抛出的BadRequestException异常, 会包装在HystrixBadRequestException中抛出, 这样就不会触发后续的fallback逻辑.

2.2 异常获取

使用传统方式: 使用getFallback()方法通过Throwable getExecutionException()方法来获取具体的异常, 通过判断进行不同的异常处理.

@Override
protected String getFallback() {
    return "服务执行出错!"+getExecutionException().getMessage();
}

注解配置方法实现异常获取, 只需要在fallback实现方法的参数中增加Throwable e对象:

public String fallback(Throwable e){
      return "抛出了异常!"+e.getMessage();
}

3 命令名称, 分组以及线程池划分

  • 继承方式
    可以在构造函数中通过Setter静态类设置
    private static HystrixCommandGroupKey groupKey = HystrixCommandGroupKey
            .Factory.asKey("ExampleGroup");
    private static HystrixCommandKey commandKey = HystrixCommandKey
            .Factory.asKey("Command");
    private static HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey
            .Factory.asKey("ThreadPool");
    public CustomerService(RestTemplate restTemplate) {
        super(Setter.withGroupKey(groupKey)
                .andCommandKey(commandKey)
                .andThreadPoolKey(threadPoolKey)
        );
        this.restTemplate = restTemplate;
    }
    

withGroupKey 设置命名组(必备, Hystrix命令默认的线程划分是根据命令分组实现的)
andCommandKey 设置命令名称
HystrixThreadPoolKey 对线程池进行设置, 细粒度的划分线程池. 尽量指定线程池划分, 不同的命令可能从业务逻辑上看是属于同一个组, 但是往往实现本身上需要跟其他命令进行隔离.

  • 注解方法
    @HystrixCommand(fallbackMethod = "fallback1"
            , groupKey = "groupkey1"
            , commandKey = "commandkey1"
            , threadPoolKey = "threadpool1"
    )
    

4 请求缓存

4.1 开启请求缓存

通过重载getCacheKey方法来开启请求缓存
但不同的处理逻辑调用同一个依赖服务的时候,Hystrix会根据getCacheKey方法返回的值来区分是否是重复的请求.重复的请求只会请求一次.其余请求则是从缓存中获取.

@Override
protected String getCacheKey() {
    logger.info("开启请求缓存,返回参数为缓存对应的值");
    return "res";
}

使用注解设置请求缓存

  @CacheResult(cacheKeyMethod = "getCacheKey")
  public String visitServiceSync(){

  }
  // 用于生成key的方法 Nikola Zhang  【2018/12/23 12:29】
  private String getCacheKey(){
      return "res";
  }

当方法中存在一个参数(能区分请求)时,可以使用如下的方法

  @CacheResult
  public String visitServiceSync(@CackeKey("id") String id){

  }

下面的这种情况也是可以的:
@CackeKey("id") User user 其中User中含有id属性
注意:
@CackeKey的优先级比cacheKeyMethod的优先级要低,如果已经使用cacheKeyMethod, 则前者不会生效.

4.2 清理失效缓存

当更新数据的时候需要考虑缓存失效问题

  • 可以通过HystrixRequestCache.clear()方法来进行缓存的清理.
public static void flushCache(String res){
    HystrixRequestCache.getInstance(commandKey, 
    	HystrixConcurrencyStrategyDefault.getInstance())
    .clear(res);
}

HystrixRequestCache.getInstance(commandKey, HystrixConcurrencyStrategyDefault.getInstance()).clear(res);这种方式从默认的Hystrix并发策略中根据commandKey获取到该命令的请求缓存对象HystrixRequestCache的实例, 调用clear()方法完成缓存的清除.

  • 使用注解方式

    @CacheRemove
    public String visitServiceSync(@CackeKey("id") String id){
    
    }
    

4.3 请求合并

Hystrix提供HystrixCollapser进行请求的合并, 以减少通信消耗和线程数的占用.

  • 在HystrixCollapser抽象类中定义了三个类型:
    BatchReturnType: 合并后批量请求的返回类型
    ResponseType: 单个请求返回类型
    RequestArgumentType: 请求参数类型
  • 需要使用以上三中类型的三种方法及作用:
    RequestArgumentType getRequestArgument():该函数用来定义获取请求参数的方法.
    HystrixCommand<BatchReturnType> createCommand(Collection<CollapserRequest<ResponseType, RequestArgumentType>> requests): 合并请求产生批量命令的具体实现方法.该方法的collapsedRequests参数保存了延迟时间窗(一想到窗,窗函数就心痛)中收集到的所有的请求.获取所有的请求参数后,调用一个访问服务的方法, 并把这个请求参数的集合做为参数.
    mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapserRequest<ResponseType, RequestArgumentType>> requests): 批量命令结果返回后的处理, 这里需要实现将批量结果拆分并传递给合并前的各个原子请求命令的逻辑.
4.3.1 使用继承的方法实现

首先我们要创建一个类用于访问服务的方法,只是这个方法和之前的方法有点区别的是它的参数是一个集合类型, 并且这个集合中存放到并不是我们的直接的请求参数而是组装请求命令后, 获取到的所有请求中的参数的集合. 需要注意的是, 这些请求是在一段时间内(我们可以设置super(Setter.withCollapserKey(HystrixCollapserKey.Factory.asKey("collapser")).andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter().withTimerDelayInMilliseconds(100)));)的所有请求的集合.
新建一个用于访问服务的类

package com.demo.customer.service;

import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;

import java.util.ArrayList;
import java.util.List;

/************************************************
 *@ClassName : BatchCommand
 *@Description : TODO
 *@Author : NikolaZhang
 *@Date : 【2018/12/23 13:08】
 *@Version : 1.0.0
 *************************************************/
public class BatchCommand extends HystrixCommand<List<String>> {

    public BatchCommand() {
        super(Setter.withGroupKey(
                HystrixCommandGroupKey
                    .Factory.asKey("servicecommand")));

    }

    @Override
    protected List<String> run() throws Exception {
    	// 此处只是模拟一下服务的返回
        List<String> list = new ArrayList<>();
        list.add("val1");
        list.add("val2");
        list.add("val3");
        return list;
    }
}



请求合并器的实现

package com.demo.customer.service;

import com.netflix.hystrix.HystrixCollapser;
import com.netflix.hystrix.HystrixCollapserKey;
import com.netflix.hystrix.HystrixCollapserProperties;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCollapser.CollapsedRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/************************************************
 *@ClassName : Collaspser
 *@Description : 继承HystrixCollapser实现请求合并器
 *@Author : NikolaZhang
 *@Date : 【2018/12/23 13:05】
 *@Version : 1.0.0
 *************************************************/

public class Collaspser extends HystrixCollapser<List<String>, String, String>{

    private static Logger logger = LoggerFactory.getLogger(Collaspser.class);

    public Collaspser() {
        super(Setter.withCollapserKey(HystrixCollapserKey
            .Factory.asKey("collapser"))
            .andCollapserPropertiesDefaults(HystrixCollapserProperties
                .Setter().withTimerDelayInMilliseconds(100)));
    }

    @Override
    public String getRequestArgument() {
        logger.info("访问getRequestArgument");
        return "访问: getRequestArgument";
    }

    @Override
    protected HystrixCommand<List<String>> createCommand(
            Collection<CollapsedRequest<String, String>> collection) {
        logger.info("访问createCommand");
        // 请求合并器只当批量处理实例,会调用run方法进行请求处理
        return new BatchCommand();
    }


    @Override
    protected void mapResponseToRequests(List<String> batchResponse
            , Collection<CollapsedRequest<String, String>> collection) {

        logger.info("访问mapResponseToRequests, 请求的数量为: "+collection.size());
        int cnt = 0;
        for (CollapsedRequest<String, String> collapsedRequest : collection) {
            String res = batchResponse.get(cnt++);
            collapsedRequest.setResponse(res);
        }
    }
}

尤其需要注意的一点是我们继承的类是HystrixCollapser<List, String, String> 这里的泛型定义分别对应我们一开始提到的BatchReturnType/ResponseType/RequestArgumentType.
下面是HystrixCollapser的具体信息:

  public abstract class HystrixCollapser<BatchReturnType,
    ResponseType, RequestArgumentType>
  extends Object 
  implements 
  com.netflix.hystrix.HystrixExecutable<ResponseType>,com.netflix.hystrix.HystrixObservable<ResponseType>

如果我们的请求参数类型是String, 那么组合后的类型是List相关的, 这个决定就在于第一步我们定义访问服务的那个方法的需要的参数类型. 在第一步里,我只是直接返回了一个List, 你可以把它改成类似下面的形式:

//访问某一个服务的方法, usercodes是List<String>类型, 返回类型对应User
List<User> result = *.queryUserInfo(usercodes);

书上的获取所有请求参数的方法: list.addAll(collection.stream().map(CollapsedRequest::getArgument).collect(Collectors.toList()))

4.3.2 使用注解方式实现请求合并器
@HystrixCollapser(batchMethod = "visitServiceSyncAll"
  , collapserProperties = {@HystrixProperty(name = "timeDelayInMilliseconds", value = "100")
})
@CacheResult(cacheKeyMethod = "getCacheKey")
public String visitServiceSync(){
  ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://bookservice/UserController/hello", String.class);
  return responseEntity.getBody();
}

@HystrixCommand
public List<String> visitServiceSyncAll(){
  ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://bookservice/UserController/helloAll", List.class);
  return responseEntity.getBody();
}

这里我们定义了两个HystrixCommand, 一个用于直接访问服务, 一个用于请求多个条件的服务方法. 我们在前者上添加了请求合并器注解, 通过batchMethod属性制定了批量请求的实现方法为visitServiceSyncAll方法.

虽然通过请求合并可以减少请求的数量以缓解依赖服务线程池的资源, 但是请求合并是有额外开销的, 设置请求合并窗的时间是影像请求响应的关键. 一般时间窗的设置需要考虑两个方面:

  • 请求命令本身的延迟: 若果依赖服务端请求命令本身延迟较高, 那么就可以使用请求合并器, 这时候时间窗的时间(基本上是毫秒级)可以忽略.
  • 延迟时间窗内的并发量: 并发量较高, 且服务方提供批量处理接口, 使用请求合并器可以有效减少网络连接数量并提高系统的吞吐量.此时时间窗增加的消耗可以忽略不计.

end

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值