目录
一、什么是网关?
网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。使用在不同的通信协议、数据格式或语言,甚至体系结构完全不同的两种系统之间,网关是一个翻译器。与网桥只是简单地传达信息不同,网关对收到的信息要重新打包,以适应目的系统的需求。同层--应用层。-----摘自《百度百科》
二、Spring Cloud Gateway 原理
三、构建简单的网关项目
pom文件如下
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xiaojie</groupId>
<artifactId>xiaojie-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
</parent>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Feign Client for loadBalancing -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.0.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Feign Client for loadBalancing -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 阿里巴巴数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.vladimir-bukhtoyarov/bucket4j-core -->
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>6.0.1</version>
</dependency>
</dependencies>
</project>
启动类和简单api省略
四、路由谓词工厂
server:
port: 80
spring:
application:
name: xiaojie-gateway
cloud:
gateway:
routes:
# - id: test_route
# uri: https://www.baidu.com/
# filters:
# #这个意思是截取第一个前缀后访问 比如,请求/name/bar/foo,去除掉前面一个前缀之后,最后转发到目标服务的路径为/bar/foo
# - StripPrefix=1
# predicates:
# - Path=/test #路径拦截
# #ZonedDateTime dateTime = ZonedDateTime.now(); java8之后ZonedDateTime时间
# - After=2021-07-14T09:51:55.052+08:00[Asia/Shanghai]
#- Before=2021-07-15T09:51:55.052+08:00[Asia/Shanghai]
#- Between=2021-07-14T09:51:55.052+08:00[Asia/Shanghai],2021-07-15T09:51:55.052+08:00[Asia/Shanghai]
#- Cookie=xiaojie, [a-z] #匹配cookie名字满足后面表达式的时候
#- Header=X-Request-Id, \d+ # 匹配请求头中X-Request-Id 为数字的
#- Host=**.xiaojie.com #匹配域名路由
#- Method=GET,POST #拦截方法是get post 的请求
#- Query=red, gree. #如果请求包含red其值与正则gree.表达式匹配的查询参数,则前面的路由匹配,因此green和greet将匹配
#- RemoteAddr=192.168.1.1/24 #匹配ip
#一下是权重路由匹配
- id: weight_high
uri: https://www.baidu.com
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://www.sina.com
predicates:
- Weight=group1, 2
五、网关集群
通过Nginx转发到网关服务,网关经过轮训算法,或者其他的算法,转发到真实的服务器,进行服务的访问,下面开始配置。
网关端的配置文件
server:
port: 82
####服务网关名称
spring:
application:
name: xiaojie-gateway
cloud:
gateway:
###路由策略
routes:
###根据我们的服务名称查找地址实现调用
- id: member
#uri: http://127.0.0.1:8090
uri: lb://xiaojie-member/ #服务的名称
filters:
- StripPrefix=1
###匹配规则
predicates:
- Path=/member/**
#开启服务注册中心获取服务
discovery:
locator:
enabled: true
nacos:
discovery:
#注册地址
server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
#是否开启nacos注册
enabled: true
#账号
username: xiaojie
#密码
password: nacos
#命名空间 ,一定要对应上自己注册的服务的命名空间
namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
#分组
group: DEV_GROUP
会员服务端的配置文件
spring:
application:
name: xiaojie-member
cloud:
nacos:
config:
#前缀
prefix: xiaojie-member
#地址
server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
#扩展名
file-extension: yaml
#命名空间
namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
#分组
group: DEV_GROUP
#开关
enabled: true
#动态刷新配置
refresh-enabled: true
discovery:
#注册地址
server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
#是否开启nacos注册
enabled: true
#账号
username: xiaojie
#密码
password: nacos
#命名空间
namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
#分组
group: DEV_GROUP
Nginx添加如下配置
upstream xiaojie_gateway{
server 127.0.0.1:81;
server 127.0.0.1:82;
}
server {
listen 80;
server_name gateway.xiaojie.com;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://xiaojie_gateway;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
在网关中添加以下filter
package com.xiaojie.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description: 配置token过滤器
* @author: xiaojie
* @date: 2021.07.14
*/
@Component
@Slf4j
public class TokenFilter implements GlobalFilter {
@Value("${server.port}")
private String serverPort;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取参数
String token = exchange.getRequest().getQueryParams().getFirst("token");
if(!StringUtils.hasText(token)){
//token为空转发到其他的地址
ServerHttpResponse response = exchange.getResponse();
//
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
String msg = "token not is null ";
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(buffer));
}
// 在请求头中存放serverPort serverPort
ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort)
.build();
log.info("端口号是...............{}"+serverPort);
return chain.filter(exchange.mutate().request(request).build());
}
}
访问http://gateway.xiaojie.com/member/getMember?token=456 可以看到实现了集群效果。
六、动态网关
动态网管可以使用配置中心,Redis,和数据库(我用的方法)
直接上代码,其实就是增删改查
package com.xiaojie.entity;
import lombok.Data;
/**
* @Description: 路由实体类
* @author: xiaojie
* @date: 2021.07.15
*/
@Data
public class Route {
private Integer id;
private String routeId; //路由id
private String routeName;//路由名称
private String routePattern;//路由转发地址,
private Integer routeType;//路由类型1从注册中心获取,0-转发地址
private String routeUrl;//转发到的地址
private Integer isOpen;//是否开放0-否1-开放
private Integer isUse;//是否可用0-否1-开放
}
package com.xiaojie.mapper;
import com.xiaojie.entity.Route;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* @Description:Mapper接口
* @author: xiaojie
* @date: 2021.07.15
*/
public interface RouteMapper {
@Select("SELECT id,routeId,routeName,routePattern,routeType,routeUrl,is_open isOpen,is_use isUse FROM `tb_route` WHERE is_open=1 and is_use=1;")
List<Route> selectAllRoute();
@Insert("INSERT INTO tb_route(routeId,routeName,routePattern,routeType,routeUrl,is_open,is_use) " +
"VALUES(#{routeId},#{routeName},#{routePattern},#{routeType},#{routeUrl},#{isOpen},#{isUse})")
@Options(useGeneratedKeys = true, keyProperty = "id")
Integer add(Route route);
@Update("UPDATE tb_route set routePattern=#{routePattern},routeUrl=#{routeUrl} WHERE routeId=#{routeId}")
Integer update(Route route);
@Delete("DELETE FROM tb_route WHERE routeId=#{routeId}")
Integer delete(String routeId);
}
package com.xiaojie.service.impl;
import com.xiaojie.entity.Route;
import com.xiaojie.mapper.RouteMapper;
import com.xiaojie.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:
* @author: xiaojie
* @date: 2021.07.15
*/
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private RouteMapper routeMapper;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Override
public List<Route> getAll() {
List<Route> routes = routeMapper.selectAllRoute();
for (Route route: routes){
loadRoute(route);
}
return routes;
}
public void loadRoute(Route route) {
RouteDefinition definition = new RouteDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
FilterDefinition filterDefinition = new FilterDefinition();
Map<String, String> filterParams = new HashMap<>(8);
URI uri = null;
if (1==route.getRouteType()) {
// 如果配置路由type为1的话 则从注册中心获取服务地址
uri = UriComponentsBuilder.fromUriString(route.getRouteUrl()).build().toUri();
} else {
uri = UriComponentsBuilder.fromHttpUrl(route.getRouteUrl()).build().toUri();
}
// 定义的路由唯一的id
definition.setId(route.getRouteId());
predicate.setName("Path");
//路由转发地址
predicateParams.put("pattern", route.getRoutePattern());
predicate.setArgs(predicateParams);
filterDefinition.setName("StripPrefix");
filterParams.put("_genkey_0", "1");
filterDefinition.setArgs(filterParams);
definition.setPredicates(Arrays.asList(predicate));
definition.setFilters(Arrays.asList(filterDefinition));
definition.setUri(uri);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.info("路由刷新成功.............................");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher=applicationEventPublisher;
}
}
定义接口,当修改路由之后,需要刷新路由规则
@RequestMapping("/refreshGw")
public Object refreshGw() {
return routeService.getAll();
}
SQL如下
/*
Navicat MySQL Data Transfer
Source Server : 本地
Source Server Type : MySQL
Source Server Version : 80024
Source Host : localhost:3306
Source Schema : my_test
Target Server Type : MySQL
Target Server Version : 80024
File Encoding : 65001
Date: 15/07/2021 15:25:24
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_route
-- ----------------------------
DROP TABLE IF EXISTS `tb_route`;
CREATE TABLE `tb_route` (
`id` int NOT NULL AUTO_INCREMENT,
`routeId` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由id',
`routeName` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由名称',
`routePattern` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '路由转发地址',
`routeType` tinyint NULL DEFAULT NULL COMMENT '路由类型1从注册中心获取,0-转发地址',
`routeUrl` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '转发到的地址',
`is_open` tinyint NULL DEFAULT NULL COMMENT '是否开放0-否1-开放',
`is_use` tinyint NULL DEFAULT NULL COMMENT '是否可用0-否1-开放',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin COMMENT = '动态网关表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_route
-- ----------------------------
INSERT INTO `tb_route` VALUES (1, 'member', 'xiaojie-member', '/member/**', 1, 'lb://xiaojie-member/', 1, 1);
INSERT INTO `tb_route` VALUES (3, 'baidu', 'xiaojie-baidu', '/bai/**', 0, 'http://www.baidu.com', 1, 1);
SET FOREIGN_KEY_CHECKS = 1;
配置文件如下
server:
port: 82
####服务网关名称
spring:
application:
name: xiaojie-gateway
cloud:
gateway:
###路由策略
discovery:
locator:
enabled: true
nacos:
discovery:
#注册地址
server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
#是否开启nacos注册
enabled: true
#账号
username: xiaojie
#密码
password: nacos
#命名空间
namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
#分组
group: DEV_GROUP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat
initialSize: 10
maxActive: 50
maxOpenPreparedStatements: 20
maxWait: 60000
minEvictableIdleTimeMillis: 300000
minIdle: 10
password: root
poolPreparedStatements: true
testOnBorrow: false
testOnReturn: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
validationQuery: select 1
七、网关解决跨域
package com.xiaojie.filter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @Description: 解决微服务的跨域问题
* @Author: yan
* @Date: 2021/5/12 23:18
* @return: null
**/
@Component
public class CrossOriginFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
return chain.filter(exchange);
}
}
八、网关实现限流
使用的算法是令牌桶 Redis+Lua
令牌流与令牌桶
系统会以一定的速度生成令牌,并将其放置到令牌桶中,可以将令牌桶想象成一个缓冲区(可以用队列这种数据结构来实现),当缓冲区填满的时候,新生成的令牌会被扔掉。 这里有两个变量很重要:
第一个是生成令牌的速度,一般称为 rate 。比如,我们设定 rate = 2 ,即每秒钟生成 2 个令牌,也就是每 1/2 秒生成一个令牌;
第二个是令牌桶的大小,一般称为 burst 。比如,我们设定 burst = 10 ,即令牌桶最大只能容纳 10 个令牌。
数据流
数据流是真正的进入系统的流量,对于接口来讲,如果平均每秒钟会调用2次,则认为速率为 2次/s 。
算法原理
系统接收到一个单位数据(对于网络传输,可以是一个包或者一个字节;对于微服务,可以是一个请求)后,从令牌桶中取出一个令牌,然后对数据或请求进行处理。如果令牌桶中没有令牌了,会直接将数据或者请求丢弃。当然,对于微服务,就不能是丢弃这么简单了:可以返回一个异常消息,用于提示用户其请求速率超过了系统限制。
有以下三种情形可能发生:
数据流的速率 等于 令牌流的速率。这种情况下,每个到来的数据包或者请求都能对应一个令牌,然后无延迟地通过队列;
数据流的速率 小于 令牌流的速率。通过队列的数据包或者请求只消耗了一部分令牌,剩下的令牌会在令牌桶里积累下来,直到桶被装满。剩下的令牌可以在突发请求的时候消耗掉。
数据流的速率 大于 令牌流的速率。这意味着桶里的令牌很快就会被耗尽。导致服务中断一段时间,如果数据包或者请求持续到来,将发生丢包或者拒绝响应。
比如前面举的例子,生成令牌的速率和令牌桶的大小分别为 rate = 2, burst = 10 ,则系统能承受的突发请求速率为 10次/s ,平均请求速率为 2次/s 。
摘自:https://blog.csdn.net/xgw1010/article/details/107595141
代码如下
package com.xiaojie.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;
/**
* @Description:
* @author: xiaojie
* @date: 2021.07.16
*/
@Configuration
public class MyKeyResolver {
/*
* ip限流 每一个ip只能在限流的规则内访问的规则,超出规则,进行限流
* @todo
* @author xiaojie
* @date 2021/7/16 9:44
* @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
*/
// @Bean("ipKeyResolver")
// @Primary
// public KeyResolver ipKeyResolver() {
// return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
// }
/*
* 用户限流 每一个用户的限流规则 访问时需要携带userId 参数
* @todo
* @author xiaojie
* @date 2021/7/16 9:44
* @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
*/
@Bean("userKeyResolver")
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
/*
* 接口限流 根据接口路径限流
* @todo
* @author xiaojie
* @date 2021/7/16 9:45
* @return org.springframework.cloud.gateway.filter.ratelimit.KeyResolver
*/
// @Bean("apiKeyResolver")
// KeyResolver apiKeyResolver() {
// return exchange -> Mono.just(exchange.getRequest().getPath().value());
// }
}
路由配置如下
server:
port: 82
####服务网关名称
spring:
application:
name: xiaojie-gateway
cloud:
gateway:
###路由策略
# routes:
# ###根据我们的服务名称查找地址实现调用
# - id: member
# #uri: http://127.0.0.1:8090
# uri: lb://xiaojie-member/
# filters:
# - StripPrefix=1
# ###匹配规则
# predicates:
# - Path=/member/*
routes:
- id: requestratelimiter_route
uri: lb://xiaojie-member/
filters:
- name: RequestRateLimiter
args:
#允许用户每秒执行多少请求,而没有任何丢弃的请求。这是令牌桶填充的速率
redis-rate-limiter.replenishRate: 1
#允许用户在一秒内执行的最大请求数。这是令牌桶可以容纳的令牌数量。将此值设置为零会阻止所有请求。
redis-rate-limiter.burstCapacity: 1
#每个请求从存储桶中获取的令牌数量,默认为 1
redis-rate-limiter.requestedTokens: 1
key-resolver:
- "#{@userKeyResolver}" #根据用户限流
- StripPrefix=1
predicates:
- Path=/member/*
#从注册中心获取服务
discovery:
locator:
enabled: true
nacos:
discovery:
#注册地址
server-addr: 127.0.0.1:8848,127.0.0.1:8849,127.0.0.1:8850
#是否开启nacos注册
enabled: true
#账号
username: xiaojie
#密码
password: nacos
#命名空间
namespace: 28eb29ea-5a04-4714-8ae8-77d37c01166a
#分组
group: DEV_GROUP
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
filters: stat
initialSize: 10
maxActive: 50
maxOpenPreparedStatements: 20
maxWait: 60000
minEvictableIdleTimeMillis: 300000
minIdle: 10
password: root
poolPreparedStatements: true
testOnBorrow: false
testOnReturn: false
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/my_test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true
username: root
validationQuery: select 1
#基于redis实现限流
redis:
host: 192.168.6.130
password: xiaojie
port: 6379
限流后返回429状态码
动态限流 基于Sentinel
九、设置黑白名单
package com.xiaojie.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
/**
* @Description: 黑名单
* @author: xiaojie
* @date: 2021.07.16
*/
@Component
@Slf4j
public class IpFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String ipAddress = getIpAddress(request);
log.info("获取的IP地址为{}",ipAddress);
//实际生产可以基于redis,数据库实现记录
if ("127.0.0.1".equals(ipAddress)){
ServerHttpResponse response = exchange.getResponse();
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
String msg="已被加入黑名单";
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
/*
* 获取用户真实ip
* @todo
* @author xiaojie
* @date 2021/7/16 14:52
* @return java.lang.String
*/
public static String getIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddress().getAddress().getHostAddress();
}
return ip;
}
}
参考:https://blog.csdn.net/lizz861109/article/details/108530364;
https://blog.csdn.net/qq_32652767/article/details/112257082;
https://blog.csdn.net/xgw1010/article/details/107595141
感谢几位大佬