Spring Boot 集成 RocksDB 实战:打造高性能 KV 存储加速层

好的,以下是为您整理的文章内容,您可以自行将之前讨论的代码填充进去。


Spring Boot 集成 RocksDB 实战:打造高性能 KV 存储加速层

一、为什么选择 RocksDB?

在现代微服务架构中,PostgreSQL 等关系型数据库承担着核心的事务处理职责,但当面对海量高频读写毫秒级低延迟查询等场景时,传统的磁盘型关系数据库往往力不从心。

RocksDB 是 Facebook 基于 Google LevelDB 开发的高性能嵌入式 KV 存储引擎,具有以下显著优势:

  • 极致写入性能:LSM-Tree 架构,顺序写入,吞吐量远超 B+Tree 结构
  • 极低读取延迟:配合布隆过滤器,点查询可达微秒级
  • 零网络开销:嵌入式运行在应用进程内,无额外网络往返
  • 高度可定制:支持多种压缩算法、列族管理、增量备份

将 RocksDB 作为 PostgreSQL 的热数据缓存层冷数据归档层,可以形成互补协同的存储架构:PostgreSQL 负责复杂事务与关系查询,RocksDB 负责高频低延迟的 KV 读写。


二、架构设计:冷热分离与查询加速

2.1 整体架构

本方案采用模式一:冷热数据分离与查询加速架构,核心设计如下:

  • PostgreSQL:作为主数据源,处理所有事务性写入和复杂关系查询
  • RocksDB:作为本地嵌入式缓存层,承载海量热点数据的极速读写
  • Spring Boot:作为应用层,统一调度双端存储,对上层业务透明

2.2 数据流设计

写入流程

  1. 业务请求到达 Service 层
  2. 先写入 PostgreSQL 确保持久化
  3. 同步写入 RocksDB 作为缓存(失败不影响主流程)
  4. 可选:通过 Debezium + Kafka 异步同步,进一步解耦

读取流程

  1. 业务请求到达 Service 层
  2. 优先从 RocksDB 查询(命中则直接返回,延迟 < 1ms)
  3. 未命中则回源 PostgreSQL
  4. 异步将结果回填到 RocksDB(缓存预热)

删除流程

  1. 先删除 PostgreSQL 中的数据
  2. 再删除 RocksDB 中的缓存(带重试机制)

三、核心实现

3.1 项目依赖

本文使用 Hutool 替代 Jackson 进行 JSON 序列化,API 更简洁,无需处理受检异常。

主要依赖包括:

  • rocksdbjni:RocksDB Java 绑定
  • hutool-all:JSON 工具及通用工具包
  • spring-retry:提供重试模板支持

3.2 配置层:RocksDB 实例管理

配置类是 RocksDB 集成的基础,核心职责包括:

  • 加载 RocksDB 本地库
  • 配置数据库选项(内存、并发、布隆过滤器等)
  • 以单例模式管理 RocksDB 实例生命周期
  • 支持默认数据目录 :若配置文件中未指定 rocksdb.data-dir,自动使用 /data/app

关键技术点 :使用 Spring 的 @Value 注解的默认值语法 ${rocksdb.data-dir:/data/app},实现"有配置则用配置,无配置则用默认"的优雅降级。

3.3 服务层:RocksDB 操作封装

RocksDBService 封装了所有基础 CRUD 操作,对外提供统一接口:

  • 字符串级别操作putgetdeleteexists
  • 对象级别操作putObjectgetObject,内部自动完成 JSON 序列化与反序列化
  • 批量操作batchPutbatchDelete,支持批量写入和删除

设计要点

  • get 方法在出错时返回 Optional.empty(),不抛异常,保证读路径的高可用
  • putObjectgetObject 使用 Hutool 的 JSONUtil,一行代码完成对象与 JSON 的转换
  • 所有 RocksDB 原生对象(如 WriteBatchWriteOptions)均使用 try-with-resources 确保正确释放

3.4 工具层:JSON 序列化与重试机制

JSON 工具类 :基于 Hutool 的 JSONUtil 封装静态方法,替代传统的 ObjectMapper。相比 Jackson,Hutool 无需构造实例,无受检异常,代码更简洁。

