SpringCloud 05 —— Hystrix(下)SpringCloud使用

Hystrix Ribbon

在Spring Cloud 中,Hystrix 主要用在被调用方,服务A调用服务B,如果服务A调用不同服务B是,及时回退信息给用户,防止因为超时问题引起更多的问题至服务不可用。

本次演示需要先将前面的ribbon-server启动起来,用于提供服务,也就是上图的服务B,现在我们需要自己开发服务A。

新建项目模块cloud-hystrix-ribbon,引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

启动类:

@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
public class HystrixRibbonApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixRibbonApplication.class, args);
    }
}

和前面一样实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    /**
     * 主键Id
     */
    private long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 昵称
     */
    private String rolename;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 备注
     */
    private String remark;
}

创建Ribbon调用类:

这里需要注意我们在远程调用的方法上添加了@HystrixCommand(fallbackMethod = "findUserByIdFailure"),也就是说如果调用失败会执行findUserByIdFailure方法

@Service
@Slf4j
public class EurekaHystrixRibbonService {


    @Autowired
    RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "findUserByIdFailure")
    public User findUserById(Long id) {
        // http://服务提供者的serviceId/url
        return restTemplate.getForObject("http://ribbon-server/user/" + id, User.class);
    }

    /**
     * 服务feign-server/user/id 调用失败的回调方法
     *
     * @return
     */
    public User findUserByIdFailure(Long id) {
        return new User(id, null, null, null, "网络繁忙,请稍后再试");
    }
}

Ribbon 基本配置

这里添加了一个getServlet()方法,用于后面Hystrix dashboard,见后面。

@Configuration
public class EurekaRibbonConfig {

    @Bean
    @LoadBalanced // 实现负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public IRule ribbonRule() {

        //默认ZoneAvoidanceRule请求,实现自定义的规则
        //自定义成随机

        return new RandomRule();
    }

    /**
     * 解决dashboard显示 Unable to connect to Command Metric Stream
     */
    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

}

控制层:

@RestController
public class EurekaHystrixController {
    @Resource
    private EurekaHystrixRibbonService eurekaRibbonService;

    /**
     * 根据id获取用户信息
     *
     * @param id
     * @return
     */
    @GetMapping("/user/{id:\\d+}")
    public User findUserById(@PathVariable long id) {
        return eurekaRibbonService.findUserById(id);
    }
}

应用配置

server:
  port: 8675
spring:
  application:
    name: hystrix-ribbon
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/

启动Eureka和cloud-ribbon-server再启动当前项目,postman测试127.0.0.1:8675/user/1

下面我们将ribbon-server关掉,再测试,可以看到返回就是findUserByIdFailure设置的描述

@HystrixCommand配置拓展

除了指定fallbackMethod以外,还有很多属性可以配置,下图是@HystrixCommand的全部属性

例如
指定回退方法和默认方法都是findUserByIdFailure,也可以不同,指定超时时间100毫秒,线程池大小为1,忽略Exception触发的回退等,具体的属性,根据字面意思应该没问题。一般我们也用不到这么多属性

@HystrixCommand(fallbackMethod = "findUserByIdFailure"
        , groupKey = "MyGroup"
        , commandKey = "MyCommandKey"
        , threadPoolKey = "MyThreadPool"
        , commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "100")}
        , threadPoolProperties = {
        @HystrixProperty(name = "coreSize", value = "1")}
        , ignoreExceptions = {Exception.class}
        , observableExecutionMode = ObservableExecutionMode.EAGER
        , raiseHystrixExceptions = {HystrixException.RUNTIME_EXCEPTION}
        , defaultFallback = "findUserByIdFailure"
)

默认配置

对于一些默认的配置,可以使用@DefaultProperties,可以减少@HystrixCommand中的代码量,如下

@Service
@Slf4j
@DefaultProperties(groupKey = "MyGroup")
public class EurekaHystrixRibbonService {


