微服务项目中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文件中引入相关依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

其中,spring-boot-starter-data-redis是Spring Data Redis的 starter 依赖,commons-pool2为Redis连接池提供支持,有助于提高Redis连接的管理效率。

(二)配置Redis连接

application.propertiesapplication.yml中进行Redis连接信息的配置:

spring:
redis:
host: localhost
port: 6379
password: 123456
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: -1ms

这里配置了Redis的主机地址、端口、密码以及连接池参数。采用Lettuce作为Redis客户端,它是一个高性能的异步Redis客户端,在Spring Boot 2.x及以上版本中成为了默认的客户端。

(三)Redis操作模板

Spring Data Redis提供了RedisTemplateStringRedisTemplate两种模板类用于操作Redis。StringRedisTemplateRedisTemplate的子类,专门用于处理键和值都是字符串的情况,使用起来更加便捷。

@Component
@RequiredArgsConstructor
public class RedisUtils { private final RedisTemplate<String, Object> redisTemplate; public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
} public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
} public void set(String key, Object value, long seconds) {
redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
} public Object get(String key) {
return redisTemplate.opsForValue().get(key);
} public String getString(String key) {
Object obj = redisTemplate.opsForValue().get(key);
return obj == null ? null : obj.toString();
} public Boolean delete(String key) {
return redisTemplate.delete(key);
} public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
} public Boolean setNx(String key, Object value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
} public Boolean tryLock(String lockKey, String requestId, long seconds) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, seconds, TimeUnit.SECONDS);
} public Boolean tryLock(String lockKey, String requestId, long timeout, TimeUnit unit) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, timeout, unit);
} public Boolean releaseLock(String lockKey, String requestId) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(RELEASE_SCRIPT);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), requestId);
return RELEASE_SUCCESS.equals(result);
}
}

在上述代码中,通过Lombok中热@RequireArgsConstructor注入StringRedisTemplate,然后利用其opsForValue()方法获取操作字符串类型数据的ValueOperations对象,进而实现对Redis中字符串数据的增、删、查等操作。

(四)缓存注解的使用

Spring还提供了缓存注解,如@Cacheable@CachePut@CacheEvict等,可以更方便地实现缓存功能。

首先,需要在配置类上添加@EnableCaching注解开启缓存功能:

@Configuration
@EnableCaching
public class RedisCacheConfig { @Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}

在这个配置中,定义了Redis缓存的默认配置,包括缓存过期时间、键和值的序列化方式等。使用GenericJackson2JsonRedisSerializer对值进行序列化,能够将对象转换为JSON格式存储,方便读取和解析。

然后在Service层的方法上使用缓存注解:

