SpringCloud-Alibaba-Gateway+Sentinel
网关简介
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
- 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性
- 认证复杂,每个服务都需要独立认证。
- 存在跨域请求,在一定场景下处理相对复杂。
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
添加上API网关之后,系统的架构图变成了如下所示:
我们也可以观察下,我们现在的整体架构图:
在业界比较流行的网关,有下面这些:
Ngnix+lua:使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用,lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
Kong:基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
Zuul :Netflflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx
Spring Cloud Gateway:Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
注意:SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关
Gateway简介
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代Netflflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
性能强劲:是第一代网关Zuul的1.6倍
功能强大:内置了很多实用的功能,例如转发、监控、限流等
设计优雅,容易扩展
缺点:
- 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
- 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
- 需要Spring Boot 2.0及以上的版本,才支持
假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?你要这样玩儿,那真是友谊的小船,说翻就翻!
上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。
而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。
这样你就不用在所以服务中去重复干这些事情了,只需要在网关中统一管理
Gateway基本配置介绍
就介绍常用的:
-
id: 当前路由配置的唯一标识
-
uri: 服务的访问路径
可以是http://127.0.0.1:10010/
也可以是http://baidu.com/
还可以是
lb://服务名称
(需要开启服务发现) -
predicates 断言( 就是路由转发要满足的条件) 以下配置选择一个就行
-
Path(配置对于请求路径的匹配规则) 多个参数用逗号隔开
- Path = /aa/**,/bb/**
或者
- Path=/red/{segment},/blue/{segment}
例如匹配/red/1或/red/blue或/blue/green
-
时间相关 在什么时间段能访问 (美国时间)
- After 设置在xxx时间之后可以访问
```yaml - After = 2017-01-20T17:42:47.789-07:00[America/Denver] ```
-
Before 设置在xx时间之前可以访问
- Before = 2017-01-20T17:42:47.789-07:00[America/Denver]
-
Between 设置时间段内可以访问
- Between = 2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]
5.**Filters ** 路径过滤
-
添加前缀 (用不多)
比如:原来的请求路径是: http://127.0.0.1:10010/feign/user2
访问的时候http://127.0.0.1:10010/user2就行了 省略/feign 就行了
但是实际上会在你请求的路径上自动给你在前面补上http://127.0.0.1:10010/feign/user2
- PrefixPath=/feign
-
去除前缀 (用的比较多-一般配合predicates的- Path使用 )
比如: 原来访问的时候http://127.0.0.1:10010/abc/feign/user1
那么实际上会变成http://127.0.0.1:10010/feign/user1
- StripPrefix=1 #StripPrefix=1代表就是去掉前1个 StripPrefix=2 代表就是去掉前2个 以此类推
-
组合使用
原访问路径 http://127.0.0.1:10010/abc/user1/
predicates: #允许的路由地址 - Path=/abc/user1/** filters: #路径过滤 - StripPrefix=1 #删除前缀/abc - PrefixPath=/feign #添加前缀 /feign
经过代理处理后实际访问的路径http://127.0.0.1:10010/feign/user1/
-
跨域配置
spring:
cloud:
gateway:
globalcors: # 跨域配置
cors-configurations:
'[/**]':
allow-credentials: true #允许携带认证信息Cookie
# 我们可以通 过域名的方式 也可以通过指定ip 还可以 allowed-origins: "*" #放行所有跨域请求
allowed-origins: "*"
allowed-headers: "*" #允许所有请求头
allowed-methods: "*" #允许所有请求方式
max-age: 86400 # 86400 秒,也就是 24 小时 在有效时间内,浏览器无须为同一请求再次发起预检请求,可以减少发送请求的次数,减少系统部分压力。
开启gateway-nacos服务发现
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
然后就可以使用lb://服务名称
的方式
配置sentinel服务
参考 SpringCloud-Alibaba之Sentinel-流量监控
这篇文章 只需要把jar包运行就行了
Gateway+sentinel项目
项目结构
Maven
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring.cloud.alibaba.version>2.2.3.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Springboot 版本要高于 spring-cloud-alibaba 否则报错-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<dependencies>
<!-- spring-cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 在网关中不能添加 web服务依赖,因为网关服务就是代理各大服务的作用 而不是业务服务端 否则报错 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- <version>2.4.1</version>-->
<!-- </dependency>-->
<!-- 将服务注册到Nacos里-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--gateway网关 版本必须和Springboot 一致否则会出现版本问题-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.3.RELEASE</version>
</dependency>
<!-- alibaba-gateway网关限流 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.1</version>
</dependency>
<!-- sentinel 流量监控控制台 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.yml
server:
port: 21212
spring:
application:
name: nacos-gateway
cloud:
nacos:
discovery:
server-addr: 192.168.81.100:80 #注册中心地址
sentinel:
transport:
dashboard: 192.168.81.129:8888 #sentinel的地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
gateway:
globalcors: # 跨域配置
cors-configurations:
'[/**]':
allow-credentials: true #允许携带认证信息Cookie
# 我们可以通 过域名的方式 也可以通过指定ip 还可以 allowed-origins: "*" #放行所有跨域请求
allowed-origins: "*"
allowed-headers: "*" #允许所有请求头
allowed-methods: "*" #允许所有请求方式
max-age: 86400 # 86400 秒,也就是 24 小时 在有效时间内,浏览器无须为同一请求再次发起预检请求,可以减少发送请求的次数,减少系统部分压力。
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
routes:
- id: path_route_1 # 当前路由的标识, 要求唯一
uri: lb://nacos-consumer # lb指的是从nacos中按照名称获取微服务,并遵循负载均
predicates: # 断言(就是路由转发要满足的条件)
- Path=/api/consumer/** # 配置Path路由 使用 http://localhost:21212/api 进行访问即可
filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
- StripPrefix=2 # 转发之前去掉2层路径 比如 http://localhost:21212/api/consumer 转发之后http://localhost:21212/
- id: path_route_2 # 当前路由的标识, 要求唯一
uri: lb://nacos-provider # lb指的是从nacos中按照名称获取微服务,并遵循负载均
predicates: # 断言(就是路由转发要满足的条件)
- Path=/api/provider/** # 配置Path路由 使用 http://localhost:21212/api 进行访问即可
filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
- StripPrefix=2 # 转发之前去掉2层路径 比如 http://localhost:21212/api/provider/ 转发之后http://localhost:21212/
GatewayConfig
package com.gateway.config;
import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
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;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfig {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfig(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolvers;
this.serverCodecConfigurer = serverCodecConfigurer;
}
// 初始化一个限流的过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
// 配置初始化的限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
//分别给指定的资源 配置限流
// GatewayFlowRule 资源名称,对应路由id setCount 限流阈值 setIntervalSec 统计时间窗口,单位是秒,默认是 1 秒
//简单来说就是 如果Count=1 IntervalSec=1 那么一秒钟只允许访问一次 (用于测试)
// 如果Count=5 IntervalSec=2 那么2秒钟只允许访问5次 (用于测试)
// 如果Count=60 IntervalSec=3 那么3秒钟只允许访问60次 也就是1秒20次 一般用于生产(一般够了)
// 如果是商城网站那么Count和IntervalSec 这个需要计算平均每天每秒最大的请求量是多少然后在设置
int count=1;
int intervalSec=1;
//设置指定路由的限流
rules.add(new GatewayFlowRule("path_route_1").setCount(count).setIntervalSec(intervalSec));
rules.add(new GatewayFlowRule("path_route_2").setCount(count).setIntervalSec(intervalSec));
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 429);
map.put("message", "1s内请求次数过多-接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
在这里我们把每一个服务的总限流都配置了 ,如果想要单独配置服务的某一个路径…的具体限流或者降级…
我们可以访问: http://192.168.81.129:8888/ sentinel的控制台来具体配置
其他配置方式 参考 SpringCloud-Alibaba之Sentinel-流量监控
这篇文章里有
NacosGatewayApplication
package com.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableDiscoveryClient
public class NacosGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(NacosGatewayApplication.class,args);
}
}
测试
运行我们之前的 nacos-consumer 服务和 nacos-provider服务
http://localhost:21212/api/consumer/echo/Hello
正确
http://localhost:21212/echo/Hello
错误
http://localhost:21212/api/provider/echo/Hello
正确
http://localhost:21212/echo/Hello
错误
网关集群
利用nginx,给多个网关做代理轮询模式 , 然后nginx在集群就行了