优雅服务

背景

随着美团点评服务框架和服务治理体系的逐步成熟,服务化已成为公司内部系统设计的趋势。本着大系统小做、职责单一的原则,我们度假技术团队对业务系统进行了不少服务化拆分工作。随着业务复杂度的增加,依赖的服务也逐步增加,出现了不少由于服务调用出现异常问题而导致的重大事故,如:

1)系统依赖的某个服务发生延迟或者故障,数秒内导致所有应用资源(线程,队列等)被耗尽,造成所谓的雪崩效应 (Cascading Failure),导致整个系统拒绝对外提供服务。

2)系统遭受恶意爬虫袭击,在放大效应下没有对下游依赖服务做好限速处理,最终导致下游服务崩溃。

容错是一个很大的话题,受篇幅所限,本文将介绍仅限定在服务调用间常用的一些容错模式。

设计原则

服务容错的设计有个基本原则,就是“Design for Failure”。为了避免出现“千里之堤溃于蚁穴”这种情况,在设计上需要考虑到各种边界场景和对于服务间调用出现的异常或延迟情况,同时在设计和编程时也要考虑周到。这一切都是为了达到以下目标:

1)一个依赖服务的故障不会严重破坏用户的体验。

2)系统能自动或半自动处理故障,具备自我恢复能力。

基于这个原则和目标,衍生出下文将要介绍的一些模式,能够解决分布式服务调用中的一些问题,提高系统在故障发生时的存活能力。

一些经典的容错模式

所谓模式,其实就是某种场景下一类问题及其解决方案的总结归纳,往往可以重用。模式可以指导我们完成任务,作出合理的系统设计方案,达到事半功倍的效果。而在服务容错这个方向,行业内已经有了不少实践总结出来的解决方案。

超时与重试(Timeout and Retry)

超时模式,是一种最常见的容错模式,在美团点评的工程实践中大量存在。常见的有设置网络连接超时时间,一次RPC的响应超时时间等。在分布式服务调用的场景中,它主要解决了当依赖服务出现建立网络连接或响应延迟,不用无限等待的问题,调用方可以根据事先设计的超时时间中断调用,及时释放关键资源,如Web容器的连接数,数据库连接数等,避免整个系统资源耗尽出现拒绝对外提供服务这种情况。

重试模式,一般和超时模式结合使用,适用于对于下游服务的数据强依赖的场景(不强依赖的场景不建议使用!),通过重试来保证数据的可靠性或一致性,常用于因网络抖动等导致服务调用出现超时的场景。与超时时间设置结合使用后,需要考虑接口的响应时间分布情况,超时时间可以设置为依赖服务接口99.5%响应时间的值,重试次数一般1-2次为宜,否则会导致请求响应时间延长,拖累到整个系统。

一些实现说明:

public class RetryCommand<T> {
    private int maxRetries = 2;// 重试次数 默认2次
    private long retryInterval = 5;//重试间隔时间ms 默认5ms
    private Map<String, Object> params;

       public RetryCommand() {

    }

    public RetryCommand(long retryInterval, int maxRetries) {
           this.retryInterval = retryInterval;
        this.maxRetries = maxRetries;
    }

    public T command(Map<String, Object> params){
          //Some remote service call with timeout
           serviceA.doSomethingWithTimeOut(timeout);
    }

    private final T retry() throws RuntimeException {
        int retryCounter = 0;
        while (retryCounter < maxRetries) {
            try {
                return command(params);
            } catch (Exception e) {
                retryCounter++;
                if (retryCounter >= maxRetries) {
                    break;
           }
        }
    }
    throw new RuntimeException("Command failed on all of " + maxRetries + " retries");
}

       //省略
}

限流(Rate Limiting/Load Shedder)

限流模式,常用于下游服务容量有限,但又怕出现突发流量猛增(如恶意爬虫,节假日大促等)而导致下游服务因压力过大而拒绝服务的场景。常见的限流模式有控制并发和控制速率,一个是限制并发的数量,一个是限制并发访问的速率。

控制并发

属于一种较常见的限流手段,在工程实践中可以通过信号量机制(如Java中的Semaphore)来控制,举个例子:

假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。这个时候,我们就可以使用Semaphore来控制并发数,如:

public class SemaphoreTest {

