赞
踩
在微服务架构中,由于系统和服务的细分,导致系统结构变得非常复杂, 为了跨平台、统一集中管理api,同时不暴露后置服务。甚至有时候需要对请求进行一些安全、负载均衡、限流、熔断、灰度等中间操作,基于此类种种的客观需求一个类似综合前置的系统就产生了,这就是API网关(API Gateway)。(网关)是一种在微服务架构中使用的服务器端组件,用于管理请求的转发和路由。它充当了系统的入口点,接收来自客户端的请求,并将其转发到相应的微服务实例上。
项目使用了微服务架构后,没有统一的网关服务,客户端直接与具体的微服务交互
随着微服务架构在落地的过程中,如何统一入口,统一安全、高性能等问题成为新的挑战,所以微服务网关的重要性就体现出来了。
网关的核心功能
网关提供了统一的请求入口
以下是 Gateway 的详解:
总结来说,Gateway 是微服务架构中的入口组件,它通过灵活的路由、负载均衡、安全认证、请求过滤等功能,为系统提供了高效、可靠的请求处理机制。它可以帮助实现微服务的解耦、横向扩展、安全性和可维护性。在使用 Gateway 时,需要考虑系统的架构需求、性能要求和安全要求,并根据实际情况进行配置和定制化开发。
对比这几种网关可以从多个方面进行分析,包括性能、扩展性、生态支持、功能丰富程度等。下面是对这几种网关的详细分析:
优点:
缺点:
优点:
缺点:
优点:
缺点:
优点:
缺点:
优点:
缺点:
选择适合自己需求的网关取决于具体的业务场景、性能要求、团队技术栈等因素。
<!--spring cloud gateway 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
spring:
cloud:
gateway:
routes:
- id: example-route # 路由的Id,没有固定规则但要求唯一,建议配合服务名
uri: http://com.xiu.com # 匹配后提供服务的路由根路径地址
predicates:
- Path=/api-gateway/xiu/** # 断言 路径相匹配的进行路由
filters:
- StripPrefix=1 #去掉请求的第一个路径元素
上述配置意思:当请求路径匹配 /api-gateway/xiu/**
时,会将请求转发到 http://com.xiu.com
,并去除掉请求路径的第一个路径元素。
比如访问http://ip:port/api-gateway/xiu/sayHello 则会通过路由访问真实的请求地址为http://com.xiu.com/sayHello
路由主要有四个配置:
应用程序主类上添加 @EnableGateway
注解或者 spring.cloud.gateway.enabled=true
启用 Spring Cloud Gateway。
其实默认网关是开启的,所以不加上述配置也行也可以
下面是GateWay的请求流程概述
一个请求进入网关,首先请求会通过GateWay Handler Mapping 进行路由匹配,匹配成功后请求会交由GateWay Web Handler。请求会经过一系列的过滤器链(责任链模式),进行每个filer的前置和后置处理。
Route 路由 网关的基本组成部分。它由一个ID、一个目标URI、一组谓词predicates和一组过滤器filters组成
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
#简写
- Cookie=mycookie,regexp
#完整写法
- name: Cookie
args:
name: mycookie2
regexp: regexp
上述配置的predicates 标识 请求中必须要包含Cookie参数 且cookie的name 为mycookie,value为regex 正则表达式匹配的值。
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
#简写
- AddRequestHeader=token, 111
如上,会给匹配的请求添加 请求头为token=111的header信息。
转发是GateWay最普通的功能,我们大部分的路由规则也都是进行转发处理。
spring: cloud: gateway: routes: # 转发路由配置 路径匹配/forward/** 请求路由到http://www.baidu.com上 - id: forward-route uri: http://www.baidu.com predicates: - Path=/forward/** filters: - StripPrefix=1 # 重定向路由配置 路径匹配/redirect/** 请求路由到http://www.jd.com上,原来的uri不起作用了 - id: redirect-route uri: https://www.baidu.com predicates: - Path=/redirect/** filters: - RedirectTo=302,https://www.jd.com
在实际工作场景中,大部分需要路由转发的微服务都是多个实例,并部署到不同的服务机器上(不同的ip或者不同的域名),所以这里的uri 直接写死ip不合适。此处可以将网关服务作为微服务注册到注册中心,其可以从注册中心获取到所有服务实例同时路由配置中uri 使用lb://,这样,客户端请求网关,路径匹配成功后,网关会将请求负载到多个实例中的其中一个,这样服务变更不用频繁修改路由配置。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
主类中添加@EnableDiscoveryClient
spring:
cloud:
nacos:
discovery:
server-addr: ip:port
namespace: my-namespace
gateway:
routes:
- id: customer
#此处uri没有写死,而是使用了(load blance) lb://${service_name} 负载均衡策略到某个具体实例
uri: lb://customer-server
predicates:
- Path=/api-gateway/customer/**
filters:
- StripPrefix=2
上述服务路由配置都是硬编码配置这种配置方式,在启动网关服务后,将无法修改路由配置,若有新服务上线的话,则需要重新部署网关服务。如果使用动态路由则能很好的解决上述问题。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: '*'
exclude: env
endpoint:
health:
show-details: always
#通过如下两个url进行访问(端口根据以及ip根据自己设定进行调整)
#http://localhost:9001/actuator
#http://localhost:9001/actuator/gateway/routes
@Service public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware { @Resource private RouteDefinitionWriter routeDefinitionWriter; //事件发布组件 用于网关运行中 发布RefreshRoutesEvent事件刷新内存中路由配置 @Resource private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher; } //新增路由 public void add(RouteDefinition routeDefinition){ /** * 新增的Actuator Endpoint,刷新路由的时候,先加载路由配置到内存中, * 然后再使用RefreshRoutesEvent事件刷新内存中路由配置。 */ routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); } // 删除路由 public void delete(String id){ routeDefinitionWriter.delete(Mono.just(id)).subscribe(); } }
添加路由入口
可以通过暴露API接口、或者容器启动的时候添加
@Service public class RouterConfig implements InitializingBean { private final static Logger log = LoggerFactory.getLogger(RouterConfig.class); @Resource private DynamicRouteServiceImpl dynamicRouteService; @Override public void afterPropertiesSet() throws Exception { //模拟容器启动过程中 添加动态路由 //路由数据可以在服务启动过程中从存储层获取 RouteDefinition route = new RouteDefinition(); route.setId("example-route"); route.setUri(new URI("lb://customer-server")); //添加 predicates List<PredicateDefinition> predicates = Lists.newArrayList(); //path 断言 PredicateDefinition path = new PredicateDefinition(); path.setName("Path"); Map<String,String> args = Maps.newHashMap(); args.put("pattern","/api-gateway/customer/**"); path.setArgs(args); predicates.add(path); route.setPredicates(predicates); //添加filter List<FilterDefinition> filters = Lists.newArrayList(); FilterDefinition filter = new FilterDefinition(); filter.setName("StripPrefix"); Map<String,String> filterArgs = Maps.newHashMap(); filterArgs.put("_genkey_0","2"); filter.setArgs(filterArgs); filters.add(filter); route.setFilters(filters); String routJson = JSON.toJSONString(route); log.info("添加的路由信息:{}",routJson); dynamicRouteService.add(route); } }
等价于
routes:
- id: customer
#此处uri没有写死,而是使用了(load blance) lb://${service_name} 负载均衡策略到某个具体实例
uri: lb://customer-server
predicates:
- Path=/api-gateway/customer/**
filters:
- StripPrefix=2
访问http://ip:port/api-gateway/customer/v1/customer/sayHello 会转发到customer模块的某个实例节点http://ip:port/v1/customer/sayHello。
断言就是说: 在 什么条件下 才能进行路由转发。 gateWay提供了如下多种断言
设置传入的age参数在指定范围区间才能正常访问。
/** * 自定义的断言工厂<br> * 1.名称必须是 ${配置}+RoutePredicateFactory<br> * 2.必须继承AbstractRoutePredicateFactory<配置类> */ @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> { public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } //用于从配置文件中获取参数值赋值到配置类中的属性上 @Override public List<String> shortcutFieldOrder() { //这里的顺序要跟配置文件中的参数顺序一致 return Arrays.asList("minAge", "maxAge"); } //断言 @Override public Predicate<ServerWebExchange> apply(Config config) { return new Predicate<ServerWebExchange>() { @Override public boolean test(ServerWebExchange serverWebExchange) { //从serverWebExchange获取传入的参数 String ageStr = serverWebExchange.getRequest().getQueryParams().getFirst("age"); if (StringUtils.isNotEmpty(ageStr)) { int age = Integer.parseInt(ageStr); return age > config.getMinAge() && age < config.getMaxAge(); } return true; } }; } //配置类 @Data public static class Config{ private int minAge; private int maxAge; } }
上面的代码创建了一个AgeRoutePredicateFactory 自定义的断言组件。自定义的断言类名是"Age"+"RoutePredicateFactory"的形式。
所以在断言语句中可以配置Age。
配置 指定参数在年龄范围的请求才会被路由到对应的。
gateway:
routes:
#负载均衡
- id: customer
# 此处uri没有写死,而是使用了(load balance)
# lb://${service_name} 负载均衡策略到某个具体实例
uri: lb://customer-server
predicates:
- Path=/api-gateway/customer/**
- Age=24,35 # 限制年龄只有在24到35岁之间的人才能找到工作
filters:
- StripPrefix=2
请求
http://127.0.0.1:8804/api-gateway/customer/v1/customer/sayHello?age=30 #请求正常转发
http://127.0.0.1:8804/api-gateway/customer/v1/customer/sayHello?age=15 #请求不会转发
#注意转发到的真实请求地址需要接收GET请求的参数age。
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。如下
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
/** * 自定义的局部过滤器工厂<br> * 1.名称必须是 ${配置}+GatewayFilterFactory<br> * 2.必须继承AbstractGatewayFilterFactory<配置类> */ @Component public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> { public LogGatewayFilterFactory() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("consoleLog"); } @Override public GatewayFilter apply(Config config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { if (config.isConsoleLog()) { System.out.println("consoleLog已经开启了...."); } return chain.filter(exchange); } }; } @Data public static class Config{ private boolean consoleLog; } }
配置
gateway:
routes:
- id: customer
#此处uri没有写死,而是使用了(load balance) lb://${service_name} 负载均衡策略到某个具体实例
uri: lb://customer-server
predicates:
- Path=/api-gateway/customer/**
- Age=24,35 # 限制年龄只有在24到35岁之间的人才能找到工作
filters:
- StripPrefix=2
- Log=true #开启日志打印
/** * 自定义的局部过滤器工厂<br> * 需要实现GlobalFilter、Ordered<br> */ @Component public class MyGlobalGateWayFactory implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { System.out.println("全局过滤器前置处理"); return chain.filter(exchange) .then(Mono.fromRunnable( () -> System.out.println("全局过滤器后置处理") )); } @Override public int getOrder() { return 0; } }
使用@Component直接会生效。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。