网关使用Redis存储路由信息

This commit is contained in:
Wenchao Gong 2021-09-13 14:33:57 +08:00
parent 7da40951e7
commit 71a58178eb
9 changed files with 391 additions and 112 deletions

View File

@ -20,26 +20,23 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!-- pool 对象池 -->
<!--<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>-->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@ -1,8 +1,7 @@
package com.ruoyi.gateway.config;
import com.ruoyi.gateway.config.provider.ComplexRouteDefinitionRepository;
import com.ruoyi.gateway.config.provider.RedisRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.annotation.Bean;
@ -21,10 +20,13 @@ import javax.annotation.Resource;
* @date 2021-09-11
*/
@Configuration
public class GatewayConfig {
public class GatewayConfig
{
@Resource
private RedisTemplate<String, RouteDefinition> redisTemplate;
@Resource
private RedissonClient redissonClient;
@Bean
public RedisTemplate<String, RouteDefinition> redisTemplate(RedisConnectionFactory redisConnectionFactory)
@ -40,29 +42,14 @@ public class GatewayConfig {
return template;
}
@Bean
public RouteDefinitionRepository complexRouteDefinitionRepository()
{
return new ComplexRouteDefinitionRepository(inMemoryRouteDefinitionRepository(), redisRouteDefinitionRepository());
}
/**
* 内存 路由仓库
*
* @return 本地内存路由仓库
*/
RouteDefinitionRepository inMemoryRouteDefinitionRepository()
{
return new InMemoryRouteDefinitionRepository();
}
/**
* Redis 路由仓库
*
* @return redis路由仓库
*/
RouteDefinitionRepository redisRouteDefinitionRepository()
@Bean
public RouteDefinitionRepository redisRouteDefinitionRepository()
{
return new RedisRouteDefinitionRepository(redisTemplate);
return new RedisRouteDefinitionRepository(redissonClient);
}
}

View File

@ -0,0 +1,104 @@
package com.ruoyi.gateway.config;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.gateway.config.provider.RedissonProperties;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.io.IOException;
/**
* redis配置
*
* @author Lion Li
*/
@Slf4j
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
private static final String REDIS_PROTOCOL_PREFIX = "redis://";
private static final String REDISS_PROTOCOL_PREFIX = "rediss://";
@Autowired
private RedisProperties redisProperties;
@Autowired
private RedissonProperties redissonProperties;
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(RedissonClient.class)
public RedissonClient redisson() throws IOException {
String prefix = REDIS_PROTOCOL_PREFIX;
if (redisProperties.isSsl()) {
prefix = REDISS_PROTOCOL_PREFIX;
}
Config config = new Config();
config.setThreads(redissonProperties.getThreads())
.setNettyThreads(redissonProperties.getNettyThreads())
.setCodec(JsonJacksonCodec.INSTANCE)
.setTransportMode(redissonProperties.getTransportMode());
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
// 使用单机模式
config.useSingleServer()
.setAddress(prefix + redisProperties.getHost() + ":" + redisProperties.getPort())
.setConnectTimeout(((Long) redisProperties.getTimeout().toMillis()).intValue())
.setDatabase(redisProperties.getDatabase())
.setPassword(StrUtil.isNotBlank(redisProperties.getPassword()) ? redisProperties.getPassword() : null)
.setTimeout(singleServerConfig.getTimeout())
.setRetryAttempts(singleServerConfig.getRetryAttempts())
.setRetryInterval(singleServerConfig.getRetryInterval())
.setSubscriptionsPerConnection(singleServerConfig.getSubscriptionsPerConnection())
.setClientName(singleServerConfig.getClientName())
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
.setSubscriptionConnectionMinimumIdleSize(singleServerConfig.getSubscriptionConnectionMinimumIdleSize())
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize())
.setDnsMonitoringInterval(singleServerConfig.getDnsMonitoringInterval());
RedissonClient redissonClient = Redisson.create(config);
log.info("初始化 redis 配置");
return redissonClient;
}
@Bean
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(limitScriptText());
redisScript.setResultType(Long.class);
return redisScript;
}
/**
* 限流脚本
*/
private String limitScriptText() {
return StrUtil.builder()
.append("local key = KEYS[1]\n")
.append("local count = tonumber(ARGV[1])\n")
.append("local time = tonumber(ARGV[2])\n")
.append("local current = redis.call('get', key);\n")
.append("if current and tonumber(current) > count then\n")
.append(" return current;\n")
.append("end\n")
.append("current = redis.call('incr', key)\n")
.append("if tonumber(current) == 1 then\n")
.append(" redis.call('expire', key, time)\n")
.append("end\n")
.append("return current;")
.toString();
}
}

