Spring Cloud(四)断路器Hystrix

新的一年开始了,先在这里祝各位朋友新年快乐、猪年大吉。新的一年可以拿到自己心仪的offer,开到满意的工资。总而言之,就是升职加薪,迎娶白富美,走上人生巅峰!

ps:本文结尾,有干货~

回归正题,前面三章,分别讲了Eureka注册中心,Ribbon服务消费、Feign声明式调用,这节课主要介绍Hystrix断路器。在讲这个之前,不知道大家听说过“雪崩”效应没?

雪崩效应

为了保证高可用,单个服务通常会以集群的方式进行部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,则调用方不能得到及时响应。当并发量大时,容器资源会被消耗殆尽,导致服务瘫痪。又因为服务之间存在依赖、调用的关系,所以,故障在服务之间进行传播。对整个微服务架构造成严重的后果,这就是服务故障所引起的“雪崩”效应。

熔断、降级

很多人觉得,自己接触的项目,访问量不会有那么大,服务器不会发生故障,也不会出现高并发。但是,谁又能保证,服务器不会发神经,网络不会出现问题,出现宕机的情况呢?当服务器宕机,无法正常提供服务?服务之间的调用不就出现问题了吗?此时,就要说到Hystrix了,它能为上面出现的问题,提供相应的解决方案:熔断、降级、熔断和降级互相交集。
那么什么是熔断?什么是降级呢?

一、服务降级:
系统的整体资源匮乏,为了使系统正常运转,就需要关闭某些服务,等到资源充足,再将服务开启。
二、服务熔断
当服务出现了异常,但是,还是会有大量请求调用这个服务,从而浪费大量的系统资源。为了保护整个系统不因单个服务的异常而发生故障,这个时候,就需要使用熔断器,来阻止外来请求的访问,防止系统资源被耗尽。比如:家中的电闸,当电流过大的时候,出问题,电闸就会自动断开,保护用电器,防止用电器烧坏。

实战

在Ribbon中使用断路器

项目是在前面的订单和用户服务的基础上进行改造。
首先在order-service的pom文件中添加相应的约束文件。

<!-- 断路器 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

在启动类上加入@EnableCircuitBreaker注解,表示开启断路器Hystrix。

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }

    /**
     * 标记是负载均衡
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

因为这里出现了很多注解,所以Spring人性化的提供了新的注解,来代替上面的注解。它就是

@SpringCloudApplication

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.client;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

通过源码,可以看到,@SpringCloudApplication 是一个注解的集合,其中包含:
@SpringBootApplication:表明这是一个SpringBoot应用。
@EnableDiscoveryClient:表明将服务注册到Eureka,让注册中心可以扫描到此服务。
@EnableCircuitBreaker:是断路器的注解,表明开启Hystrix。

接下来,需要对业务层进行修改。在save()方法上加上@HystrixCommand注解,表示该方法使用熔断器的功能,并指定了fallback方法。

package com.root.project.orderservice.service.impl;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import com.root.project.orderservice.pojo.Order;
import com.root.project.orderservice.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @ClassName: OrderServiceImpl
 * @Author: 清风一阵吹我心
 * @Description: TODO  订单服务实现类
 * @Date: 2019/1/18 16:15
 * @Version 1.0
 **/
@Service
public class OrderServiceImpl implements OrderService {

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

    /**
     * url:服务名称+接口名称
     */
    private static final String USER_REQ_URL = "http://user-service/api/v1.0/user/{userId}";

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 注意,这里要设置Hystrix的超时时间,默认时间是一秒
     * 服务端方法响应超过1秒将会触发降级
     *
     * @param userId
     * @return
     */
    @Override
    @HystrixCommand(fallbackMethod = "saveError")
    public Order save(Long userId) {
        Object object = restTemplate.getForObject(USER_REQ_URL, Object.class, userId);
        LOGGER.info("object:{}", object);
        Order order = new Order();
        order.setObject(object);
        return order;
    }