    private static final int THREAD_COUNT = 30;

    private static ExecutorService threadPool = Executors
        .newFixedThreadPool(THREAD_COUNT);

    private static Semaphore s = new Semaphore(10);

    public static void main(String[] args) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        s.acquire();
                        System.out.println("save data");
                        s.release();
                    } catch (InterruptedException e) {
                        e.printStack();
                    }
                }
            });
        }

        threadPool.shutdown();
    }
}

在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证。

控制速率

在我们的工程实践中,常见的是使用令牌桶算法来实现这种模式,其他如漏桶算法也可以实现控制速率,但在我们的工程实践中使用不多,这里不做介绍,读者请自行了解。

令牌桶算法

在Wikipedia上,令牌桶算法是这么描述的:

  1. 每秒会有r个令牌放入桶中,或者说,每过1/r秒桶中增加一个令牌。

  2. 桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。

  3. 当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。

  4. 如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。

令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。

在我们的工程实践中,通常使用Guava中的Ratelimiter来实现控制速率,如我们不希望每秒的任务提交超过两个:

//速率是每秒两个许可
final RateLimiter rateLimiter = RateLimiter.create(2.0);

void submitTasks(List tasks, Executor executor) {
    for (Runnable task : tasks) {
        rateLimiter.acquire(); // 也许需要等待
        executor.execute(task);
    }
}

电路熔断器(Circuit Breaker)

在我们的工程实践中,偶尔会遇到一些服务由于网络连接超时,系统有异常或load过高出现暂时不可用等情况,导致对这些服务的调用失败,可能需要一段时间才能修复,这种对请求的阻塞可能会占用宝贵的系统资源,如:内存,线程,数据库连接等等,最坏的情况下会导致这些资源被消耗殆尽,使得系统里不相关的部分所使用的资源也耗尽从而拖累整个系统。在这种情况下,调用操作能够立即返回错误而不是等待超时的发生或者重试可能是一种更好的选择,只有当被调用的服务有可能成功时我们再去尝试。

熔断器模式可以防止我们的系统不断地尝试执行可能会失败的调用,使得我们的系统继续执行而不用等待修正错误,或者浪费CPU时间去等到长时间的超时产生。熔断器模式也可以使我们系统能够检测错误是否已经修正,如果已经修正,系统会再次尝试调用操作。下图是个使用熔断器模式的调用流程:

熔断器模式

可以从图中看出,当超时出现的次数达到一定条件后,熔断器会触发打开状态,客户端的下次调用将直接返回,不用等待超时产生。

在熔断器内部,往往有以下几种状态:

熔断器模式

1)闭合(closed)状态:该状态下能够对目标服务或方法进行正常的调用。熔断器类维护了一个时间窗口内调用失败的次数,如果某次调用失败,则失败次数加1。如果最近失败次数超过了在给定的时间窗口内允许失败的阈值(可以是数量也可以是比例),则熔断器类切换到断开(Open)状态。此时熔断器设置了一个计时器,当时钟超过了该时间,则切换到半断开(Half-Open)状态,该睡眠时间的设定是给了系统一次机会来修正导致调用失败的错误。

2)断开(Open)状态:在该状态下,对目标服务或方法的请求会立即返回错误响应,如果设置了fallback方法,则会进入fallback的流程。

3)半断开(Half-Open)状态:允许对目标服务或方法的一定数量的请求可以去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态(并且将错误计数器重置);如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切回到断开方式,然后开始重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。

在我们的工程实践中,熔断器模式往往应用于服务的自动降级,在实现上主要基于Netflix开源的组件Hystrix来实现,下图和代码分别是Hystrix中熔断器的原理和定义,更多了解可以查看Hystrix的源码:

模式组合

public interface HystrixCircuitBreaker {

    /**
     * Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not.
      * <p>
      * This takes into account the half-open logic which allows some requests through when determining if it should be closed again.
      *
      * @return boolean whether a request should be permitted
      */
     public boolean allowRequest();

     /**
      * Whether the circuit is currently open (tripped).
      *
      * @return boolean state of circuit breaker
     */
     public boolean isOpen();

    /**
     * Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.
     */
    public void markSuccess();
}

舱壁隔离(Bulkhead Isolation)

