微服务网关概述:
不同的微服务一般会有不同的网 络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以 维护。如图:
1.客户端直接与各个微服务通讯,可能会有很多问题:
(1).客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
(2).在某些场景下存在跨域请求的问题
(3).加大身份认证的难度,每个微服务需要独立认证
因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可,这样简化了开发还有以下优点:
(1).易于监控
(2).易于认证
(3).减少了客户端与各个微服务之间的交互次数
如下图:
服务网关的概念:
什么是微服务网关 :
API网关是一个服务器,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供一个定制的API。
API网关方式的核心要点:
所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。
作用和应用场景:
网关具有的职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。当然,最主要的职责还是与“外界联系”。
常见的API网关实现方式:
Kong:
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
问题:只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
Zuul:
Netflflix开源,功能丰富,使用JAVA开发,易于二次开发;需要运行在web容器中,如Tomcat。
问题:缺乏管控,无法动态配置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如 Nginx;
Traefifik:
Go语言开发;轻量易用;提供大多数的功能:服务路由,负载均衡等等;提供WebUI
问题:二进制文件部署,二次开发难度大;UI更多的是监控,缺乏配置、管理能力;
Spring Cloud GatewaySpringCloud提供的网关服务
Nginx+lua实现:
使用Nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
问题:自注册的问题和网关本身的扩展性
基于Nginx的网关实现:
Nginx介绍:
正向/反向代理
正向代理:
正向代理,"它代理的是客户端,代客户端发出请求",是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
反向代理:
多个客户端给服务器发送的请求,Nginx服务器接收到之后,按照一定的规则分发给了后端的业务处 理服务器进行处理了。此时~请求的来源也就是客户端是明确的,但是请求具体由哪台服务器处理的并不明确了,Nginx扮演的就是一个反向代理角色。客户端是无感知代理的存在的,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。因为客户端不需要任何配置就可以访问。反向代理,"它代理的是服务端,代服务端接收请求",主要用于服务器集群分布式部署的情况下,反向代理隐 了服务器的信息
如果只是单纯的需要一个最基础的具备转发功能的网关,那么使用Ngnix是一个不错的选择。
微服务网关GateWay:
Gateway简介:
Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开的网关,旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式,统一访问接口。SpringCloud Gateway 作为 Spring Cloud 生态系中的网关,目标是替代 Netflflix ZUUL,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。它是基于Nttey的响应式开发模式。
Spring Cloud Gateway与Zuul的性能对比
从对比结果可知,Spring Cloud Gateway的RPS是Zuul的1.6倍
核心概念:
1. 路由(route) 路由是网关最基础的部分,路由信息由一个ID、一个目的URL、一组断言工厂和一 组Filter组成。如果断言为真,则说明请求URL和配置的路由匹配。
2. 断言(predicates) Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自Http Request中的任何信息,比如请求头和参数等。
3. 过滤器(filter) 一个标准的Spring webFilter,Spring Cloud Gateway中的Filter分为两种类型,分别是Gateway Filter和Global Filter。过滤器Filter可以对请求和响应进行处理。
入门案例:
创建工程导入依赖:
在项目中添加新的模块 gateway_server ,并导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
如果报以上错误,说明启动时和spring-boot-startet-web有冲突,将父工程模块写的jar转移,
那个模块用就复制到那个模块。
配置启动类:
@SpringBootApplication
public class GatewayServerApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServerApplication.class, args);
}
}
编写配置文件
创建 application.yml 配置文件 (注意缩进)
server:
port: 8080
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true
spring:
application:
name: gateway-notzull-server
cloud:
gateway:
routes:
- id: user-server
uri: http://127.0.0.1:9002
predicates:
- Path=/user/**
id:我们自定义的路由 ID,保持唯一
uri:目标服务地址
predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默
认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
filters:过滤规则,暂时没用。
上面这段配置的意思是,配置了一个 id 为 user-server的路由规则,当访问网关请求地址以
user开头时,会自动转发到地址: http://127.0.0.1:9002/ 。配置完成启动项目即可在浏览器
访问进行测试。
访问地址 http://localhost:9002/user/show?uid=2 时会展示页面展示如下:
原始页面:
网关请求:
路由规则:
Spring Cloud Gateway 的功能很强大,前面我们只是使用了 predicates 进行了简单的条件匹配,其实 Spring Cloud Gataway 帮我们内置了很多 Predicates 功能。在 Spring Cloud Gateway 中 Spring 利用Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。
示例:
#路由断言之后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://xxxx.com
#路由断言之前匹配
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
#路由断言之前匹配
- id: before_route
uri: https://xxxxxx.com
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
#路由断言之间
- id: between_route
uri: https://xxxx.com
predicates:
- Between=xxxx,xxxx
#路由断言Cookie匹配,此predicate匹配给定名称(chocolate)和正则表达式(ch.p)
curl -H 'Cookie:name=forezp' localhost:8081
- id: cookie_route
uri: https://xxxx.com
predicates:
- Cookie=name, forezp
#路由断言Header匹配,header名称匹配X-Request-Id,且正则表达式匹配\d+
- id: header_route
uri: https://xxxx.com
predicates:
- Header=X-Request-Id, \d+
#路由断言匹配Host匹配,匹配下面Host主机列表,**代表可变参数
- id: host_route
uri: https://xxxx.com
predicates:
- Host=**.somehost.org,**.anotherhost.org
#路由断言Method匹配,匹配的是请求的HTTP方法
- id: method_route
uri: https://xxxx.com
predicates:
- Method=GET
#路由断言匹配,{segment}为可变参数
- id: host_route
uri: https://xxxx.com
predicates:
- Path=/foo/{segment},/bar/{segment}
#路由断言Query匹配,将请求的参数param(baz)进行匹配,也可以进行regexp正则表达式匹配 (参数包含 foo,并且foo的值匹配ba.)
- id: query_route
uri: https://xxxx.com
predicates:
- Query=baz 或 Query=foo,ba.
#路由断言RemoteAddr匹配,将匹配192.168.1.1~192.168.1.254之间的ip地址,其中24为子网掩码位数即255.255.255.0
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
过滤器:
过滤器基础
过滤器的生命周期
Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”。
PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择
请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等
(2) 过滤器类型
Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种GatewayFilter 与 GlobalFilter。
GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
局部过滤器
局部过滤器(GatewayFilter),是针对单个路由的过滤器。可以对访问的URL过滤,进行切面处理。在Spring Cloud Gateway中通过GatewayFilter的形式内置了很多不同类型的局部过滤器。如下:
每个过滤器工厂都对应一个实现类,并且这些类的名称必须以 GatewayFilterFactory 结尾,这是
Spring Cloud Gateway的一个约定,例如 AddRequestHeader 对应的实现类为
AddRequestHeaderGatewayFilterFactory 。对于这些过滤器的使用方式可以参考官方文档
全局过滤器
全局过滤器(GlobalFilter)作用于所有路由,Spring Cloud Gateway 定义了Global Filter接口,用户可以自定义实现自己的Global Filter。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能,并且全局过滤器也是程序员使用比较多的过滤器。
Spring Cloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:
统一鉴权
内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
鉴权逻辑
开发中的鉴权逻辑:
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证 以后每次请求,客户端都携带认证的token 服务端对token进行解密,判断是否有效。
如上图,对于验证用户是否已经登录鉴权的过程可以在网关层统一检验。检验的标准就是请求中是否携带token凭证以及token的正确性。
代码实现
下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。
@Component
public class LoginFilter implements GlobalFilter , Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(token==null || "".equals(token)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
自定义全局过滤器需要实现GlobalFilter和Ordered接口。
在filter方法中完成过滤器的逻辑判断处理
在getOrder方法指定此过滤器的优先级,返回值越大级别越低
ServerWebExchange 就相当于当前请求和响应的上下文,存放着重要的请求-响应属性、请求实
例和响应实例等等。一个请求中的request,response都可以通过 ServerWebExchange 获取
调用 chain.filter 继续向下游执行
基于Filter的限流
SpringCloudGateway官方就提供了基于令牌桶的限流支持。基于其内置的过滤器工厂
RequestRateLimiterGatewayFilterFactory 实现。在过滤器工厂中是通过Redis和lua脚本结合的方式进行流量控制。
环境搭建
导入redis的依赖
首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:
<!--监控依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
准备redis :(在输入 MONTOR 之前 有的可能还要输入密码:auth 密码)
修改application.yml配置文件
在application.yml配置文件中加入限流的配置,代码如下:(注意缩进)
server:
port: 8080
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
instance:
prefer-ip-address: true #显示浏览器中的状态栏显示ip
spring:
application:
name: gateway-notzull-server
cloud:
gateway:
routes:
- id: user-server
# uri: http://127.0.0.1:9004
uri: lb://user-consumer-fegin
predicates:
- Path=/user-server/**
filters:
- RewritePath=/user-server/(?<segment>.*), /$\{segment}
- name: RequestRateLimiter
args:
key-resolver: '#{@pathKeyResolver}' # 使用SpEL从容器中获取对象
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 3 # 令牌桶的总容量
redis:
host: 127.0.0.1
port: 6379
database: 0
在 application.yml 中添加了redis的信息,并配置了RequestRateLimiter的限流过滤器:
burstCapacity,令牌桶总容量。
replenishRate,令牌桶每秒填充平均速率。
key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#
{@beanName}从 Spring 容器中获取 Bean 对象。
配置KeyResolver
为了达到不同的限流效果和规则,可以通过实现 KeyResolver 接口,定义不同请求类型的限流键。
@Configuration
public class LimitFilter {
@Bean
public KeyResolver pathKeyResolver(){
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getPath().toString());
}
};
}
}
根据用户限流:
@Bean
public KeyResolver userKeyResolver(){
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getQueryParams().getFirst("uid"));
}
};
}
Yml配置:
spring:
application:
name: gateway-notzull-server
cloud:
gateway:
routes:
- id: user-server
# uri: http://127.0.0.1:9004
uri: lb://user-consumer-fegin
predicates:
- Path=/user-server/**
filters:
- RewritePath=/user-server/(?<segment>.*), /$\{segment}
- name: RequestRateLimiter
args:
# key-resolver: '#{@pathKeyResolver}' # 使用SpEL从容器中获取对象
key-resolver: '#{@userKeyResolver}' # 使用SpEL从容器中获取对象
redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
redis-rate-limiter.burstCapacity: 3 # 令牌桶的总容量
网关高可用
高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。我们都知道,单点是系统高可用的大敌,单点往往是系统高可用最大的风险和敌人,应该尽量在系统设计的过程中避免单点。方法论上,高可用保证的原则是“集群化”,或者叫“冗余”:只有一个单点,挂了服务会受影响;如果有冗余备份,挂了还有其他backup能够顶上。
我们实际使用 Spring Cloud Gateway 的方式如上图,不同的客户端使用不同的负载将请求分发到后端的 Gateway,Gateway 再通过HTTP调用后端服务,最后对外输出。因此为了保证 Gateway 的高可用性,前端可以同时启动多个 Gateway 实例进行负载,在 Gateway 的前端使用 Nginx 或者 F5 进行负载转发以达到高可用性。
配置ngnix
找到ngnix添加负载均衡配置
upstream gateway {
server 127.0.0.1:8081;
server 127.0.0.1:8080;
}
#请求转向mysvr 定义的服务器列表
location / {
proxy_pass http://gateway;
}