    @Autowired
    RestTemplate restTemplate;
    ......

Hystrix 缓存

在上一章原生使用中已经讲过了Hystrix的缓存,我们现在在Spring Cloud 篇也实现一个缓存的功能。
创建缓存我们需要创建一个实现java.servlet.Filter类的过滤器,并且需要初始化请求上下文,跟入门篇一样。这次可以减少每次创建上下文的代码量。

新建过滤器

这里我们对全部的url进行过滤,因此配置了/*

@WebFilter(urlPatterns = "/*",filterName = "hystrixFilter")
@Component
public class HystrixFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest,servletResponse);
        }finally {
            context.shutdown();
        }
    }
}

对于接口请求的改造只需要加一个@CacheResult的注解,注意需要配合@HystrixCommand一起使用。

重启项目将ribbon-server启动起来,并且重启当前hystrix项目,侯然Postman 测试接口127.0.0.1:8675/user/1

可以看到,第一次请求之后,同样的请求就走缓存了,后面不会再调用接口了。

缓存其它注解

同样的问题,缓存不能一直不变的,如果数据发生变动,缓存里的数据要可以更新或者删除,否则就会产生脏数据。
可以使用@CacheRemove移除缓存 、@CacheKey修饰方法参数

Hystrix 合并请求注解

同样上一章中我们已经讲过请求合并,Spring Cloud 中也同样支持请求合并,并且Spring Cloud 提供了一个注解,非常方便处理合并请求。
为了方便演示合并请求,我们需要在ribbon-server里添加一个接收方法,用于接收id的集合。

新建ribbon-server的controller方法

    @GetMapping("/list")
    public List<User> getUserById(String  ids, HttpServletRequest req){
        log.info("ids,{}",ids);
        List<User> list=new ArrayList<>();
        String[] splitIds = ids.split(",");
        for (String id : splitIds) {
            String url = req.getRequestURL().toString();
            User user = userService.getUserById(Long.valueOf(id));
            user.setRemark(user.getRemark()+":提供服务的是:"+url);
            list.add(user);
        }
        return list;
    }

在EurekaHystrixRibbonService中新建合并请求方法

在hystrix-ribbon里EurekaHystrixRibbonService类中新建合并请求的方法,在一个单个接收方法中加上@HystrixCollapser注解,用于收集相同请求的数据,然后批量执行。

    @HystrixCollapser(batchMethod = "findUsers"
            ,scope = com.netflix.hystrix.HystrixCollapser.Scope.REQUEST,
            collapserProperties = {
                    @HystrixProperty(name = "timerDelayInMilliseconds",value = "1000")
            })
    public Future<User> getUserSingle(Long id){
        log.info("执行单调调用");
        return null;
    }

    /**
     * 请求多个用户数据
     * @param ids
     * @return
     */
    @HystrixCommand(fallbackMethod = "findUsersFailure")
    public List<User> findUsers(List<Long> ids){
        return restTemplate.getForObject("http://ribbon-server/user/list?ids=" + StringUtils.join(ids, ","), List.class);
    }

    public List<User> findUsersFailure(List<Long> ids){
        log.info("fallback");
        List<User> list=new ArrayList<>();
        for (Long id : ids) {
            list.add(new User(id, null, null, null, "网络繁忙,请稍后再试,请确认手牌"));
        }
        return list;
    }

fallbackMethod里面返回的数量要和@HystrixCollapser里面接收到的一致,否则汇报下面的错误

Caused by: java.lang.RuntimeException: Failed to map all collapsed requests to response. The expected contract has not been respected. Collapser key: 'getUserSingle', requests size: '3', response size: '1'

合并作用域,默认是REQUEST,就是不会跨越多个请求会话的,只在当前用户请求中合并多次请求为批处理请求。这里改成GLOBAL,就是可以跨越request context,合并不同用户的请求为一次批处理请求。

添加EurekaHystrixController控制层方法

这里是模拟,一次请求发出3个调用方法,返回是一个数组。同时,如果失败,也会拿到一个失败的数组。