在造船行业,往往使用此类模式对船舱进行隔离,利用舱壁将不同的船舱隔离起来,这样如果一个船舱破了进水,只损失一个船舱,其它船舱可以不受影响,而借鉴造船行业的经验,这种模式也在软件行业得到使用。

线程隔离(Thread Isolation)就是这种模式的常见的一个场景。例如,系统A调用了ServiceB/ServiceC/ServiceD三个远程服务,且部署A的容器一共有120个工作线程,采用线程隔离机制,可以给对ServiceB/ServiceC/ServiceD的调用各分配40个线程。当ServiceB慢了,给ServiceB分配的40个线程因慢而阻塞并最终耗尽,线程隔离可以保证给ServiceC/ServiceD分配的80个线程可以不受影响。如果没有这种隔离机制,当ServiceB慢的时候,120个工作线程会很快全部被对ServiceB的调用吃光,整个系统会全部慢下来,甚至出现系统停止响应的情况。

这种Case在我们实践中经常遇到,如某接口由于数据库慢查询,外部RPC调用超时导致整个系统的线程数过高,连接数耗尽等。我们可以使用舱壁隔离模式,为这种依赖服务调用维护一个小的线程池,当一个依赖服务由于响应慢导致线程池任务满的时候,不会影响到其他依赖服务的调用,它的缺点就是会增加线程数。

舱壁隔离模式

无论是超时/重试,熔断器,还是舱壁隔离模式,它们在使用过程中都会出现异常情况,异常情况的处理方式间接影响到用户的体验,针对异常情况的处理也有一种模式支撑,这就是回退(fallback)模式。

回退(Fallback)

在超时,重试失败,熔断或者限流发生的时候,为了及时恢复服务或者不影响到用户体验,需要提供回退的机制,常见的回退策略有:

  1. 自定义处理:在这种场景下,可以使用默认数据,本地数据,缓存数据来临时支撑,也可以将请求放入队列,或者使用备用服务获取数据等,适用于业务的关键流程与严重影响用户体验的场景,如商家/产品信息等核心服务。

  2. 故障沉默(fail-silent):直接返回空值或缺省值,适用于可降级功能的场景,如产品推荐之类的功能,数据为空也不太影响用户体验。

  3. 快速失败(fail-fast):直接抛出异常,适用于数据非强依赖的场景,如非核心服务超时的处理。

应用实例

在实际的工程实践中,这四种模式既可以单独使用,也可以组合使用,为了让读者更好的理解这些模式的应用,下面以Netflix的开源组件Hystrix的流程为例说明。

模式组合

图中流程的说明:

  1. 将远程服务调用逻辑封装进一个HystrixCommand。

  2. 对于每次服务调用可以使用同步或异步机制,对应执行execute()或queue()。

  3. 判断熔断器(circuit-breaker)是否打开或者半打开状态,如果打开跳到步骤8,进行回退策略,如果关闭进入步骤4。

  4. 判断线程池/队列/信号量(使用了舱壁隔离模式)是否跑满,如果跑满进入回退步骤8,否则继续后续步骤5。

  5. run方法中执行了实际的服务调用。

    a. 服务调用发生超时时,进入步骤8。

  6. 判断run方法中的代码是否执行成功。

    a. 执行成功返回结果。

    b. 执行中出现错误则进入步骤8。

  7. 所有的运行状态(成功,失败,拒绝,超时)上报给熔断器,用于统计从而影响熔断器状态。

  8. 进入getFallback()回退逻辑。

    a. 没有实现getFallback()回退逻辑的调用将直接抛出异常。

    b. 回退逻辑调用成功直接返回。

    c. 回退逻辑调用失败抛出异常。

  9. 返回执行成功结果。

总结

服务容错模式在美团点评系统的稳定性保障方面应用很多,学习模式有助于新人直接利用熟练软件工程师的经验,对于提升系统的稳定性有很大的帮助。服务容错的目的主要是为了防微杜渐,除此之外错误的及时发现和监控其实同等重要。随着技术的演化,新的模式在不断的学习与实践中沉淀出来,美团点评度假技术团队在构建一个高可用高性能的系统目标之外,让系统越来越有弹性(Resilience)也是我们新的追求。

