You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RocksDB是否支持嵌套键存储与基于嵌套键的取值?

在RocksDB中实现嵌套键值结构(对应Map<Type1, Map<Type2, Type3>>)

RocksDB本身是单层级键值存储,没有原生的嵌套键支持,但你可以通过复合键拼接的方式轻松模拟Map<Type1, Map<Type2, Type3>>这种结构,完全满足你的需求。

核心思路

把外层键Type1和内层键Type2序列化为字节数组后,用一个唯一分隔符拼接成单个复合键,作为RocksDB的Key;Type3直接序列化为字节数组作为Value。这样既可以通过Type1+Type2的组合键精准获取对应值,也能利用RocksDB的有序特性,批量查询某个Type1下所有的Type2->Type3键值对。

具体实现步骤

  1. 键值序列化:为Type1、Type2、Type3实现统一的序列化/反序列化逻辑(比如用Jackson处理自定义对象,基础类型直接转字节数组)。
  2. 复合键拼接:在Type1和Type2的字节数组之间插入一个不会出现在键内容中的特殊字节(比如0x00),避免不同键组合出现歧义。
  3. 操作封装:封装put、get、按外层键批量查询的方法,隐藏复合键的拼接和解析细节。

Java Spring Boot 示例代码

假设Type1是String,Type2是Integer,Type3是自定义POJO(如UserInfo),用Jackson做序列化:

依赖配置

<!-- 已有的RocksDB依赖 -->
<dependency>
    <groupId>org.rocksdb</groupId>
    <artifactId>rocksdbjni</artifactId>
    <version>7.5.3</version> <!-- 替换为你使用的版本 -->
</dependency>
<!-- Jackson 用于对象序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

RocksDB操作封装类

import org.rocksdb.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Component
public class NestedRocksDBManager {
    private RocksDB rocksDB;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private static final byte KEY_SEPARATOR = 0x00; // 键分隔符,避免冲突

    @PostConstruct
    public void initDB() throws RocksDBException {
        RocksDB.loadLibrary();
        Options options = new Options().setCreateIfMissing(true);
        rocksDB = RocksDB.open(options, "./rocksdb/nested-store"); // 自定义存储路径
    }

    // 存储嵌套键值对
    public <T1, T2, T3> void put(T1 outerKey, T2 innerKey, T3 value) throws IOException, RocksDBException {
        byte[] outerBytes = serialize(outerKey);
        byte[] innerBytes = serialize(innerKey);
        byte[] compositeKey = buildCompositeKey(outerBytes, innerBytes);
        byte[] valueBytes = serialize(value);
        rocksDB.put(compositeKey, valueBytes);
    }

    // 通过外层+内层键获取值
    public <T1, T2, T3> T3 get(T1 outerKey, T2 innerKey, Class<T3> valueClass) throws IOException, RocksDBException {
        byte[] outerBytes = serialize(outerKey);
        byte[] innerBytes = serialize(innerKey);
        byte[] compositeKey = buildCompositeKey(outerBytes, innerBytes);
        byte[] valueBytes = rocksDB.get(compositeKey);
        return valueBytes != null ? deserialize(valueBytes, valueClass) : null;
    }

    // 批量获取某个外层键下的所有内层键值对
    public <T1, T2, T3> List<InnerEntry<T2, T3>> getByOuterKey(T1 outerKey, Class<T2> innerKeyClass, Class<T3> valueClass) throws IOException, RocksDBException {
        byte[] outerBytes = serialize(outerKey);
        byte[] startRange = buildCompositeKey(outerBytes, new byte[0]);
        byte[] endRange = incrementLastByte(startRange); // 构造范围查询的结束边界

        List<InnerEntry<T2, T3>> entries = new ArrayList<>();
        try (RocksIterator iterator = rocksDB.newIterator()) {
            iterator.seek(startRange);
            while (iterator.isValid()) {
                byte[] currentKey = iterator.key();
                if (!isOuterKeyMatch(currentKey, outerBytes)) {
                    break;
                }
                // 解析内层键
                byte[] innerBytes = extractInnerKey(currentKey, outerBytes.length + 1);
                T2 innerKey = deserialize(innerBytes, innerKeyClass);
                T3 value = deserialize(iterator.value(), valueClass);
                entries.add(new InnerEntry<>(innerKey, value));
                iterator.next();
            }
        }
        return entries;
    }