View File

@ -1,65 +0,0 @@
package com.ruoyi.gateway.config.provider;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Objects;
/**
* 复合的路由存储器
*
* @author Wenchao Gong
* @date 2021-09-11
*/
public class ComplexRouteDefinitionRepository implements RouteDefinitionRepository {
private RouteDefinitionRepository localCache;
private RouteDefinitionRepository remoteReps;
public ComplexRouteDefinitionRepository(RouteDefinitionRepository localCache, RouteDefinitionRepository remoteReps)
{
this.localCache = localCache;
this.remoteReps = remoteReps;
}
@Override
public Flux<RouteDefinition> getRouteDefinitions()
{
final Flux<RouteDefinition> routeDefinitions = localCache.getRouteDefinitions();
return routeDefinitions.collectList().flatMapIterable(list -> {
if(list.isEmpty()) {
final Flux<RouteDefinition> definitions = remoteReps.getRouteDefinitions();
definitions.collectList().flatMapIterable(routes -> {
if(!routes.isEmpty()) {
routes.stream().map(m -> {
routes.add(m);
return Mono.just(m);
}).forEach(m -> {
localCache.save(m);
});
}
return routes;
}).filter(l -> Objects.nonNull(l)).map(m -> list.add(m)).collectList();
}
return list;
});
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route)
{
remoteReps.save(route);
return localCache.save(route);
}
@Override
public Mono<Void> delete(Mono<String> routeId)
{
remoteReps.delete(routeId);
return localCache.delete(routeId);
}
}

View File

