在前面几节,我给大家介绍了当一个系统拆分成微服务后,会产生的问题与解决方案:服务如何发现与管理(Nacos注册中心实战),服务与服务如何通信(Ribbon, Feign实战)
今天我们就来聊一聊另一个问题:客户端如何访问?
在单体架构时,我们的系统只有一个入口,前端人员调用起来十分的简单。
但是当我们拆分为一个微服务系统后,每个服务都有属于自己ip和端口号,我们不可能跟前端说:诶,调用这个接口的时候你就使用这个地址哈。
前端:
既然这样不行的话,那我们能不能利用已有的知识想一个解决方案呢?
不是真的能用的解决方案
其实我们很容易的就能想到,我们的服务是具备互相发现及通信的能力的,那么,我们是不是可以搞一个类似统一入口(网关)样的服务,前端只请求这个服务,由这个服务去调用真实服务的Feign接口。
举个例子:
- 商品服务的获取商品接口:localhost:8080/get/goods
- 订单服务的下订单接口:localhost:8081/order
现在有个网关服务, 里面有两个接口:localhost:5555/get/goods, localhost:5555/order
前端调用获取商品接口时,访问:localhost:5555/get/goods,然后网关服务调用商品服务的Feign接口
下单时:访问:localhost:5555/order,然后网关服务调用订单服务的Feign接口
小结一下:
这个方案是否解决了服务入口统一的问题:解决了
能用吗:能用,但不是完全能用
因为这样会有一个问题,服务写的每一个接口,都需要给出一个Feign接口,给我们的网关服务调用。
真正的解决方案
Spring Cloud为我们提供了一个解决方案:Spring Cloud Gateway
Spring Cloud Gateway提供了一个建立在Spring生态系统之上的API网关,能够简单而有效的方式来路由到API,并基于 Filter 的方式提供一些功能,如:安全、监控。
Spring Cloud Gateway是由Spring Boot 2.x、Spring WebFlux和Reactor实现的,需要Spring Boot和Spring Webflux提供的Netty运行环境。它不能在传统的Servlet容器中工作,也不能在以WAR形式构建时工作。
官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
概念
Route(路由):网关的基本构件,它由一个ID、一个目的地URI、一个断言集合和一个过滤器集合定义。如果集合断言为真,则路由被匹配。
Predicate(断言):Java 8断言函数。参数类型是Spring Framework ServerWebExchange。可以让开发者在HTTP请求中的任何内容上进行匹配,比如头文件或参数。
Filter(过滤):由特定的工厂构建的GatewayFilter的实例,与传统的Filter一样,能够请求前后对请求就行处理。
工作原理
客户端向Spring Cloud Gateway发出请求。如果Gateway处理程序映射确定一个请求与路由相匹配,它将被发送到Gateway Web处理程序。这个处理程序通过一个特定于该请求的过滤器链来运行该请求。
过滤器可以在代理请求发送之前和之后运行pre和post逻辑。
简单使用
准备
预先准备一个服务,用来测试路由
我这里准备了个一个商品服务,并提供了一个接口:http://localhost:8082/goods/get-goods
现在,开始编写网关服务
引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
编写配置
bootstrap.yaml
server: port: 5555 spring: application: name: my-gateway cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: public username: nacos password: nacos logging: level: org.springframework.cloud.gateway: info com.alibaba.nacos.client.naming: warn
application.yaml
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/goods/**
启动类
package com.my.micro.service.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author Zijian Liao * @since 1.0.0 */ @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
测试
启动服务,并访问:http://localhost:5555/goods/get-goods
可以看到,服务成功被路由了
一个简单的网关服务就这样完成了,小伙伴看完过有没有对网关的概念更加深刻呢?
断言
在上面的例子中,我们就用到了一个断言工厂:Path
在Spring Cloud Gateway中,所有的断言工厂都是继承于AbstractRoutePredicateFactory
, 并且命名规则为:XxxRoutePredicateFactory
, 比如Path的类名为:PathRoutePredicateFactory
那么,Spring Cloud Gateway给我们内置了哪些断言工厂呢?
以下展示我觉得常用的断言工厂,更多的内容还请小伙伴自己查看文档
After
匹配在某个时间(ZonedDateTime)后的请求
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/goods/** # 匹配23:05分后的请求 - After=2021-08-08T23:05:13.605+08:00[Asia/Shanghai]
我们在23:03进行测试
访问失败了
Before
匹配在某个时间(ZonedDateTime)前的请求
与After相似,不再演示
Between
匹配在某个时间段(ZonedDateTime)的请求
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/goods/** # 匹配23:05-23:10的请求 - Between=2021-08-08T23:05:13.605+08:00[Asia/Shanghai],2021-08-08T23:10:13.605+08:00[Asia/Shanghai]
Host
匹配某个Host的请求
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/goods/** #配置host为192.168.1.105请求 - Host=192.168.1.105
注意,测试时需要将端口号改为80
尝试使用127.0.0.1发起调用
改为192.168.1.105进行调用
RemoteAddr
匹配指定的远程源地址
spring: cloud: gateway: # 路由配置 routes: # 路由id, 保证唯一性 - id: my-goods # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名 uri: lb://my-goods # 断言 predicates: # 匹配goods开头的请求 - Path=/goods/** #配置RemoteAddr为192.168.1网段的地址 - RemoteAddr=192.168.1.1/24
测试
启用内网穿透测试
访问失败了