参考文献

  1. Netflix Hystrix Wiki

  2. Martin Fowler. CircuitBreaker

  3. Hanmer R. Patterns for Fault Tolerant Software. Wiley, 2007.

  4. Nygard M. 发布!软件的设计与部署. 凃鸣 译. 人民邮电出版社, 2015

----------------------

应用限流

 

前言

在一个高并发系统中对流量的把控是非常重要的,当巨大的流量直接请求到我们的服务器上没多久就可能造成接口不可用,不处理的话甚至会造成整个应用不可用。

比如最近就有个这样的需求,我作为客户端要向kafka生产数据,而kafka的消费者则再源源不断的消费数据,并将消费的数据全部请求到web服务器,虽说做了负载(有4台web服务器)但业务数据的量也是巨大的,每秒钟可能有上万条数据产生。如果生产者直接生产数据的话极有可能把web服务器拖垮。

对此就必须要做限流处理,每秒钟生产一定限额的数据到kafka,这样就能极大程度的保证web的正常运转。

其实不管处理何种场景,本质都是降低流量保证应用的高可用。

常见算法

对于限流常见有两种算法:

  • 漏桶算法
  • 令牌桶算法

漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照一定的速率流出,如果流量过快的话就会溢出(漏桶并不会提高流出速率)。溢出的流量则直接丢弃。

如下图所示:

漏桶算法

这种做法简单粗暴。

漏桶算法虽说简单,但却不能应对实际场景,比如突然暴增的流量。

这时就需要用到令牌桶算法:

令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有流量来时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞。

令牌桶算法

相比之下令牌桶可以应对一定的突发流量.

RateLimiter实现

对于令牌桶的代码实现,可以直接使用Guava包中的RateLimiter

    @Override
    public BaseResponse<UserResVO> getUserByFeignBatch(@RequestBody UserReqVO userReqVO) {
        //调用远程服务
        OrderNoReqVO vo = new OrderNoReqVO() ;
        vo.setReqNo(userReqVO.getReqNo());

        RateLimiter limiter = RateLimiter.create(2.0) ;
        //批量调用
        for (int i = 0 ;i< 10 ; i++){
            double acquire = limiter.acquire();
            logger.debug("获取令牌成功!,消耗=" + acquire);
            BaseResponse<OrderNoResVO> orderNo = orderServiceClient.getOrderNo(vo);
            logger.debug("远程返回:"+JSON.toJSONString(orderNo));
        }

        UserRes userRes = new UserRes() ;
        userRes.setUserId(123);
        userRes.setUserName("张三");

        userRes.setReqNo(userReqVO.getReqNo());
        userRes.setCode(StatusEnum.SUCCESS.getCode());
        userRes.setMessage("成功");

        return userRes ;
    }

详见此

调用结果如下:

1.jpg

代码可以看出以每秒向桶中放入两个令牌,请求一次消耗一个令牌。所以每秒钟只能发送两个请求。按照图中的时间来看也确实如此(返回值是获取此令牌所消耗的时间,差不多也是每500ms一个)。

使用RateLimiter有几个值得注意的地方:

允许先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。

总结

针对于单个应用的限流RateLimiter够用了,如果是分布式环境可以借助redis来完成。

Hystrix-服务容错与保护

 

前言

看过 应用限流的朋友应该知道,限流的根本目的就是为了保障服务的高可用。

本次再借助SpringCloud中的集成的Hystrix组件来谈谈服务容错。

其实产生某项需求的原因都是为了解决某个需求。当我们将应用进行分布式模块部署之后,各个模块之间通过远程调用的方式进行交互(RPC)。拿我们平时最常见的下单买商品来说,点击下单按钮的一瞬间可能会向发送的请求包含:

  • 请求订单系统创建订单。
  • 请求库存系统扣除库存。
  • 请求用户系统更新用户交易记录。

这其中的每一步都有可能因为网络、资源、服务器等原因造成延迟响应甚至是调用失败。当后面的请求源源不断的过来时延迟的资源也没有的到释放,这样的堆积很有可能把其中一个模块拖垮,其中的依赖关系又有可能把整个调用链中的应用Over最后导致整个系统不可能。这样就会产生一种现象:雪崩效应

之前讲到的限流也能起到一定的保护作用,但还远远不够。我们需要从各个方面来保障服务的高可用。

比如:

  • 超时重试。
  • 断路器模式。
  • 服务降级。
    等各个方面来保障。

