❝沉淀、分享、成长,让自己和他人都能有所收获!😜❞
大家好,我是技术UP主小傅哥。
我发现了一个很有意思的缩写单词 gw
、wg
,都是网关的意思。因为 gw = gateway
、wg = wangguan
,所以在各个系统开发中,既有 gw 也有 wg 的存在。而网关也是各个互联网中用于统一对外的核心系统,当然使用网关的手段也不同,有中大厂自研,也有中小厂使用开源的组件。所以小傅哥的这个系列会陆续的分享出各个类型的网关,让大家了解以及按需选择使用。
其实只要一个公司有拆分较多的微服务,有很多的应用都要对外提供接口,就需要引入网关系统。否则就会有非常多共性功能重复在各个系统开发。比如;负载、熔断、降级、限流、切量、统一登录、地址转发等功能。这些东西要是让每个系统实现一遍,后续是非常难管理的。
那么这么多开源网关选择哪个,或者如何自研网关呢,小傅哥会陆续的分享出各个网关的介绍和使用,以及自研的教程,让大家可以积累自己的知识体系以及做技术选型。
前面已经分享了一篇 Higress 今天分享的是 SpringCloud Gateway
Spring Cloud Gateway 是一套非常容易使用的网关服务,通过yml配置或者代码编程的方式实现网关功能。对于网关你可以理解在接收一个 http 请求后,通过网关的配置过滤、替换、拦截、转发等操作到指定的请求地址上去。
Spring Cloud Gateway 是一个基于 Spring Framework 和 Spring Boot 提供的网关解决方案。可以帮助开发者轻松地构建出具有动态路由、限流、熔断等特性的 API 网关。
Spring Cloud Gateway 的工作原理是将进入的 HTTP 请求根据配置的路由规则转发到对应的后端服务。它在转发请求的过程中可以执行一系列的过滤器链,这些过滤器可以修改请求和响应,或者根据特定的逻辑决定是否继续处理请求。
xfg-dev-tech-gateway-provider-01、xfg-dev-tech-gateway-provider-02,分别提供了2个生产的 http 接口。你可以启动服务后单独访问接口测试。我们这里主要的用途是通过网关来使用这2个接口。
📢 注意以下测试,都要先启动这2个接口提供者工程。
@RestController()
@CrossOrigin("*")
@RequestMapping("/api/user/")
public class HiGatewayController {
/**
* curl http://127.0.0.1:8091/api/user/hi
*/
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String hi() {
return "hello gateway,provider 01";
}
}
@RestController()
@CrossOrigin("*")
@RequestMapping("/api/user/")
public class HiGatewayController {
/**
* curl http://127.0.0.1:8092/api/user/hi
*/
@RequestMapping(value = "hi", method = RequestMethod.GET)
public String hi() {
return "hello gateway,provider 02!";
}
}
基于 webflux 开发 api网关,不要引入 spring web 组件,而是引入以下组件;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.97.Final</version>
</dependency>
@Configuration
public class GatewayRouter {
@Bean
public RouterFunction<ServerResponse> routeToService() {
return RouterFunctions
.route(GET("/service1").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
request -> ServerResponse.ok().bodyValue("Response from Service 1"))
.andRoute(GET("/service2").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
request -> ServerResponse.ok().bodyValue("Response from Service 2"));
}
}
http://localhost:9091/service1
http://localhost:9091/service2
@Configuration
public class ApiGatewayConfiguration {
private final WebClient.Builder webClientBuilder;
public ApiGatewayConfiguration(WebClient.Builder webClientBuilder) {
this.webClientBuilder = webClientBuilder;
}
/**
* curl http://localhost:9091/wg/service1/8091
* curl http://localhost:9091/wg/service2/8092
* @return
*/
@Bean
public RouterFunction<ServerResponse> routerFunction() {
return route(GET("/wg/service1/{id}"), this::service1Handler)
.andRoute(GET("/wg/service2/{id}"), this::service2Handler);
}
public Mono<ServerResponse> service1Handler(ServerRequest request) {
String id = request.pathVariable("id");
Mono<String> response = webClientBuilder.build()
.get()
.uri("http://127.0.0.1:8091/api/user/hi?" + id)
.retrieve()
.bodyToMono(String.class);
return ServerResponse.ok().body(response, String.class);
}
public Mono<ServerResponse> service2Handler(ServerRequest request) {
String id = request.pathVariable("id");
Mono<String> response = webClientBuilder.build()
.get()
.uri("http://127.0.0.1:8092/api/user/hi?" + id)
.retrieve()
.bodyToMono(String.class);
return ServerResponse.ok().body(response, String.class);
}
}
curl http://localhost:9091/wg/service1/8091
、curl http://localhost:9091/wg/service2/8092
可以得到不同的响应结果。spring:
redis:
host: 127.0.0.1
port: 16379
database: 0
lettuce:
pool:
max-active: 10
max-wait: 1000
max-idle: 5
min-idle: 3
application:
name: xfg-dev-tech-springcloud-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
locator:
enabled: true
gateway:
discovery:
locator:
enabled: true
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
alloedHeaders: "*"
routes:
- id: route_01
uri: lb://provider-01
order: 1
predicates:
- Path=/gw/**
- Weight=group1, 1
filters:
- StripPrefix=1
- id: route_02
uri: lb://provider-02
order: 1
predicates:
- Path=/gw/**
- Weight=group1, 9
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}" # 限流方式:Bean名称
redis-rate-limiter.replenishRate: 1 # 生成令牌速率:个/秒
redis-rate-limiter.burstCapacity: 3 # 令牌桶容量
redis-rate-limiter.requestedTokens: 1 # 每次消费的Token数量
Path=/gw/**
路径,filters 过滤掉 StripPrefix=1
1个路径 gw 其余的打到 provider-01 服务上,也就是可以访问具体的服务了。另外 Weight=group1, 1
是权重配置,group1 代表这一组的,1 表示权重比。如果你不用 nacos,uri 也可以配置一个具体的 http 地址测试源码:cn.bugstack.xfg.dev.tech.config.RouteConfiguration
@Bean
public RouteLocator route(RouteLocatorBuilder builder, UriConfiguration uriConfiguration) {
String httpUri = uriConfiguration.getHttp();
return builder.routes()
.route(p -> p.path("/baidu").uri("https://www.baidu.com/"))
.route(p -> p.path("/bugstack").uri("https://bugstack.cn/md/road-map/road-map.html"))
.route(p -> p.path("/error").uri("forward:/fallback"))
.route(p -> p.path("/get").filters(f -> f.addRequestHeader("Hello", "World")).uri(httpUri))
.build();
}
启动服务后,访问地址:curl http://localhost:8090/gw/api/user/hi
、curl http://localhost:8090/error
除了业务开发,小傅哥自己也是非常感兴趣于这样的网关技术组件的实现,所以在日常的工作中也积累了很多网关的设计。后来在22年做了一套轻量的网关系统,把核心的内核逻辑实现出来让大家学习。帮助了很多伙伴学习项目后找到了不错的工作。
整个API网关设计核心内容分为这么五块;
第一块
:是关于通信的协议处理,也是网关最本质的处理内容。这里需要借助 NIO 框架 Netty 处理 HTTP 请求,并进行协议转换泛化调用到 RPC 服务返回数据信息。第二块
:是关于注册中心,这里需要把网关通信系统当做一个算力,每部署一个网关服务,都需要向注册中心注册一个算力。而注册中心还需要接收 RPC 接口的注册,这部分可以是基于 SDK 自动扫描注册也可以是人工介入管理。当 RPC 注册完成后,会被注册中心经过AHP权重计算分配到一组网关算力上进行使用。第三块
:是关于路由服务,每一个注册上来的Netty通信服务,都会与他对应提供的分组网关相关联,例如:wg/(a/b/c)/user/... a/b/c 需要匹配到 Nginx 路由配置上,以确保不同的接口调用请求到对应的 Netty 服务上。PS:如果对应错误或者为启动,可能会发生类似B站事故。第四块
:责任链下插件模块的调用,鉴权、授信、熔断、降级、限流、切量等,这些服务虽然不算是网关的定义下的内容,但作为共性通用的服务,它们通常也是被放到网关层统一设计实现和使用的。【这块内容可以自行扩展】第五块
:管理后台,作为一个网关项目少不了一个与之对应的管理后台,用户接口的注册维护、mock测试、日志查询、流量整形、网关管理等服务。项目学习地址:https://bugstack.cn/md/assembly/api-gateway/api-gateway.html