SpringCloud:提供的微服务架构整体管理的一站式解决方案
- Eureka:注册中心(相当于Zookeeper)
- Ribbon:客户端负载均衡
- Feign:远程接口的声明式调用(大致上相当于Dubbo)
- Hystrix:服务的熔断、降级、监控
- Zuul:网关
使用版本:Greenwich.SR2,SpringCloud是工作在SpringBoot基础上的
1. SpringCloud 核心
基于HTTP协议,这是它和Dubbo的最本质的区别。Dubbo的核心是基于RPC远程方法调用
2. SpringCloud 组件
- Eureka:注册中心(相当于Zookeeper)
- Ribbon:客户端负载均衡
- Feign:远程接口的声明式调用(大致上相当于Dubbo)
- Hystrix:服务的熔断、降级、监控
- Zuul:网关
说明:
- 声明式调用:使用@Autowired装配代表远程方法的接口
- 实现声明式调用:需要借助于注册中心Eureka(优瑞卡)
- 同一个微服务启动多个相同实例构成:集群
- 有了集群之后,客户端如何决定访问哪一个:负载均衡Ribbon
- Feign的使用:需要以Ribbon为基础,导入Feign的环境之后,会自动把Ribbon加入进来
- 暴露的接口通过Feign装配到Consumer中,就可以使用了
- Hystrix,降级和熔断都是为了预防调用Provider端时的未知风险
3. SpringCloud的使用
3.1 准备基础测试环境
3.1.1 结构
3.1.2 创建父工程pro01-spring-cloud-parent
<groupId>com.atguigu.spring.cloud</groupId>
<artifactId>pro01-spring-cloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--父工程以pom方式打包-->
<packaging>pom</packaging>
<!--配置依赖管理-->
<dependencyManagement>
<dependencies>
<!-- 导入 SpringCloud 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<!-- import依赖范围表示将spring-cloud-dependencies包中的依赖信息导入 -->
<scope>import</scope>
</dependency>
<!-- 导入 SpringBoot 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
3.1.3 创建通用工程pro02-spring-cloud-common
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro02-spring-cloud-common</artifactId>
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
public Employee() {
}
public Employee(Integer empId, String empName, Double empSalary) {
this.empId = empId;
this.empName = empName;
this.empSalary = empSalary;
}
......
}
3.1.4 创建提供者工程
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro03-spring-cloud-provider</artifactId>
<!--引入依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.spring.cloud</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
创建主启动类
@SpringBootApplication
public class ProviderMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderMainApplication.class,args);
}
}
创建application.yaml配置文件
server:
port: 1000
创建Handler类和方法
@RestController
public class EmployeeHandler {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote() {
return new Employee(555, "tom555", 555.55);
}
}
3.1.5 创建消费者工程
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro04-spring-cloud-consumer</artifactId>
<!--引入依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.spring.cloud</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
创建主启动类
@SpringBootApplication
public class ConsumerMainApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerMainApplication.class,args);
}
}
创建配置类提供 RestTemplate,以便Consumer调用Provider
@Configuration
public class MySpringCloudConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
创建application.yaml配置文件
server:
port: 4000
创建Handler类和方法
@RestController
public class HumanResourceHandler {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/get/employee")
public Employee getEmployeeRemote() {
// 1.声明远程微服务的主机地址加端口号
String host = "http://localhost:1000";
// 2.声明具体要调用的功能的URL地址
String url = "/provider/get/employee/remote";
// 3.通过RestTemplate调用远程微服务
return restTemplate.getForObject(host + url, Employee.class);
}
}
3.2 创建 Eureka 注册中心
3.2.1 创建 Eureka 注册中心工程pro05-spring-cloud-eureka
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro05-spring-cloud-eureka</artifactId>
<!--引入依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
创建主启动类,需要启用 Eureka 服务器功能
// 启用 Eureka 服务器功能
@EnableEurekaServer
@SpringBootApplication
public class EurekaMainApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaMainApplication.class,args);
}
}
创建application.yaml配置文件
server:
port: 5000
eureka:
instance:
hostname: localhost # 配置当前Eureka服务的主机地址
client:
registerWithEureka: false # 注册:自己就是注册中心,所以自己不注册自己
fetchRegistry: false # 订阅:自己就是注册中心,所以不需要 "从注册中心取回信息"
service-url: # 客户端(指的是Consumer、Provider)访问 当前Eureka 时使用的地址
defaultZone: http://localhost:${server.port}/eureka/
3.2.2 客户端和服务器端
说明:
- 如果只看browser和consumer:browser是客户端,consumer是服务器端
- 如果只看consumer和provider:consumer是客户端,provider是服务器端
- 如果只看consumer和Eureka:consumer是客户端,Eureka是服务器端
- 如果只看provider和redis:provider是客户端,redis是服务器端
- 所以综上所述,调用者是客户端,被调用者是服务器端
3.2.3 将Provider注册到Eureka中
pro03-spring-cloud-provider工程加入如下依赖:
为什么加的是客户端的依赖?是因为相对于Eureka而言,Provider是客户端,Eureka是服务器端
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置application.yaml配置文件
server:
port: 1000
eureka:
client:
service-url: # 配置当前微服务作为Eureka客户端访问Eureka服务器端时使用的地址
defaultZone: http://localhost:5000/eureka/
spring:
application:
name: provider-project # 指定当前微服务的名称,以便将来通过微服务名称调用当前微服务时能够找到
补充:※关于相关注解是否标记主程序类
- 较低版本需要使用@EnableEurekaClient 注解标记主程序类。
- 稍高版本也可以使用@EnableDiscoveryClient注解标记主程序类。
- 当前版本可以省略。
// 下面两个注解功能大致相同
// @EnableDiscoveryClient 启用发现服务功能,不局限于Eureka注册中心
// @EnableEurekaClient 启用Eureka客户端功能,必须是Eureka注册中心
@SpringBootApplication
public class ProviderMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderMainApplication.class,args);
}
}
效果展示:localhost:5000
3.3 consumer访问provider时使用微服务名称代替主机名:端口号
3.3.1 分析
3.3.2 操作
在Consumer工程中引入如下依赖
<!--Ribbon(负载均衡)的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在 application.yml 中加入如下配置
spring:
application:
name: consumer-project
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
在 RestTemplate 的配置方法处使用@LoadBalanced 注解,该注解的作用是:用于支持负载均衡
@Configuration
public class MySpringCloudConfig {
@Bean
// 这个注解让RestTemplate有负载均衡的功能,通过调用Ribbon访问Provider的集群
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
修改 consumer 工程的 handler 方法
@RestController
public class HumanResourceHandler {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/ribbon/get/employee")
public Employee getEmployeeRemote() {
// 1.声明远程微服务的主机地址加端口号
// String host = "http://localhost:1000";
// 引入 Eureka 和 Ribbon 后,就可以使用微服务名称替代 IP地址和端口号
String host = "http://provider-project";
// 2.声明具体要调用的功能的URL地址
String url = "/provider/get/employee/remote";
// 3.通过RestTemplate调用远程微服务
return restTemplate.getForObject(host + url, Employee.class);
}
}
3.4 provider以集群方式启动,体现Ribbon的效果
3.4.1 修改 provider 的 handler 方法
@RestController
public class EmployeeHandler {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote(HttpServletRequest request) {
// 获取当前 web 应用的端口号
int serverPort = request.getServerPort();
return new Employee(555, "tom555-"+serverPort, 555.55);
}
}
3.4.2 provider 以集群方式启动,步骤:idea中选择editConfiguration,里面选择ProviderMainApplication,之后在右侧蓝色字体Modify Options中勾选Allow multiple instance,然后修改端口号依次运行主配置类即可。
- 按照端口号 1000 启动第一个实例
- 按照端口号 2000 启动第二个实例
- 按照端口号 3000 启动第三个实例
注意:provider 的微服务名称必须使用 同一个名称才能构成一个集群,否则将不会认定为是属于同一个集群。
3.4.3 consumer正常访问
3.5 使用Feign实现远程方法声明式调用
3.5.1 分析
3.5.2 操作
3.5.2.1 common工程
引入Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建远程调用方法的接口:
- 远程调用的接口方法:
- 要求@RequestMapping注解映射的地址要一致
- 要求方法声明一致
- 用来获取请求参数的@RequestParam、@PathVariable、@RequestBody不能省略,两边一致
// @FeignClient注解表示当前接口和一个Provider对应,注解中value属性指定要调用的Provider的微服务名称
@FeignClient(value = "provider-project")
public interface EmployeeRemoteService {
// 远程调用的接口方法
// 要求@RequestMapping注解映射的地址要一致
// 要求方法声明一致
// 用来获取请求参数的@RequestParam、@PathVariable、@RequestBody不能省略,两边一致
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote();
}
3.5.2.2 新建Feign-consumer工程
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro06-spring-cloud-feign-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.spring.cloud</groupId>
<artifactId>pro02-spring-cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--不引入Ribbon的原因是因为该工程依赖common工程,common引入了Feign依赖,Feign会自动导入Ribbon依赖-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
主启动类
// 启用 Feign 客户端功能
@EnableFeignClients
@SpringBootApplication
public class FeignConsumerMainApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerMainApplication.class,args);
}
}
application.yaml配置文件
server:
port: 7000
spring:
application:
name: feign-consumer-project
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
handler的类和方法
@RestController
public class FeignHumanResourceHandler {
// 装配调用远程微服务的接口,后面向调用本地方法一样直接使用
@Autowired
private EmployeeRemoteService employeeRemoteService;
@RequestMapping("/feign/consumer/get/employee")
public Employee getEmployeeRemote(){
return employeeRemoteService.getEmployeeRemote();
}
}
3.5.2.3 传参时,该加的注解不能省略
简单类型:@RequestParam、@PathVariable、复杂类型:@RequestBody,注解都不能省略
接口写法:
@RequestMapping("/provider/get/employee/list/remote")
public List<Employee> getEmployeeListRemote(@RequestParam("keyword") String keyword);
Provider的handler方法:
@RequestMapping("/provider/get/employee/list/remote")
public List<Employee> getEmployeeListRemote(@RequestParam("keyword") String keyword){
logger.info("keyword="+keyword);
ArrayList<Employee> employeeList = new ArrayList<>();
employeeList.add(new Employee(33,"empName33",333.3));
employeeList.add(new Employee(44,"empName44",444.4));
employeeList.add(new Employee(55,"empName55",555.5));
return employeeList;
}
3.6 Hystrix-熔断、降级、监控
3.6.1 分布式系统面临的问题
在微服务架构体系下,服务间的调用错综复杂,交织成一张大网。如果其中某个节点突然无法正常工作,则访问它的众多服务都会被卡住,进而有更多服务被卡住,系统中的线程、CPU、内存等资源有可能被迅速耗尽,最终整个服务体系崩溃。我们管这样的现象叫服务雪崩。
3.6.2 Hytrix介绍
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hytrix 能够提供服务降级、服务熔断、服务限流、接近实时的监控等方面的功能。
3.6.3 服务熔断机制,熔断的是该节点微服务的调用,是在服务端(provider)完成的
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速响应错误信息。当检测到该节点微服务调用响应正常后恢复调用链路。在 SpringCloud 框架里熔断机制通过 Hystrix 实现。Hystrix 会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是 5 秒内 20 次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。
3.6.3.1 引入依赖,在Provider微服务工程引入Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.6.3.2 主启动类,添加注解@EnableCircuitBreaker,启用断路器功能
// 启用断路器功能
@EnableCircuitBreaker
@SpringBootApplication
public class ProviderMainApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderMainApplication.class,args);
}
}
3.6.3.3 ResultEntity,类似于工具类,放在Common工程里
// 整个项目统一使用这个类型作为 Ajax 请求或远程方法调用返回响应的数据格式
public class ResultEntity<T> {
public static final String SUCCESS = "SUCCESS";
public static final String FAILED = "FAILED";
public static final String NO_MESSAGE = "NO_MESSAGE";
public static final String NO_DATA = "NO_DATA";
private String result;
private String message;
private T data;
public ResultEntity() {
}
public ResultEntity(String result, String message, T data) {
this.result = result;
this.message = message;
this.data = data;
}
/**
* 操作成功,不需要返回数据
*/
public static ResultEntity<String> successWithoutData(){
return new ResultEntity<String>(SUCCESS,NO_MESSAGE,NO_DATA);
}
/**
* 操作成功,需要返回数据
*/
public static <E> ResultEntity<E> successWithData(E data){
return new ResultEntity<>(SUCCESS,NO_MESSAGE,data);
}
/**
* 操作失败,返回错误消息
*/
public static <E> ResultEntity<E> failed(String message) {
return new ResultEntity<>(FAILED, message, null);
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "ResultEntity{" +
"result='" + result + '\'' +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
3.6.3.4 handler方法
// 正常情况下调用的方法
// @HystrixCommand 注解通过 fallbackMethod 属性指定断路情况下要调用的备份方法
@HystrixCommand(fallbackMethod = "getEmpBackup")
@RequestMapping("/provider/get/emp/with/circuit/breaker")
public ResultEntity<Employee> getEmp(@RequestParam("signal") String signal) {
if("bang".equals(signal)) {
throw new RuntimeException();
}
return ResultEntity.successWithData(new Employee(666, "sam666", 666.66));
}
// 出现错误时的应急预案,备用方法
public ResultEntity<Employee> getEmpBackup(@RequestParam("signal") String signal) {
String message = "方法执行出现问题,执行断路 signal = " + signal;
return ResultEntity.failed(message);
}
3.6.3.5 测试
启动Eureka工程、Provider工程,直接访问handler方法,
访问地址:localhost:1000/provider/get/emp/with/circuit/breaker?signal=
访问正常情况下:
访问异常时:
3.6.4 服务降级机制
服务降级处理是在客户端(Consumer端)实现完成的,与服务端(Provider 端)没有关系。当某个 Consumer 访问一个 Provider 却迟迟得不到响应时执行预先设定好的一个解决方案,而不是一直等待。
3.6.4.1 common工程:依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
3.6.4.2 common 工程:FallbackFactory
/**
* 1.实现Consumer端服务降级功能
* 2.实现FallBackFactory接口时要传入@FeignClient注解标记的接口类型EmployeeRemoteService
* 3.在create()方法中返回@FeignClient注解标记的接口类型的对象,当我们的Provider调用失败后,会执行这个对象的对应方法
* 4.这个类必须使用@Component注解将当前类的对象加入IOC容器,当前类必须能被扫描到
* @author wuxy
* @create 2022-08-24 17:14
*/
@Component
public class MyFallBackFactory implements FallbackFactory<EmployeeRemoteService> {
@Override
public EmployeeRemoteService create(Throwable cause) {
return new EmployeeRemoteService() {
@Override
public Employee getEmployeeRemote() {
return null;
}
@Override
public List<Employee> getEmployeeListRemote(String keyword) {
return null;
}
@Override
public ResultEntity<Employee> getEmp(String signal) {
return ResultEntity.failed("降级机制生效:" + cause.getMessage());
}
};
}
}
3.6.4.3 common 工程:Feign 接口
// @FeignClient注解表示当前接口和一个Provider对应
// 注解中value属性指定要调用的Provider的微服务名称
// 注解中fallbackFactory属性指定Provider不可用时提供备用方案的工厂类型
@FeignClient(value = "provider-project",fallbackFactory = MyFallBackFactory.class)
public interface EmployeeRemoteService {
// 远程调用的接口方法
// 要求@RequestMapping注解映射的地址要一致
// 要求方法声明一致
// 用来获取请求参数的@RequestParam、@PathVariable、@RequestBody不能省略,两边一致
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote();
@RequestMapping("/provider/get/employee/list/remote")
public List<Employee> getEmployeeListRemote(@RequestParam("keyword") String keyword);
@RequestMapping("/provider/get/emp/with/circuit/breaker")
public ResultEntity<Employee> getEmp(@RequestParam("signal") String signal);
}
3.6.4.4 feign-consumer 工程:application.yml,启用降级功能
# 表示客户端启用hystrix的降级功能
feign:
hystrix:
enabled: true
3.6.4.5 feign-consumer工程:handler方法
@RequestMapping("/feign/consumer/test/fallback")
public ResultEntity<Employee> testFallBack(@RequestParam("signal") String signal){
return employeeRemoteService.getEmp(signal);
}
3.6.4.6 测试:hystrix降级机制
正常访问:
断开Provider服务:
com.netflix.client.ClientException: Load balancer does not have available server for client: provider-project:负载均衡器没有为client: provider-project提供可用的服务器
3.6.5 Hystrix-监控仪表盘
3.6.5.1 provider工程导入如下依赖,才能被仪表盘监控
加入依赖 spring-boot-starter-actuator ,SpringBoot抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、追踪、审计、控制等功能。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置application.yaml
management:
endpoints:
web:
exposure:
include: hystrix.stream
3.6.5.2 创建监控工程
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro07-spring-cloud-hystrix-dashboard</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
主启动类
// 启用 Hystrix 仪表盘功能
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixDashboardMainApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMainApplication.class,args);
}
}
application.yaml配置文件
server:
port: 8000
spring:
application:
name: hystrix-dashboard-project
3.6.5.3 查看监控数据
- ① 直接查看监控本身数据
- http://localhost:1000/actuator/hystrix.stream
- 说明 1:http://localhost:1000 访问的是被监控的 provider 工程
- 说明 2:/actuator/hystrix.stream 是固定格式
- 说明 3:如果从 provider 启动开始它的方法没有被访问过,那么显示的数据只有“ping:”,要实际访问一个带熔断功能的方法才会有实际数据。
- ② 通过仪表盘工程访问监控数据
- 第一步:打开仪表盘工程的首页:http://localhost:8000/hystrix
- 第二步:填入获取监控数据的地址(上面直接查看时使用的地址)
3.7 网关Zuul
不同的微服务一般有不同的网络地址,而外部的客户端可能需要调用多个服务的接口才能完成一个业务需求。比如一个电影购票的手机app,可能会调用电影分类微服务,用户微服务,支付微服务等。如果客户端直接和微服务进行通信,会存在以下问题:
- 客户端会多次请求不同微服务,增加客户端的复杂性
- 存在跨域请求,在一定场景下处理相对复杂
- 认证复杂,每一个服务都需要独立认证
- 难以重构,随着项目的迭代,可能需要重新划分微服务,如果客户端直接和微服务通信,那么重构会难以实施。
- 某些微服务可能使用了其他协议,直接访问有一定困难
Zuul 包含了对请求的路由和过滤两个最主要的功能:
- 路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
- 过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul 和 Eureka 进行整合,将 Zuul 自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获得其他微服务的信息,也即以后的访问微服务都是通过Zuul跳转后获得。
总体来说,Zuul 提供了 代理、路由 和 过滤 的功能。
3.7.1 创建 Zuul 工程
<parent>
<artifactId>pro01-spring-cloud-parent</artifactId>
<groupId>com.atguigu.spring.cloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pro08-spring-cloud-zuul</artifactId>
<dependencies>
<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-zuul</artifactId>
</dependency>
</dependencies>
配置application.yaml文件
server:
port: 9000
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
spring:
application:
name: zuul-gateway-project
主启动类
// 启用 Zuul 代理功能
@EnableZuulProxy
@SpringBootApplication
public class ZuulMainApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulMainApplication.class,args);
}
}
3.7.2 访问测试
3.7.2.1 初步访问
此时:通过 Zuul 可以访问,也可以不经过 Zuul 直接访问目标微服务。
出现的错误:
浏览器页面显示的:
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Aug 07 17:49:01 CST 2018
There was an unexpected error (type=Gateway Timeout, status=504).
com.netflix.zuul.exception.ZuulException: Hystrix Readed time out
idea控制台显示的错误:
Error during filtering com.netflix.zuul.exception.ZuulException: Filter threw Exception
错误的原因:
zuul的默认超时时间比较小,一些请求的请求时间太长,触发了熔断器导致
错误的解决:配置下zuul的超时时间,因zuul启用了ribbon的负载均衡,还需要设置ribbon的超时时间,注意ribbon的超时时间要小于zuul超时时间 。
zuul:
host:
connect-timeout-millis: 15000 # HTTP连接超时要比Hystrix的大
socket-timeout-millis: 60000 # socket超时
ribbon:
ReadTimeout: 10000 # 通信超时时间(ms)
ConnectTimeout: 10000 # 连接超时时间(ms)
3.7.2.2 使用指定地址代替微服务名称
zuul:
routes:
employee: # 自定义路由规则的名称,在底层的数据结构中是 Map 的键
serviceId: feign-consumer-project # 目标微服务名称,ZuulRoute类型的一个属性
path: /zuul-emp/** # 用来代替目标微服务名称的路径,ZuulRoute类型的一个属性
# /**表示匹配多层路径,如果没有加/**则不能匹配后续的多层路径了
效果:使用微服务名称和新配置的地址都可以访问
- http://localhost:9000/feign-consumer-project/feign/consumer/get/employee
- http://localhost:9000/zuul-emp/feign/consumer/get/employee
3.7.2.3 让用户不能通过微服务名称访问
zuul:
ignored-services: # 忽略指定微服务名称,让用户不能通过微服务名称访问
- atguigu-feign-consumer
routes:
employee: # 自定义路由规则的名称,在底层的数据结构中是 Map 的键
serviceId: feign-consumer-project # 目标微服务名称,ZuulRoute类型的一个属性
path: /zuul-emp/** # 用来代替目标微服务名称的路径,ZuulRoute类型的一个属性
# /**表示匹配多层路径,如果没有加/**则不能匹配后续的多层路径了
效果:微服务名称不能访问,只有新配置的地址可以访问
http://localhost:9000/feign-consumer-project/feign/consumer/get/employee- http://localhost:9000/zuul-emp/feign/consumer/get/employee
3.7.2.4 忽略所有微服务名称
zuul:
host:
connect-timeout-millis: 15000 # HTTP连接超时要比Hystrix的大
socket-timeout-millis: 60000 # socket超时
routes:
employee: # 自定义路由规则的名称,在底层的数据结构中是 Map 的键
serviceId: feign-consumer-project # 目标微服务名称,ZuulRoute类型的一个属性
path: /zuul-emp/** # 用来代替目标微服务名称的路径,ZuulRoute类型的一个属性
# /**表示匹配多层路径,如果没有加/**则不能匹配后续的多层路径了
# ignored-services: # 忽略指定微服务名称,让用户不能通过微服务名称访问
# - feign-consumer-project
ignored-services: '*' # 忽略所有微服务名称
3.7.2.5 给访问路径添加统一前缀
zuul:
host:
connect-timeout-millis: 15000 # HTTP连接超时要比Hystrix的大
socket-timeout-millis: 60000 # socket超时
routes:
employee: # 自定义路由规则的名称,在底层的数据结构中是 Map 的键
serviceId: feign-consumer-project # 目标微服务名称,ZuulRoute类型的一个属性
path: /zuul-emp/** # 用来代替目标微服务名称的路径,ZuulRoute类型的一个属性
# /**表示匹配多层路径,如果没有加/**则不能匹配后续的多层路径了
# ignored-services: # 忽略指定微服务名称,让用户不能通过微服务名称访问
# - feign-consumer-project
ignored-services: '*' # 忽略所有微服务名称
prefix: /maomi # 给访问路径添加统一前缀
使用:http://localhost:9000/maomi/zuul-emp/feign/consumer/get/employee
3.7.3 ZuulFilter
@Component
public class MyZuulFilter extends ZuulFilter {
Logger logger = LoggerFactory.getLogger(MyZuulFilter.class);
// 判断当前请求是否要进行过滤
// 要过滤:返回true,继续执行run()方法
// 不过滤:返回false,直接放行
@Override
public boolean shouldFilter() {
// 1.获取当前 RequestContext 对象
RequestContext context = RequestContext.getCurrentContext();
// 2.获取当前请求对象
HttpServletRequest request = context.getRequest();
// 判断当前请求参数是否为signal=hello
String signal = request.getParameter("signal");
// 3.获取当前请求要访问的目标地址
// String servletPath = request.getServletPath();
// 4.打印
// System.out.println("servletPath=" + servletPath);
// 5.当前方法返回值
// true: 表示应该过滤,下面执行run()方法进行过滤
// false: 表示不过滤,直接放行
return "hello".equals(signal);
}
@Override
public Object run() throws ZuulException {
// 执行具体过滤逻辑
logger.info("当前请求要进行过滤,run()方法执行了!");
// 官方文档说:当前实现会忽略这个返回值,所以返回 null 即可
return null;
}
@Override
public String filterType() {
// 返回当前过滤器类型,决定当前过滤器在什么时候执行
// 可选类型包括:pre、route、post、static
// 如果需要在目标微服务前面执行过滤操作,选用 pre 类型
return "pre";
}
@Override
public int filterOrder() {
// 过滤器执行顺序
return 0;
}
}
3.7.4 使用 Zuul 进行用户是否登录的检查大致方案
4. SpringCloud小结
4.1 SpringBoot和SpringCloud的关系
SpringBoot是基础,SpringCloud要基于SpringBoot开发
4.2 SpringCloud和Dubbo对比
- 核心
- Dubbo底层基于RPC
- SpringCloud底层基于RestFul,也可以说是基于HTTP
- 其他区别
- SpringCloud相对于Dubbo功能更全面
- SpringCloud是一个一站式解决方案
- SpringCloud能够天然的基于Spring全家桶开发
4.3 开发业务功能相关程度
- Eureka:★★★★★
- Ribbon:★★★☆☆
- Feign: ★★★★★
- Hystrix:★★★☆☆
- Zuul : ★★★★★