一、介绍
1 、网关选择
- 所有的微服务都需要网关,挡在微服务前,起到日志,限流,权鉴等作用;
- netflix公司的zuul,zuul2;zuul功能存在一定的缺陷,zuul2进行了一定的提升;
- Spring Cloud GataWay等网关组件: 由spring内部研发出了新的网关;
2 、zuul 1.x
- 基于Servlet2.5 阻塞架构,Zuul用Java实现;
- 采用非Reactor模式,基于阻塞I/O,性能较低;
3 、zuul 2.x
- 高性能的;
4 、Spring Cloud GataWay
- GataWay意欲取代zuul,SpringCloud 2.0以上版本中,没有对zuul2.x进行整合,只支持1.x;
- GataWay 以 Spring Boot 2.x, Spring WebFlux, Project Reactor为基础;
- Spring WebFlux底层 使用了高性能的Reactor模式通信框架Netty;
- netty可以支持高并发,异步式非阻塞式;
二、组件
1. 路由(Route)
- 构建网关的基本模块 (包含ID,目标URL,一系列的断言和过滤器组成);
2. 断言(Predicate)
- 如果请求与断言相匹配,则进行路由,如果不匹配,则路径出错;
3. 过滤(Filter)
- 路由进行匹配后,通过过滤器来进行前面或者后面的过滤;
三、案列演示
1. 服务提供方微服务
- 服务提供方必须注册到注册中心,从而进行动态路由
2. GateWay网关微服务
- 网关微服务也要入驻注册中心,从而进行动态路由
pom.xml
<dependencies>
<!--eureka 服务端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--网关相关依赖,不能添加web的starter,否则报错-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
application.yaml
server:
port: 9527
# 向注册中心注册时候的服务的名字
spring:
application:
name: zte-cloud-gateway
cloud:
gateway:
# 可以配置多个路由的具体配置
routes:
- id: firstmethod_route # 路由的名字,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 转发服务的ip及端口
predicates:
- Path=/zte-payment-provider/consumer/getname/** # 断言: 路径匹配 /consumer/getname/{name}
- id: secondmethod_route
uri: http://localhost:8001
predicates:
- Path=/zte-payment-provider/consumer/getage/** # 断言: 路径匹配 /consumer/getAge/{age}
3. 访问GateWay微服务
- 对于微服务工程,可以通过对应的ip和端口及路径进直接行访问;
- 也可通过网关微服务的端口+ip,再配上微服务工程的路径进行访问;
- 这个东西类似nginx;
#这个localhost代表的是原来的工程的ip及端口
http://localhost:8001/zte-payment-provider/consumer/getname/Lucy
http://localhost:8001/zte-payment-provider/consumer/getAge/10
#这个localhost代表的是网关的ip及端口
http://localhost:9527/zte-payment-provider/consumer/getname/Lucy
http://localhost:9527/zte-payment-provider/consumer/getAge/10
# 断言如果匹配到,就会将访问路径中: gateway的ip和端口替换为微服务的ip和端口
4. 路由注入方式
- 方式一: 通过配置yaml的方式,较常用;
- 方式二:通过代码注入路由的方式;
4.1. yaml配置,如上所示
4.2. 代码注入
package com.zte.cloud.config;
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder){
RouteLocatorBuilder.Builder routes = routeBuilder.routes();
/**要访问的网址: http://news.baidu.com/guonei
* 参数一: first_gateway_route, 路由的id
* 参数二: /guonei, 目标网站的路径,也即断言
* 参数三: http://news.baidu.com/guonei, 目标网站的地址,其实就是域名加路径
* 如果访问 http:localhost:9527/guonei,将会转发到 http://news.baidu.com/guonei*/
routes.route("first_gateway_route",
r ->r.path("/guonei").
uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
为什么没调通,因为公司内网吗?
5. 动态路由
- 微服务进行集群部署;
- 路由匹配:动态路由根据服务名,而不是ip+端口方式匹配,实现负载均衡(默认轮询);
Gateway的application.yaml
server:
port: 9527
spring:
application:
name: zte-cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务进行路由
# 可以配置多个路由的具体配置
routes:
- id: firstmethod_route
uri: lb://ZTE-PAYMENT-PROVIDER # 匹配注册中心的微服务的名字,lb代表开启负载均衡
predicates:
- Path=/zte-payment-provider/consumer/getname/** # 断言:
- id: secondmethod_route
uri: lb://ZTE-PAYMENT-PROVIDER
predicates:
- Path=/zte-payment-provider/consumer/getage/** # 断言: 路径匹配 /consumer/getAge/{age}
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://firsteureka:7001/eureka/
四、常用断言
- 多个断言,类似于sql中的and后面的条件;
- 只有每个断言都匹配到,才会真正进行服务的转发;
- 具体的各种断言可以去spring的官网进行查看;
1. 时间断言
- After : 在某个时间点后,该路由才生效,
- Before
- Between
application.yaml
# 向注册中心注册时候的服务的名字
spring:
application:
name: zte-cloud-gateway
cloud:
gateway:
# 可以配置多个路由的具体配置
routes:
- id: firstmethod_route # 路由的名字,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 匹配服务的ip及端口
predicates:
- Path=/zte-payment-provider/consumer/getname/** # 断言: 路径匹配 /consumer/getname/{name}
- After=2019-06-18T09:23:55.269+08:00[Asia/Shanghai] # 时间格式用java8的新类来获取
main方法获取时间时间断言的格式
package com.zte.cloud;
import java.time.ZonedDateTime;
public class MainClass {
public static void main(String[] args) {
// Java8新增的特性,显示当前时间及当前时区
ZonedDateTime zbj = ZonedDateTime.now();
// 2020-06-18T09:23:55.269+08:00[Asia/Shanghai]
System.out.println(zbj);
}
}
2. Cookie断言
- Cookie=username, zzyy # username:cookie的名字; zzyy: 值的正则表达式
3. Header断言
- Header=Request-Id, \d+ # header的名字, 正则表达式(为整数)
4. Path断言
- Path=/zte-payment-provider/consumer/getname/** # 断言: 路径匹配 /consumer/getname/{name}
5. Method断言
- Method=GET
五、常用过滤器(Filter)
- Web请求,在服务转发的过程中,进行一些如日志,限流,权鉴等作用;
- 生命周期:pro(请求前过滤)和post(请求后过滤)两种;
- 种类:GatawayFilter(单一的,31种),GlobalFilter(全局的,10种)过滤器;
1. 单一过滤器
- 单一过滤器是对一个路由进行规定的,包含31种;
- 以AddRequestHeader过滤器,其他的功能后续需要再去官网查看;
- 请求经过该路由时候,增加了请求头,可以增加多个;
- 增加的请求头,可以在被代理服务的Controller中获取;
gateway的applicaiton.yaml
# 向注册中心注册时候的服务的名字
spring:
application:
name: zte-cloud-gateway
cloud:
gateway:
# 可以配置多个路由的具体配置
routes:
- id: firstmethod_route # 路由的名字,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 # 匹配服务的ip及端口
predicates:
- Method=GET
filters:
- AddRequestHeader=color, blue #添加两个请求头
- AddRequestHeader=hobby, swim
代理微服务controller
package com.zte.cloud.consumer.controller;
import javax.servlet.http.HttpServletRequest;
@RestController
public class PaymentController {
@GetMapping("/consumer/getname/{name}")
public String getName(@PathVariable("name") String name, HttpServletRequest request){
return name + "======consumer服务方" +
request.getHeader("color")+
request.getHeader("hobby");
}
@GetMapping("/consumer/getAge/{age}")
public String getName(@PathVariable("age") Integer age, HttpServletRequest request){
return "您的年龄为:" + age +
request.getHeader("color")+
request.getHeader("hobby");
}
}
2. 全局过滤器:自定义
- 对所有的路由进行过滤的;
- 可以定义多个过滤器,优先级数字越小,过滤级别越高;
0号过滤器
package com.zte.cloud.filter;
@Component
public class GatewayUserNameFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进入全局过滤器--用户名过滤:" + new Date());
/** 获取访问路由时候带的请求头
* 1.如果匹配不上,则返回错误信息
* 2.如果匹配上,则通过,去下一个过滤链*/
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> list = headers.get("username");
String username = list.get(0);
if(username.equals("shuzhan")){
return chain.filter(exchange);
}else{
System.out.println("Illegal Appler");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder() {
/**过滤器优先级: -2147483648---2147483647
* 1. 优先级越小,级别越高*/
return 0;
}
}
1号过滤器
package com.zte.cloud.filter;
@Component
public class GatewayPasswordFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("进入全局过滤器---密码过滤:" + new Date());
/** 获取访问路由时候带的请求头
* 1.如果匹配不上,则返回错误信息
* 2.如果匹配上,则通过,去下一个过滤链*/
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> list = headers.get("password");
String password = list.get(0);
if(password.equals("123456")){
return chain.filter(exchange);
}else{
System.out.println("Illegal Appler");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder() {
/**过滤器优先级: -2147483648---2147483647
* 1. 优先级越小,级别越高*/
return 1;
}
}