找回密码
 立即注册
首页 业界区 业界 微服务项目中基于 Servlet 的业务模块与 WebFlux 网关模 ...

微服务项目中基于 Servlet 的业务模块与 WebFlux 网关模块的 Redis 统一化配置教程

水苯 2025-8-13 12:05:00
微服务项目中Servlet模块与WebFlux网关的Redis使用指南

在微服务架构的蓬勃发展浪潮中,Redis凭借其超高的性能、丰富的功能,已然成为缓存、分布式锁、会话存储等场景下的核心支撑技术。然而,在微服务项目里,基于Servlet的普通业务模块和基于WebFlux的网关模块,由于它们底层的技术架构存在显著差异,在使用Redis时也呈现出不同的特点和实现方式。下面,我们就深入探讨这两种场景下Redis的使用之道。
一、技术架构差异简析

在微服务的技术生态中,基于Servlet的普通业务模块和基于WebFlux的网关模块,在处理请求的方式上有着本质区别。
基于Servlet的普通模块,遵循的是同步阻塞的I/O模型。这就意味着当一个请求进入模块后,线程会一直等待I/O操作完成,在这个过程中,线程无法去处理其他请求,容易造成线程资源的浪费,尤其是在高并发场景下,可能会出现线程池耗尽的情况。
而基于WebFlux的网关模块,则采用了异步非阻塞的I/O模型。它能够在一个线程上处理多个请求,当遇到I/O操作时,线程不会阻塞等待,而是会去处理其他请求,待I/O操作完成后再回来继续处理,极大地提高了线程的利用率,非常适合高并发、I/O密集型的网关场景。
这种底层技术架构的差异,直接影响了Redis在这两种模块中的使用方式。
二、基于Servlet的普通模块使用Redis

在基于Servlet的普通模块中,我们通常会选择Spring Data Redis作为操作Redis的框架,它对Redis的各种操作进行了友好封装,让开发者能够更便捷地使用Redis。
(一)引入依赖

在 Maven 项目中,需要在pom.xml文件中引入相关依赖:
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>org.apache.commons</groupId>
  7.     commons-pool2</artifactId>
  8. </dependency>
复制代码
其中,spring-boot-starter-data-redis是Spring Data Redis的 starter 依赖,commons-pool2为Redis连接池提供支持,有助于提高Redis连接的管理效率。
(二)配置Redis连接

在application.properties或application.yml中进行Redis连接信息的配置:
  1. spring:
  2.   redis:
  3.     host: localhost
  4.     port: 6379
  5.     password: 123456
  6.     lettuce:
  7.       pool:
  8.         max-active: 8
  9.         max-idle: 8
  10.         min-idle: 2
  11.         max-wait: -1ms
复制代码
这里配置了Redis的主机地址、端口、密码以及连接池参数。采用Lettuce作为Redis客户端,它是一个高性能的异步Redis客户端,在Spring Boot 2.x及以上版本中成为了默认的客户端。
(三)Redis操作模板

Spring Data Redis提供了RedisTemplate和StringRedisTemplate两种模板类用于操作Redis。StringRedisTemplate是RedisTemplate的子类,专门用于处理键和值都是字符串的情况,使用起来更加便捷。
  1. @Component
  2. @RequiredArgsConstructor
  3. public class RedisUtils {
  4.     private final RedisTemplate<String, Object> redisTemplate;
  5.     public void set(String key, Object value) {
  6.         redisTemplate.opsForValue().set(key, value);
  7.     }
  8.     public void set(String key, Object value, long timeout, TimeUnit unit) {
  9.         redisTemplate.opsForValue().set(key, value, timeout, unit);
  10.     }
  11.     public void set(String key, Object value, long seconds) {
  12.         redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
  13.     }
  14.     public Object get(String key) {
  15.         return redisTemplate.opsForValue().get(key);
  16.     }
  17.     public String getString(String key) {
  18.         Object obj = redisTemplate.opsForValue().get(key);
  19.         return obj == null ? null : obj.toString();
  20.     }
  21.     public Boolean delete(String key) {
  22.         return redisTemplate.delete(key);
  23.     }
  24.     public Boolean hasKey(String key) {
  25.         return redisTemplate.hasKey(key);
  26.     }
  27.     public Boolean setNx(String key, Object value) {
  28.         return redisTemplate.opsForValue().setIfAbsent(key, value);
  29.     }
  30.     public Boolean tryLock(String lockKey, String requestId, long seconds) {
  31.         return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, seconds, TimeUnit.SECONDS);
  32.     }
  33.     public Boolean tryLock(String lockKey, String requestId, long timeout, TimeUnit unit) {
  34.         return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, timeout, unit);
  35.     }
  36.     public Boolean releaseLock(String lockKey, String requestId) {
  37.         DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
  38.         redisScript.setScriptText(RELEASE_SCRIPT);
  39.         redisScript.setResultType(Long.class);
  40.         Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
  41.         return RELEASE_SUCCESS.equals(result);
  42.     }
  43. }
