SpringCloud 08 - Hystrix 熔断器
1. 概述简介
1.1 官网
上一代 zuul 1.x: https://github.com/Netflix/zuul/wiki
当前 gateway:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
1.2 简介
① 概述
Cloud 全家桶中有个很重要的组件就是网关,在 1.x 版本中都是采用的 Zool 网关;但在 2.x 版本中,zool 的升级一直跳票,SpringCloud 最后自己研发了一个网关代替 Zool,那就是 SpringCloud Gateway。就是说 Gateway 是原 zoo1.x 的替代。

Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring 5,Spring Boot2 和 Project Reactor 等技术。Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+ Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在 Spring Cloud 2.0 以上版本中,没有对新版本的Zuul 2.0 以上最新高性能版本进行集成,仍然还是使用的 Zuul 1.x 非 Reactor 模式的老版本。而为了提升网关的性能,SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。
Spring Cloud Gateway 的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标和限流。
② 一句话
SpringCloud Gateway 使用的是 Webflux 中的 reactor-netty 响应式编程组件,底层使用了 Netty 通讯框架。
源码架构:
1.3 能干嘛
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
- .....
1.4 微服务架构中网关在哪里
1.5 有Zuull了怎么又出来gateway
① 我们为什么选择Gateway?
- netflix 不太靠谱,zuul2.0 一直跳票,迟迟不发布
- SpringCloud Gateway 具有如下特性
- SpringCloud Gateway 与 Zuul 的区别
② Zuul1.x 模型
Springcloud 中所集成的 Zuul 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet I0 处理模型。
Servlet的生命周期:
servlet 由 servlet container 进行生命周期管理。container 启动时构造 servlet 对象并调用 servlet init() 进行初始化;
container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用 service()。container 关闭时调用servlet destory() 销毁servlet;
上述模式的缺点:
servlet 是一个简单的网络 I0 模型, 当请求进入 servlet container 时,servlet container 就会为其绑定一个线程, 在并发不高的场景下这种模型是适用的。但是一旦高并发(此如抽风用 jemeter 压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个 request 分配一个线程, 只需要 1 个或几个线程就能应对极大并发的请求,这种业务场景下 servlet 模型没有优势。
所以 Zuul1.X 是基于 servlet 之上的一个阻塞式处理模型,即 spring 实现了处理所有 request 请求的一个 servlet (DispatcherServlet) 并由该 servlet 阻塞式处理。所以 Springcloud Zuul 无法摆脱 servlet 模型的弊端。
③ Gateway 模型
WebFlux 是什么:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux
说明:
传统的 Web 框架,比如说:struts2,springmvc 等都是基于 Servlet API 与 Servlet 容器基础之上运行的。
但是,在 Servlet 3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。 相对于传统的 web 框架来说,它可以运行在诸如 Netty, Undertow 及支持 Servlet3.1 的容器上。非阻塞式+函数式编程(Spring5 必须让你使用 Java8)
Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖 Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
2. 三个核心概念
2.1 Route(路由):路由是构建网关的基本模块,它由 ID,目标URI,一系列的断言和过滤器组成,如断言为 true 则匹配该路由。
2.2 Predicate(断言):参考的是 Java8 的 java.util.function.Predicate 开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
2.3 Filter(过滤):指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
2.4 总结
web 请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制,predicate 就是我们的匹配条件;
而 filter,就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标 url,就可以实现一个具体的路由了。
3. Gateway 工作流程
3.1 官网总结
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前( "pre" )或之后( "post" )执行业务逻辑。
Filter 在"pre" 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
3.2 核心逻辑
路由转发 + 执行过滤器链
4. 入门配置
4.1 新建Module:cloud-gateway-gateway9527
4.2 POM
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.janet.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.3 YML
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.4 主启动类
package com.janet.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @Date 2020/5/11
* @Author Janet
*/
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
4.5 业务类: 无
4.6 9527网关如何做路由映射?
cloud-provider-payment8001看看 controller 的访问地址:get、lb
我们目前不想暴露 8001 端口,希望在 8001 外面套一层 9527
4.7 YML 新增网关配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
4.8 测试
启动7001,启动8001(cloud-provider-payment8001),启动9527
访问说明:
- 添加网关前:http://localhost:8001/payment/get/1
- 添加网关后:http://localhost:9527/payment/get/1
4.9 YML配置说明
Gateway网关路由有两种配置方式:
① 在配置文件yaml中配置:见前面的步骤
② 代码中注入 RouteLocator 的 Bean
- 官网案例
- 百度国内新闻网站,需要外网:https://news.baidu.com/guonei
- 自己写一个 百度新闻
业务需求:通过9527网关访问到外网的百度新闻网址
编码:cloud-gateway-gateway9527
业务实现:config
package com.janet.springcloud.config;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description 这个类是自己用代码配置路由的 config 类
* @Date 2020/5/11
* @Author Janet
*/
@Configuration
public class GateWayConfig {
/*
* 配置了一个 id 为 rout-name 的路由规则,
* 当访问地址 http://localhost:9527/guonei 时会自动转发到地址:https://news.baidu.com/guonei
*
* */
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//
routes.route("path_rout_janet", r -> r.path("/guonei").uri("https://news.baidu.com/guonei")).build();
return routes.build();
}
}
5. 通过服务名实现动态
默认情况下 Gatway 会根据注册中心注册的服务列表, 以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
① 启动: 一个 eureka7001 + 两个服务提供者 8001/8002
② POM(之前都加过了)
③ YML
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
需要注意的是 uri 的协议 lb,表示启用 Gateway 的负载均衡功能
lb://serverName 是spring cloud gatway在微服务中自动为我们创建的负载均衡 url
④ 测试:http://localhost:9527/payment/lb
8001/8002两个端口切换:
6. Predicate
6.1 简介
启动微服务 gateway9527:
6.2 Route Predicate Factories
6.3 常用的Route Predicate
① After Route Predicate
② Before Route Predicate
③ Between Route Predicate
④ Cookie Route Predicate
Cookie Route Predicate 需要两个参数,一个是 Cookie name,一个是正则表达式。路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
不带cookies访问:
带上cookies访问:
⑤ Header Route Predicate
两个参数:一个属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
⑥ Host Route Predicate
⑦ Method Route Predicate
⑧ Path Route Predicate
⑨ Query Route Predicate
⑩ RemoteAddr Route Predicate
⑪ Weight Route Predicate
说白了,Predicate 就是为了实现一组匹配规则, 让请求过来找到对应的 Route 进行处理。
7. Filter 的使用
7.1 是什么
7.2 Spring Cloud Gateway 的 filter
① 生命周期
pre
post
② 种类
- GatewayFilter:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
- GlobalFilter:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
7.3 常用的GatewayFilter
AddRequestParameter:YML
7.4 自定义过滤器
自定义全局GlobalFilter
① 两个主要接口介绍:implements GlobalFilter, Ordered
② 能干嘛
- 全局日志记录
- 统一网关鉴权
- .....
③ 案例代码
package com.janet.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
/**
* @author Janet
* @date 2020/5/12
*/
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("--------------------come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null) {
log.info("------------用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //数据越小,优先级越高
}
}
④ 测试
启动 7001,8001,8002,9527
正确: http://localhost:9527/payment/lb?uname=z3
错误:
SpringCloud 10 - SpringCloud config 分布式配置中心