@Service
public class UserService { @Autowired
private UserMapper userMapper; @Cacheable(value = "user", key = "#id")
public User getUserById(Long id) {
return userMapper.selectById(id);
} @CachePut(value = "user", key = "#user.id")
public User updateUser(User user) {
userMapper.updateById(user);
return user;
} @CacheEvict(value = "user", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
}

@Cacheable表示在调用方法之前,会先从缓存中查询,如果缓存中存在,则直接返回缓存中的数据,不执行方法体;如果缓存中不存在,则执行方法体,并将方法的返回值存入缓存。@CachePut会将方法的返回值存入缓存,无论缓存中是否已存在该数据。@CacheEvict用于删除缓存中的数据。

三、基于WebFlux的Gateway中使用Redis

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

(一)引入依赖

pom.xml中引入响应式Redis的依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

spring-boot-starter-data-redis-reactive提供了响应式的Redis操作支持,同样需要commons-pool2来支持连接池。

(二)配置Redis连接

与基于Servlet的模块类似,在application.yml中配置Redis连接信息:

spring:
redis:
host: localhost
port: 6379
password: 123456
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 2
max-wait: -1ms

这里的配置与Servlet模块中的配置基本一致,因为连接Redis的基本信息是相同的。

(三)响应式Redis操作

响应式Redis操作主要通过ReactiveRedisTemplateReactiveStringRedisTemplate来实现,它们返回的是MonoFlux类型的结果,契合WebFlux的响应式编程模型。

@Service
public class ReactiveRedisService { @Autowired
private ReactiveStringRedisTemplate reactiveStringRedisTemplate; // 设置字符串类型数据
public Mono<Boolean> setString(String key, String value) {
return reactiveStringRedisTemplate.opsForValue().set(key, value);
} // 获取字符串类型数据
public Mono<String> getString(String key) {
return reactiveStringRedisTemplate.opsForValue().get(key);
} // 设置带过期时间的字符串数据
public Mono<Boolean> setStringWithExpire(String key, String value, long timeout, TimeUnit unit) {
return reactiveStringRedisTemplate.opsForValue().set(key, value, timeout, unit);
} // 删除数据
public Mono<Long> delete(String key) {
return reactiveStringRedisTemplate.delete(key);
}
}

在响应式操作中,每个方法返回的都是Mono类型,Mono表示一个包含0或1个元素的异步序列。当调用这些方法时,并不会立即执行Redis操作,而是返回一个操作的承诺,只有当订阅这个Mono时,操作才会真正执行。

(四)在Gateway过滤器中使用Redis

在Gateway中,经常需要在过滤器中使用Redis来实现一些功能,如限流、令牌验证等。下面以一个简单的令牌验证过滤器为例:

@Component
public class TokenValidateFilter implements GlobalFilter, Ordered { @Autowired
private ReactiveRedisService reactiveRedisService; @Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("token");
if (token == null || token.isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
} return reactiveRedisService.getString("token:" + token)
.flatMap(userId -> {
if (userId != null) {
// 令牌有效,继续执行后续过滤器
return chain.filter(exchange);
} else {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
})
.switchIfEmpty(Mono.defer(() -> {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}));
} @Override
public int getOrder() {
return -100;
}
}

在这个过滤器中,首先从请求头中获取令牌token,然后通过ReactiveRedisService从Redis中查询该令牌对应的用户ID。如果查询到结果,说明令牌有效,继续执行后续的过滤器;如果未查询到结果或令牌不存在,则返回未授权的响应。

这里充分利用了响应式编程的特性,通过flatMapswitchIfEmpty等操作符来处理异步流,保证了整个操作的异步非阻塞性。

四、两种场景下Redis使用的对比

(一)编程模型

基于Servlet的普通模块采用的是同步阻塞的编程模型,使用RedisTemplate进行Redis操作时,方法的调用会阻塞当前线程,直到操作完成。

基于WebFlux的Gateway采用的是异步非阻塞的编程模型,使用ReactiveRedisTemplate进行Redis操作时,方法返回MonoFlux对象,不会阻塞线程,开发者通过订阅这些对象来处理操作结果。

(二)性能表现

在高并发场景下,基于WebFlux的Gateway使用响应式Redis客户端能够更高效地利用线程资源,减少线程切换的开销,从而表现出更好的性能。

而基于Servlet的普通模块由于采用同步阻塞的方式,在面对大量并发请求时,可能会因为线程阻塞而导致性能瓶颈。

(三)适用场景

基于Servlet的普通模块的Redis使用方式适用于业务逻辑相对复杂、对响应时间要求不是特别高的场景。

基于WebFlux的Gateway的Redis使用方式适用于高并发、I/O密集型的场景,如网关的限流、令牌验证等,能够更好地应对大量的并发请求。

五、总结

在微服务项目中,基于Servlet的普通模块和基于WebFlux的Gateway在使用Redis时,由于底层技术架构的不同,选择的Redis操作方式也有所差异。

普通模块通过Spring Data RedisRedisTemplate进行同步操作,简单直观,适合处理复杂的业务逻辑;Gateway则通过spring-boot-starter-data-redis-reactiveReactiveRedisTemplate进行异步非阻塞操作,能够更好地应对高并发场景。

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

  1. 如何理解springcloud微服务项目中,eureka,provider,consumer它们之间的关系?

    eureka负责注册provider和consumer的服务信息 provider负责与数据库进行交互,实现数据持久化,并给consumer提供服务 consumer与前端交互,通过与Eureka同源 ...

  2. (3)go-micro微服务项目搭建

    目录 一 微服务项目介绍 二 go-micro安装 1.拉取micro镜像 2.生成项目目录 三 项目搭建 使用DDD模式开发项目: 四 最后 一 微服务项目介绍 账户功能是每一个系统都绕不开的一部分 ...

  3. 微服务架构中API网关的角色

    [上海尚学堂的话]:本文主要讲述了Mashape的首席技术执行官Palladino对API网关的详细介绍,以及API网关在微服务中所起的作用,同时介绍了Mashape的一款开源API网关Kong. A ...

  4. 在Azure DevOps Server中运行基于Spring Boot和Consul的微服务项目单元测试