重试工具类 :基于 Spring Retry 的 RetryTemplate,封装了 RocksDB 操作的重试逻辑:

  • 默认重试 3 次,间隔 100ms 固定退避
  • 自定义 RocksDBOperation 函数式接口,允许抛出 RocksDBException 受检异常
  • 提供无返回值和有返回值两种重载方法

3.5 业务层:UserService 完整示例

业务层是架构的核心调度者,负责编排 PostgreSQL 和 RocksDB 的读写顺序。

写入示例

  • 先通过 JPA Repository 写入 PostgreSQL
  • 再调用 rocksDBService.putObject() 写入缓存
  • 缓存写入失败仅记录日志,不影响主事务

查询示例

  • 优先调用 rocksDBService.getObject() 从缓存读取
  • 缓存命中则直接返回
  • 缓存未命中则回源 userRepository.findById()
  • 使用 CompletableFuture.runAsync() 异步回填缓存

删除示例

  • 先调用 userRepository.deleteById() 删除主数据
  • 再通过 RocksDBRetryUtil.executeWithRetry() 删除缓存
  • 缓存删除失败时自动重试 3 次

四、关键技术细节

4.1 默认配置路径

Spring 占位符 ${rocksdb.data-dir:/data/app} 的解析机制:

配置情况 实际使用路径
配置文件未定义 rocksdb.data-dir /data/app
配置文件定义为 /custom/path /custom/path
配置文件定义为空值 /data/app

4.2 异步缓存回填

查询未命中时,使用 CompletableFuture.runAsync() 异步回填缓存,避免缓存未命中时的额外延迟,保证读路径的响应速度。

4.3 缓存一致性策略

本方案采用 Cache-Aside Pattern(旁路缓存) 模式:

  • 写入:先写数据库,再写缓存(或删除缓存)
  • 读取:先读缓存,未命中则读数据库并回填
  • 删除:先删数据库,再删缓存

对于强一致性场景,建议配合 Debezium CDC 实现异步同步,确保最终一致性。

4.4 重试与容错

RocksDB 写入/删除失败时,通过 Spring Retry 自动重试 3 次(间隔 100ms)。若重试全部失败,记录错误日志并可由监控系统告警,或发往死信队列等待补偿处理。


五、性能优化建议

  1. 布隆过滤器:点查询场景下启用布隆过滤器,可大幅减少不必要的磁盘 I/O
  2. 合理设置缓存大小 :根据服务器内存设置 BlockBasedTableConfig 的 LRU 缓存大小
  3. 批量写入 :对大批量数据使用 WriteBatch 批量提交,减少写放大
  4. 列族管理:按业务模块划分列族,实现粒度更细的资源隔离
  5. 定期 Compaction:根据业务负载配置合适的 Compaction 策略,平衡读放大和写放大
  6. 监控指标 :导出 RocksDB 的 statisticsperf_context,接入 Prometheus 监控

六、总结

本文介绍了如何在 Spring Boot 项目中集成 RocksDB,构建 PostgreSQL + RocksDB 的协同存储架构。通过配置层、服务层、工具层、业务层四层封装,实现了:

  • ✅ 零侵入的业务代码(对上层透明)
  • ✅ 毫秒级热点数据查询
  • ✅ 自动序列化与反序列化(基于 Hutool)
  • ✅ 自动重试与容错机制
  • ✅ 默认配置路径的优雅降级

这种架构适用于高并发查询、热点数据缓存、冷热数据分离等多种场景,是提升系统性能的有效手段。


代码如下:

xml 复制代码
<properties>
	<retry.version>2.0.12</retry.version>
	<hutool.version>5.8.44</hutool.version>
	<rocksdb.version>10.10.1.1</rocksdb.version>
</properties>
 <dependencies>
   <!-- Spring Retry -->
	<dependency>
		<groupId>org.springframework.retry</groupId>
		<artifactId>spring-retry</artifactId>
		<version>${retry.version}</version>
	</dependency>
	<dependency>
		<groupId>org.rocksdb</groupId>
		<artifactId>rocksdbjni</artifactId>
		<version>${rocksdb.version}</version>
	</dependency>
	<!-- json模块 -->
	<dependency>
		<groupId>cn.hutool</groupId>
		<artifactId>hutool-json</artifactId>
		<version>${hutool.version}</version>
	</dependency>
</dependencies>
java 复制代码
package com.cqcloud.platform.common.matter.config;