    public Order saveError(Long userId) {
        Order order = new Order();
        String msg = "请求超时,服务发生异常!";
        order.setObject(msg);
        return order;
    }
}

注意:在定义fallbackMethod时,必须保证熔断方法和本身的save()方法一致(签名一定要和api方法一致,对应的参数的个数也要一致)。伪代码没有那么严谨,主要是为了展示效果。希望大家可以理解。

然后启动注册中心、用户服务、订单服务。访问:http://localhost:8901/api/v1.0/order?userId=3 出现了让人难以接受的东东。
在这里插入图片描述
这里疑惑的是,我的用户服务明明正常,但是为什么访问走的却是fallbackMethod的方法。然后根据相关资料了解到,Hystrix的超时时间,默认①秒,服务端方法响应超过①秒将会触发降级。然后,打开Google浏览器看响应时长。
在这里插入图片描述
确实是超过了①秒。当然并不是所有人都会遇到这种情况。所以,出现了问题,就要解决。以下提供两种解决办法。
①、修改配置文件。

#1.默认
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

#2.配置具体方法的超时时间
#hystrix:
#  command:
#    serverMethod:
#      execution:
#        isolation:
#          thread:
#            timeoutInMilliseconds: 3000

设置Hystrix的超时时间为5秒。重启订单服务。再次访问:http://localhost:8901/api/v1.0/order?userId=3
在这里插入图片描述
看到了想要的结果。

②、通过@HystrixCommand注解,增加子属性@HystrixProperty来解决。
上面使用了@HystrixCommand注解,来表明某个方法使用熔断器的功能。同时,还可以指明它的超时时间。

    /**
     * 注意,这里要设置Hystrix的超时时间,默认时间是一秒
     * 服务端方法响应超过1秒将会触发降级
     *
     * @param userId
     * @return
     */
    @Override
    @HystrixCommand(fallbackMethod = "saveError", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
    })
    public Order save(Long userId) {
        Object object = restTemplate.getForObject(USER_REQ_URL, Object.class, userId);
        LOGGER.info("object:{}", object);
        Order order = new Order();
        order.setObject(object);
        return order;
    }

这里就只对save方法上的注解进行了修改,所以就贴了部分代码,其余的代码不变。可以看到括号中的(“execution.isolation.thread.timeoutInMilliseconds”)实际上和配置文件的配置相似。大致都是对Hystrix的超时时间进行修改。
重启服务,访问:http://localhost:8901/api/v1.0/order?userId=1
在这里插入图片描述
看到了正常的响应。

在Feign中使用断路器

项目是在Feign博文中的管理员服务和用户服务进行改造。如有不明白的朋友,请看前面的几篇博文。

Feign默认集成了断路器,所以不用导入相关的约束。在老的本版中,默认打开断路器。但是新版本中需要进行配置,来开启。修改Admin-service的yml文件。

feign:
  #开启Feign对Hystrix的支持
  hystrix:
    enabled: true

加入上面的配置后,对以前的UserFeignClient进行修改。加上fallback,后面指定一个实现类,表明发生异常的处理方法。

package com.root.project.adminservice.service;

import com.root.project.adminservice.service.impl.UserFeignClientFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @ClassName: UserClient
 * @Author: 清风一阵吹我心
 * @Description: TODO  加入注解,指定需要调用的服务名称
 * @Date: 2019/1/27 15:13
 * @Version 1.0
 **/
@FeignClient(name = "user-service",fallback = UserFeignClientFallBack.class)
public interface UserFeignClient {

    /**
     * 调用用户接口,查询用户信息,让其返回字符串
     * @param userId
     * @return
     */
    @GetMapping("/api/v1.0/user/{userId}")
    String findUserById(@PathVariable("userId")Long userId);
}

然后,实现这个接口,编写实现类UserFeignClientFallBack