    /**
     * 根据id获取用户信息
     * @return
     */
    @GetMapping("/user/list")
    public List<User> findUsers() throws ExecutionException, InterruptedException {
        Future<User> userSingle1 = eurekaRibbonService.getUserSingle(1L);
        Future<User> userSingle2 = eurekaRibbonService.getUserSingle(2L);
        Future<User> userSingle3 = eurekaRibbonService.getUserSingle(3L);
        List<User> list=new ArrayList<>();
        list.add(userSingle1.get());
        list.add(userSingle2.get());
        list.add(userSingle3.get());
        return list;
    }

重启ribbon-server项目和当前项目,然后Postman 测试127.0.0.1:8675/user/list.再将ribbon关掉再次请求,可以看到请求合并成功。

Hystrix Feign

前面我们详细介绍了Hystrix与Ribbon的使用,下面我们来详细说明Hystrix与Feign的使用,Feign本身已经对Hystrix提供了支持,使用时只要打开开关就行了。下面我们新建一个cloud-hystrix-feign项目模块演示。

引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

启动类

@EnableFeignClients
@EnableHystrix
@EnableDiscoveryClient
@SpringBootApplication
public class HystrixFeignApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixFeignApplication.class, args);
    }
}

应用配置

最主要的是feign.hystrix.enabled: true需要打开,否则Hystrix 不起作用,默认是关闭的。

server:
  port: 8676
spring:
  application:
    name: hystrix-feign
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
feign:
  hystrix:
    enabled: true #开启hystrix

新建调用的service

@FeignClient注解的使用和普通的Feign 客户端没什么区别,主要就是加了一个fallback的类,回退的方法需要实现当前service的接口。

@FeignClient(value = "register-eureka-client", fallback = EurekaFeignServiceFailure.class)
public interface EurekaFeignService {

    //feign中你可以有多个@RequestParam,但只能有不超过一个@RequestBody
    @GetMapping("/hi")
    String hello();
}

新建回退类EurekaFeignServiceFailure

@Service
public class EurekaFeignServiceFailure implements EurekaFeignService {
    @Override
    public String hello() {
        return "网络异常,稍后再试,请拿好手牌";
    }
}

新建控制层EurekaFeignController

@RestController
@Slf4j
public class EurekaFeignController {

    @Resource
    private EurekaFeignService eurekaFeignService;

    @GetMapping("/feignInfo")
    public String feignInfo() {
        String message = eurekaFeignService.hello();
        log.info(message);
        return message;
    }
}

注释掉前面我们在eureka-client的controller中的休眠等待时间(否则会超时)

启动项目

先启动Eureka和cloud-client-eureka项目,然后在启动当前项目.

测试:

关掉eureka-client,再次请求返回网络异常。

对Hystrix配置

指定接口方法的配置就是默认的CommandKey,@FeignClient注解上的名称是默认的GroupKey,具体很多配置,可以自己尝试一下

hystrix:
  command:
    EurekaFeignService#hello(): #指定接口方法的超时规则
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500  #超时时间
      circuitBreaker:
        requestVolumeThreshold: 3 #请求并发数
#全局的
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 500  #超时时间
      circuitBreaker:
        requestVolumeThreshold: 3 #请求并发数

Hystrix Dashboard

Hystrix提供了对于微服务调用状态的监控信息,需要结合spring-boot-actuator模块一起使用。Hystrix Dashboard主要用来实时监控Hystrix的各项指标信息。通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题

已下是官方展示的图片

新建项目模块cloud-hystrix-dashboard,引入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

启动类

额外@EnableHystrixDashboard注解

@EnableHystrixDashboard
@EnableEurekaClient
@SpringBootApplication
public class HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardApplication.class, args);
    }
}

配置应用信息

