焦点关键词主题博文配图,突出核心要点与实用价值

Meta In-Memory KV 面试题 2026:时间戳存储与历史回溯高分指南

Meta In-Memory KV 面试题 2026:时间戳存储与历史回溯高分指南

Meta In-Memory KV 面试题 2026 是高频真题。并且,它在 2026 年更常出现。来源上,这是我们学员贡献的最新面经。
此外,这题覆盖实现与沟通两线。你要写对代码。你也要讲清权衡。

2026 面试流程深度复盘:Meta In-Memory KV 面试题 2026

首先,面试官常从 set/get 开场。然后追问 compareAndUpdate。但是重点不在语法。重点在语义一致。
接着,会追加 scanscanByPrefix。因此你要主动选 TreeMap。你还要解释前缀上界。
然后,题目会加入 TTL。与此同时,面试官会检查过期一致性。所有读写入口都要先判过期。
因此,Meta In-Memory KV 面试题 2026 的节奏很固定。先基础。再查询。再 TTL。最后历史回溯。
此外,Meta In-Memory KV 面试题 2026 在收尾时,常问空间成本。你要明确说出。历史全量记录更占内存。

核心题目解析

具体来说,核心结构可分两层。当前态用 Map<key, TreeMap<field, Record>>。历史态用 Map<key, Map<field, TreeMap<ts, Record>>>
此外,Record 至少含四项。它们是 valueexpireAthasTTLtombstone。这样才能区分删除与未存在。
与此同时,前缀查询要走有序范围。你可构造 upperPrefix = prefix + Character.MAX_VALUE。再用 subMap(prefix, true, upperPrefix, false)

flowchart TD
A[API 请求] --> B[按 key 定位主 Map]
B --> C[按 field 定位 TreeMap]
C --> D{操作分支}
D -->|get / scan| E[先做 TTL 检查]
E --> F[返回可见值]
D -->|set / CAS| G[写入当前态]
G --> H[写入历史 TreeMap<ts,Record>]
H --> I[getValueAt 用 floorEntry]
D -->|scanByPrefix| J[subMap 前缀范围]

此外,下面是可面试直用的 Java 参考实现。

import java.util.*;

public class TimeKVStore {
    // 单条记录: 支持值、TTL 与删除标记
    static class Record {
        final String value;
        final long expireAt;
        final boolean hasTTL;
        final boolean tombstone;

        Record(String value, long expireAt, boolean hasTTL, boolean tombstone) {
            this.value = value;
            this.expireAt = expireAt;
            this.hasTTL = hasTTL;
            this.tombstone = tombstone;
        }

        static Record alive(String value) {
            return new Record(value, 0L, false, false);
        }

        static Record aliveWithTTL(String value, long expireAt) {
            return new Record(value, expireAt, true, false);
        }

        static Record tombstone() {
            return new Record(null, 0L, false, true);
        }

        boolean expiredAt(long ts) {
            return hasTTL && ts >= expireAt;
        }
    }

    // 当前可见状态: key -> (field 有序映射)
    private final Map<String, TreeMap<String, Record>> latest = new HashMap<>();
    // 历史版本: key -> field -> (timestamp -> record)
    private final Map<String, Map<String, TreeMap<Long, Record>>> history = new HashMap<>();

    public void set(long ts, String key, String field, String value) {
        writeCurrent(key, field, Record.alive(value));
        appendHistory(ts, key, field, Record.alive(value));
    }

    public void setWithTTL(long ts, String key, String field, String value, long ttl) {
        long expireAt = ts + ttl;
        Record r = Record.aliveWithTTL(value, expireAt);
        writeCurrent(key, field, r);
        appendHistory(ts, key, field, r);
    }

    public String get(long ts, String key, String field) {
        Record r = visibleNow(ts, key, field, true);
        return r == null ? null : r.value;
    }

    public boolean compareAndUpdate(long ts, String key, String field,
                                    String expected, String newValue) {
        String cur = get(ts, key, field);
        if (!Objects.equals(cur, expected)) return false;
        set(ts, key, field, newValue);
        return true;
    }

    public boolean compareAndUpdateWithTTL(long ts, String key, String field,
                                           String expected, String newValue, long ttl) {
        String cur = get(ts, key, field);
        if (!Objects.equals(cur, expected)) return false;
        setWithTTL(ts, key, field, newValue, ttl);
        return true;
    }

    public boolean compareAndDelete(long ts, String key, String field, String expected) {
        String cur = get(ts, key, field);
        if (!Objects.equals(cur, expected)) return false;
        TreeMap<String, Record> fields = latest.get(key);
        if (fields != null) fields.remove(field);
        appendHistory(ts, key, field, Record.tombstone());
        return true;
    }