package com.root.project.adminservice.service.impl;

import com.root.project.adminservice.service.UserFeignClient;
import org.springframework.stereotype.Component;

/**
 * @ClassName: UserFeignClientFallBack
 * @Author: 清风一阵吹我心  @component注解不加  启动会报错
 * @Description: TODO
 * @Date: 2019/2/11 16:13
 * @Version 1.0
 **/
@Component
public class UserFeignClientFallBack implements UserFeignClient {
    @Override
    public String findUserById(Long userId) {
        String str = "不好意思,用户id为" + userId + "的服务调用超时";
        return str;
    }
}

注意,这里使用了@Component注解,将这个类加入到IOC容器中。为了能直观的反映调用结果,还对AdminServiceImpl进行了修改。

@Service
public class AdminServiceImpl implements AdminService {

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

    @Resource
    private UserFeignClient userFeignClient;

    @Override
    public String save(Long uId) {
        String result = userFeignClient.findUserById(uId);
        return result;
    }
}

让它返回字符串。然后启动注册中心、管理员服务、用户服务。访问:http://localhost:9100/api/v1.0/admin?userId=1
在这里插入图片描述
果然没让我失望。同样的结果,又出现了一次。不用我说,根据前面的教训,应该就是要配置Hystrix的超时时间了。直接使用配置文件进行配置。

#hystrix的熔断机制
hystrix:
  command:
    #default全局有效,service id 指定应用有效
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
          enabled: true
        isolation:
          thread:
            #断路器超时时间,默认1000ms
            timeoutInMilliseconds: 3000

设置完后,重启服务,进行访问,又出现了相同的问题。这下我有些迷茫了,为什么该配置的都配置了,还是出现问题呢?那是因为,这里使用的是Feign。
上一篇博文讲了Feign默认集成了Ribbon。而使用Feign调用接口,分为两层,Ribbon的调用和Hystrix的调用。所以Ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间。所以,这里需要对Feign中的Ribbon进行超时时间的配置。修改yml文件。

yml最终版本

feign:
  #开启Feign对Hystrix的支持
  hystrix:
    enabled: true
  #设置调用超时时间
  client:
    config:
      default:
        connectTimeout: 1000
        readTimeout: 2000
#hystrix的熔断机制
hystrix:
  command:
    #default全局有效,service id 指定应用有效
    default:
      execution:
        timeout:
          #如果enabled设置为false,则请求超时交给ribbon控制,为true,则超时作为熔断根据
          enabled: true
        isolation:
          thread:
            #断路器超时时间,默认1000ms
            timeoutInMilliseconds: 3000

然后再重启服务,访问相应的接口。
在这里插入图片描述
得到了正确的响应。

ps:
-------------------------------------------------------------------------------------------------------------------------
相信看到最底下的,都是被上面的话骗下来的。估计各位心中都有一个xxx想说,但是,我还是要对文中出现的错误进行纠正。对上文出现调用超时的问题说明一下原因。因为代码写的时间比较长,用户服务(user-service)中,为了测试Feign的调用超时,增加了一段线程睡眠的代码,正好是①秒。所以,上面Ribbon和Feign中使用Hystrix才会调用失败,这是一个小细节。

就是这一段代码,影响了体验感,哈哈~

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

由于博主比较懒,就没有改动整个篇幅了,决定在文章的底部,给大家说一下自己的错误。也希望大家在写代码的时候能够更加细心。但是不影响断路器的讲解,刚好以错改错,如果发生了上面出现的情况,就可以按照给出的解决办法来解决问题了。
-------------------------------------------------------------------------------------------------------------------------

以上就是断路器Hystrix的使用,如果还有什么问题或者错误,欢迎各位朋友指出。新的一年,大家共同进步,完成自己的梦想。

做一只有梦想的咸鱼。

猜你喜欢

转载自blog.csdn.net/qq_32101993/article/details/87028694