SpringCloud之服务网关
网关统一服务入口,可方便实现对平台众多服务接口进行管控,对访问服务的身份认证、防报文重放与防数据篡改、功能调用的业务鉴权、响应数据的脱敏、流量与并发控制,甚至基于API调用的计量或者计费等等。
1.zuul
1.1定义
zuul叫路由网关,它包含对请求的路由和过滤的功能。路由负责将外部的请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。而过滤是负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能基础。
目前zuul已更新到2.0版本,但SpringCloud官方不推荐使用zuul2.0,但支持zuul2.0。
1.2基础环境搭建
源代码:https://github.com/zhongyushi-git/cloud-zuul-demo.git
创建maven的父工程cloud-zuul-demo,删除src目录,在pom中导入依赖,对SpringBoot和SpringCloud版本进行锁定
<properties> <spring.boot.version>2.2.2.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR1</spring.cloud.version> </properties> <!-- 依赖管理,父工程锁定版本--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
1.3搭建服务提供者
1)新建maven子模块(cloud-provider8001),导入依赖
<dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
2)新建启动类ProviderMain8001并添加注解
package com.zys.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ProviderMain8001 { public static void main(String[] args) { SpringApplication.run(ProviderMain8001.class, args); } }
3)配置application.yml
server: port: 8001 spring: application: name: cloud-consul-provider cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
4)新建controller接口
package com.zys.cloud.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Value("${server.port}") private String port; @GetMapping("/user/get") public String get() { return "我是服务提供者,端口:" + port; } }
1.4搭建路由子模块
1)创建子模块(cloud-zuul-gateway9527),导入依赖
<dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <!-- zuul路由网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies>
2)application.yml配置
server: port: 9527 spring: application: name: cloud-zuul-gateway cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} #配置微服务代理 zuul: #忽略真实的服务名,即不能使用服务名访问,只能通过代理访问以保证安全 ignored-services: "*" routes: #指定要代理的微服务名称和路径 cloud-consul-provider: /users/**
这里也需要把服务注册到consul中,除此之外主要配置了网关路由的详细信息。当访问到/users路径时会转发到cloud-consul-provider服务中,根据后面的路径进行转发。
3)创建启动类
package com.zys.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class ZuulMain9527 { public static void main(String[] args) { SpringApplication.run(ZuulMain9527.class, args); } }
4)启动测试
先启动服务提供者,再启动网关服务。浏览器输入http://localhost:9527/users/user/get可以正常访问到提供者的服务,此时路由功能就完成了。
注意:这里必须要给每个服务加一个前缀。
2.gateway
2.1定义
旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤功能,如熔断、限流、重试等。三大核心概念如下:
Route(路由) | 构建网关的基本模块,由ID、URL、一系列断言和过滤器组成 |
Predicate(断言) | 用于匹配Http请求中的所有内容,如果匹配则进行路由 |
Filter(过滤) | Spring框架中GatewayFilter的实例 |
2.2zuul与gateway的区别
gateway是基于WebFlux框架实现的,而WebFlux框架底层使用了高性能的Reactor模式通信框架Netty,是基于异步非阻塞模型开发的。而zuul使用的是阻塞框架。
2.3基础环境搭建
源代码:https://github.com/zhongyushi-git/cloud-gateway-demo.git
创建maven的父工程cloud-gateway-demo,删除src目录,在pom中导入依赖,对SpringBoot和SpringCloud版本进行锁定
<properties> <spring.boot.version>2.2.2.RELEASE</spring.boot.version> <spring.cloud.version>Hoxton.SR1</spring.cloud.version> </properties> <!-- 依赖管理,父工程锁定版本--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring.cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
2.4搭建服务提供者
1)新建maven子模块(cloud-provider8001),导入依赖
<dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
2)新建启动类ProviderMain8001并添加注解
package com.zys.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class ProviderMain8001 { public static void main(String[] args) { SpringApplication.run(ProviderMain8001.class, args); } }
3)配置application.yml
server: port: 8001 spring: application: name: cloud-consul-provider cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
4)新建controller接口
package com.zys.cloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Value("${server.port}")
private String port;
@GetMapping("/user/get")
public String get() {
return "我是服务提供者,端口:" + port;
}
}
根据8001再创建一个服务提供者模块,端口为8002.
2.5搭建服务消费者
1)新建maven子模块(cloud-consumer80),导入依赖
<dependencies> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies>
2)新建启动类ConsumerMain80并添加注解
package com.zys.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
class ConsumerMain80 {
public static void main(String[] args) {
SpringApplication.run(ConsumerMain80.class, args);
}
}
3)配置application.yml
server:
port: 80
spring:
application:
name: cloud-consul-consumer
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
feign:
client:
config:
#指定全局
default:
#连接超时时间
connectTimeout: 5000
#服务等待时间
readTimeout: 5000
loggerLevel: full
logging:
level:
com.zys.cloud.service.UserServiceClient: debug
3)创建服务接口UserServiceClient,对于服务提供者接口
package com.zys.cloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
//指定微服务名称
@FeignClient(value = "cloud-consul-provider")
public interface UserServiceClient {
@GetMapping("/user/get")
String get();
}
4)创建controller接口,将UserServiceClient注入使用
package com.zys.cloud.controller;
import com.zys.cloud.service.UserServiceClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/consumer")
public class TestController {
@Resource
private UserServiceClient userServiceClient;
@GetMapping("/get")
public String get() {
return userServiceClient.get();
}
}
2.6搭建路由子模块
1)创建子模块(cloud-gateway9527),导入依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>
需要注意的是,这里不能导入web,否则会报错
2)yml配置
server: port: 9527 spring: application: name: cloud-gateway cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name} gateway: #开启从注册中心动态创建路由的功能,利用微服务名进行路由 discovery: locator: enabled: true #配置路由列表 routes: - id: provider_route #路由的ID,没有固定规则但要求唯一 uri: lb://cloud-consul-provider #匹配后提供服务的路由地址,根据服务名访问,lb表示后台使用负载均衡 predicates: - Path=/user/** #断言,路径相匹配的进行路由 - id: consumer_route uri: lb://cloud-consul-consumer predicates: - Path=/consumer/**
这里也需要把服务注册到consul中,除此之外主要配置了网关路由的详细信息。
使用断言配置了路径匹配路由,当访问/user相关的请求,都会转发到服务名为cloud-consul-provider的服务上,并把路由拼接到后面,对外暴露统一的接口。如访问http://localhost:9527/user/get那么会转发到http://cloud-consul-provider/user/get。
当然也可以对路由进行切割,需配置filters:
#配置路由列表 routes: - id: provider_route #路由的ID,没有固定规则但要求唯一 uri: lb://cloud-consul-provider #匹配后提供服务的路由地址,根据服务名访问,lb表示后台使用负载均衡 predicates: - Path=/api/user/** #断言,路径相匹配的进行路由 filters: - StripPrefix=1 #路由截取
StripPrefix的意思是将路径切掉一级,这个例子中 /api被剪。若请求路径是localhost:9527/api/user/list,则最终转发的路径是lb://cloud-consul-provider/user/list。
3)创建启动类
package com.zys.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class GatewayMain9527 { public static void main(String[] args) { SpringApplication.run(GatewayMain9527.class, args); } }
4)启动测试
先启动服务提供者集群,再启动服务消费者,最后启动网关服务。浏览器输入http://localhost:9527/consumer/get可以正常访问到消费者的服务,此时路由功能就完成了。多刷新几次,可以看出是服务提供者轮询的。
2.7编码方式配置路由
除了上述使用yml的方式配置路由外,还可以通过编码方式配置(不推荐)。新建配置类GateWayConfig,这里设置别的路由,避免和yml方式混淆。
package com.zys.cloud.config; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 编码方式配置路由 */ @Configuration public class GateWayConfig { @SuppressWarnings("JavaDoc") @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("baidu_route", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build(); return routes.build(); } }
重启9527,访问地址http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei。
2.8断言配置
上面的小节中在yml中也配置了断言,用于路由匹配。除此之外,还可以配置其他的断言。这些配置只在路由转发时生效,对于服务之间的调用,没有任何意义。
2.8.1 案例说明
假如配置服务提供者请求在某个时间后生效。只需要在yml的predicates设置After即可:
predicates: - Path=/user/** #断言,路径相匹配的进行路由 - After=2021-08-08T16:04:28.872+08:00[Asia/Shanghai]
配置后访问http://localhost:9527/user/get,在此时间之前访问,会返回404信息,只有过了设置时间才能正常访问。
需要注意是的,后面的时间格式必须正确,获取格式时间的方法:
public static void main(String[] args) { //获取当前的时间 ZonedDateTime zbj = ZonedDateTime.now(); int year = 2021, month = 8, day = 10, hour = 10, minute = 20, second = 0; //获取指定的时间 ZonedDateTime of = ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault()); System.out.println(zbj); System.out.println(of); }
可根据需求进行获取时间格式。
2.8.2其他的配置
1)配置Before
表示在某个时间之前
predicates: - Path=/user/** - Before=2020-04-09T21:15:28.872+08:00[Asia/Shanghai]
Between表示在某个时间段内,配置两个时间,中间用逗号隔开。
2)配置Method
Method用于配置请求的方法,若设置必须是post请求
predicates: - Path=/user/** - Method=GET
2.9查看网关路由规则
在开发时,若需要查看网关路由规则信息,可以在yml中进行开启,默认是关闭的:
management: endpoints: web: exposure: #开启所有web端点暴露 include: "*"
配置后,重启网关服务,访问http://localhost:9527/actuator/gateway/routes,即可看到相关的信息:
2.10过滤器配置
在路由中提供了许多了过滤器配置,可以直接使用,但也可以根据需求自定义配置,把公共的服务放到网关中。例如在请求中header必须携带token才能访问。
创建过滤器类MyGateWayFilter
package com.zys.cloud.config; import com.alibaba.fastjson.JSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; /** * 自定义过滤器 */ @Component public class MyLogGateWayFilter implements GlobalFilter, Ordered { private static Logger log = LoggerFactory.getLogger(MyLogGateWayFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getHeaders().getFirst("token"); //非法用户直接拦截 if (token == null) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); Map<String, Object> map = new HashMap<>(); map.put("code", 401); map.put("msg", "未授权,无法访问"); byte[] errs = JSON.toJSONBytes(map); DataBuffer buffer = response.bufferFactory().wrap(errs); return response.writeWith(Flux.just(buffer)); } return chain.filter(exchange); } //order是过滤器的执行顺序,数字越小,优先级越高 @Override public int getOrder() { return 0; } }
重启9527,访问地址http://127.0.0.1:9527/user/get请求头中未携带token就无法访问,而携带token可以正常访问。
为了能够让读者正常访问,故先把此过滤器注释。
2.11跨域配置
跨域配置有两种方式,一种是在配置文件配置,一种是通过编码方式。这里只介绍配置文件 方式,因为编码方式和SpringBoot配置跨域类似:
spring: cloud: gateway: #跨域配置 globalcors: corsConfigurations: '[/**]': allowedOrigins: "*" allowedMethods: "*" allowedHeaders: "*"
只需一个配置即可。
2.12项目部署
对于网关项目,不能使用war方式启动,必须使用jar方式。因此需要将其打包为jar包,在启动时需要指定编码:
java -Dfile.encoding=utf-8 -jar xxx-gateway.jar
若不配置编码,则启动时会读取不到nacos的配置内容。