import com.cqcloud.platform.common.matter.utils.RocksDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;

import java.io.File;

import org.rocksdb.*;
/**
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2024年3月4日 🐬🐇 💓💕
 */
@Slf4j
@Configuration
public class RocksDBConfig {
   /**
     * 配置项:rocksdb.data-dir
     * 如果 application.yml 中未配置,则使用默认值 /data/app
     */
    @Value("${rocksdb.data-dir:/data/app}")
    private String dataDir;
    @Bean
    public RocksDB rocksDB() {
        try {
            // 加载本地库
            RocksDB.loadLibrary();

            // 确保目录存在
            File dbDir = new File(dataDir);
            if (!dbDir.exists()) {
                boolean created = dbDir.mkdirs();
                if (!created) {
                    throw new RuntimeException("无法创建 RocksDB 数据目录: " + dataDir);
                }
                log.info("已创建 RocksDB 数据目录: {}", dataDir);
            }

            // 配置选项
            try (Options options = new Options()) {
                options.setCreateIfMissing(true);
                options.setCreateMissingColumnFamilies(true);
                options.setMaxBackgroundJobs(4);
                options.setMaxOpenFiles(-1);
                options.setAllowConcurrentMemtableWrite(true);

                // 布隆过滤器
                BlockBasedTableConfig tableConfig = new BlockBasedTableConfig();
                tableConfig.setFilterPolicy(new BloomFilter(10, false));
                tableConfig.setWholeKeyFiltering(true);
                tableConfig.setCacheIndexAndFilterBlocks(true);
                options.setTableFormatConfig(tableConfig);

                log.info("正在打开 RocksDB,数据目录: {}", dataDir);
                RocksDB db = RocksDB.open(options, dataDir);
                log.info("RocksDB 启动成功");
                return db;
            }
        } catch (RocksDBException e) {
            log.error("RocksDB 初始化失败", e);
            throw new RuntimeException("RocksDB 初始化失败", e);
        }
    }

    @Bean
    public RocksDBService rocksDBService(RocksDB rocksDB) {
        return new RocksDBService(rocksDB);
    }
}
java 复制代码
package com.cqcloud.platform.common.matter.utils;

import cn.hutool.json.JSONUtil;

/**
 *  JSON 序列化工具类(基于 Hutool)
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2024年3月4日 🐬🐇 💓💕
 */
public class RocksDBJsonUtil {
    /**
     * 对象转 JSON 字符串
     */
    public static String toJson(Object obj) {
        return JSONUtil.toJsonStr(obj);
    }

    /**
     * JSON 字符串转对象
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        return JSONUtil.toBean(json, clazz);
    }

    /**
     * JSON 字符串转对象(带默认值,解析失败返回默认对象)
     */
    public static <T> T fromJsonOrDefault(String json, Class<T> clazz, T defaultValue) {
        try {
            return JSONUtil.toBean(json, clazz);
        } catch (Exception e) {
            return defaultValue;
        }
    }

    /**
     * 格式化输出(用于日志打印)
     */
    public static String toJsonPretty(Object obj) {
        return JSONUtil.toJsonPrettyStr(obj);
    }
}
java 复制代码
package com.cqcloud.platform.common.matter.utils;


import lombok.extern.slf4j.Slf4j;
import org.rocksdb.RocksDBException;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

import java.util.Collections;


/**
 * RocksDB 重试工具类
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2024年3月4日 🐬🐇 💓💕
 */
@Slf4j
public class RocksDBRetryUtil {

    private static final RetryTemplate RETRY_TEMPLATE = new RetryTemplate();

    static {
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
                3,
                Collections.singletonMap(RocksDBException.class, true)
        );
        RETRY_TEMPLATE.setRetryPolicy(retryPolicy);

        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(100);
        RETRY_TEMPLATE.setBackOffPolicy(backOffPolicy);
    }

    /**
     * 自定义函数式接口:允许抛出 RocksDBException
     */
    @FunctionalInterface
    public interface RocksDBOperation {
        void execute() throws RocksDBException;
    }

    /**
     * 执行普通 Runnable(不需要处理受检异常的场景)
     */
    public static void executeWithRetry(Runnable operation, String key) {
        try {
            RETRY_TEMPLATE.execute((RetryCallback<Void, RocksDBException>) context -> {
                operation.run();
                return null;
            });
        } catch (RocksDBException e) {
            log.error("RocksDB 操作失败,已重试3次,key: {}", key, e);
        }
    }

    /**
     * 执行 RocksDB 专用操作(需要抛出 RocksDBException 的场景)
     */
    public static void executeWithRetry(RocksDBOperation operation, String key) {
        try {
            RETRY_TEMPLATE.execute((RetryCallback<Void, RocksDBException>) context -> {
                operation.execute();
                return null;
            });
        } catch (RocksDBException e) {
            log.error("RocksDB 操作失败,已重试3次,key: {}", key, e);
        }
    }
}
java 复制代码
package com.cqcloud.platform.common.matter.utils;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.WriteBatch;
import org.rocksdb.WriteOptions;

