RuoYi-Vue 3.8.6 项目瘦身实战:不用Redis,改用ConcurrentHashMap做本地缓存(附完整代码)

RuoYi-Vue 3.8.6 轻量化改造实战:用ConcurrentHashMap替代Redis的架构思考与实现

在中小型项目快速迭代的开发场景中,系统架构的轻量化往往比单纯追求技术先进性更具实际价值。最近在为一个高校实验室部署内部管理系统时,遇到了一个典型问题:项目基于RuoYi-Vue 3.8.6开发,但服务器资源仅1核2G,Redis实例就占用了近300MB内存,导致服务频繁崩溃。这促使我开始思考:在不影响核心功能的前提下,能否通过架构调整实现系统瘦身?

1. 为什么需要考虑去除Redis?

Redis作为高性能缓存解决方案,在大流量、高并发场景下表现优异。但在特定环境下,它可能成为"过度设计"的典型案例:

  • 资源消耗问题:单个Redis实例基础内存占用约300MB,对于学生服务器或轻量云主机(如1核1G配置)可能吃掉30%以上资源
  • 维护复杂度:需要额外配置持久化、监控和灾备方案,增加运维负担
  • 开发环境痛点:本地开发时经常需要启动多个服务(MySQL、Redis等),降低开发效率

适用场景评估表

场景类型推荐方案理由
生产环境高并发Redis集群支持高可用和水平扩展
内部管理系统本地缓存低流量、一致性要求低
开发测试环境本地缓存简化环境依赖
边缘计算节点本地缓存减少网络开销

提示:缓存方案选择应遵循"够用原则",在保证功能的前提下,选择最简单的实现

2. 核心改造方案设计

2.1 架构改造思路

改造的核心目标是实现缓存接口的统一抽象,使得底层存储可插拔替换。RuoYi-Vue原本采用Spring Cache抽象层,这为我们的改造提供了良好基础:

// 原Redis缓存接口 public interface Cache { String getName(); Object getNativeCache(); ValueWrapper get(Object key); void put(Object key, Object value); void evict(Object key); void clear(); }

改造步骤:

  1. 创建基于ConcurrentHashMap的Cache实现类
  2. 替换原有RedisTemplate调用
  3. 保留Redis相关代码(注释方式)以便未来切换

2.2 内存缓存实现关键点

@Component public class MemoryCache implements Cache { private final Map<String, Object> storage = new ConcurrentHashMap<>(256); @Override public void put(Object key, Object value) { if (key == null || value == null) return; storage.put(key.toString(), serialize(value)); } @Override public ValueWrapper get(Object key) { Object value = storage.get(key.toString()); return value != null ? new SimpleValueWrapper(deserialize(value)) : null; } // 使用Jackson进行序列化/反序列化 private byte[] serialize(Object obj) { try { return objectMapper.writeValueAsBytes(obj); } catch (JsonProcessingException e) { throw new RuntimeException("序列化失败", e); } } private Object deserialize(Object obj) { // 反序列化逻辑... } }

注意事项

  1. 需要处理对象序列化,保持与Redis方案兼容
  2. 初始容量设置避免频繁扩容
  3. 考虑添加软引用防止内存溢出

3. 具体实施步骤

3.1 配置层调整

首先注释掉Redis相关配置(保留原配置以便恢复):

# application.yml #spring: # redis: # host: localhost # port: 6379

3.2 缓存适配器改造

在原有RedisCache类基础上进行改造,关键修改点:

@Component public class CustomCache { @Resource private MemoryCache memoryCache; public <T> void setCacheObject(String key, T value) { memoryCache.put(key, value); } public <T> T getCacheObject(String key) { ValueWrapper wrapper = memoryCache.get(key); return wrapper != null ? (T) wrapper.get() : null; } // 保留原Redis方法(注释状态) // @Autowired // private RedisTemplate redisTemplate; }

3.3 限流方案改造

原Redis限流改为本地令牌桶实现:

public class LocalRateLimiter { private final ConcurrentMap<String, TokenBucket> buckets = new ConcurrentHashMap<>(); public boolean tryAcquire(String key, int capacity, int refillTokens) { TokenBucket bucket = buckets.computeIfAbsent(key, k -> new TokenBucket(capacity, refillTokens)); return bucket.tryConsume(); } private static class TokenBucket { private final int capacity; private long lastRefillTime; private double tokens; // 令牌桶实现逻辑... } }

4. 性能优化与风险控制

4.1 内存管理策略

本地缓存需要特别注意内存控制:

  1. 容量限制:设置最大条目数
private static final int MAX_ENTRIES = 1000; private final Map<String, Object> storage = Collections.synchronizedMap(new LinkedHashMap<String, Object>(16, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; } });
  1. 过期策略:添加简易过期检查
private final ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor(); public void startCleaner(long period, TimeUnit unit) { cleaner.scheduleAtFixedRate(() -> { storage.entrySet().removeIf(entry -> isExpired(entry.getKey(), entry.getValue())); }, 0, period, unit); }

4.2 一致性考量

在分布式环境下,本地缓存会面临一致性问题。可通过以下方式缓解:

  • 添加@RefreshScope支持配置热更新
  • 关键数据添加版本号校验
  • 实现简单的广播机制(基于WebSocket或HTTP)
@RestController @RequestMapping("/cache") public class CacheRefreshController { @PostMapping("/refresh/{key}") public void refreshKey(@PathVariable String key) { memoryCache.evict(key); } }

5. 改造效果评估

在实际项目中实施后,我们观察到以下改进:

  1. 资源占用

    • 内存使用从1.2GB降至800MB
    • 启动时间缩短40%(无需等待Redis连接)
  2. 性能对比(本地环境测试):

操作类型Redis(ms)本地缓存(ms)
单次读取1.20.05
并发读取158
批量写入12065
  1. 开发体验提升
    • 新人无需配置Redis即可开发
    • CI/CD流程简化(减少一个服务依赖)

注意:本地缓存方案不适合以下场景:

  1. 需要持久化的缓存数据
  2. 多节点数据一致性要求高的场景
  3. 缓存数据量超过JVM可用内存的情况

在资源受限的服务器上运行三个月后,系统稳定性显著提升,再未出现因内存不足导致的崩溃。这种改造特别适合预算有限但需要快速交付的学术项目或内部管理系统。