@ -1,40 +1,41 @@
package com.ruoyi.gateway.config.provider;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.data.redis.core.RedisTemplate;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
/**
* Redis 路由仓库
*
* @author Wenchao Gong
* @date 2021-09-11
*/
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository
{
public static final String KEY_ROUTES = "ROUTES::";
private final RedisTemplate<String, RouteDefinition> redisTemplate;
private final RedissonClient redissonClient;
public RedisRouteDefinitionRepository(RedisTemplate<String, RouteDefinition> redisTemplate)
public RedisRouteDefinitionRepository(RedissonClient redissonClient)
{
this.redisTemplate = redisTemplate;
this.redissonClient = redissonClient;
}
@Override
public Flux<RouteDefinition> getRouteDefinitions()
{
List<RouteDefinition> values = redisTemplate.<String,RouteDefinition>opsForHash().values(KEY_ROUTES);
return Flux.fromIterable(values);
RMap<String, RouteDefinition> map = redissonClient.getMap(KEY_ROUTES);
return Flux.fromIterable(map.values());
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route)
{
return route.flatMap(definition -> {
redisTemplate.opsForHash().put(KEY_ROUTES, definition.getId(), definition);
RMap<String, RouteDefinition> map = redissonClient.getMap(KEY_ROUTES);
map.put(definition.getId(), definition);
return Mono.empty();
});
}
@ -43,7 +44,8 @@ public class RedisRouteDefinitionRepository implements RouteDefinitionRepository
public Mono<Void> delete(Mono<String> routeId)
{
return routeId.flatMap(id -> {
redisTemplate.opsForHash().delete(KEY_ROUTES, id);
RMap<String, RouteDefinition> map = redissonClient.getMap(KEY_ROUTES);
map.remove(id);
return Mono.empty();
});
}

View File

@ -0,0 +1,134 @@
package com.ruoyi.gateway.config.provider;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.redisson.config.TransportMode;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* Redisson 配置属性
*
* @author Lion Li
*/
@Data
@Component
@ConfigurationProperties(prefix = "redisson")
public class RedissonProperties
{
/**
* 线程池数量,默认值 = 当前处理核数量 * 2
*/
private int threads;
/**
* Netty线程池数量,默认值 = 当前处理核数量 * 2
*/
private int nettyThreads;
/**
* 传输模式
*/
private TransportMode transportMode;
/**
* 单机服务配置
*/
private SingleServerConfig singleServerConfig;
/**
* 缓存组
*/
private List<CacheGroup> cacheGroup;
@Data
@NoArgsConstructor
public static class SingleServerConfig {
/**
* 客户端名称
*/
private String clientName;
/**
* 最小空闲连接数
*/
private int connectionMinimumIdleSize;
/**
* 连接池大小
*/
private int connectionPoolSize;
/**
* 连接空闲超时单位毫秒
*/
private int idleConnectionTimeout;
/**
* 命令等待超时单位毫秒
*/
private int timeout;
/**
* 如果尝试在此限制之内发送成功则开始启用 timeout 计时
*/
private int retryAttempts;
/**
* 命令重试发送时间间隔单位毫秒
*/
private int retryInterval;
/**
* 发布和订阅连接的最小空闲连接数
*/
private int subscriptionConnectionMinimumIdleSize;
/**
* 发布和订阅连接池大小
*/
private int subscriptionConnectionPoolSize;
/**
* 单个连接最大订阅数量
*/
private int subscriptionsPerConnection;
/**
* DNS监测时间间隔单位毫秒
*/
private int dnsMonitoringInterval;
}
@Data
@NoArgsConstructor
public static class CacheGroup {
/**
* 组id
*/
private String groupId;
/**
* 组过期时间
*/
private long ttl;
/**
* 组最大空闲时间
*/
private long maxIdleTime;
/**
* 组最大长度
*/
private int maxSize;
}
}

View File

@ -29,6 +29,38 @@ spring:
# 是否开启ssl
ssl: false
redisson:
# 线程池数量
threads: 16
# Netty线程池数量
nettyThreads: 32
# 传输模式
transportMode: "NIO"
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000
--- # Actuator 监控端点的配置项
management:
endpoints:
@ -41,4 +73,4 @@ management:
include: @endpoints.include@
endpoint:
logfile:
external-file: ./logs/sys-console.log
external-file: ./logs/sys-console.log

View File

@ -0,0 +1,76 @@
--- # 监控配置
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
# 增加客户端开关
enabled: true
# 设置 Spring Boot Admin Server 地址
url: http://localhost:9090/admin
instance:
prefer-ip: true # 注册实例时,优先使用 IP
username: ruoyi
password: 123456
--- # redis 配置
spring:
redis:
# 地址
host: 127.0.0.1
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl: false
redisson:
# 线程池数量
threads: 16
# Netty线程池数量
nettyThreads: 32
# 传输模式
transportMode: "NIO"
# 单节点配置
singleServerConfig:
# 客户端名称
clientName: ${ruoyi.name}
# 最小空闲连接数
connectionMinimumIdleSize: 32
# 连接池大小
connectionPoolSize: 64
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
retryAttempts: 3
# 命令重试发送时间间隔,单位:毫秒
retryInterval: 1500
# 发布和订阅连接的最小空闲连接数
subscriptionConnectionMinimumIdleSize: 1
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
# 单个连接最大订阅数量
subscriptionsPerConnection: 5
# DNS监测时间间隔单位毫秒
dnsMonitoringInterval: 5000
--- # Actuator 监控端点的配置项
management:
endpoints:
web:
# Actuator 提供的 API 接口的根目录。默认为 /actuator
base-path: /actuator
exposure:
# 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# 生产环境不建议放开所有 根据项目需求放开即可
include: @endpoints.include@
endpoint:
logfile:
external-file: ./logs/sys-console.log

View File

@ -1,3 +1,15 @@
# 项目相关配置
ruoyi:
# 名称
name: RuoYi-Vue-Plus
# 版本
version: ${ruoyi-vue-plus.version}
# 版权年份
copyrightYear: 2021
# 实例演示开关
demoEnabled: true
# 获取ip地址开关
addressEnabled: true
server:
port: 8090
# Spring配置