    public List<String> scan(long ts, String key) {
        List<String> ans = new ArrayList<>();
        TreeMap<String, Record> fields = latest.get(key);
        if (fields == null) return ans;

        Iterator<Map.Entry<String, Record>> it = fields.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Record> e = it.next();
            Record r = e.getValue();
            if (r.tombstone) continue;
            if (r.expiredAt(ts)) {
                it.remove(); // 过期清理, 保证后续路径一致
                continue;
            }
            ans.add(e.getKey() + "=" + r.value);
        }
        return ans;
    }

    public List<String> scanByPrefix(long ts, String key, String prefix) {
        List<String> ans = new ArrayList<>();
        TreeMap<String, Record> fields = latest.get(key);
        if (fields == null) return ans;

        String upperPrefix = prefix + Character.MAX_VALUE;
        NavigableMap<String, Record> range =
                fields.subMap(prefix, true, upperPrefix, false);

        Iterator<Map.Entry<String, Record>> it = range.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, Record> e = it.next();
            Record r = e.getValue();
            if (r.tombstone) continue;
            if (r.expiredAt(ts)) {
                it.remove();
                continue;
            }
            ans.add(e.getKey() + "=" + r.value);
        }
        return ans;
    }

    // 在 atTimestamp 时刻回溯值
    public String getValueAt(long ts, String key, String field, long atTimestamp) {
        if (atTimestamp > ts) return null; // 不查未来时间
        Map<String, TreeMap<Long, Record>> byField = history.get(key);
        if (byField == null) return null;
        TreeMap<Long, Record> timeline = byField.get(field);
        if (timeline == null) return null;

        Map.Entry<Long, Record> floor = timeline.floorEntry(atTimestamp);
        if (floor == null) return null;

        Record r = floor.getValue();
        if (r.tombstone) return null;
        if (r.expiredAt(atTimestamp)) return null;
        return r.value;
    }

    private void writeCurrent(String key, String field, Record r) {
        latest.computeIfAbsent(key, k -> new TreeMap<>()).put(field, r);
    }

    private void appendHistory(long ts, String key, String field, Record r) {
        history.computeIfAbsent(key, k -> new HashMap<>())
               .computeIfAbsent(field, f -> new TreeMap<>())
               .put(ts, r);
    }

    private Record visibleNow(long ts, String key, String field, boolean purgeExpired) {
        TreeMap<String, Record> fields = latest.get(key);
        if (fields == null) return null;
        Record r = fields.get(field);
        if (r == null || r.tombstone) return null;
        if (r.expiredAt(ts)) {
            if (purgeExpired) fields.remove(field);
            return null;
        }
        return r;
    }
}

但是,写完代码不够。你还要给复杂度。set/get/CAS 平均是 O(1)O(logF)scanscanByPrefix 取决于命中字段数。
换句话说,getValueAt 关键在 floorEntry。它是 O(logV)F 是字段数。V 是版本数。

专家备考策略与高频考点:Meta In-Memory KV 面试题 2026

换句话说,Meta In-Memory KV 面试题 2026 不只考代码。它也考你是否像工程师。你要主动讲边界与取舍。
此外,下面是你必须背熟的核心考点。

  1. 因此,先讲两层映射建模。
  2. 此外,解释为何字段层用 TreeMap
  3. 但是,强调 CAS 先比较再写入。
  4. 与此同时,说明 TTL 放在 Record 内。
  5. 因此,所有读写都先判过期。
  6. 具体来说,历史层按时间有序存。
  7. 此外,删除要写 tombstone。
  8. 然后,回溯用 floorEntry(atTimestamp)
  9. 但是,结果还要再判 TTL。
  10. 总而言之,空间换时间要说清。

此外,BQ 建议用 STAR 快速作答。

  1. 因此,冲突更新题。S: 并发更新频繁。T: 保证正确写入。A: 设计 CAS 语义。R: 错写率下降。
  2. 此外,过期数据题。S: 脏读影响业务。T: 保持读写一致。A: 全路径先判过期。R: 查询稳定。
  3. 但是,历史追溯题。S: 审计要回看旧值。T: 支持任意时间点。A: 版本树加 tombstone。R: 可追踪且可解释。
  4. 与此同时,成本权衡题。S: 内存吃紧。T: 控制空间增长。A: 设保留窗与压缩。R: 性能和成本平衡。

总结与行动号召(CTA)

总而言之,Meta In-Memory KV 面试题 2026 的高分点很清楚。你要先给正确实现。然后给一致性证明。最后讲工程权衡。
因此,如果你要在 2026 面试中稳过这题,现在就做一次限时模拟。

此外,预约辅导请点这里:联系我们的专家进行一对一面试辅导
与此同时,算法扩展阅读可看:权威算法参考