    // 序列化对象为字节数组
    private <T> byte[] serialize(T obj) throws IOException {
        if (obj instanceof String) {
            return ((String) obj).getBytes(StandardCharsets.UTF_8);
        } else if (obj instanceof Integer) {
            return intToBytes((Integer) obj);
        } else {
            return objectMapper.writeValueAsBytes(obj);
        }
    }

    // 反序列化字节数组为对象
    private <T> T deserialize(byte[] bytes, Class<T> clazz) throws IOException {
        if (clazz == String.class) {
            return clazz.cast(new String(bytes, StandardCharsets.UTF_8));
        } else if (clazz == Integer.class) {
            return clazz.cast(bytesToInt(bytes));
        } else {
            return objectMapper.readValue(bytes, clazz);
        }
    }

    // 构建复合键
    private byte[] buildCompositeKey(byte[] outerKey, byte[] innerKey) {
        byte[] composite = new byte[outerKey.length + 1 + innerKey.length];
        System.arraycopy(outerKey, 0, composite, 0, outerKey.length);
        composite[outerKey.length] = KEY_SEPARATOR;
        System.arraycopy(innerKey, 0, composite, outerKey.length + 1, innerKey.length);
        return composite;
    }

    // 提取内层键
    private byte[] extractInnerKey(byte[] compositeKey, int startIndex) {
        byte[] innerKey = new byte[compositeKey.length - startIndex];
        System.arraycopy(compositeKey, startIndex, innerKey, 0, innerKey.length);
        return innerKey;
    }

    // 检查复合键是否匹配指定外层键
    private boolean isOuterKeyMatch(byte[] compositeKey, byte[] outerKey) {
        if (compositeKey.length < outerKey.length + 1) return false;
        for (int i = 0; i < outerKey.length; i++) {
            if (compositeKey[i] != outerKey[i]) return false;
        }
        return compositeKey[outerKey.length] == KEY_SEPARATOR;
    }

    // 递增最后一个字节,构造范围查询的结束键
    private byte[] incrementLastByte(byte[] key) {
        byte[] endKey = key.clone();
        endKey[endKey.length - 1]++;
        return endKey;
    }

    // int与字节数组互转
    private byte[] intToBytes(int value) {
        return new byte[]{(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value};
    }

    private int bytesToInt(byte[] bytes) {
        return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);
    }

    @PreDestroy
    public void closeDB() {
        if (rocksDB != null) rocksDB.close();
    }

    // 封装内层键值对的实体类
    public static class InnerEntry<K, V> {
        private K key;
        private V value;

        public InnerEntry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        // getter & setter
        public K getKey() { return key; }
        public void setKey(K key) { this.key = key; }
        public V getValue() { return value; }
        public void setValue(V value) { this.value = value; }
    }
}

使用示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import org.rocksdb.RocksDBException;
import java.util.List;

@Service
public class DemoService {
    @Autowired
    private NestedRocksDBManager dbManager;

    public void testNestedStorage() throws IOException, RocksDBException {
        // 存储数据
        dbManager.put("user_group_1", 1001, new UserInfo("Alice", 25));
        dbManager.put("user_group_1", 1002, new UserInfo("Bob", 30));
        dbManager.put("user_group_2", 1001, new UserInfo("Charlie", 28));

        // 获取单个值
        UserInfo alice = dbManager.get("user_group_1", 1001, UserInfo.class);
        System.out.println(alice.getName()); // 输出 Alice

        // 批量获取某个分组下的所有用户
        List<NestedRocksDBManager.InnerEntry<Integer, UserInfo>> group1Users = dbManager.getByOuterKey("user_group_1", Integer.class, UserInfo.class);
        group1Users.forEach(entry -> {
            System.out.printf("用户ID:%d,姓名:%s%n", entry.getKey(), entry.getValue().getName());
        });
    }
}

// 自定义POJO
class UserInfo {
    private String name;
    private int age;

    // 无参构造器(Jackson序列化需要)
    public UserInfo() {}
    public UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter & setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

关键注意事项

  • 序列化一致性:确保同类型的键/值使用完全相同的序列化逻辑,否则会出现解析失败或数据不匹配的问题。
  • 分隔符选择:必须选择一个不会出现在Type1或Type2序列化结果中的字节,比如0x00(如果你的键不会包含空字节),否则会导致键冲突。
  • 性能优化:RocksDB的键是有序存储的,按外层键批量查询的效率很高,适合这种嵌套场景;如果需要更高性能,可以考虑使用前缀布隆过滤器(Prefix Bloom Filter)。

内容的提问来源于stack exchange,提问作者Praveen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 08:59:17