import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;

/**
 * RocksDB 基础操作服务
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2024年3月4日 🐬🐇 💓💕
 */
@Slf4j
public class RocksDBService {

    private final RocksDB rocksDB;

    public RocksDBService(RocksDB rocksDB) {
        this.rocksDB = rocksDB;
    }

    // ==================== 字符串操作 ====================

    public void put(String key, String value) throws RocksDBException {
        try {
            rocksDB.put(key.getBytes(), value.getBytes());
            log.debug("RocksDB put 成功: key={}", key);
        } catch (RocksDBException e) {
            log.error("RocksDB put 失败: key={}", key, e);
            throw e;
        }
    }

    public Optional<String> get(String key) {
        try {
            byte[] value = rocksDB.get(key.getBytes());
            return value != null ? Optional.of(new String(value)) : Optional.empty();
        } catch (RocksDBException e) {
            log.error("RocksDB get 失败: key={}", key, e);
            return Optional.empty();
        }
    }

    public void delete(String key) throws RocksDBException {
        try {
            rocksDB.delete(key.getBytes());
            log.debug("RocksDB delete 成功: key={}", key);
        } catch (RocksDBException e) {
            log.error("RocksDB delete 失败: key={}", key, e);
            throw e;
        }
    }

    public boolean exists(String key) {
        // 最简单的用法,只传入 key
        return rocksDB.keyMayExist(ByteBuffer.wrap(key.getBytes()));
    }

    // ==================== 批量操作 ====================

    public void batchPut(Map<String, String> keyValues) throws RocksDBException {
        try (WriteBatch batch = new WriteBatch();
             WriteOptions writeOptions = new WriteOptions()) {
            writeOptions.setSync(false);
            for (Map.Entry<String, String> entry : keyValues.entrySet()) {
                batch.put(entry.getKey().getBytes(), entry.getValue().getBytes());
            }
            rocksDB.write(writeOptions, batch);
            log.info("RocksDB 批量写入成功,数量: {}", keyValues.size());
        }
    }

    public void batchDelete(String... keys) throws RocksDBException {
        try (WriteBatch batch = new WriteBatch();
             WriteOptions writeOptions = new WriteOptions()) {
            for (String key : keys) {
                batch.delete(key.getBytes());
            }
            rocksDB.write(writeOptions, batch);
            log.info("RocksDB 批量删除成功,数量: {}", keys.length);
        }
    }

    // ==================== 对象操作(使用 Hutool JSON) ====================

    /**
     * 直接存储 Java 对象(自动转 JSON)
     */
    public void putObject(String key, Object obj) throws RocksDBException {
        String json = JSONUtil.toJsonStr(obj);
        put(key, json);
    }

    /**
     * 读取并自动反序列化为指定类型
     */
    public <T> Optional<T> getObject(String key, Class<T> clazz) {
        Optional<String> json = get(key);
        if (json.isPresent()) {
            try {
                return Optional.of(JSONUtil.toBean(json.get(), clazz));
            } catch (Exception e) {
                log.error("JSON 反序列化失败: key={}, type={}", key, clazz.getName(), e);
            }
        }
        return Optional.empty();
    }
}

调用案例

java 复制代码
package com.cqcloud.platform.common.matter.utils;


import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.rocksdb.RocksDBException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

/**
 * 调用案例
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2024年3月4日 🐬🐇 💓💕
 */
