相关概念 Spring Cloud是分布式系统的整体解决方案,而不是一项具体的技术。
SpringBoot 和 Spring 的关系 SpringBoot底层就是Spring,简化使用Spring的方式而已,多加了好多的自动配置;
Spring Cloud 和 SpringBoot 的关系 Spring Cloud是分布式系统的整体解决方案,底层用的SpringBoot来构建项目,Cloud新增很多的分布式的starter,包括这些starter的自动配置;
资料信息 http://spring.io/projects https://projects.spring.io/spring-cloud/#quick-start https://springcloud.cc/
SpringCloud 案例开发 本次 SpringCloud 开发案例如下:需要创建注册中心,用于服务和电影服务。两个服务之间采用 SpringCloud 自带的 RESETful 进行调用。可以查询用户,购买电影票查询最新电影等操作。
注册中心 概念 注册中心的作用是管理所有服务(服务发现和注册)。注册中心中的 Region 和 Zone 就相当于大区和机房,一个 Region(大区)可以有很多的 Zone(机房)。在Spring Cloud 中,服务消费者会优先查找在同一个 Zone 的服务,之后在去查找其他的服务。如果该项配置使用的好,那么项目请求的响应时间将大大缩短!
创建注册中心 创建一个 Spring Starter Project
引入 eureka-server
新增 application.yml,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 spring: application: name: cloud-eureka-registry-center server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主程序使用注解 @EnableEurekaServer
开启 Eureka 注册中心功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer @SpringBootApplication public class CloudEurekaRegistryCenterApplication { public static void main (String[] args) { SpringApplication.run(CloudEurekaRegistryCenterApplication.class , args ) ; } }
访问 http://localhost:8761/ 就能查看注册中心的 Dashboard 页面信息
创建电影服务 新建 Spring Starter Project
选择 Eureka Client Starter 和 Spring Web Starter
创建 Controller,dao,service,service.impl,bean 包,MovieController 中的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.itguigu.springcloud.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.service.MovieService;@RestController public class MovieController { @Autowired MovieService moviceService; @GetMapping ("/getMovieById/{id}" ) public Movie getMovieById (@PathVariable("id" ) Integer id) { return moviceService.getMovieById(id); } }
其余 dao,service,service.impl,bean 中的内容不在这里记录,直接启动项目(注意这里虽然没有配置 Eureka注册和发现,但是要确保你的 Eureka 已经启动了)
直接测试 http://localhost:8080/getMovieById/1 看看是否有数据返回,有数据返回则接口就可以了,接下来配置 Eureka,新建 application.yml 文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: application: name: cloud-provider-movie server: port: 8000 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
在主程序中添加注解 @EnableDiscoveryClient
让其注册到注册中心当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.eureka.EnableEurekaClient;@EnableDiscoveryClient @SpringBootApplication public class CloudProviderMovieApplication { public static void main (String[] args) { SpringApplication.run(CloudProviderMovieApplication.class , args ) ; } }
注意⚠️:在启动类上面添加 @EnableDiscoveryClient
、@EnableEurekaClient
这二个注解作用,都可以让该服务注册到注册中心上去。不同点:@EnableEurekaClient
只支持Eureka注册中心,@EnableDiscoveryClient
支持 Eureka、Zookeeper、Consul 这三个注册中心。
在此查看 Eureka 的 dashboard,就会看到服务已经注册进来了:
创建用户服务 新建 Spring Starter Project
选择 Eureka Client Starter 和 Spring Web Starter
创建 Controller,dao,service,service.impl,bean 包,UserController 中的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.itguigu.springcloud.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import com.itguigu.springcloud.bean.User;import com.itguigu.springcloud.service.UserService;@RestController public class UserController { @Autowired UserService userService; @GetMapping ("/getUserById/{id}" ) public User getUserById (@PathVariable("id" ) Integer id) { return userService.getUserById(id); } }
直接测试 http://localhost:8080/getUserById/1 看看是否有数据返回,有数据返回则接口就可以了,接下来配置 Eureka,新建 application.yml 文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: application: name: cloud-provider-user server: port: 9000 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true
在主程序中添加注解 @EnableDiscoveryClient
让其注册到注册中心当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;@EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
查看 Eureka Dashboard,就会看到 Movie 服务和 User 服务都注册到里面了
远程调用 远程调用可以使用 RestTemplate 和 Feign。两种方式都需要掌握,Feign 可能会用得更多些。
RestTemplate 引入 Ribbon 在 dependencies 中使用 Alt + /
然后选择 Insert dependeny
这样就能选择导入被父工程管理的包。
容器注入 RestTemplate 给容器中注入一个RestTemplate并使用Ribbon进行负载均衡调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate(); } public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
远程调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.itguigu.springcloud.controller;import java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.bean.User;import com.itguigu.springcloud.service.UserService;@RestController public class UserController { @Autowired UserService userService; @Autowired RestTemplate restTemplate; @GetMapping ("/getUserById/{id}" ) public User getUserById (@PathVariable("id" ) Integer id) { return userService.getUserById(id); } @GetMapping ("/buyMovie/{userId}/{movieId}" ) public Map<String, Object> buyMovie ( @PathVariable("userId" ) Integer userId, @PathVariable ("movieId" ) Integer movieId ) { HashMap<String, Object> result = new HashMap<>(); User user = userService.getUserById(userId); Movie movie = restTemplate.getForObject("http://CLOUD-PROVIDER-MOVIE/getMovieById/" +movieId, Movie.class ) ; result.put("user" , user); result.put("movie" , movie); return result; } }
访问 http://localhost:9000/buyMovie/1/2 返回数据如下:
1 {"movie" :{"id" :2 ,"name" :"电影名称2" },"user" :{"id" :1 ,"name" :"User1" }}
集成 Ribbon 多运行几个 Movie 服务(多拷贝几份配置,并将配置端口设置为不一样的值)
配置多个 Movie 服务的端口。并在 Controller 中打印服务的端口信息,用来显示当前运行的端口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.itguigu.springcloud.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.service.MovieService;@RestController public class MovieController { @Autowired MovieService moviceService; @Value ("${server.port}" ) int port; @GetMapping ("/getMovieById/{id}" ) public Movie getMovieById (@PathVariable("id" ) Integer id) { System.out.println("port: " + port); return moviceService.getMovieById(id); } }
启动项目
查看注册中心,就会发现 Movie 的三个服务都注册到注册中心了
再次调用购买电影票的接口 http://localhost:9000/buyMovie/1/2 6 次就会看到打印的端口信息。每个服务都打印了端口两次,也就说被调用了两次。(默认是轮训策略,每个 Movie 服务被调用一次,六次就是每个被调用两次。这说明 RestTemplate 默认就是和 Ribbon 集成的)
注意⚠️:这里的 @LoadBalanced
是配置在 User 服务上的,也就是说这里 Ribbon 是在发挥正向代理的作用,请求发给 User 服务,然后进入到 Ribbon 中进行负载均衡,将请求发往不同的 Movie 服务。Nginx 的那种代理是反向代理。
Feign 创建项目 复制一份 User 服务,名字叫做 cloud-provider-user-feign,并将以前的 User 服务名称修改为 cloud-provider-user-restTemplate,分别修改两个服务中的 pom 坐标和 application.yml 中的名称。
引入 Feign 在 cloud-provider-user-feign 的 pom 文件中引入 feign 的依赖
1 2 3 4 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
启用 Feign 在主程序中加上注解 @EnableFeignClients
启用 Feign 客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
创建 Feign 远程调用接口 在 service 包下新建 MovieServiceFeign 接口,并在接口上加上 @FeignClient
注解。并添加和要远程调用的 Movie 中的方法(确保名称和参数都一摸一样,只是不需要实现而已)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itguigu.springcloud.service;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import com.itguigu.springcloud.bean.Movie;@FeignClient (value="CLOUD-PROVIDER-MOVIE" )public interface MovieServiceFeign { @GetMapping ("/getMovieById/{id}" ) public Movie getMovieById (@PathVariable("id" ) Integer id) ; }
实现调用 在 User Controller 中修改购买电影票接口,让其去调用 Feign 中配置的接口。只需要将以前 RestTemplate 中的远程调用方法更换为使用 Feign 去调用就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.itguigu.springcloud.controller;import java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.bean.User;import com.itguigu.springcloud.service.MovieServiceFeign;import com.itguigu.springcloud.service.UserService;@RestController public class UserController { @Autowired UserService userService; @Autowired MovieServiceFeign moviceServiceFeign; @GetMapping ("/getUserById/{id}" ) public User getUserById (@PathVariable("id" ) Integer id) { return userService.getUserById(id); } @GetMapping ("/buyMovie/{userId}/{movieId}" ) public Map<String, Object> buyMovie ( @PathVariable("userId" ) Integer userId, @PathVariable ("movieId" ) Integer movieId ) { HashMap<String, Object> result = new HashMap<>(); User user = userService.getUserById(userId); Movie movie = moviceServiceFeign.getMovieById(movieId); result.put("user" , user); result.put("movie" , movie); return result; } }
集成 Ribbon Feign 默认就是和 Ribbon 集成的。调用六次会发现,每个 Movie 服务都被平均的调用了两次。
熔断 当停止其中一个 Movie 服务接口的时候,继续调用 http://localhost:9001/buyMovie/1/2 时可能会出现卡顿或者中断的情况,原因就是请求发送到停止的 Movie 服务上去了。过一会儿就恢复了,原因是注册中心发现此 Movie 服务心跳没了,所以将该服务从注册中心中进行了剔除。
如果三个电影服务都挂了,那就导致了 User 服务完成不能调用 Movie 服务的情况。
熔断的作用是防止其中一个服务不可用而导致整个服务链路的雪崩,会在发生熔断的时候返回假数据。Ribbon 和 Hystrix 可以组合使用。Feign 和 Hystrix 也能组合使用。
熔断的状态转换如下:默认熔断是关闭的,当服务请求失败且达到一定的阀值的时候就会被打开,随着服务的慢慢恢复,会由半开状态慢慢的又转换为关闭状态。
Hystrix + Ribbon 添加 pom 依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency >
在主程序上开启断路保护功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { @Bean @LoadBalanced public RestTemplate restTemplate () { return new RestTemplate(); } public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
编写断路处理方法【注意⚠️:这个方法的定义要和远程调用方法的定义一摸一样,也就是除了方法名,参数,返回值什么的都需要一样,请求映射 @GetMapping 不用加】,当发生服务不能远程调用的时候,就使用断路方法的逻辑进行处理。需要在远程调用的方法上加上 @HystrixCommand
注解,并指定发生断路的时候,要调用的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 package com.itguigu.springcloud.controller;import java.util.HashMap;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.client.RestTemplate;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.bean.User;import com.itguigu.springcloud.service.UserService;import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;@RestController public class UserController { @Autowired UserService userService; @Autowired RestTemplate restTemplate; @GetMapping ("/getUserById/{id}" ) public User getUserById (@PathVariable("id" ) Integer id) { return userService.getUserById(id); } @HystrixCommand (fallbackMethod = "buyMovieHystrix" ) @GetMapping ("/buyMovie/{userId}/{movieId}" ) public Map<String, Object> buyMovie ( @PathVariable("userId" ) Integer userId, @PathVariable ("movieId" ) Integer movieId ) { HashMap<String, Object> result = new HashMap<>(); User user = userService.getUserById(userId); Movie movie = restTemplate.getForObject("http://CLOUD-PROVIDER-MOVIE/getMovieById/" +movieId, Movie.class ) ; result.put("user" , user); result.put("movie" , movie); return result; } public Map<String, Object> buyMovieHystrix ( @PathVariable("userId" ) Integer userId, @PathVariable ("movieId" ) Integer movieId ) { Map<String, Object> map = new HashMap<>(); User user = new User(-1 , "无此用户" ); Movie movie = new Movie(-1 , "无此电影" ); map.put("user" , user); map.put("movie" , movie); return map; } }
再次将三个 Movie 服务进行重启,然后中断其中某一个或者两个 Movie 服务,或者全部中断,访问 http://localhost:9002/buyMovie/1/2 就会看到会出现以下返回:
1 {"movie" :{"id" :-1 ,"name" :"无此电影" },"user" :{"id" :-1 ,"name" :"无此用户" }}
过一会儿之后,就会发现没有这种返回了,原因是被注册中心已经将该服务给去掉了。
关闭 Ribbon 重试机制,在远程服务调用不通的情况下,Ribbon 会进行重试。只需要配置文件中关闭重试即可
1 2 3 4 5 6 7 8 9 10 11 12 ribbon: # http建立socket超时时间,毫秒 ConnectTimeout: 2000 # http读取响应socket超时时间 ReadTimeout: 10000 # 同一台实例最大重试次数,不包括首次调用 MaxAutoRetries: 0 # 重试负载均衡其他的实例最大重试次数,不包括首次server MaxAutoRetriesNextServer: 0 # 是否所有操作都重试,POST请求注意多次提交错误。 # 默认false,设定为false的话,只有get请求会重试 OkToRetryOnAllOperations: false
Hystrix + Feign 添加 pom 依赖
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency >
application.yml 中开启 Feign 对 Hystrix 支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spring: application: name: cloud-provider-user-feign server: port: 9001 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true feign: hystrix: enabled: true
主程序入口开启断路保护功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableCircuitBreaker @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
新建一个 exception 包,里面定义一个异常处理类 MovieServiceFeignException
并实现之前的远程方法调用接口 MovieServiceFeign
。重写里面的远程调用方法,返回一个虚假的 Movie 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itguigu.springcloud.exception;import org.springframework.stereotype.Component;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.service.MovieServiceFeign;@Component public class MovieServiceFeignException implements MovieServiceFeign { @Override public Movie getMovieById (Integer id) { Movie movie = new Movie(-1 , "无此电影" ); return movie; } }
在远程调用接口 MovieServiceFeign
中的 @FeignClient
装饰器上加上 fallback 回调的属性,值为刚才新增的异常处理类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.itguigu.springcloud.service;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import com.itguigu.springcloud.bean.Movie;import com.itguigu.springcloud.exception.MovieServiceFeignException;@FeignClient (value="CLOUD-PROVIDER-MOVIE" , fallback = MovieServiceFeignException.class ) public interface MovieServiceFeign { @GetMapping ("/getMovieById/{id}" ) public Movie getMovieById (@PathVariable("id" ) Integer id) ; }
做同样测试,再次将三个 Movie 服务进行重启,然后中断其中某一个或者两个 Movie 服务,或者全部中断,访问 http://localhost:9001/buyMovie/1/2【注意,这里 9001 是演示 Hystrix + Feign 的地址,之前的 9002 是演示 Hystrix + Ribbon 的地址 】 就会看到会出现以下返回:
1 {"movie" :{"id" :-1 ,"name" :"无此电影" },"user" :{"id" :-1 ,"name" :"无此用户" }}
过一会儿之后,就会发现没有这种返回了,原因是被注册中心已经将该服务给去掉了。
注意⚠️:这里的熔断是加在用户端的,也就是说当电影服务出现问题的时候,默认执行的是在用户端设置的断路处理逻辑,即返回的是虚假的数据。所以哪怕是电影服务全部挂掉之后,用户服务调用失败也会有返回值,因为这个断路处理逻辑是在用户端加上的。
Hystrix Dashboard Hystrix 提供了近实时的监控,Hystrix 会实时、累加地记录所有关于 HystrixCommand 的执行信息,包括每秒执行多少请求,多少成功,多少失败等。Netflix 通过 hystrix-metrics-event-stream 项目实现了对以上指标的监控。
spring-boot-starter-actuator 为 SpringBoot 提供了监控,以上面的使用 Feign 的项目为例子来做演示。在 pom 文件中加上以下依赖:
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
直接启动 cloud-provider-user-feign 项目,访问 http://localhost:9001/actuator/health 就能获得当前项目的运行状态:
还能查看其他更多信息:
此时如果我们想用 Hystrix 的 Dashboard 的话,还需修改配置文件,暴露出数据监控流。在 application.yml 中加入以下配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 spring: application: name: cloud-provider-user-feign server: port: 9001 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true feign: hystrix: enabled: true management: endpoints: web: exposure: include: hystrix.stream
访问 http://localhost:9001/actuator/hystrix.stream 地址能看到页面输出一些 ping 信息,但是不能看数据的详细情况。此时,我们需要引入 HystrixDashboard,在 pom 文件中加入以下依赖:
1 2 3 4 5 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix-dashboard</artifactId > </dependency >
在主程序上加入 @EnableHystrixDashboard
注解,开启可视化监控功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.itguigu.springcloud;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@EnableHystrixDashboard @EnableCircuitBreaker @EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class CloudProviderUserApplication { public static void main (String[] args) { SpringApplication.run(CloudProviderUserApplication.class , args ) ; } }
重启项目后访问 dashboard 监控地址 http://localhost:9001/hystrix,并填入之前的监控流地址 http://localhost:9001/actuator/hystrix.stream 进入后就可能看到监控页面。
编写一个shell 脚本,让它一直请求购买电影的服务接口 http://localhost:9001/buyMovie/1/2
1 2 3 while true; do curl http://localhost:9001/buyMovie/1/2; done;
可以看到服务是正常提供访问的,且熔断是关闭的。然后慢慢的一个一个的关闭 Movie 服务,当 Movie 服务全部关闭后,熔断被打开。再次恢复一个 Movie 服务,就会看到熔断又被关闭了,且服务在慢慢恢复。
服务正常
服务熔断
项目地址