使用Hystrix

SpringCloud中已经为我们集成了Netflix开源的Hystrix框架,使用该框架可以很好的帮我们做到服务容错。

Hystrix简介

下面是一张官方的流程图:

简单介绍下:

在远程调用时,将请求封装到HystrixCommand进行同步或是异步调用,在调用过程中判断熔断器是否打开、线程池或是信号量是否饱和、执行过程中是否抛出异常,如果是的话就会进入回退逻辑。并且整个过程中都会收集运行状态来控制断路器的状态。

不但如此该框架还拥有自我恢复功能,当断路器打开后,每次请求都会进入回退逻辑。当我们的应用恢复正常后也不能再进入回退逻辑吧。

所以hystrix会在断路器打开后的一定时间将请求发送到服务提供者,如果正常响应就关闭断路器,反之则继续打开,这样就能很灵活的自我修复了。

Feign整合Hystrix

在之前的章节中已经使用Feign来进行声明式调用了,并且在实际开发中也是如此,所以这次我们就直接用Feign来整合Hystrix。

使用了项目原有的sbc-user,sbc-order来进行演示,调用关系如下图:

User应用通过Order提供出来的order-client依赖调用了Order中的创建订单服务。

其中主要修改的就是order-client,在之前的OrderServiceClient接口中增加了以下注解:

@RequestMapping(value="/orderService")
@FeignClient(name="sbc-order",
        // fallbackFactory = OrderServiceFallbackFactory.class,
        // FIXME: 2017/9/4 如果配置了fallback 那么fallbackFactory将会无效
        fallback = OrderServiceFallBack.class,
        configuration = OrderConfig.class)
@RibbonClient
public interface OrderServiceClient extends OrderService{

    @ApiOperation("获取订单号")
    @RequestMapping(value = "/getOrderNo", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) ;
}

由于Feign已经默认整合了Hystrix所以不需要再额外加入依赖。

服务降级

对应的@FeignClient中的fallback属性则是服务容错中很关键的服务降级的具体实现,来看看OrderServiceFallBack类:

public class OrderServiceFallBack implements OrderServiceClient {
    @Override
    public BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) {
        BaseResponse<OrderNoResVO> baseResponse = new BaseResponse<>() ;
        OrderNoResVO vo = new OrderNoResVO() ;
        vo.setOrderId(123456L);
        baseResponse.setDataBody(vo);
        baseResponse.setMessage(StatusEnum.FALLBACK.getMessage());
        baseResponse.setCode(StatusEnum.FALLBACK.getCode());
        return baseResponse;
    }
}

该类实现了OrderServiceClient接口,可以很明显的看出其中的getOrderNo()方法就是服务降级时所触发的逻辑。

光有实现还不够,我们需要将改类加入到Spring中管理起来。这样上文中@FeignClientconfiguration属性就起到作用了,来看看对应的OrderConfig的代码:

@Configuration
public class OrderConfig {
    @Bean
    public OrderServiceFallBack fallBack(){
        return new OrderServiceFallBack();
    }
    @Bean
    public OrderServiceFallbackFactory factory(){
        return new OrderServiceFallbackFactory();
    }

}

其中new OrderServiceFallBack()并用了@Bean注解,等同于:

<bean id="orderServiceFallBack" class="com.crossoverJie.order.feign.config.OrderServiceFallBack">
</bean>

这样每当请求失败就会执行回退逻辑,如下图:

 

值得注意的是即便是执行了回退逻辑断路器也不一定打开了,我们可以通过应用的health端点来查看Hystrix的状态。

ps:想要查看该端点需要加入以下依赖:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId> 
</dependency>

就拿刚才的例子来说,先关闭Order应用,在Swagger访问下面这个接口,肯定是会进入回退逻辑:

@RestController
@Api("用户服务API")
@RequestMapping(value = "/userService")
@Validated
public interface UserService {

    @ApiOperation("hystrix容错调用")
    @RequestMapping(value = "/getUserByHystrix", method = RequestMethod.POST)
    BaseResponse<OrderNoResVO> getUserByHystrix(@RequestBody UserReqVO userReqVO) ;
}

查看health端点:

 


发现Hystrix的状态依然是UP状态,表明当前断路器并没有打开。

 

反复调用多次接口之后再次查看health端点:

 