@Slf4j
@AllArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    private final RocksDBService rocksDBService;
    
    private static final String KEY_PREFIX = "user:";

    private String buildKey(Long id) {
        return KEY_PREFIX + id;
    }

    // ==================== 写入 ====================
    @Transactional
    public SysUser save(SysUser user) {
        // 1. 写入 PostgreSQL
        SysUser saved = userRepository.save(user);

        // 2. 同步到 RocksDB(使用 Hutool 序列化)
        String key = buildKey(saved.getId());
        try {
            String json = JSONUtil.toJsonStr(saved);  // ← Hutool 序列化
            rocksDBService.put(key, json);
        } catch (Exception e) {
            log.error("同步到 RocksDB 失败: key={}", key, e);
        }
        return saved;
    }

    // ==================== 使用 putObject 的简化写入 ====================
    @Transactional
    public SysUser saveV2(SysUser user) {
        SysUser saved = userRepository.save(user);
        String key = buildKey(saved.getId());
        try {
            rocksDBService.putObject(key, saved);  // ← 直接存对象
        } catch (Exception e) {
            log.error("同步到 RocksDB 失败: key={}", key, e);
        }
        return saved;
    }

    // ==================== 查询(优先缓存,未命中回源) ====================
    public Optional<SysUser> findById(Long id) {
        String key = buildKey(id);

        // 1. 优先从 RocksDB 查询(方式一:手动反序列化)
        try {
            Optional<String> json = rocksDBService.get(key);
            if (json.isPresent()) {
                SysUser user = JSONUtil.toBean(json.get(), SysUser.class);  // ← Hutool 反序列化
                return Optional.of(user);
            }
        } catch (Exception e) {
            log.warn("RocksDB 查询失败,回退到 PostgreSQL: key={}", key, e);
        }

        // 2. 回源 PostgreSQL
        Optional<SysUser> userOpt = userRepository.findById(id);

        // 3. 异步回填缓存
        userOpt.ifPresent(user ->
                CompletableFuture.runAsync(() -> {
                    try {
                        String json = JSONUtil.toJsonStr(user);  // ← Hutool 序列化
                        rocksDBService.put(key, json);
                    } catch (Exception ex) {
                        log.error("异步回填缓存失败: key={}", key, ex);
                    }
                })
        );

        return userOpt;
    }

    // ==================== 使用 getObject 的简化查询 ====================
    public Optional<SysUser> findByIdV2(Long id) {
        String key = buildKey(id);

        // 直接取对象
        Optional<SysUser> cached = rocksDBService.getObject(key, SysUser.class);
        if (cached.isPresent()) {
            return cached;
        }

        // 回源并回填
        Optional<SysUser> fromDb = userRepository.findById(id);
        fromDb.ifPresent(user ->
                CompletableFuture.runAsync(() -> {
                    try {
                        rocksDBService.putObject(key, user);
                    } catch (Exception e) {
                        log.error("回填失败: key={}", key, e);
                    }
                })
        );
        return fromDb;
    }

    // ==================== 删除 ====================
    @Transactional
    public void deleteById(Long id) {
        String key = buildKey(id);
        userRepository.deleteById(id);

        RocksDBRetryUtil.executeWithRetry(
                (Runnable) () -> {
                    try {
                        rocksDBService.delete(key);
                    } catch (RocksDBException e) {
                        throw new RuntimeException(e);
                    }
                },
                key
        );
    }
}
相关推荐
TeamDev1 小时前
如何在 DotNetBrowser 中使用本地 AI 模型
前端·后端·.net
BENA ceic1 小时前
Spring 的三种注入方式?
java·数据库·spring
小雅痞2 小时前
[Java][Leetcode middle] 209. 长度最小的子数组
java·算法·leetcode
二哈赛车手2 小时前
新人笔记---项目中简易版的RAG检索后评测指标(@Recall ,Mrr..)实现
java·开发语言·笔记·spring·ai
做时间的朋友。2 小时前
精准核酸检测
java·数据结构·算法
Rust语言中文社区2 小时前
【Rust日报】2026-05-02 Temper - 用 Rust 编写的 Minecraft 服务器项目发布 0.1.0 版
运维·服务器·开发语言·后端·rust
许彰午2 小时前
CacheSQL(五):桥接篇
java·数据库·缓存·系统架构
NaMM CHIN2 小时前
Spring Boot + Spring AI快速体验
人工智能·spring boot·spring
陈随易2 小时前
2年没用Nodejs了,Bun很香
前端·后端·程序员