复制代码
在上述代码中,通过Lombok中热@RequireArgsConstructor注入StringRedisTemplate,然后利用其opsForValue()方法获取操作字符串类型数据的ValueOperations对象,进而实现对Redis中字符串数据的增、删、查等操作。
(四)缓存注解的使用

Spring还提供了缓存注解,如@Cacheable、@CachePut、@CacheEvict等,可以更方便地实现缓存功能。
首先,需要在配置类上添加@EnableCaching注解开启缓存功能:
  1. @Configuration
  2. @EnableCaching
  3. public class RedisCacheConfig {
  4.     @Bean
  5.     public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
  6.         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  7.                 .entryTtl(Duration.ofMinutes(10))
  8.                 .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
  9.                 .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
  10.                 .disableCachingNullValues();
  11.         return RedisCacheManager.builder(connectionFactory)
  12.                 .cacheDefaults(config)
  13.                 .build();
  14.     }
  15. }
复制代码
在这个配置中,定义了Redis缓存的默认配置,包括缓存过期时间、键和值的序列化方式等。使用GenericJackson2JsonRedisSerializer对值进行序列化,能够将对象转换为JSON格式存储,方便读取和解析。
然后在Service层的方法上使用缓存注解:
  1. @Service
  2. public class UserService {
  3.     @Autowired
  4.     private UserMapper userMapper;
  5.     @Cacheable(value = "user", key = "#id")
  6.     public User getUserById(Long id) {
  7.         return userMapper.selectById(id);
  8.     }
  9.     @CachePut(value = "user", key = "#user.id")
  10.     public User updateUser(User user) {
  11.         userMapper.updateById(user);
  12.         return user;
  13.     }
  14.     @CacheEvict(value = "user", key = "#id")
  15.     public void deleteUser(Long id) {
  16.         userMapper.deleteById(id);
  17.     }
  18. }
复制代码
@Cacheable表示在调用方法之前,会先从缓存中查询,如果缓存中存在,则直接返回缓存中的数据,不执行方法体;如果缓存中不存在,则执行方法体,并将方法的返回值存入缓存。@CachePut会将方法的返回值存入缓存,无论缓存中是否已存在该数据。@CacheEvict用于删除缓存中的数据。
三、基于WebFlux的Gateway中使用Redis

在基于WebFlux的Gateway中,由于WebFlux是异步非阻塞的,所以需要使用响应式的Redis客户端来操作Redis,以契合其异步非阻塞的特性。Spring提供了spring-boot-starter-data-redis-reactive来支持响应式Redis操作。
(一)引入依赖

在pom.xml中引入响应式Redis的依赖:
  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     spring-boot-starter-data-redis-reactive</artifactId>
  4. </dependency>
  5. <dependency>
  6.     <groupId>org.apache.commons</groupId>
  7.     commons-pool2</artifactId>
  8. </dependency>
复制代码
spring-boot-starter-data-redis-reactive提供了响应式的Redis操作支持,同样需要commons-pool2来支持连接池。
(二)配置Redis连接

与基于Servlet的模块类似,在application.yml中配置Redis连接信息:
  1. spring:
  2.   redis:
  3.     host: localhost
  4.     port: 6379
  5.     password: 123456
  6.     lettuce:
  7.       pool:
  8.         max-active: 8
  9.         max-idle: 8
  10.         min-idle: 2
  11.         max-wait: -1ms
复制代码
这里的配置与Servlet模块中的配置基本一致,因为连接Redis的基本信息是相同的。
(三)响应式Redis操作

