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。
<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
cloud:
gateway:
routes:
- id: product-service
uri: http://127.0.0.1:9001
predicates:
- Path=/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
cloud:
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/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
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接口 并实现方法 具体作用看图 把这个类放到容器中
@Component
public class LonginFilter implements GlobalFilter, Ordered {
@Override
public Mono< Void > filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("执行了自定义过滤器");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
3.4 统一鉴权
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己 编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
3.4.1 鉴权逻辑

3.4.2 代码实现
@Component
public class LonginFilter implements GlobalFilter, Ordered {
@Override
public Mono< Void > filter(ServerWebExchange exchange, GatewayFilterChain chain) {
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);
}
@Override
public int getOrder() {
return 0;
}
}

3.5 网关限流
3.5.1 常见的限流算法
 
3.5.2 基于Filter的限流
SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂 RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方 式进行流量控制。 
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<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
cloud:
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/product/**
- id: order-service
uri: http://127.0.0.1:9002
predicates:
- Path=/order/**
filters:
- name: RequestRateLimiter
args:
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
eureka:
client:
serviceUrl:
defaultZone: http://localhost:9000/eureka
registry-fetch-interval-seconds: 5
instance:
preferIpAddress: true
ip-address: 127.0.0.1
 (3)配置KeyResolver 创建一个类 加上@Configuration注解声明她是配置类 再使用@Bean创建返回值为KeyResolver的函数 再对其进行编写
@Configuration
public class KeyResolverConfiguration {
public KeyResolver pathKeyResolver() {
return new KeyResolver() {
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just( exchange.getRequest().getPath().toString());
}
};
}
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getQueryParams().getFirst("userId")
);
}
}
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;
}
@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("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);
}
@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
cloud:
gateway:
routes:
- id: product-service
uri: lb://service-product
predicates:
- Path=/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
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)
);
rules.add(new GatewayFlowRule("order-service")
.setCount(1)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM).setFieldName("id")
)
);
(6) 自定义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中配置如下信息 配置文件在安装目录下面 
|