SpringCloudDay3
1 微服务网关概述
1.1 服务网关的概念
API网关是一个服务器,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供
一个定制的API。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在
网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和
管理服务。
1.2 常见的API网关实现方式
1.3 基于Nginx的网关实现
1.3.3 准备工作
准备一个基本的feign调用的微服务 消费者 服务提供者 注册中心
feign项目下载地址
下载nginx-1.8.0解压到没有中文的目录
运行即可
访问地址 localhost
如何配置网关
打开安装目录/conf/nginx.conf文件
在http{service{}}里配置如下文件
#设置路由到商品 对应的为自己微服务的地址
location /api-product {
proxy_pass http://127.0.0.1:9001/;
}
#设置路由到订单
location /api-order {
proxy_pass http://127.0.0.1:9002/;
}
访问地址变为 http://localhost/api-order/order/1
实际上是他帮助我们转发到了http://127.0.0.1:9002/order/1
2 微服务网关Zuul(过一下)
所有内置过滤器列表:
3 微服务网关GateWay
Zuul 1.x 是一个基于阻塞 IO 的 API Gateway 以及 Servlet;直到 2018 年 5 月,Zuul 2.x(基于
Netty,也是非阻塞的,支持长连接)才发布,但 Spring Cloud 暂时还没有整合计划。Spring Cloud
Gateway 比 Zuul 1.x 系列的性能和功能整体要好。
3.1 Gateway简介
3.1.1 简介
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开
发的网关,旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式,统一访问接口。Spring
Cloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflix ZUUL,其不仅提供统一的路
由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基
于Nttey的响应式开发模式。
3.1.2 核心概念
3.2 入门案例
3.2.1 入门案例
(1) 创建工程导入依赖
在项目中添加新的模块 shop_gateway_server ,并导入依赖
注意SpringCloud Gateway使用的web框架为webflux,和SpringMVC不兼容。引入的限流组件是
hystrix。redis底层不再使用jedis,而是lettuce。
<!-- 这个gateway是通过netty+wabflux实现
和springmvc存在冲突 所以他的工程以及父工程中不能有web包
哪里需要web包 就移动到哪里去以下的这个包是web包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
(2) 配置启动类
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class, args);
}
}
(3) 编写配置文件
创建 application.yml 配置文件
server:
port: 8080
spring:
application:
name: gateway-server #服务名称
#配置Gateway路由
cloud:
gateway:
routes:
- id: product-service #服务id可以随便设置 一般是设置成转到的服务名称
uri: http://127.0.0.1:9001 #服务的地址
predicates:
- Path=/product/** #断言 就是说当请求地址为 127.0.0.1:8080/product/**的所有请求会转发
- id: order-service
uri: http://127.0.0.1:9002
predicates:
- Path=/order/**
等于说 服务启动过后 访问127.0.0.0:8080/perduct/1 他会自动转换为http://127.0.0.1:9001/product/1
3.2.2 路由规则
Spring Cloud Gateway 的功能很强大,前面我们只是使用了 predicates 进行了简单的条件匹配,其实
Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用
Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件
匹配到对应的路由。
3.2.3 动态路由
和zuul网关类似,在SpringCloud GateWay中也支持动态路由:即自动的从注册中心中获取服务列表并
访问。
(1)添加注册中心依赖
在工程的pom文件中添加注册中心的客户端依赖(这里以Eureka为例)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)配置动态路由
修改 application.yml 配置文件,添加eureka注册中心的相关配置,并修改访问映射的URL为服务名
称
server:
port: 8080
spring:
application:
name: gateway-server #服务名称
#配置Gateway路由
cloud:
gateway:
routes:
- id: product-service #服务id可以随便设置 一般是设置成转到的服务名称
# uri: http://127.0.0.1:9001 #服务的地址
uri: lb://service-product
predicates:
- Path=/product/** #断言 就是说当请求地址为 127.0.0.1:8080/product/**的所有请求会转发
- id: order-service
uri: http://127.0.0.1:9002
predicates:
- Path=/order/**
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9000/eureka/
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
instance:
preferIpAddress: true
ip-address: 127.0.0.1
访问地址为:http://localhost:8080/product/1 会被转发
3.2.4 重写转发路径
3.3 过滤器
3.3.1 过滤器基础
3.3.2 局部过滤器
局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在
Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。这里简单将
Spring Cloud Gateway内置的所有过滤器工厂整理成了一张表格,虽然不是很详细,但能作为速览使
用。如下:
3.3.3 全局过滤器
全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户
可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功
能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
如何实现一个全局过滤器:
创建一个类实现GlobalFilter, Ordered接口 并实现方法 具体作用看图
把这个类放到容器中
/**
* @Author XuZhuHong
* @CreateTime 2021/7/29 20:08
* 自定义的过滤器
*/
@Component
public class LonginFilter implements GlobalFilter, Ordered {
/**
* 过滤器总的业务逻辑
* @param exchange 跟请求或者响应沾边的都用它获取
* @param chain
* @return
*/
@Override
public Mono< Void > filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义过滤器");
//放行
return chain.filter(exchange);
}
/**
* 指定执行顺序 值越小 执行优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
3.4 统一鉴权
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己
编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
3.4.1 鉴权逻辑
3.4.2 代码实现
/**
* @Author XuZhuHong
* @CreateTime 2021/7/29 20:08
* 自定义的过滤器
*/
@Component
public class LonginFilter implements GlobalFilter, Ordered {
/**
* 过滤器总的业务逻辑
* @param exchange
* @param chain
* @return
*/
@Override
public Mono< Void > filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// getQueryParams()得到所有的参数 getFirst() 得到第一个token
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)) {
System.out.println("没有登录");
exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 指定执行顺序 值越小 执行优先级越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
3.5 网关限流
3.5.1 常见的限流算法
3.5.2 基于Filter的限流
SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂
RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方
式进行流量控制。
<!-- redis的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- redis的依赖监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
配置redis 配置filters
server:
port: 8080
spring:
application:
name: gateway-server #服务名称
#配置Gateway路由
cloud:
gateway:
routes:
- id: product-service #服务id可以随便设置 一般是设置成转到的服务名称
# uri: http://127.0.0.1:9001 #服务的地址
uri: lb://service-product
predicates:
- Path=/product/** #断言 就是说当请求地址为 127.0.0.1:8080/product/**的所有请求会转发
- id: order-service
uri: http://127.0.0.1:9002
predicates:
- Path=/order/**
filters: #主要的限流配置=====================start======================
- name: RequestRateLimiter
args:
# 使用SpEL从容器中获取对象 就是返回KeyResolver数据的方法名
key-resolver: '#{@pathKeyResolver}'
# 令牌桶每秒填充平均速率
redis-rate-limiter.replenishRate: 1
# 令牌桶的上限
redis-rate-limiter.burstCapacity: 3
- RewritePath=/product-service/(?<segment>.*), /$\{segment}
redis:
host: localhost
pool: 6379
database: 0
#主要的限流配置===============end============================
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9000/eureka
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
instance:
preferIpAddress: true
ip-address: 127.0.0.1
(3)配置KeyResolver
创建一个类 加上@Configuration注解声明她是配置类 再使用@Bean创建返回值为KeyResolver的函数 再对其进行编写
@Configuration
public class KeyResolverConfiguration {
/**
* 编写基于请求路径的限流规则
* //abc
* //基于请求ip 127.0.0.1
* //基于参数
*/
//@Bean
public KeyResolver pathKeyResolver() {
//自定义的KeyResolver
return new KeyResolver() {
/**
* ServerWebExchange :
* 上下文参数
*/
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just( exchange.getRequest().getPath().toString());
}
};
}
/**
* 基于请求参数的限流
*
* 请求 abc ? userId=1
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getQueryParams().getFirst("userId")
//exchange.getRequest().getHeaders().getFirst("X-Forwarded-For") 基于请求ip的限流
);
}
}
3.5.3 基于Sentinel的限流
Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。
(1)环境搭建
导入Sentinel 的响应依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>x.y.z</version>
</dependency>
(2)编写配置类
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
/**
* 配置限流的异常处理器:SentinelGatewayBlockExceptionHandler
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
/**
* 配置限流过滤器
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
/**
* 配置初始化的限流参数
*/
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// 以下这段注释 是针对某个路由参数来限流
// rules.add(
// new GatewayFlowRule("order-service") //资源名称
// .setCount(1) // 限流阈值
// .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
// );
// rules.add(new GatewayFlowRule("order-service")
// .setCount(1)
// .setIntervalSec(1)
// .setParamItem(new GatewayParamFlowItem()
// .setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id")
// )
// );
rules.add(new GatewayFlowRule("product_api")
.setCount(1)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("order_api")
.setCount(1)
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
/**
* 自定义异常提示
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 001);
map.put("message", "对不起,接口限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
/**
* 自定义api分组
*/
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/order-service/order"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
}
(3)配置yml
erver:
port: 8080
spring:
application:
name: gateway-server #服务名称
#配置Gateway路由
cloud:
gateway:
routes:
- id: product-service #服务id可以随便设置 一般是设置成转到的服务名称
# uri: http://127.0.0.1:9001 #服务的地址
uri: lb://service-product
predicates:
- Path=/product/** #断言 就是说当请求地址为 127.0.0.1:8080/product/**的所有请求会转发
- id: order-service
uri: http://127.0.0.1:9002
predicates:
- Path=/order/**
filters: #限流配置开始*************************
- RewritePath=/order-service/(?<segment>.*), /$\{segment}
#限流配置结束*************************
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9000/eureka
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
instance:
preferIpAddress: true
ip-address: 127.0.0.1
在一秒钟内多次访问http://localhost:8080/order/buy/1就可以看到限流启作用了。
(4)自定义异常提示
在 配置类里加上如下的方法
/**
* 自定义异常提示
*/
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 001);
map.put("message", "对不起,接口限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
(5) 参数限流
上面的配置是针对整个路由来限流的,如果我们只想对某个路由的参数做限流,那么可以使用参数限流
方式:
// 以下这段注释 是针对某个路由参数来限流
rules.add(
new GatewayFlowRule("order-service") //资源名称
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
rules.add(new GatewayFlowRule("order-service")
.setCount(1)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id")
)
);
(6) 自定义API分组
/**
* 自定义api分组
*/
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/product-service/product/**").
setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/order-service/order"));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
上面的所有方法都是写在配置类里面
3.6 网关高可用
**高可用HA(High Availability)**是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计
减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的
风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者
叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。
首先准备多台gateway
然后在nignx中配置如下信息 配置文件在安装目录下面