响应式Redis操作主要通过ReactiveRedisTemplate和ReactiveStringRedisTemplate来实现,它们返回的是Mono或Flux类型的结果,契合WebFlux的响应式编程模型。
  1. @Service
  2. public class ReactiveRedisService {
  3.     @Autowired
  4.     private ReactiveStringRedisTemplate reactiveStringRedisTemplate;
  5.     // 设置字符串类型数据
  6.     public Mono<Boolean> setString(String key, String value) {
  7.         return reactiveStringRedisTemplate.opsForValue().set(key, value);
  8.     }
  9.     // 获取字符串类型数据
  10.     public Mono<String> getString(String key) {
  11.         return reactiveStringRedisTemplate.opsForValue().get(key);
  12.     }
  13.     // 设置带过期时间的字符串数据
  14.     public Mono<Boolean> setStringWithExpire(String key, String value, long timeout, TimeUnit unit) {
  15.         return reactiveStringRedisTemplate.opsForValue().set(key, value, timeout, unit);
  16.     }
  17.     // 删除数据
  18.     public Mono<Long> delete(String key) {
  19.         return reactiveStringRedisTemplate.delete(key);
  20.     }
  21. }
复制代码
在响应式操作中,每个方法返回的都是Mono类型,Mono表示一个包含0或1个元素的异步序列。当调用这些方法时,并不会立即执行Redis操作,而是返回一个操作的承诺,只有当订阅这个Mono时,操作才会真正执行。
(四)在Gateway过滤器中使用Redis

在Gateway中,经常需要在过滤器中使用Redis来实现一些功能,如限流、令牌验证等。下面以一个简单的令牌验证过滤器为例:
  1. @Component
  2. public class TokenValidateFilter implements GlobalFilter, Ordered {
  3.     @Autowired
  4.     private ReactiveRedisService reactiveRedisService;
  5.     @Override
  6.     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  7.         String token = exchange.getRequest().getHeaders().getFirst("token");
  8.         if (token == null || token.isEmpty()) {
  9.             exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  10.             return exchange.getResponse().setComplete();
  11.         }
  12.         return reactiveRedisService.getString("token:" + token)
  13.                 .flatMap(userId -> {
  14.                     if (userId != null) {
  15.                         // 令牌有效,继续执行后续过滤器
  16.                         return chain.filter(exchange);
  17.                     } else {
  18.                         exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  19.                         return exchange.getResponse().setComplete();
  20.                     }
  21.                 })
  22.                 .switchIfEmpty(Mono.defer(() -> {
  23.                     exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
  24.                     return exchange.getResponse().setComplete();
  25.                 }));
  26.     }
  27.     @Override
  28.     public int getOrder() {
  29.         return -100;
  30.     }
  31. }
复制代码
在这个过滤器中,首先从请求头中获取令牌token,然后通过ReactiveRedisService从Redis中查询该令牌对应的用户ID。如果查询到结果,说明令牌有效,继续执行后续的过滤器;如果未查询到结果或令牌不存在,则返回未授权的响应。
这里充分利用了响应式编程的特性,通过flatMap、switchIfEmpty等操作符来处理异步流,保证了整个操作的异步非阻塞性。
四、两种场景下Redis使用的对比

(一)编程模型

基于Servlet的普通模块采用的是同步阻塞的编程模型,使用RedisTemplate进行Redis操作时,方法的调用会阻塞当前线程,直到操作完成。
基于WebFlux的Gateway采用的是异步非阻塞的编程模型,使用ReactiveRedisTemplate进行Redis操作时,方法返回Mono或Flux对象,不会阻塞线程,开发者通过订阅这些对象来处理操作结果。
(二)性能表现

在高并发场景下,基于WebFlux的Gateway使用响应式Redis客户端能够更高效地利用线程资源,减少线程切换的开销,从而表现出更好的性能。
而基于Servlet的普通模块由于采用同步阻塞的方式,在面对大量并发请求时,可能会因为线程阻塞而导致性能瓶颈。
(三)适用场景

基于Servlet的普通模块的Redis使用方式适用于业务逻辑相对复杂、对响应时间要求不是特别高的场景。
基于WebFlux的Gateway的Redis使用方式适用于高并发、I/O密集型的场景,如网关的限流、令牌验证等,能够更好地应对大量的并发请求。
五、总结

在微服务项目中,基于Servlet的普通模块和基于WebFlux的Gateway在使用Redis时,由于底层技术架构的不同,选择的Redis操作方式也有所差异。
普通模块通过Spring Data Redis的RedisTemplate进行同步操作,简单直观,适合处理复杂的业务逻辑;Gateway则通过spring-boot-starter-data-redis-reactive的ReactiveRedisTemplate进行异步非阻塞操作,能够更好地应对高并发场景。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册