系列文章目录
前言
接上篇,本篇记录网关组件的使用
一、网关
1、什么是网关?
就是网络请求的统一入口,网关的本质就是过滤器的组合
2、为什么需要网关?
-
如果我们的有成千上万个服务,我们在请求每个服务的时候都需要进行认证,难度与工作量可想而知,要控制用户对于整个服务的访问次数的限制。
-
如果没有统一的入口,那么前端在与服务端交互的时候定位到各个服务,假设服务器端作服务的重构,那么前端也得跟着一起修改。
3、SpringCloudAlibaba GateWay
gateway是spring cloud的第二代网关,其性能是zuul的1.6倍左右,其内部是基于netty、reactor(多路复用)、webflux进行构建,性能强大。gateway需要从注册中心获取服务,然后通过网关来调用对应的服务。但是gateway不在web环境下运行,也就是说不能打成war包放在tomcat下运行
二、快速入门
1.导入依赖
提示:所有的案例代码,都是基于前几篇的基础上
创建springcloudalibaba-micro-service-gateway-9090子工程(使用maven创建),导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<!--此处修改了父工程的组织id-->
<artifactId>springcloudalibaba-GateWay</artifactId>
<groupId>com.lzl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloudalibaba-micro-service-gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除掉springmvc相关的配置信息 -->
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
<!-- 排除掉tomcat相关的配置 -->
<exclusion>
<groupId>org.springframework.bootk</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
</exclusions>
</dependency>
<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>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.lzl</groupId>
<artifactId>springcloudalibaba-micro-service-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
</project>
2.创建 GateWayApplication 启动类
package com.lzl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/3/15 15:16
*/
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
3.创建application.yml文件
spring:
application:
name: micro-service-gateway # 网关名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
register-enabled: false # 不注册到nacos上
gateway:
discovery:
locator:
enabled: true #开启网关端口并使用服务方的名称来访问
#之前:http://localhost:8080/feign/getById?id=1
#现在:http://localhost:9090/consumer/feign/getById?id=1
server:
port: 9090
4.启动服务方,消费方,网关,进行测试
通过网关配置的端口9090,和消费方的服务名访问消费方接口
成功
三、谓词配置
谓词(predicate)是gateway内置的的一下关于请求相关的处理,在application.yml中增加routes的配置
注意:谓词配置后,访问时就不需要再写服务名称了,因为谓词里面已经配置过了
配置谓词之前:http://localhost:9090/consumer/feign/findAll
配置谓词之后:http://localhost:9090/feign/findAll
简言之,谓词配置,就是在原本访问的基础上,加一些条件。
yml文件的内容:
server:
port: 9090
spring:
application:
name: micro-service-gateway # 网关名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
register-enabled: false # 不注册到nacos上
gateway:
discovery:
locator:
enabled: true #开启网关端口并使用服务方的名称来访问
#之前:http://localhost:8080/feign/getById?id=1
#现在:http://localhost:9090/consumer/feign/getById?id=1
routes:
# id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
- id: consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://consumer
# 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
predicates:
# 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign/findAll来访问了
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
# 请求的方式
- Method=get,post
# 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
- RemoteAddr=10.8.162.0/24
# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
#- Header=token,[a-zA-Z0-9]+
不满足谓词条件时,访问不到对应的接口,origin必须是英文大小写,数字不满足谓词条件
满足谓词条件,可以访问
四、过滤器配置
1.令牌桶算法
GateWay提供了很多内置的过滤器让我们使用,具体的过滤器在spring-cloud-gateway-core-2.1.2.RELEASE.jar下的org.springframework.cloud.gateway.filter.factory包下,接下来我们挑其中一个非常常用的过滤来讲解用法,在实际的开发过程中,有这样一种业务需求,就是限制同一个IP对服务器频繁的请求,例如我们限制每个IP在每秒只能访问3次,那么要怎么实现呢?其实spring-boot已经帮我们实现好了一个,只需要做一定的配置就可以了。
IP限制的原理就是令牌桶算法, 随着时间流逝,系统会按恒定 1/QPS 时间间隔(如果 QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务。如下图所示:
2.使用步骤
(1)添加依赖
在springcloudalibaba-micro-service-gateway-9090工程中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)创建RedisHostKeyResovler类
package com.lzl.resolver;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/3/17 14:12
*/
@Component
public class RedisHostKeyResolver implements KeyResolver {
/**
* webflux:
* Mono: 用于返回单个值
* Flux: 用于返回集合数据
*/
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//获取用户的访问的 ip
String host = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(host); //构建 Mono<String>
}
}
(3)在application.yml中增加filters和redis的配置
server:
port: 9090
spring:
application:
name: micro-service-gateway # 网关名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
register-enabled: false # 不注册到nacos上
gateway:
discovery:
locator:
enabled: true #开启网关端口并使用服务方的名称来访问
#之前:http://localhost:8080/feign/getById?id=1002
#现在:http://localhost:9090/micro-service-consumer/feign/getById?id=1002
routes:
# id可以不是服务名,名字任意,但是不能重复,推荐使用服务名的方式
- id: consumer
# uri才是控制着某个具体的访问到达我们特定的服务
uri: lb://consumer
# 谓词: 就是满足的条件,可以在org.springframework.cloud.gateway.handler.predicate这个包下
predicates:
# 配置访问消费方controller的一级目录名称,这样就可以通过http://localhost:9090/feign-user-consumer/getUsers来访问了
- Path=/feign/**
# 请求的参数中必须携带origin这个参数名,参数值符合[a-zA-Z]+ 这个正则
- Query=origin,[a-zA-Z]+
# 请求的方式
- Method=get,post
# 设置时间区间内访问: 2020年12月31日 - 2030年12月31日,可以访问,+08:00表示时区
#- After=2020-12-31T00:00:00+08:00[Asia/Shanghai]
#- Before=2030-12-31T00:00:00+08:00[Asia/Shanghai]
# 描述IP在10.8.13.1~10.8.13.255之间的地址才可以访问
#- RemoteAddr=10.8.162.0/24
# 请求的头中必须得携带token, value值符合[a-zA-Z0-9]+ 这个正则
#- Header=token,[a-zA-Z0-9]+
filters:
# RequestRateLimiter是固定值
- name: RequestRateLimiter
args:
# key-resolver是用于限流的bean对象,通过SpEL的方式 #{@XXX} 取出spring容器中的bean
keyResolver: '#{@redisHostKeyResolver}'
# 每秒往令牌桶中存放的数量
redis-rate-limiter.replenishRate: 1
# 令牌桶中最多的令牌的数量
redis-rate-limiter.burstCapacity: 3
#redis配置
redis:
host: 127.0.0.1
port: 6379
(4)启动Redis,测试
启动windows版redis
可以让局域网内的其他用户访问本机:http://本机ip:9090/feign/findAll?origin=abc
本机访问如下:
五、自定义全局过滤器
1.导入依赖
在GateWay子工程导入下边依赖
<dependency>
<groupId>com.qf</groupId>
<artifactId>springcloudalibaba-micro-service-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
2.创建全局过滤器类
package com.lzl.filters;
import com.alibaba.fastjson.JSON;
import com.lzl.utils.JsonResult;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/3/17 15:00
*/
//创建多个全局过滤器
@Configuration
public class FilterConfig {
@Bean
@Order(-100)//正数值越小,负数的绝对值越大,优先级越高
public GlobalFilter loginFilter(){
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-100");
//获取传入的用户名和密码
ServerHttpRequest request = exchange.getRequest();
//获取所有参数
MultiValueMap<String, String> queryParams = request.getQueryParams();
//获取前端传过来的name以及password
List<String> nameList = queryParams.get("name");
List<String> passwordList = queryParams.get("password");
System.out.println(nameList);
//判断
if(nameList !=null && nameList.size() > 0 && passwordList !=null && passwordList.size() > 0){
//模拟数据库查询
if("jack".equals(nameList.get(0)) && "123".equals(passwordList.get(0))){
//放行
return chain.filter(exchange);
}else{
//参数输入错误
JsonResult jsonResult = JsonResult.error();
jsonResult.setData("name or password is error");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}else{
//参数输入错误
JsonResult jsonResult = JsonResult.error();
jsonResult.setData("name or password is null");
//返回
ServerHttpResponse response = exchange.getResponse();
String jsonString = JSON.toJSONString(jsonResult);
DataBuffer dataBuffer = response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(dataBuffer));
}
}
};
}
@Bean
@Order(-90)//正数值越小,负数的绝对值越大,优先级越高
public GlobalFilter otherFilter(){
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-90");
return chain.filter(exchange);
}
};
}
}
3.测试
浏览器访问http://localhost:9090/feign/getUsers?origin=qwer&name=jack&password=123,进行测试
没有携带过滤器指定参数时,报错
参数不正确,报错
参数输入正确,成功访问
总结
本篇记录了,springcloudAlibab的网关组件GateWay组件的使用,更多内容见下篇