微服务项目中基于 Servlet 的业务模块与 WebFlux 网关模块的 Redis 统一化配置教程
在微服务架构中,我们经常会遇到这样的场景:业务模块基于传统的 Servlet(如 Spring MVC)构建,而网关模块使用了响应式的 WebFlux。二者都需要访问 Redis,但配置方式却大相径庭。这往往导致配置重复、维护困难,甚至出现运行时异常。本文将带你一步步实现 Redis 的统一化配置,让你的微服务项目更加整洁高效。## 为什么需要统一化配置?传统的 Servlet 项目使用 spring-boot-starter-data-redis 依赖,依赖的是 LettuceConnectionFactory 和同步 API。而 WebFlux 项目则使用 spring-boot-starter-data-redis-reactive,依赖的是 ReactiveRedisConnectionFactory 和响应式 API。如果直接在两个模块中分别配置,不仅代码冗余,而且当 Redis 连接参数(如地址、密码、超时时间)发生变化时,需要修改多个地方,极易出错。统一化配置的核心思想是:定义一个公共的 Redis 配置模块,由业务模块和网关模块共同引用,根据运行环境自动选择同步或响应式连接工厂 。## 第一步:创建公共 Redis 配置模块首先,我们需要创建一个独立的 Maven 模块(例如 redis-common),该模块不依赖于任何 Web 容器,只负责提供 Redis 配置的抽象。### 1.1 添加依赖在 redis-common 模块的 pom.xml 中添加所需的依赖:xml<dependencies> <!-- Spring Data Redis 核心依赖(包含 Lettuce) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 响应式 Redis 支持(可选,用于 WebFlux) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> <optional>true</optional> </dependency></dependencies>这里将响应式依赖设为 optional,这样业务模块引用时不会强制加载。### 1.2 定义配置属性类创建一个 RedisProperties 类,用于统一管理所有 Redis 配置参数:javaimport org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@Component@ConfigurationProperties(prefix = "app.redis")public class RedisProperties { private String host = "localhost"; private int port = 6379; private String password; private int timeout = 5000; // 连接超时(毫秒) private int database = 0; // getters and setters 省略(实际开发中请自动生成) public String getHost() { return host; } public void setHost(String host) { this.host = host; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } public int getDatabase() { return database; } public void setDatabase(int database) { this.database = database; }}### 1.3 创建配置选择器这是核心部分:根据类路径中是否存在响应式连接工厂类,动态选择配置类型。javaimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import java.time.Duration;@Configurationpublic class RedisAutoConfiguration { private final RedisProperties properties; public RedisAutoConfiguration(RedisProperties properties) { this.properties = properties; } /** * 当类路径中存在 ReactiveRedisConnectionFactory 时,创建响应式连接工厂 * 这个 Bean 会被 WebFlux 模块使用 */ @Bean @ConditionalOnClass(name = "org.springframework.data.redis.connection.ReactiveRedisConnectionFactory") public LettuceConnectionFactory reactiveConnectionFactory() { return createLettuceConnectionFactory(); } /** * 默认情况下创建同步连接工厂 * 这个 Bean 会被 Servlet 模块使用 */ @Bean @ConditionalOnMissingBean(ReactiveRedisConnectionFactory.class) public LettuceConnectionFactory connectionFactory() { return createLettuceConnectionFactory(); } private LettuceConnectionFactory createLettuceConnectionFactory() { // 配置 Lettuce 客户端 LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() .commandTimeout(Duration.ofMillis(properties.getTimeout())) .build(); // 配置 Redis 地址 RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration(); redisConfig.setHostName(properties.getHost()); redisConfig.setPort(properties.getPort()); redisConfig.setPassword(RedisPassword.of(properties.getPassword())); redisConfig.setDatabase(properties.getDatabase()); return new LettuceConnectionFactory(redisConfig, clientConfig); } /** * 配置 RedisTemplate,用于 Servlet 模块 */ @Bean @ConditionalOnMissingBean(ReactiveRedisConnectionFactory.class) public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 设置序列化策略 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); template.afterPropertiesSet(); return template; }}关键点解释 :- @ConditionalOnClass:当类路径中存在指定类时才创建 Bean,这让我们可以按需启用响应式配置。- @ConditionalOnMissingBean:当没有响应式工厂时才创建同步工厂,避免了 Bean 冲突。- 两个模块共享同一个 createLettuceConnectionFactory() 方法,确保连接配置一致。## 第二步:在业务模块(Servlet)中使用业务模块引用 redis-common 模块后,只需要在 application.yml 中配置 Redis 参数即可:yaml# 业务模块 application.ymlapp: redis: host: 192.168.1.100 port: 6379 password: mypassword timeout: 3000 database: 1然后在业务代码中直接注入 RedisTemplate:javaimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class UserController { @Autowired private RedisTemplate<String, Object> redisTemplate; @GetMapping("/user/count") public String getUserCount() { // 同步操作 Redis redisTemplate.opsForValue().set("user:count", 100); Integer count = (Integer) redisTemplate.opsForValue().get("user:count"); return "当前用户数: " + count; }}## 第三步:在网关模块(WebFlux)中使用网关模块同样引用 redis-common,但需要额外添加响应式依赖:xml<dependency> <groupId>com.example</groupId> <artifactId>redis-common</artifactId> <version>1.0.0</version></dependency><!-- 响应式 Redis 依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>由于 redis-common 中的 @ConditionalOnClass 检测到了响应式工厂,会自动创建 ReactiveRedisConnectionFactory 的 Bean。此时我们需要配置响应式的 ReactiveRedisTemplate:javaimport org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;import org.springframework.data.redis.core.ReactiveRedisTemplate;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.StringRedisSerializer;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;@Configurationpublic class ReactiveRedisConfig { @Bean public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate( ReactiveRedisConnectionFactory factory) { // 创建序列化上下文 RedisSerializationContext<String, Object> context = RedisSerializationContext.<String, Object>newSerializationContext() .key(StringRedisSerializer.UTF_8) .value(new Jackson2JsonRedisSerializer<>(Object.class)) .hashKey(StringRedisSerializer.UTF_8) .hashValue(new Jackson2JsonRedisSerializer<>(Object.class)) .build(); return new ReactiveRedisTemplate<>(factory, context); }}然后在网关的过滤器中使用响应式 API:javaimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.gateway.filter.GatewayFilter;import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;import org.springframework.data.redis.core.ReactiveRedisTemplate;import org.springframework.stereotype.Component;import reactor.core.publisher.Mono;@Componentpublic class RateLimitFilterFactory extends AbstractGatewayFilterFactory<Object> { @Autowired private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate; @Override public GatewayFilter apply(Object config) { return (exchange, chain) -> { // 响应式操作 Redis return reactiveRedisTemplate.opsForValue() .increment("gateway:request:count") .flatMap(count -> { System.out.println("当前请求计数: " + count); return chain.filter(exchange); }); }; }}## 统一配置的进阶技巧### 1. 使用连接池对于高并发场景,可以配置 Lettuce 连接池:javaprivate LettuceConnectionFactory createLettuceConnectionFactory() { // 配置连接池 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxTotal(16); poolConfig.setMaxIdle(8); poolConfig.setMinIdle(4); LettucePoolingClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .poolConfig(poolConfig) .commandTimeout(Duration.ofMillis(properties.getTimeout())) .build(); // ... 其余配置不变}### 2. 多 Redis 实例支持如果项目需要连接多个 Redis 实例,可以在 RedisProperties 中添加列表属性,然后使用 @Qualifier 指定不同的连接工厂。## 总结通过本文的教程,你学会了如何在一个微服务项目中实现 Servlet 业务模块和 WebFlux 网关模块的 Redis 统一化配置。核心思路是:1. 抽象公共配置 :创建一个独立的 Redis 配置模块,定义统一的配置属性和连接工厂创建逻辑。2. 条件化加载 :利用 Spring Boot 的条件注解(@ConditionalOnClass、@ConditionalOnMissingBean)根据运行环境自动选择同步或响应式连接工厂。3. 模块化依赖:业务模块只需引入基础依赖,网关模块额外引入响应式依赖,互不干扰。这种方案不仅减少了代码重复,还提高了配置的可维护性。当 Redis 参数需要调整时,只需修改一处即可全局生效。希望本文能帮助你构建更加健壮的微服务系统!