目录
网关
微服务之所以存在,就是把一个单体服务拆分成多个服务,可以让多台机器共同协作,提高处理请求的效率,从某种程度上说,微服务可以理解成一个个Service层,所以微服务的url不应该被暴露,从网络安全和服务效率方面来看,微服务需要一个统一的入口去处理,统一调用。
微服务调用容易产生的问题
解决方案——网关
常用的网关
- Zuul:基于Servlet框架搭建,但是延迟比较严重,效率不是很高
- GateWay:Spring公司研发,底层为Netty的非阻塞异步调用,效率是Zuul的1.6倍,实用功能更多,更易扩展。
- Nginx+lua:效率更高但是无法融入到微服务中,一般都是单独拿出来用
- Kong:基于Nginx+lua,不适用未服务。
GateWay
概念
就是一个网关工具,提供一种简单有效的统一的 API 路由管理方式。并提供了网关基本的功能,例如:安全,监控和限流。GateWay官方说明文档
工作流程
GateWay底层使用Netty通讯框架,
工作流程的核心就是路由转发+执行过滤链
核心概念
- Route 路由:路由是一个过程,及经过断言的判断,将符合要求的请求转发到对应的微服上,让该微服务处理请求
- Predicate 断言:断言是配置一些规则,让请求与规则相匹配,如果匹配为真,则可以继续访问,如果匹配为假,则不会路由到微服务
- Filter 过滤:在路由的过程中,Filter会提供一些功能,例如权限校验、响应内容修改、协议转换等。
可以如下理解:
搭建过程
一、创建新项目cloud-gateway-9999
二、pom依赖
版本对应规则 https://spring.io/projects/spring-cloud
由于本人使用的boot是2.2.6的,所以使用的gateway版本为Hoxton.SR8
父级项目引入
<properties>
<spring.cloud-version>Hoxton.SR8</spring.cloud-version>
</properties>
<dependencyManagement>
<dependencies>
<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>
子级项目引入
注意,由于gateway的依赖和spring-boot-starter-web有冲突,因此需要注意删除依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
三、application.yml文件配置
server:
port: 9999
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
#开启注册中心路由功能
gateway:
discovery:
locator:
enabled: true
# 路由
routes:
# 路由id
- id: nacos-provider
# 匹配提供服务的路由地址
uri: http://localhost:9001/nacos-provider
predicates: # 断言
- Path=/demo/** # 断言,路径相匹配进行路由
四、服务提供者Controller
@RestController
@RequestMapping("demo")
public class DemoController {
@Value(value = "${server.port}")
private String serverPort;
@GetMapping("get")
public String getServerPort(){
return "Hello Nacos Discovery" + serverPort;
}
}
启动后可以通过gateway服务访问到真正的服务提供者。
五、GatewayConfig通过代码配置
Gateway除了可以使用yml配置文件来配置路由,也可以通过代码方式来进行配置,方法如下:
配置类
package com.cloud.cloudgateway9999.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 {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
// 新建了一个路由规则构建对象,这个对象可以构建多个路由
RouteLocatorBuilder.Builder routes = builder.routes();
// ,就会
/**
* 如果满足路径http://localhost:9999/demo/customer断言条件,那么
* 请求就会被转发至http://localhost/9001/nacos-provider/demo/customer
*/
routes.route("path-custumer",r->r.path("/demo/customer")
.uri("http://localhost:9001/nacos-provider")).build();
// 返回路由规则
return routes.build();
}
}
访问时会有相同的效果,但是这样配置比较麻烦,建议优先使用yml文件配置。
Gateway负载均衡
自动负载均衡
添加配置:
# 开启自动路由功能,根据服务名称自动创建routes
gateway:
discovery:
locator:
enabled: true
关于这个配置:默认为关闭状态,如果这个配置为开启状态,则说明:gateway与服务可以通过serviceId完成默认转发,如果多个服务集群的话,可以自定进行负载均衡
对比nacos自带的负载均衡Dubbo,在启动类配置
@Bean
// @Bean注解表示将restTemplate装配到容器中来
@LoadBalanced
// 负载均衡,表示这个restTemplate在调用服务提供者接口时经过负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
就可以实现,而nacos可以直接通过discovery.locator.enabled开关来做到。
discovery.locator.enabled开关可以根据serviceId自动转发,但是会导致服务名称暴露,因此,实际应用中不会打开该开关。
手动负载均衡
可以在yml配置负载均衡的规则:
# 路由
routes:
# 路由id
- id: routeNo1
# 负载均衡:如果符合断言,则按照服务名为nacos-provider的服务进行负载均衡访问,并且,例如,访问gateway的请求地址为:http://9999/demo/get,会被路由成http://localhost:9001/demo/get和http:/9002/demo/get
uri: lb://nacos-provider
predicates:
- Path=/demo/** # 断言,路径相匹配进行路由
效果如下:
GateWay断言
断言是指一种匹配规则,可以对路径、请求头、请求发送的时间等属性进行匹配,断言通过以后,才能够被转发到其他微服务。GateWay断言官方说明文档
断言的种类
- After:匹配在指定日期时间之后发生的请求。
- Before:匹配在指定日期之前发生的请求。
- Between:需要指定两个日期参数,设定一个时间区间,匹配此时间区间内的请求。
- Cookie:需要指定两个参数,分别为name和regexp(正则表达式),也可以理解Key和Value,匹配具有给定名称且其值与正则表达式匹配的Cookie。
- Header:需要两个参数header和regexp(正则表达式),也可以理解为Key和Value,匹配请求携带信息。
- Host:匹配当前请求是否来自于设置的主机。
- Method:可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
- Path:匹配指定路径下的请求,可以是多个用逗号分隔
- Query:需要指定一个或者多个参数,一个必须参数和一个可选的正则表达式,匹配请求中是否包含第一个参数,如果有两个参数,则匹配请求中第一个参数的值是否符合正则表达式。
- RemoteAddr:匹配指定IP或IP段,符合条件转发。
- Weight:需要两个参数group和weight(int),实现了路由权重功能,按照路由权重选择同一个分组中的路由。
使用示例
routes:
# 路由id
- id: routeNo1
uri: lb://nacos-provider
predicates:
routes:
# 路由id
- id: routeNo1
# 匹配提供服务的路由地址
- Path=/demo/**
# 必须在该时间之后发起的请求才能被处理
- After=2022-07-18T19:18:44.635+08:00[Asia/Shanghai]
# 匹配Cookie的key和value(正则表达式),cookie中一定要有一key为username的键值对,并且值必须匹配正则表达式,此处必须为字母,不能为数字
- Cookie=username,[a-z]+
# 匹配请求头,请求头必须含有X-Request-Id,并且必须是数字
- Header=X-Request-Id,\d+
# 匹配当前的主机地址发出的请求host
- Host=**.cloud.com
# 可以设置一个或多个参数,匹配HTTP请求,比如GET、POST
- Method=GET,POST
# 匹配请求参数,这里如果需要匹配多个参数,可以写多个Query
- Query=id,.+
时间类的标签必须是ZonedDateTime类型,可以参考以下代码获取具体写法:
public static void main(String[] args){
ZonedDateTime time = ZonedDateTime.now();
System.out.println(time);
}
weight规则如下:
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量到weightlow.org。
GateWay过滤器Filter
GateWay内置的Filter有两种生命周期,一种是pre,作用于业务逻辑之前,一种是post,作用于业务逻辑之后;其中,Gateway自带的Filter有两种,一种是GateWayFilter,单一过滤器,有32种,一种是GlobalFilter,全局过滤器,由9种,详见GateWay过滤器官网
StripPrefix过滤器
如果在9001的配置文件中配置了context-path,那么,按照原来的地址就访问不到服务了,而是需要在原地址上拼接一部分内容。
server:
port:
9001
servlet:
context-path: /nacos-provider
例如,原来访问http://localhost:9999/demo/get就会转发成http://9001/demo/get,但是现在,访问的地址变为http://localhost://9001/nacos-provider/demo/get,如此这样的话就访问不到资源了。
StripPrefix过滤器的作用就是给访问路径去除一部分
# 路由
routes:
# 路由id
- id: routeNo1
# 匹配提供服务的路由地址
uri: lb://nacos-provider
predicates:
- Path=/demo/** # 路径相匹配进行路由
# 必须在该时间之后发起的请求才能被处理
- After=2022-07-18T19:18:44.635+08:00[Asia/Shanghai]
filters:
# 去掉uri的第一部分
- StripPrefix=1
访问路径为:http://localhost:9999/demo/nacos-provider/demo/get
转换为:http://localhost:9001/nacos-provider/demo/get
就能顺利访问到资源了
自定义过滤器
一、新建MyFilter
package com.cloud.cloudgateway9999.filter;
import com.alibaba.csp.sentinel.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class MyFilter implements Ordered,GlobalFilter {
/**
*
* @param exchange 可以拿到一个方法的请求和响应
* @param chain 过滤器链
* @return 是否放行
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取请求里请求参数,第一个username的值
String username = exchange.getRequest().getQueryParams().getFirst("username");
log.info("---------进入GateWay过滤器-------------");
if(StringUtil.isEmpty(username)){
log.info("---------非法用户名,请求被拒绝-------------");
//返回状态码为406,不可接受的请求
exchange.getResponse().setRawStatusCode(HttpStatus.SC_NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加载过滤器的顺序
* @return 返回一个整数,数字越小优先级越高
*/
@Override
public int getOrder() {
return 0;
}
}
二、测试访问生效
不携带过滤器设置的请求参数,请求失败
携带过滤器设置的请求参数,访问成功