发现这个时候断路器已经打开了。

这是因为断路器只有在达到了一定的失败阈值之后才会打开。

输出异常

进入回退逻辑之后还不算完,大部分场景我们都需要记录为什么回退,也就是具体的异常。这些信息对我们后续的系统监控,应用调优也有很大帮助。

实现起来也很简单:
上文中在@FeignClient注解中加入的fallbackFactory = OrderServiceFallbackFactory.class属性则是用于处理回退逻辑以及包含异常信息:

/**
 * Function:查看fallback原因
 *
 * @author crossoverJie
 *         Date: 2017/9/4 00:45
 * @since JDK 1.8
 */
public class OrderServiceFallbackFactory implements FallbackFactory<OrderServiceClient>{

    private final static Logger LOGGER = LoggerFactory.getLogger(OrderServiceFallbackFactory.class);

    @Override
    public OrderServiceClient create(Throwable throwable) {

        return new OrderServiceClient() {
            @Override
            public BaseResponse<OrderNoResVO> getOrderNo(@RequestBody OrderNoReqVO orderNoReq) {
                LOGGER.error("fallback:" + throwable);

                BaseResponse<OrderNoResVO> baseResponse = new BaseResponse<>() ;
                OrderNoResVO vo = new OrderNoResVO() ;
                vo.setOrderId(123456L);
                baseResponse.setDataBody(vo);
                baseResponse.setMessage(StatusEnum.FALLBACK.getMessage());
                baseResponse.setCode(StatusEnum.FALLBACK.getCode());
                return baseResponse;
            }
        };
    }
}

代码很简单,实现了FallbackFactory接口中的create()方法,该方法的入参就是异常信息,可以按照我们的需要自行处理,后面则是和之前一样的回退处理。

2017-09-21 13:22:30.307 ERROR 27838 --- [rix-sbc-order-1] c.c.o.f.f.OrderServiceFallbackFactory : fallback:java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: sbc-order

Note:

fallbackFactoryfallback属性不可共用。

Hystrix监控

Hystrix还自带了一套监控组件,只要依赖了spring-boot-starter-actuator即可通过/hystrix.stream端点来获得监控信息。

 

冰冷的数据肯定没有实时的图表来的直观,所以Hystrix也自带Dashboard

Hystrix与Turbine聚合监控

为此我们新建了一个应用sbc-hystrix-turbine来显示hystrix-dashboard
目录结构和普通的springboot应用没有差异,看看主类:

//开启EnableTurbine

@EnableTurbine
@SpringBootApplication
@EnableHystrixDashboard
public class SbcHystrixTurbineApplication {

    public static void main(String[] args) {
        SpringApplication.run(SbcHystrixTurbineApplication.class, args);
    }
}
  • 其中使用@EnableHystrixDashboard开启Dashboard
  • @EnableTurbine开启Turbine支持。

以上这些注解需要以下这些依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
        </dependency>

实际项目中,我们的应用都是多节点部署以达到高可用的目的,单个监控显然不现实,所以需要使用Turbine来进行聚合监控。

关键的application.properties配置文件:

# 项目配置
spring.application.name=sbc-hystrix-trubine
server.context-path=/
server.port=8282

# eureka地址
eureka.client.serviceUrl.defaultZone=http://node1:8888/eureka/
eureka.instance.prefer-ip-address=true

# 需要加入的实例
turbine.appConfig=sbc-user,sbc-order
turbine.cluster-name-expression="default"

其中turbine.appConfig配置我们需要监控的应用,这样当多节点部署的时候就非常方便了(同一个应用的多个节点spring.application.name值是相同的)。

将该应用启动访问http://ip:port/hystrix.stream

由于我们的turbine和Dashboard是一个应用所以输入http://localhost:8282/turbine.stream即可。

详细指标如官方描述:

 

通过该面板我们就可以及时的了解到应用当前的各个状态,如果再加上一些报警措施就能帮我们及时的响应生产问题。

总结

服务容错的整个还是比较大的,博主也是摸着石头过河,关于本次的Hystrix只是一个入门版,后面会持续分析它的线程隔离、信号量隔离等原理。



作者:crossoverJie
链接:https://www.jianshu.com/p/3fdacd351b00
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://my.oschina.net/oosc/blog/1615282

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值