1、API网关
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。
网关应当具备以下功能:
- 性能:API高可用,负载均衡,容错机制。
- 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。
- 日志:日志记录(spainid,traceid)一旦涉及分布式,全链路跟踪必不可少。
- 缓存:数据缓存。
- 监控:记录请求响应数据,api耗时分析,性能监控。
- 限流:流量控制,错峰流控,可以定义多种限流规则。
- 灰度:线上灰度部署,可以减小风险。
- 路由:动态路由规则。
目前,比较流行的网关有:Nginx 、 Kong 、Orange等等,还有微服务网关Zuul 、Spring Cloud Gateway等等
对于 API Gateway,常见的选型有基于 Openresty 的 Kong、基于 Go 的 Tyk 和基于 Java 的 Zuul。这三个选型本身没有什么明显的区别,主要还是看技术栈是否能满足快速应用和二次开发。
2、spring-cloud-gateway 模型图:
在使用spring-cloud-gateway的时候,我们一般的做法是构建一个网关微服务,然后在里面配置各种路由信息。
3、实战案例:
3.1、构建一个spring-boot项目引入如下依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
3.2、项目启动类:
@SpringBootApplicationpublic class GatewayService {public static void main(String[] args) {SpringApplication.run(GatewayService.class, args);}}
3.3、配置路由:
我们配置一个简单的路由,主要是通过网关服务进行转发到我们的订单服务:
spring:cloud:gateway:routes:- id: orderapi #当前路由的iduri: http://localhost:7070 #当前路由转发的目标服务predicates: #当前路由的断言列表- Path=/orderapi/** 使用spring-cloud-gateway提供的Path断言filters: #当前路由的过滤器列表- StripPrefix=1 #表示在路径匹配的时候会去掉第一级,例如输入http://localhost:6060/orderapi/order/findAll#将会转发到http://localhost:7070/order/findAll#StripPrefix=1的含义就是会去掉/orderapi 如果等于2就会去掉/orderapi/order
这就是一个简单的路由配置。在spring-cloud-gateway中提供了很多的断言、过滤器的实现,详细可见官网。
4、自定义断言:
在spring-cloud-gateway中提供了很多的断言、过滤器的实现,我们也可以实现自己的断言或者过滤器,接下来我们以实现断言为案例进行讲解。
我们实现一个断言,这个断言的核心业务是需要检查请求的Header中是否存在accesstoken这个属性,如果存在我们进行请求转发,如果不存在就不进行转发。
实现的方式也比较简单,只需要实现 AbstractRoutePredicateFactory 这个接口即可,只是有一下约束而已,固定为断言Name + "RoutePredicateFactory"字符,然后配置使用的时候使用断言名称配置即可。
/*** 自定义断言,类名格式断言名称+ “RoutePredicateFactory”*/
@Component
public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {public AuthRoutePredicateFactory() {super(Config.class);}//定义配置值的顺序@Overridepublic List <String> shortcutFieldOrder() {return Arrays.asList("headeKey");}@Overridepublic Predicate<ServerWebExchange> apply(Config config) {return new GatewayPredicate() {@Overridepublic boolean test(ServerWebExchange serverWebExchange) {HttpHeaders headers = serverWebExchange.getRequest().getHeaders();List <String> strings = headers.get(config.getHeadeKey());if (CollectionUtils.isEmpty(strings)){return false;}else {return true;}}};}@Validatedpublic static class Config {@NotEmptyprivate String headeKey;public Config() {}public String getHeadeKey() {return headeKey;}public void setHeadeKey(String headeKey) {this.headeKey = headeKey;}}
}
自定义断言的配置方式:
spring:cloud:gateway:routes: - id: costomer_predicateuri: http://www.baidu.compredicates:- Path=/customer/predicate/**- Auth=accesstoken //自定义的断言名称,断言中的配置实例的headeKey=accesstokenfilters:- StripPrefix=2
5、网关层使用hystrix进行断路保护
5.1、引入hystrix的如下依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency>
5.2、使用spring-cloud-gateway提供的Hystrix相关的过滤器,配置如下:
spring:cloud:gateway:routes:
#常规断言的使用- id: orderapiuri: http://localhost:7070predicates:- Path=/orderapi/**filters:- StripPrefix=1 #表示在路径匹配的时候会去掉第一级,例如输入http://localhost:6060/orderapi/order/findAll#将会转发到http://localhost:7070/order/findAll#StripPrefix=1的含义就是会去掉/orderapi 如果等于2就会去掉/orderapi/order配置hystrix过滤器进行断路保护 - name: Hystrixargs:name: fallbackcmd 配置构建的hystrixCommand的id=fallbackcmdfallbackUri: forward:http://localhost:6060/fallback#fallbackUri: forward:/orderapi/order/mock #转发请求到http://localhost:6060//orderapi/order/mock#因此也会调到http://localhost:7070/order/mock, 也可以在#网关服务中定义controller 进行转发过去。可对id=fallbackcmd进行属性配置
hystrix:command:fallbackcmd:execution:isolation:thread:timeoutInMilliseconds: 1000
6、网关层使用redis进行网关层的限流
6.1、先引入redis相关依赖如下:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>
6.2、实现限流的key解析器,作用就是定义限流的规则,例如按照请求参数中的某个值进行限流,我们就以请求中的userId进行限流来实现一个key解析器:
@BeanKeyResolver userKeyResolver() {return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));}
6.3、配置redis:
spring: redis:host: localhostport: 6379
6.4、给某个路由配置限流:
spring:application:name: gateway-servercloud:gateway:routes:
#常规断言的使用- id: orderapiuri: http://localhost:7070predicates:- Path=/orderapi/**filters:- StripPrefix=1 #表示在路径匹配的时候会去掉第一级,例如输入http://localhost:6060/orderapi/order/findAll#将会转发到http://localhost:7070/order/findAll#StripPrefix=1的含义就是会去掉/orderapi 如果等于2就会去掉/orderapi/order- name: Hystrixargs:name: fallbackcmdfallbackUri: forward:http://localhost:6060/fallback
# fallbackUri: forward:/orderapi/order/mock #转发请求到http://localhost:6060//orderapi/order/mock#因此也会调到http://localhost:7070/order/mock, 也可以在#网关服务中定义controller 进行转发过去。- name: RequestRateLimiterargs:# redis-rate-limiter是基于令牌桶来实现的#rate-limiter: "#{@redisRateLimiter}" #指定限流器的beanName, 如果我们配置了redis-rate-limiter.*#redis-rate-limiter: "#{@redisRateLimiter}" redisRateLimiter 这个bean也是#spring-cloud-gateway自动装配的,当然如果我们自定义限流器的话,我们是需要配置#自定义的限流器beanName的。redis-rate-limiter.replenishRate: 10 #往令牌桶里放令牌的速率,3 表示每秒放3个令牌进去redis-rate-limiter.burstCapacity: 20 #令牌通中最多放多少个令牌,这个值就约等于每秒最大请求数。redis-rate-limiter.requestedTokens: 1 #每次请求消耗多少个令牌key-resolver: "#{@userKeyResolver}" #限流键的解析器,此处配置的是实现了KeyResolver的bean userKeyResolver,#例如userKeyResolver从请求中获取有查询参数的user的入参,例如 user=1#userId=1 跟userId=2 的令牌通是隔离的,不通用的。
7、整合服务注册与发现在转发服务按照服务名称进行负载均衡的配置:
我们部署微服务的时候基本上都是集群部署,并且将服务信息注册到服务注册中心,那么在网关层面就需要进行服务发现,然后进行转发的负载均衡。
7.1、在网关服务中映入eureka-clientd 的相关依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>
7.2、配置eureka-server信息:
eureka:client:service-url:defaultZone: http://localhost:9090/eureka
7.3、配置激活器网关的服务发现定位器:
spring:cloud:gateway:discovery:locator:enabled: truelower-case-service-id: true
7.4、配置路由route的uri使其能够进行负载均衡:
spring:application:name: gateway-servercloud:gateway:routes:- id: userapiuri: lb://user-service //使用lb://服务名称的方式predicates:- Path=/userapi/**filters:- StripPrefix=1
8、自定义全局过滤器
在spring-cloud-gateway中有两种类型的filter,一种的局部的filter,只有配置到路由中的时候才生效,还有就是全局的filter,全局的不需要单独配置给某个route,而是所有的route都生效,接下来我们来实现一个全局的filter,这个filter的业务就是请求前后进行日志输出:这里的实现是跟reactor的知识点有关,需要有一定的响应式编程的基础知识。
/*** 全局过滤器是不需单独给predicate 进行配置,因为默认就是给所有的predicate配置上。*/
@Component
public class CustomerLogGlobalFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {System.out.println("【CustomerLogGlobalFilter】 pre log 。 。 。");Mono <Void> mono = chain.filter(exchange).then(Mono.fromRunnable(() -> {MultiValueMap <String, HttpCookie> cookies = exchange.getRequest().getCookies();System.out.println("【CustomerLogGlobalFilter】 post log 。 。 。" + cookies.toSingleValueMap());}));return mono;}
}