随着服务模块的增加,一定会产生多个接口地址,那么客户端调用多个接口只能使用多个地址,维护多个地址是很不方便的,这个时候就需要统一服务地址。同时也可以进行统一认证鉴权的需求。那么服务网关就充当这样的角色。
Gateway
网关为众多微服务挡在前面,做路由转发、监控、限流、鉴权等功能。SpringCloudGateway就是其实现之一。SpringCloudGateway
借鉴了Spring Cloud Netfilix Zuul的思想,它的目标是替代Zuul。
Gateway
是基于WebFlux
框架实现的,而WebFlux底层是使用高性能框架Netty
,性能方面是Zuul的1.6倍,且功能强大,设计优雅。
Gateway的核心是路由
、Predicate(断言)
、Filter(过滤器)
。路由是转发规则,Predicate是判断,Filter可以认为是请求被路由前或后加一点自定义逻辑。
SpringCloudGateway需要使用SpringBoot2.0+及以上版本,并且不可以在Tomcat或Jetty等Servlet容器运行,必须是Jar包运行!!!
构建一个Gateway网关服务,再创建两个服务:用户服务和商品服务,架构如下:
user服务的UserController,用户服务端口8002
@RestController
public class UserController {
private final Map<Integer, String> userInfo = new HashMap<Integer, String>() {{
put(1, "Zhangsan");
put(2, "Lisi");
}};
@RequestMapping("/user/findById")
public String findById(@RequestParam("id") Integer id) {
return userInfo.getOrDefault(id, null);
}
}
shop服务的ShopController,商品服务端口8003
@RestController
public class ShopController {
private final Map<Integer, String> shopInfo = new HashMap<Integer, String>() {{
put(1, "这是苹果");
put(1024, "芒果");
}};
@RequestMapping("/shop/findById")
public String findById(@RequestParam("id") Integer id) {
return shopInfo.getOrDefault(id, null);
}
}
创建一个gateway的服务
依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
exclusion>
exclusions>
dependency>
Gateway中去除spring-boot-starter-web
依赖,gateway中有webflux依赖,与starter-web
有冲突。
bootstrap.yml
加入配置
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: http://localhost:8002 # 路由地址(匹配成功后的服务地址)
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- id: shop_route
uri: http://localhost:8003
order: 1
predicates:
- Path=/shop/**
网关配置也可以使用JavaConfig的方式,但是不推荐使用。
配置中表示,当请求网关
路径中地址是以/user/
开头就路由到用户服务中,/shop/
开头路由到商品服务中。
测试一下
C:\Users\Admin>curl http://localhost:8083/user/findById?id=1
Zhangsan
C:\Users\Admin>curl http://localhost:8083/shop/findById?id=1024
芒果
上面案例中,uri都是写死的一些东西,如果对应的具体服务地址改了,那么就需要修改配置文件,而且假如要提高用户承载量,做负载均衡,有很多个节点,肯定不能只配置一个服务地址。
那么就需要用到nacos,统一管理服务注册、发现,网关路由转发的地址从nacos中拿就行。
那么用户服务
和商品服务
需要引入nacos服务发现注册依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
bootstrap.yml文件
------------------------User服务
server:
port: 8002
spring:
application:
name: user # 应用名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务地址
--------------------------Shop服务
server:
port: 8003
spring:
application:
name: shop # 应用名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务地址
最后记得启动类上,启动nacos服务注册发现
@SpringBootApplication
@EnableDiscoveryClient // 启用服务注册发现
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class, args);
}
}
欧克,那么同样地,gateway服务也要启用服务注册发现
依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
bootstrap.yml
配置如下:
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的服务,gateway自动根据服务发现为每一个服务创建了一个router,# 这个router将以服务名开头的请求路径转发到对应的服务
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- id: shop_route
uri: lb://shop # 路由地址(匹配成功后的服务地址) shop是商品服务的服务名称
order: 1
predicates:
- Path=/shop/**
启动尝试下:
C:\Users\Admin>curl http://localhost:8083/user/findById?id=1
{"timestamp":"2023-08-05T00:34:40.684+00:00","path":"/user/findById","status":503,"error":"Service Unavailable","requestId":"f5f6d217-1"}
C:\Users\Admin>curl http://localhost:8083/user/findById?id=1
{"timestamp":"2023-08-05T00:35:50.223+00:00","path":"/user/findById","status":503,"error":"Service Unavailable","requestId":"21a722a2-1"}
哈?服务不可用,经查阅资料得知:缺少ReactiveLoadBalancerClientFilter
过滤器,需要LoadBalancerClientFactory
类,但是需要引入相关依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
dependency>
可以了,再试下:
C:\Users\Admin>curl http://localhost:8083/shop/findById?id=1024
{"timestamp":"2023-08-05T01:19:34.183+00:00","status":404,"error":"Not Found","path":"/findById"}
C:\Users\Admin>curl http://localhost:8083/user/findById?id=1
{"timestamp":"2023-08-05T01:19:34.183+00:00","status":404,"error":"Not Found","path":"/findById"}
好嘞漂亮,踩了大坑了!!!一直404!!!
经过百般挣扎,查资料,看源码,调试等等手段,明白原因了…
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的服务,gateway自动根据服务发现为每一个服务创建了一个router,# 这个router将以服务名开头的请求路径转发到对应的服务
重点来了注意看:
当你的gateway配置了locator.enabled: true
时,gateway自动根据服务发现为每一个服务创建了一个router, 这个router将以服务名开头的请求路径转发到对应的服务,相当于人家给你自动生成了route规则,你自己都不用配置了
。
但是:你的请求中必须要带着服务端的服务名才可以进行访问到
以上述为例:如果要访问到user
服务的/user/findById
,那么请求地址为localhost:8083/user/user/findById
C:\Users\Admin>curl http://localhost:8083/user/user/findById?id=1
Zhangsan
如何选择
提供两种写法:
实际上,如果项目路径比较简单,直接让gateway和nacos自动生成
即可
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的服务,gateway自动根据服务发现为每一个服务创建了一个router,# 这个router将以服务名开头的请求路径转发到对应的服务
请求时,记得带上服务端的application.name
名称即可,如locahost:8083/user/user/findById
,第一个user
是服务名称
假如不想这么做,想直接以一个路径,跳到对应的服务中去,不去写服务名,那么我们需要自定义routes
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
不使用nacos自动生成的routes,自己定义,那么我们访问时localhost:8083/user/findById
就能正常访问到user
服务下的/user/findById
资源了。
C:\Users\Admin>curl http://localhost:8083/user/findById?id=1
Zhangsan
两种方式都可以使用,根据项目的实际情况选择。locator.enabled: true
只要在gateway中配置了,那么你在进行网关路由时,请求地址的第一个目录一定是服务名称。