ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

若依分离版去除Redis改造:用内存缓存替代Redis的完整实践

若依分离版去除Redis改造:用内存缓存替代Redis的完整实践 最近在给客户做若依分离版项目的交付客户现场是严格的内网环境安全审计那边明确说了除了业务数据库之外不允许再单独部署 Redis 这类中间件。结果一上来就遇到个尴尬场面登录页验证码转圈后台日志刷屏“Unable to connect to Redis”。因为这版项目用的是若依分离版RuoYi-Vue默认把验证码、登录 Token 这些关键数据都存在 Redis 里一旦 Redis 没起来别说业务接口连登录门都进不去。既然环境不让用 Redis我干脆研究了一下怎么把这套分离版里的 Redis 彻底摘掉让应用只依赖 MySQL 也能正常跑。这篇文章把整个改造过程完整记录一下。核心思路其实很简单若依的业务代码并不直接操作 RedisTemplate而是通过一个叫 RedisCache 的工具类访问缓存。我把这个工具类保留下来类名包名都不动把底层改成基于 ConcurrentHashMap 的本地内存缓存这样验证码、登录会话、在线用户这些功能都能继续工作业务代码几乎零改动。下面的内容会从原理到实操一步步来适合单机部署、演示环境、内网交付的场景想给若依减负的可以照着做。1. 为什么若依分离版离不开 Redis1.1 若依把 Redis 用在了哪几个关键位置先弄清楚 Redis 在若依分离版里到底承担了什么不然改造的时候容易漏。我将 RuoYi-VueVue2/Vue3 分支逻辑基本一致依次检查代码定位到这几个核心使用点登录验证码获取验证码图片时生成一个 uuid把验证码答案存到 Redis提交登录时再根据 uuid 从 Redis 读出答案校验校验成功后立即删掉保证验证码一次性有效。登录用户会话登录成功之后后端创建一个 uuid 作为 token把这个 token 作为 key把封装好的 LoginUser 对象作为 value 存到 Redis前端每次请求携带 token后端通过 JWT 过滤器解析出这个 uuid再从 Redis 拉取 LoginUser 完成认证。会话滑动续期若依对登录会话默认有 30 分钟有效期但并不是固定 30 分钟打死不变而是每次请求都刷新过期时间这也就是常说的滑动过期。这个刷新动作同样操作 Redis 的过期时间。在线用户管理管理端的在线用户列表、强退用户功能本质上是扫描 Redis 里所有 login_tokens: 前缀的 key再把对应的 LoginUser 还原出来展示。除了这四处不同版本可能还有字典缓存、参数配置缓存、定时任务分布式锁等二次封装但最核心、绕不开的就是验证码和登录会话。你可以在 IDEA 里直接全局搜索redisCache.把当前分支下所有调用点列出来心里先有个底。搞清楚这些之后你会发现若依的 Redis 依赖其实非常集中。它不像有些项目把一堆业务数据都往 Redis 里塞而是只处理“会话相关”和“校验相关”的临时数据。这个特点决定了我们后续用内存缓存替换是可行的因为这些数据本来就不需要长期持久化重启丢了也没关系用户重新登录一次就好。1.2 去 Redis 适合什么场景不适合什么场景把 Redis 去掉不是所有情况下都合理先说清楚边界免得你改完上线后发现问题又回不来。适合的场景有这么几类第一类是单节点部署或者场景上本来就是单机演示、验收环境不需要多个应用实例共享会话状态第二类是客户内网环境安全要求严格不允许安装保留无关中间件第三类是部署机器资源紧张希望简化运维应用启动后只依赖一个 MySQL第四类是本地开发调试不想每次先折腾 Redis 环境。不适合的场景也很明确多实例部署时内存缓存只在单实例内有效用户第一次请求落在实例 A第二次落到实例 BToken 就找不到了会话直接断掉已有在线用户统计、强制踢人、多端登录互斥等依赖 Redis 全局查询和删除功能的重度需求简单替换会破坏这些能力微服务版本RuoYi-Cloud / RuoYi-Cloud-Plus里的 Redis 还承载着服务间缓存、分布式锁、认证中心数据简单替换会连累一堆功能Plus 系列版本对 Redis 的依赖更深比如 Sa-Token 会话集成、参数配置实时缓存等也不推荐按本文方式处理。一句话总结这个方案是给“单机轻量交付”准备的不是给所有若依项目一刀切用的。你只需要根据自己手上的部署形态做判断。我这次客户是两台服务器但只跑一个应用加一个 MySQL完全满足条件。2. 动手改造前先摸清 Redis 在项目里的落脚点2.1 找到依赖和配置大多数若依分离版项目里Redis 的接入位置很固定。pom.xml 里有一个 starter 依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependencyapplication.yml 里有一段 spring.redis 配置有的版本用 spring.data.redis看你的 Spring Boot 版本spring: redis: host: localhost port: 6379 password: database: 0 timeout: 10s lettuce: pool: min-idle: 0 max-idle: 8 max-active: 8 max-wait: -1ms这里有一个很容易忽略的点如果项目里用了 Lettuce 连接池pom.xml 可能还有一个 commons-pool2 依赖去掉 Redis 时连这个依赖也要一起清理。不然后续排查依赖冲突时容易一头雾水更烦的是明明 Redis 代码都删干净了还是有人往 classpath 里带进来一堆多余的连接池类。2.2 列一张“缓存白名单”我习惯把项目里所有 Redis 相关的类先拉一张清单再逐个处理。以 RuoYi-Vue 3.8.x 左右的分支为例典型清单如下组件所在位置作用改造策略RedisConfigcom.ruoyi.framework.config配置 RedisTemplate 序列化直接删除或注释RedisCachecom.ruoyi.common.core.redis业务统一操作的缓存工具类保留类名改底层实现CaptchaControllercom.ruoyi.web.controller.common验证码生成与校验入口使用 RedisCache逻辑不动TokenServicecom.ruoyi.framework.web.service登录用户会话管理使用 RedisCache逻辑不动CacheConstantscom.ruoyi.common.constant定义 login_tokens、captcha_codes 等 key 前缀保留这张表能让你清楚地看到真正要改的其实只有 RedisCache其余业务类只要不直接注入 RedisTemplate都可以不动。所以“替身方案”天然成立。如果你们的代码里有个别地方绕过 RedisCache 直接注入 RedisTemplate就需要把那些调用也改掉这是改造时唯一可能漏掉的风险点。搜索的时候建议用两个关键字一个是redisCache.另一个是RedisTemplate。前者是业务调用后者是底层桥接。两个都搜完改动面自然就清楚了。我见过的二次开发项目里最常见的翻车点就是有人写了个自定义服务直接 Autowired 了 StringRedisTemplate 去存临时数据这类代码不搜 RedisTemplate 根本发现不了。3. 用内存缓存写一个“替身” RedisCache3.1 改造思路类名不变底层换掉为什么要保留 RedisCache 这个类名原因很直接若依整个框架里到处都在注入它验证码、TokenService、在线用户、定时任务刷新等等全部依赖这个类。如果把它删掉再新建一个 MemoryCache那所有注入点都要改工作量立刻翻倍而且很容易改漏。保留类名和包名只是把类内部的 RedisTemplate 操作换成本地 Map 操作外部调用方一句都不用动这是落盘最稳、回归成本最低的改法。实现“本地内存版 RedisCache”要回答三个问题数据怎么存过期时间怎么管理以及滑动过期怎么支持。数据存储最自然的选择就是 ConcurrentHashMapkey 用字符串value 用一个内部类型把缓存对象和过期时间包在一起。过期时间管理就是每次写入时记录到期时间戳每次读取时判断是否过期已经过期的当作不存在并顺手删掉也就是常说的惰性删除。滑动过期这一块若依的 TokenService 会在每次请求时调用 expire 方法刷新过期时间所以实现里必须保留 expire 方法修改对应 key 的到期时间即可。搞懂这三个问题后实现就不难了。3.2 带过期时间和并发安全的本地缓存实现我这次用 ConcurrentHashMap 手搓了一个轻量缓存完整代码贴出来。注意这一段要根据你自己分支里 RedisCache 的方法签名微调但大逻辑是一样的package com.ruoyi.common.core.redis; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; Component public class RedisCache { /** * 内部缓存条目value 是真实对象expireAt 是过期时间戳毫秒0 表示不过期 */ private static class CacheEntry { private Object value; private long expireAt; CacheEntry(Object value, long expireAt) { this.value value; this.expireAt expireAt; } boolean isExpired() { return expireAt 0 System.currentTimeMillis() expireAt; } } private final MapString, CacheEntry localCache new ConcurrentHashMap(); public T void setCacheObject(final String key, final T value) { setCacheObject(key, value, 0L, null); } public T void setCacheObject(final String key, final T value, final Integer timeout) { // 注意如果你当前分支的这个重载原本以秒为单位这里要改成 TimeUnit.SECONDS setCacheObject(key, value, timeout null ? null : timeout.longValue(), TimeUnit.MILLISECONDS); } public T void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) { long expireAt 0L; if (timeout ! null timeUnit ! null) { expireAt System.currentTimeMillis() timeUnit.toMillis(timeout); } localCache.put(key, new CacheEntry(value, expireAt)); } SuppressWarnings(unchecked) public T T getCacheObject(final String key) { CacheEntry entry localCache.get(key); if (entry null) { return null; } if (entry.isExpired()) { localCache.remove(key); return null; } return (T) entry.value; } public boolean deleteObject(final String key) { return localCache.remove(key) ! null; } public long deleteObject(final Collection collection) { if (collection null || collection.isEmpty()) { return 0L; } long count 0L; for (Object key : collection) { if (key ! null localCache.remove(key.toString()) ! null) { count; } } return count; } public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.MILLISECONDS); } public boolean expire(final String key, final long timeout, final TimeUnit unit) { CacheEntry entry localCache.get(key); if (entry null) { return false; } entry.expireAt System.currentTimeMillis() unit.toMillis(timeout); return true; } public boolean hasKey(String key) { return getCacheObject(key) ! null; } public CollectionString keys(final String pattern) { String prefix pattern.endsWith(*) ? pattern.substring(0, pattern.length() - 1) : pattern; return localCache.keySet().stream() .filter(key - key.startsWith(prefix)) .filter(key - { CacheEntry entry localCache.get(key); return entry ! null !entry.isExpired(); }) .collect(Collectors.toList()); } }简单说明几个关键点CacheEntry 是内部静态类把业务对象和过期时间绑在一起避免为了存过期时间再搞额外的结构。setCacheObject 接收 Long timeout 和 TimeUnit 的重载是若依 TokenService 和验证码链路调用最多的方法要保证它正确换算成毫秒时间戳。getCacheObject 里做了惰性过期判断已经过期的就当没命中并顺手 remove 一下避免脏数据一直驻留。expire 方法用于改写已有 key 的过期时间这个非常关键。若依每次请求都会调用它来做会话滑动续期漏掉这个实现登录后 30 分钟一到就会全体掉线。keys 方法用于在线用户列表查询实现对 login_tokens: 前缀 key 的扫描。Redis 原生支持模糊匹配本地 Map 就直接遍历判断前缀演示环境数据量小性能没问题。写完后先不要急着动业务代码编译一下看调用的地方能不能对上。如果你的分支里有其他方法在 RedisCache 里被直接调用而这里没有编译器会立刻报错补上对应实现就行。3.3 别忘了给本地缓存做定时清理ConcurrentHashMap 版本的惰性删除有一个缺陷如果某个 key 设置了过期时间但之后再也没人访问它这个 key 就会一直留在 Map 里。验证码场景尤其明显用户每次刷新验证码都会往里面塞一个新 key很多人不登录也不填验证码2 分钟过期时间过了之后这些 key 依然躺在内存里。日积月累看起来只是内存占用增加但 Map 越来越大长期运行终究是隐患。解决办法很简单加一个后台清理任务每分钟扫描一次把过期的 CacheEntry 清掉。可以在 RedisCache 类里直接用 Scheduled 注解也可以用一个 ScheduledExecutorService。我这里用的是最朴素的方式private final ScheduledExecutorService cleaner Executors.newSingleThreadScheduledExecutor(); public RedisCache() { cleaner.scheduleAtFixedRate(this::clearExpiredEntries, 1, 1, TimeUnit.MINUTES); } private void clearExpiredEntries() { long now System.currentTimeMillis(); localCache.entrySet().removeIf(entry - entry.getValue().expireAt 0 now entry.getValue().expireAt); }这段代码会在类实例化时启动一个后台线程每分钟把过期的条目清掉。注意不要用 new Thread 在那睡循环ScheduledExecutorService 是更稳妥的选择。如果你不想引入定时任务也可以在 setCacheObject 的时候不定期触发清理但这样做有个明显问题写入本身已经很频繁了再叠加全表扫描有点得不偿失。一分钟一次的后台清理对演示环境完全够用。4. 从依赖到业务链路逐层拆掉 Redis4.1 移除 pom 和配置文件里的 Redis确认新的 RedisCache 能编译通过后开始从外部“拆线”。第一步把 pom.xml 里的 spring-boot-starter-data-redis 依赖注释掉或删除。这里不要心急注释掉后先做一次 mvn compile看有没有类直接引用了 org.springframework.data.redis 下的类。如果报错说明还有地方绕过了 RedisCache 直接操作 RedisTemplate把这些类找出来改成使用 RedisCache或者直接注释掉相关代码。第二步打开 RedisConfig整个类的作用就是给 RedisTemplate 定制序列化策略。Redis 都不用了这个类自然可以删掉或注释。注意检查 RedisConfig 里是否有 Bean 方法被其他地方注入如果有对应注入点也要处理。比如有些项目会在配置类里把 RedisTemplate 注入到其他组件里做缓存预热这类代码要一起清掉。第三步把 application.yml 里 spring.redis 整段配置注释掉包括 lettuce 连接池和 commons-pool2 依赖。如果你是用 Nacos 管理配置那就要去配置中心把 redis 相关项清掉。很多人在这一步遇到一个坑pom 里的 Redis 依赖删了但 RedisConfig 还在一启动就报 ClassNotFoundException: RedisConnectionFactory。原因就是代码里还引着 RedisTemplate。所以顺序很重要先确认代码层没有 redis 相关 import再删依赖最后清配置。4.2 验证码链路完全不用改若依分离版的验证码逻辑在 CaptchaController 和 SysLoginService 里。大致流程是前端请求 /captchaImage后端生成图形验证码同时生成一个 uuid 作为 key把验证码答案存入缓存key 的格式是 captcha_codes:uuid。前端提交登录表单时带上这个 uuid 和用户输入的验证码。后端根据 uuid 从缓存读出正确验证码比对成功后立刻删除缓存 key。因为这套流程走的是 redisCache.setCacheObject / getCacheObject / deleteObject而我们的 RedisCache 类名和这些方法签名都没变所以这段业务代码完全不用动。验证码的 2 分钟过期时间也会被本地缓存的 TTL 机制正确执行。你可能会担心以前验证码存在 Redis 里现在存在应用内存里多个实例不共享怎么办。这个在单节点部署下根本不成立只有一台应用服务器验证码本来就存在同一份内存里逻辑上是一致的。我自己在改造前还特意把验证码链路完整跑了一遍确认验证码生成、校验、错误拦截、过期失效四个环节都正常才继续往下走的。4.3 登录会话链路靠 expire 续期保证不掉线TokenService 是本次改造的重头戏虽然业务代码不用改但我们要理解它背后的机制才能确认自家替身靠不靠谱。若依的登录会话管理大概是这样的登录成功后生成一个 UUID 作为 token把 LoginUser 对象存入缓存key 为 login_tokens:uuid过期时间默认 30 分钟同时把 token 塞进 JWT 返回给前端。前端每次请求在请求头带上 Authorization后端拦截器从 JWT 里解析出这个 uuid然后调用 redisCache.getCacheObject 拿 LoginUser。每次请求解析成功后TokenService 会调用 redisCache.expire 把过期时间重新刷新成 30 分钟。用户主动退出时调用 redisCache.deleteObject 删除这个 key。换成内存缓存后getCacheObject 返回的是同一个 LoginUser 对象引用expire 方法会把过期时间戳往后推 30 分钟deleteObject 会从 Map 里移除 key。三个动作全部有对应实现所以会话逻辑可以无感切换。有一点要提醒LoginUser 对象里包含权限标识、角色集合、用户信息等字段。以前走 Redis 序列化时这些对象要经过 FastJson 或 JDK 序列化经常出现类型转换异常、循环引用、日期格式不对这样那样的坑。现在直接存对象引用这些问题反而消失了。内存缓存虽然牺牲了分布式共享能力却把序列化包袱一并甩掉了。4.4 全局搜索 redisCache把所有隐藏调用点过一遍验证码、会话、在线用户是明面上的三个大块但不同版本的若依可能在下面这些地方也用了 RedisCache参数配置被修改后清理缓存字典数据缓存刷新代码生成时的一些临时状态用户状态变更后删除该用户所有在线会话。处理方式是统一的在 IDEA 里全局搜索redisCache.把搜索结果逐条点开看一遍。只要调用的是 RedisCache 的方法我们新实现都有对应能力不用改。但如果某段代码直接注入了 RedisTemplate 或者 StringRedisTemplate就要重点处理替换成 RedisCache 或者直接移除。若依默认项目里一般不会出现这种绕行写法二次开发的代码里经常有所以这个排查步骤不能省。我当时就遇到了一个自定义的短信发送模块直接用 StringRedisTemplate 做了发送频率限制Redis 一拆它第一个炸。后来我把那段逻辑的限流存储也挪进了 RedisCache用 hasKey 和 expire 组合实现才把问题解决。5. 我的实测过程与测试清单5.1 新分支改造顺序记录我建议动手前先拉一个分支出来比如 feat/remove-redis避免主分支改坏了没法快速回退。这次我自己的改造顺序是第一步在 IDEA 里全局搜索 RedisTemplate 和 redisCache记录所有文件。第二步重建 RedisCache 类先编译通过。第三步注释 RedisConfig删除 pom 里的 redis starter 和 commons-pool2。第四步清理 application.yml 的 spring.redis 配置。第五步启动应用依次验证验证码、登录、自动续期、退出登录、在线用户列表。这个顺序的核心原则是“先让代码不依赖 Redis 也能编译再让应用不连接 Redis 也能启动最后验证业务功能”。按照这个节奏来每一步出问题都能快速定位。如果反过来先删依赖再改代码编译报错会把你淹没在几十个红叉里很难分清哪个是核心问题。把改动提交到分支的时候我习惯拆成三个 commit第一个是 RedisCache 替换实现第二个是依赖和配置清理第三个是问题修复和文档补充。这样后续如果要回滚可以精准回滚到某个环节不用整个分支一起回退。5.2 不装 Redis 直接启动这是最关键的一步验证。改造完成后我在本地直接把 Redis 服务停掉然后启动若依后端。正常情况下日志里不会出现任何 Redis 连接失败的异常应用能顺利注册控制台。如果还是看到 “Unable to connect to Redis” 之类的日志说明还有地方在启动阶段主动初始化 Redis 连接池要按第 4 章的思路继续排查。我当时还碰到过一个特殊情况若依的在线用户监控页、操作日志、登录日志这几个模块本身没有强制依赖 Redis但有些自定义工具类里加了个 PostConstruct 方法初始化 RedisTemplate导致应用启动失败。通过全局搜索 RedisTemplate 把这类代码找出来注释掉问题就解决了。这个搜索动作不要只在 src 目录里搜resources 下的 XML 配置、Mapper 配置里也可能藏着对 Redis 相关 Bean 的引用都要看一眼。5.3 业务回归测试清单去掉 Redis 后我按下面这份清单做了一遍回归你可以直接拿去用甚至做成一个最简单的冒烟测试脚本测试项操作方式预期结果验证码加载打开登录页验证码正常显示后台无异常日志验证码校验输入正确/错误验证码各一次正确可继续登录错误被拦截且验证码失效正常登录账号密码登录返回 token跳转首页接口访问请求 /getInfo 等需要登录的接口返回用户信息不 401会话续期等待 25 分钟后再次请求会话仍有效不要求重新登录会话过期不操作等待超过 30 分钟token 失效需要重新登录退出登录调用退出接口后再请求原 token 立即失效在线用户管理端查看在线用户列表能看到当前登录用户强制下线管理端执行强退被强退用户下次请求 401多用户并发两个账号同时登录两个会话互不影响应用重启重启后端后再次登录老 token 失效新登录正常这套清单跑完基本可以确认改造对业务的影响范围是可控的。尤其是“会话续期”这一项如果没实现 expire 方法或实现有误25 分钟后就会踩到全员掉线的雷。我在第一次实现时就是因为 expire 里忘了把 entry 写回 MapValue 是基本类型改完等于没改导致测试跑到 30 分钟整批掉线后来改成直接修改对象字段才解决。压测方面我们用 JMeter 模拟了 200 个并发用户同时登录并持续调用接口的操作跑了 30 分钟没有出现会话丢失或验证码失效的问题。内存缓存的读写性能本来就在微秒级比走一次网络 RPC 快得多所以单机场景下完全不用担心性能。6. 改造中的高频坑位与再进一步的建议6.1 我踩过和见过别人踩的坑第一Integer timeout 的单位问题。若依不同分支里 RedisCache.setCacheObject(String key, Object value, Integer timeout) 这个重载的单位有差异有的是毫秒有的是秒还有的版本根本没有这个重载。改造时如果不确定就去看原类里这个方法的实现把单位抄过来不要想当然。第二键扫描的实现。Redis 原生的 keys 命令支持、? 这类通配符我们本地实现里如果只做前缀匹配某些反向调用 keys(captcha_) 会出问题。好在若依业务里基本只用前缀模式比如 login_tokens:* 和 captcha_codes:*所以前缀匹配够用。如果你的二开代码里用了复杂通配符那要么扩展本地实现要么改用 Caffeine 这类功能更完整的缓存库。第三在线用户列表查不到数据。这个问题很隐蔽。若依的在线用户功能会先 redisCache.keys(LOGIN_TOKEN_KEY *)再逐条 getCacheObject。如果 keys 实现里返回了过期 key 的集合而后面 getCacheObject 又返回 null前端列表里就会出现空行或奇怪的 null 数据。我在实现里特意在 keys 时也判断了一遍过期状态就是为了避免这种脏数据。第四多实例部署的幻觉。有人改完之后觉得挺好又把它部署到两台服务器上结果用户登录后请求负载均衡到另一台机器就 401。这个方案只认单实例多节点必须上 Redis 或独立共享缓存这一点没得商量。如果你未来有扩容计划改造前就要慎重或者顺便把缓存层抽象成可切换接口后续再换 Redis 也容易。第五清理资源别忘了连接池依赖。pom 里 redis starter 删了但 commons-pool2 还留着虽然不影响运行但冗余依赖会让后续维护的人困惑。另外有些瘦身更新脚本会扫描依赖树多出来的历史依赖可能被安全扫描工具标记到时又要解释半天。6.2 如果不想手写缓存可以用 Caffeine 兜底如果你不想维护自己的 ConcurrentHashMap 缓存逻辑可以直接引入 Caffeine把 RedisCache 内部的 Map 换成 Caffeine 的 Cache。代码会更简洁dependency groupIdcom.github.ben-manes.caffeine/groupId artifactIdcaffeine/artifactId /dependencyCacheString, Object cache Caffeine.newBuilder() .maximumSize(10000) .build();不过要注意Caffeine 的 expireAfterWrite 是固定过期时间如果要实现若依那种“每次请求刷新 30 分钟”的滑动过期就得自定义 Expiry 或者每次访问时重新写一次 key实现起来比直接手写 TTL 还要绕一些。对我来说在 RedisCache 这个门面类里用 ConcurrentHashMap 管理 TTL反而是最贴合若依现有调用习惯的做法。Caffeine 更适合那些不需要滑动过期、key 数量又很大的纯缓存场景。如果你不想依赖任何第三方库那么我上面给出的 ConcurrentHashMap 版本已经足够精简它最大的好处是零依赖只靠 JDK 原生能力就能跑对部署环境的侵入最小。6.3 关于无状态 JWT 和 Plus 版本的再说明如果你的目标不仅仅是摘掉 Redis还想让若依改成完全无状态的 JWT 认证那改动面会大很多。若依目前 JWT 里只存了一个 uuid真正的 LoginUser 还是存在缓存里这样可以随时剔除用户、统计在线。改成无状态后权限数据全塞进 JWT每次请求直接解析虽然不用缓存了但也失去了主动踢人和会话过期的精细控制。除非有明确的性能或架构要求否则我不建议在这个方向上折腾收益不大改动和风险却成倍增加。至于 RuoYi-Cloud 和 RuoYi-Cloud-Plus 这类微服务版本Redis 已经深入到认证、分布式锁、缓存一致性等环节简单把 RedisCache 换成内存缓存会导致服务间会话不互通、锁失效、数据不一致等一系列问题。如果确实要降低部署复杂度更好的办法是把 Redis 做成内嵌模式或者用高可用的云数据库而不是直接去掉。最后分享一个实际体会。若依框架把缓存访问收敛到 RedisCache 这一个门面类上这个设计本来就给底层替换留了余地。我们这次用本地内存缓存顶替 Redis本质上是把“分布式缓存”换成了“单机缓存”业务代码没怎么动部署环境却清爽了不少。改造过程中最值得投入时间的不是写缓存代码而是把项目里所有 redisCache 的调用点梳理清楚。梳理清楚了剩下的活基本就是照着业务需求选一种缓存实现而已。如果哪天项目要升到多节点把 RedisCache 的底层实现换回 Redis或者换成 Caffeine 加分布式缓存业务代码依然不用怎么动只是到时候要跟运维确认好缓存服务的高可用方案就行。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表