server:
  port: 8677
spring:
  application:
    name: hystrix-feign-dashboard
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/

项目启动
浏览器访问127.0.0.1:8677/hystrix

从页面上的文件内容我们可以知道,Hystrix Dashboard共支持三种不同的监控方式:

默认的集群监控: http://turbine-hostname:port/turbine.stream

指定的集群监控: http://turbine-hostname:port/turbine.stream?cluster=[clusterName]

单体应用的监控: http://hystrix-app:port/actuator/hystrix.stream

页面上面的几个参数局域

最上面的输入框: 输入上面所说的三种监控方式的地址,用于访问具体的监控信息页面。

  • Delay: 该参数用来控制服务器上轮询监控信息的延迟时间,默认2000毫秒。
  • Title: 该参数对应头部标题Hystrix Stream之后的内容,默认会使用具体监
    控实例的Url。

我们先来看下单个服务实例的监控,从http://hystrix-app:port/actuator/hystrix.stream连接中可以看出,Hystrix Dashboard监控单节点实例需要访问实例的actuator/hystrix.stream接口

下面我们来看一下具体的监控信息

在输入框输入 http://127.0.0.1:8676/actuator/hystrix.stream其他默认,注意不要输错

若出现如下页面:

原因如下:

  • 地址输入的不正确
  • 被监控的服务没有添加spring-boot-starter-actuator的包
  • 没有对外暴露被监控的端点

在cloud-hystrix-feign中添加配置

management:
  endpoints:
    web:
      exposure:
        include: '*'

需要注意的是Spring Boot 2.0 之后所有对外暴露的端点都加上/actuator前缀

再次访问发现监控页面出现loading

通过postman多次调用127.0.0.1:8676/feignInfo接口。显示如下监控信息:

具体每个状态什么意思,直接拿官方的图片来看,看不懂应该的可以翻译一下。

Hystrix Turbine

Turbine是什么?
是Netflix提供了一个用来提供把多个hystrix.stream的内容聚合为一个数据源供Dashboard展示

从Hystrix Dashboard的监控首页中可以看出,集群的监控端点是http://turbine-hostname:port/turbine.stream,需要我们引入Trubine聚合服务,通过它来汇集监控信息,并将聚合后的信息提供给Hystrix Dashboard,下面我们来看一下多个应用的监控。

新建项目模块cloud-hystrix-turbine,引入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

启动类

加上@EnableTurbine注解

@EnableTurbine
@EnableDiscoveryClient
@SpringBootApplication
public class TurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(TurbineApplication.class, args);
    }
}

应用配置

server:
  port: 8678
spring:
  application:
    name: hystrix-feign-dashboard
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
turbine:
  app-config: hystrix-ribbon, hystrix-feign  # 配置Eureka中的serviceId列表,表明监控哪些服务
  aggregator:
    cluster-config: default # 指定聚合哪些集群,多个使用","分割,默认为default。
  cluster-name-expression: new String("default")
  instanceUrlSuffix: actuator/hystrix.stream # turbine在收集时访问的后缀

cluster-name-expression的配置默认default

  1. clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称
  2. 当clusterNameExpression: default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default
  3. 当clusterNameExpression: metadata['cluster']时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster: cluster1,则需要配置,同时turbine.aggregator.clusterConfig: cluster1

启动cloud-hystrix-ribbon、cloud-hystrix-feign、ribbon-server项目、cloud-hystrix-dashboard

在Dashboard中输入http://127.0.0.1:8678/turbine.stream

在Postman测试两个接口127.0.0.1:8676/feignInfo和127.0.0.1:8675/user/1

因为我们启动cloud-ribbon-server项目,所以findUserById报了100%的错误,你也可以尝试启动cloud-ribbon-server项目看看展示的效果。

更新时间:2020-04-26 18:52:35

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/springcloud05hystrix下springcloud使用
最后更新:2020-04-26 18:52:35

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×