1. 入门案例
1.1 approve微服务
准备一个微服务approve, 并注册到nacos中
依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
配置
server:
servlet:
context-path: /approve-service
port: 8999
spring:
application:
name: approve-service
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.112.77:3306/monitorlog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: pass
# 用来注册服务
cloud:
nacos:
discovery:
#这里是nacos本地的虚拟机地址
server-addr: 192.168.112.77:8848
namespace: df9a215d-16a2-4a5c-b92f-111fb2598944
group: DEFAULT_GROUP
# config:
# password: nacos
# username: nacos
controller层
@RestController
@RequestMapping("/approve")
public class AppController {
@Resource
private ApproveService approveService;
@GetMapping("/getUserInfo/{userId}")
public User getUserInfo(@PathVariable Integer userId){
return approveService.getUserInfo(userId);
}
}
1.2 gateway服务
准备gateway服务
依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- Maven整个生命周期内排除内置容器,排除内置容器导出成war包可以让外部容器运行spring-boot项目-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- gateway 组件依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>2.2.0.RC2</version>
</dependency>
<!-- nacos注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.5.RC2</version>
</dependency>
</dependencies>
配置文件
server:
servlet:
context-path: /gateway
port: 10010
spring:
application:
name: gateway
rabbitmq:
addresses: 192.168.112.77:15672
username: admin
password: pass
virtual-host: / # 虚拟主机
cloud:
nacos:
discovery:
server-addr: 192.168.112.77:8848
namespace: df9a215d-16a2-4a5c-b92f-111fb2598944
# server-addr: 192.168.112.77:8848
# namespace: public
# group: SEATA_GROUP
gateway:
routes:
- id: approve-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://approve-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/approve-service/** # 这个是按照路径匹配,只要以/user/开头就符合要求
discovery:
locator:
enabled:
lower-case-service-id: true
1.3 测试访问
检查服务注册
可以看到服务已经注册成功
请求地址: 127.0.0.1:10010/approve-service/approve/getUserInfo/1
可以看到请求转发,已经成功
2. 自定义配置
2.1 这里先对配置文件来进行了解
gateway:
discovery: # 服务的注册与发现
locator:
enabled: true # 默认的路由转发,一般来说都会关闭,自己指定路由转发,开启后自定义你注解就失效
lower-case-service-id: true # 大小写转换 一般用于拉取的服务列表转换为小写
routes:
- id: approve-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://approve-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/approve-service/** # 这个是按照路径匹配,只要以/user/开头就符合要求
这里特别需要注意:
- 默认的路由转发,会根据请求的端口后面的路径进行和服务名相同的转发,这会造成不必要的路由转发
如:http://ddd/getid/1 如果有拉取到服务列表有ddd的服务就会直接转发.并且自定义的路由转发规则失效
- 如果使用的注册中心是eureka,那么拉取的服务是大写的,所以最好开启转换
rounts后面的配置, 是属于快速配置, 代码层面如下图
其中-id的-
代表的是对象的意思,这里就是对RouteDefinition的属性赋值
id
不需多讲,只要符合class命名,且不重复即可
2.2 predicates谓语
谓语的作用就是满足你给定的条件就执行指定的路由
我们先来点开一下这个谓语属性,看看有哪些属性
可以看出,这个对象中有name和args参数两个属性.并且这里简化的代码书写,多个参数用逗号隔开
如
-predicates:
- name= Path
args:
- pattern=/b/**,/a/**
# 简化后
- predicates:
Path=/b/**,/a/**
那么常见的谓语参数有哪些呢,我们可以通过GatewayPredicate
这个类来查看所有的谓语工厂
这里可以看到只要把这个方法的结尾的RoutePredicateFactory
去掉就可以得到对应的谓语名称了
2.3 filter过滤器
过滤器分为默认过滤器, 自定义过滤器,全局过滤器
2. 3.1 默认过滤器
同样的name 和args 并且有简化书写
我们可以通过GatewayFilter
来找到目前已定义好的过滤器
同样的 把GatewayFilterFactory
去掉就得到了我们需要的过滤器 目前已知是24个
如在当前的路由方式下增加一些请求头信息
filters:
- AddRequestHeader=polo,one patch man!
2.3.1.1 StripPrefix
(切割路径)
这里特别说明一个过滤器
这个过滤器作用就是将我们配置的path链接,从左往右去掉路径个数,
如我们访问通过gateway的地址为
127.0.0.1:10010/approve-service/approve/getUserInfo/4
nacos中注册的服务地址为
192.168.72.1:8999
那么gateway转发后就会变成
192.168.72.1:8999/approve-service/approve/getUserInfo/4
如果我们在approve服务中添加了
server:
servlet:
context-path: /approve-service
那么就可以正常访问
如果我们没有配置服务前缀路径
server:
servlet:
context-path: / # '/'杠不能省略,否则会报错
那么我们的地址就会多出前面的服务名称,导致无法路由到对应服务,所以这时候使用StripPrefixgatewayFilter
来切割掉最前面的路径,变为
192.168.72.1:8999/approve/getUserInfo/4
从而完成访问
2.3.1.2 RequestRateLimiter
首先,先对常用的三种限制流量算法进行了解
- 计算器
一秒钟固定qps,
- 漏桶算法
所有请求放入桶中 然后可以匀速处理, 超出桶的容量的就拒绝
- 令牌桶算法
匀速产生令牌,放入桶中,每个请求进去后就去拿令牌, 拿不到令牌才会拒绝, 这样就能保证匀速处理请求
我们常用的过滤器为
这里的主要参数为 keyresolver 和defaultRateLimter
首先对于defaultRateLimter
实现
这里可以使用RedisRateLimiter
这里可以看到自动装配指定参数为redis-rate-limter
我们可以指定参数令牌数量 桶的容量
接下来对keysolver
实现
这里产生的令牌是远程主机的地址
书写配置文件
- name : RequestRateLimiter
args:
keyResolver: '#{@myKeyResolver}' # springEL表达式 获取容器中的bean
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 5
因为使用的有redis, 所以需要引入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 192.168.112.77
port: 6379
password: 1234
此时 还需要去解决redisRateLimiter中的依赖报错
去添加上对应的依赖即可
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>23.0.0</version>
</dependency>
这里 估计是版本更新问题 正常来说不需要要我们去特意添加
这样就实现了最高一秒五次, 后续一秒一次,可以用jmeter来进行压测
在这里插入图片描述
运行查看结果树
2. 3.1.3 Hystrix容错处理
首先看下它的工厂类 提供的配置
这里可以看到我们需要设置的三个参数,
name:代表失败返回的策略分组(不可省略 否则会走500或者404原始逻辑)
setter: 代表走默认的505或404逻辑,当name没有设置的时候才会生效(不建议使用)
fallbackUrl: 失败后转发到gateway当前工程中的controller地址
接下来开始测试
首先引入容错降级依赖
<!--降级处理-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
设置filter配置参数
-name: Hystrix
args:
name:fallbackcontroller
fallbackUri: forward://downgrade #访问失败 去访问gateway工程中的控制器
失败访问的gateway下的控制器
/**
* @author polo
* @createTime 2022/12/11 22:43
* @description 失败降级控制器
*/
@RestController
@RequestMapping("/downGrade")
public class DownGradeController {
@RequestMapping("/errmessage")
public String erroMessage(){
return "<html><body><div style='width:600px;margin:0 auto;text-align:center'>服务器繁忙,请稍后重试</div></body></html>";
}
}
现在来测试 当approve服务未启动
可以看出已经走了降级逻辑
2.3 .2 自定义过滤器
- 自定义全局过滤器
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//前置处理
log.info("执行自定义全局过滤器,记录请求日志信息...");
//后续执行链
Mono<Void> result = chain.filter(exchange);
//后置处理
log.info("执行自定义全局过滤器,记录响应日志信息");
return chain.filter(exchange);
}
@Override
public int getOrder() {
//在同为全局过滤器中,执行前后的控制
return -1;
}
}
自定义全局过滤器只需要实现globalFilter 就会在请求是自动调用,
官方推荐还需实现 Ordered接口,来指定在同类型过滤器中执行的先后
2. 自定义路由过滤器
官方推荐直接继承 AbstractRoutePredicateFactory
下面实现一个获取请求路径的过滤器
package com.jiang.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.List;
/**
* @author polo
* @createTime 2022/12/13 23:48
* @description
*/
@Slf4j
@Component
public class RequestPathGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestPathGatewayFilterFactory.Config> {
public RequestPathGatewayFilterFactory() {
super(Config.class);
}
//过滤链实现
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
//写在chain.filter前的为前置过滤
String path = exchange.getRequest().getPath().toString();
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
log.info("请求路径是:{},请求参数是name:{},path:{}",path,config.getName(),config.getPath());
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}
//书写简化方案
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name","path");
}
//配置参数
public static class Config {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
//Put the configuration properties for your filter here
}
}
配置文件
注意这里的简化方案需要在自定义路由过滤器中实现 shortcutFieldOrder
方法
到此,常规gateway的用法已经说完,