    1 概述 谈到微服务架构体系,绕不开服务发现这个功能.服务发现机制是简化微服务配置.实现容灾.水平扩缩容.提高运维效率的重要方式.在服务发现工具中,Consul在部署和使用方面与容器结合的天衣无缝,成 ...

  5. SpringCloud(1)---基于RestTemplate微服务项目案例

    基于RestTemplate微服务项目 在写SpringCloud搭建微服务之前,我想先搭建一个不通过springcloud只通过SpringBoot和Mybatis进行模块之间额通讯.然后在此基础上 ...

  6. 微服务架构:基于微服务和Docker容器技术的PaaS云平台架构设计(微服务架构实施原理)

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 基于微服务架构和Docker容器技术的PaaS云平台建设目标是给我们的开发人员提供一套服务快速开发.部署.运维管理.持续开发持续集成的流程 ...

  7. 微服务架构中APIGateway原理

    背景 我们知道在微服务架构风格中,一个大应用被拆分成为了多个小的服务系统提供出来,这些小的系统他们可以自成体系,也就是说这些小系统可以拥有自己的数据库,框架甚至语言等,这些小系统通常以提供 Rest ...

  8. 认证鉴权与API权限控制在微服务架构中的设计与实现(四)

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的完结篇,前面三篇已经将认证鉴权与API权限控制的流程和主要细节讲解完.本文比较长,对这个系列进行收尾,主要内容包括 ...

  9. 使用Redis为注册中心的Dubbo微服务架构(基于SpringBoot)

    title: 使用Redis为注册中心的Dubbo微服务架构(基于SpringBoot) date: 2019-07-30 14:06:29 categories: 架构 author: mrzhou ...

  10. Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理

    Java生鲜电商平台-SpringCloud微服务架构中核心要点和实现原理 说明:Java生鲜电商平台中,我们将进一步理解微服务架构的核心要点和实现原理,为读者的实践提供微服务的设计模式,以期让微服务 ...

随机推荐

  1. 网络 | Linux ping任何ip均出现 Destination Host Unreachable 排查思路与方法

    Linux ping任何地址出现 Destination Host Unreachable 基本的排错步骤如下: 1.ping 127.0.0.1ping的通说明tcp协议栈没有问题 2.ping 主 ...

  2. 【Java并发编程】面试必备之线程池

    什么是线程池 是一种基于池化思想管理线程的工具. 池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需.比如我们的对象池,数据库连接池等. 线程池好处 我们为什么要使用线程池,直接new ...

  3. (各种数组之间的互相转换)int 数组与List互相转换,object数组转换int数组

    Stream流之List.Integer[].int[]相互转化 一.int[ ] 1.1.int[ ] 转 Integer[ ] public static void main(String[] a ...

  4. 数组:ArrayList和int[]

    需要好好复习一下: 数组ArrayList和int[ ] int[ ] 多可以使用Arrays工具类导入后 使用Arrays.xxx(arr) 实现很多功能

  5. UFT 笔记(1)

  6. 《Building REST APIs with Flask》读后感

    一. 为什么读这本书? 之所以选择这本书其实是因为最近自己在梳理 JWT 的用法.自己曾参与过的一个项目虽然使用的是 Flask 开发,但是授权使用的 PyJWT,当时以为使用 PyJWT 是行业通用 ...

  7. Blazor学习之旅(1)初步了解Blazor

    2022年9月以来在学习Blazor做全栈开发,因此根据老习惯,我会将我的学习过程记录下来,一来体系化整理,二来作为笔记供将来翻看.作为第一篇,我们先来了解一下这个Blazor到底是个什么鬼. 什么是 ...

  8. vite V3.0.0 vite.config.ts 引入插件vite-plugin-vue-setup-extend-plus报错(vueSetupExtend不是一个函数)

    vite V3.0.0 vite.config.ts 引入插件报错(***** 不是函数) ·问题 #9414 ·Vitejs/Vite (github.com) 我的错误提示如下 ERROR fai ...

  9. java -- 监听器、国际化

    监听器 监听器: 主要是用来监听特定对象的创建或销毁.属性的变化的! 是一个实现特定接口的普通java类! Servlet中哪些对象需要监听? request / session / servletC ...

  10. CF1956C Nene's Magical Matrix 题解

    CF1956C Nene's Magical Matrix 被这题送走了,纪念一下. 巧妙的构造题,考虑比较方便处理的方案,假设我们从左上角的顶点开始涂,每次涂一个 \(1,2,3\dots n\) ...