1、API网关
API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问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项目引入如下依赖:
org.springframework.cloud
spring-cloud-starter-gateway
3.2、项目启动类:
@SpringBootApplication
public class GatewayService {
public static void main(String[] args) {
SpringApplication.run(GatewayService.class, args);
}
}
3.3、配置路由:
我们配置一个简单的路由,主要是通过网关服务进行转发到我们的订单服务:
spring:
cloud:
gateway:
routes:
- id: orderapi #当前路由的id
uri: 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 {
public AuthRoutePredicateFactory() {
super(Config.class);
}
//定义配置值的顺序
@Override
public List shortcutFieldOrder() {
return Arrays.asList("headeKey");
}
@Override
public Predicate apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
HttpHeaders headers = serverWebExchange.getRequest().getHeaders();
List strings = headers.get(config.getHeadeKey());
if (CollectionUtils.isEmpty(strings)){
return false;
}else {
return true;
}
}
};
}
@Validated
public static class Config {
@NotEmpty
private String headeKey;
public Config() {
}
public String getHeadeKey() {
return headeKey;
}
public void setHeadeKey(String headeKey) {
this.headeKey = headeKey;
}
}
}
自定义断言的配置方式:
spring:
cloud:
gateway:
routes:
- id: costomer_predicate
uri: http://www.baidu.com
predicates:
- Path=/customer/predicate/**
- Auth=accesstoken //自定义的断言名称,断言中的配置实例的headeKey=accesstoken
filters:
- StripPrefix=2
5、网关层使用hystrix进行断路保护
5.1、引入hystrix的如下依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
5.2、使用spring-cloud-gateway提供的Hystrix相关的过滤器,配置如下:
spring:
cloud:
gateway:
routes:
#常规断言的使用
- id: orderapi
uri: http://localhost:7070
predicates:
- Path=/orderapi/**
filters:
- StripPrefix=1 #表示在路径匹配的时候会去掉第一级,例如输入http://localhost:6060/orderapi/order/findAll
#将会转发到http://localhost:7070/order/findAll
#StripPrefix=1的含义就是会去掉/orderapi 如果等于2就会去掉/orderapi/order
配置hystrix过滤器进行断路保护
- name: Hystrix
args:
name: fallbackcmd 配置构建的hystrixCommand的id=fallbackcmd
fallbackUri: 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相关依赖如下:
org.springframework.boot
spring-boot-starter-data-redis-reactive
6.2、实现限流的key解析器,作用就是定义限流的规则,例如按照请求参数中的某个值进行限流,我们就以请求中的userId进行限流来实现一个key解析器:
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
6.3、配置redis:
spring:
redis:
host: localhost
port: 6379
6.4、给某个路由配置限流:
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
#常规断言的使用
- id: orderapi
uri: http://localhost:7070
predicates:
- Path=/orderapi/**
filters:
- StripPrefix=1 #表示在路径匹配的时候会去掉第一级,例如输入http://localhost:6060/orderapi/order/findAll
#将会转发到http://localhost:7070/order/findAll
#StripPrefix=1的含义就是会去掉/orderapi 如果等于2就会去掉/orderapi/order
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:http://localhost:6060/fallback
# fallbackUri: forward:/orderapi/order/mock #转发请求到http://localhost:6060//orderapi/order/mock
#因此也会调到http://localhost:7070/order/mock, 也可以在
#网关服务中定义controller 进行转发过去。
- name: RequestRateLimiter
args:
# 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 的相关依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
7.2、配置eureka-server信息:
eureka:
client:
service-url:
defaultZone: http://localhost:9090/eureka
7.3、配置激活器网关的服务发现定位器:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
7.4、配置路由route的uri使其能够进行负载均衡:
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
- id: userapi
uri: 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 {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("【CustomerLogGlobalFilter】 pre log 。 。 。");
Mono mono = chain.filter(exchange).then(Mono.fromRunnable(() -> {
MultiValueMap cookies = exchange.getRequest().getCookies();
System.out.println("【CustomerLogGlobalFilter】 post log 。 。 。" + cookies.toSingleValueMap());
}));
return mono;
}
}