动态扩容神技!手写 Redis 渐进式 rehash,面试官直接跪拜!

摘要 (#摘要)

引言

Redis 的哈希表性能关键在于动态扩缩容,而 渐进式 rehash 是其核心优化,避免一次性 rehash 导致的性能抖动。在高并发场景下,渐进式 rehash 分批迁移数据,保持低延迟,广泛应用于 Redis 的字典实现。面试中,“如何实现 Redis 的渐进式 rehash?” 是大厂高频考题,考察你对哈希表优化和并发设计的理解。继前六篇实现基础缓存、键过期、AOF、LRU、多种淘汰策略和自定义 HashMap 后,本篇带你手写 ProgressiveHashMap,实现渐进式 rehash,整合到 ProgressiveCache,让你在面试中直接让面试官跪拜!
目标:

  • 实现 ProgressiveHashMap,支持渐进式 rehash 和动态扩缩容。

  • 整合到 ProgressiveCache,兼容 TTL、AOF 和淘汰策略。

  • 提供可运行代码、测试用例、图表和面试要点,助你深入学习。

适合人群:

  • 想深入理解 Redis 哈希表优化的开发者。

  • 准备大厂面试、需手写渐进式 rehash 的程序员。

  • 对高并发数据结构和性能优化感兴趣的初学者和进阶者。
    前置工作:

  • 环境:Java 8+,Maven/Gradle。

  • 依赖:前六篇的 SimpleCache、ExpiryCache、AofCache、LruCache、MultiEvictionCache、HashMapCache 类(本篇继承)。

  • 代码仓库:https://github.com/codeInbpm/redis-handwritten.git。

  • 建议:运行前六篇代码后,跟着本篇敲代码,体验渐进式 rehash 的性能魔法!
    渐进式rehash

为什么需要渐进式 rehash?

传统哈希表(如 CustomHashMap)在扩容时一次性 rehash 所有键,导致高延迟,尤其在高并发或大数据量场景下。Redis 的渐进式 rehash 通过以下方式优化:

  • 分批迁移:每次 get 或 put 只迁移部分键,分摊开销。

  • 双表结构:维护旧表和新表,查询时检查两表,逐步迁移。

  • 动态缩容:支持容量缩减,优化内存使用。

  • 高并发:避免长时间阻塞,适合实时系统。

面试常见问题:

  • “Redis 的渐进式 rehash 如何实现?与一次性 rehash 相比优势是什么?”

  • “如何在高并发场景下保证 rehash 一致性?”

  • “缩容的触发条件和实现方式是什么?”
    本篇将实现 ProgressiveHashMap,支持渐进式扩缩容,整合到 ProgressiveCache,并对比 Redis 和 JDK HashMap 的 rehash 机制。

实现步骤(详细展开)

以下是实现 ProgressiveHashMapProgressiveCache 的五个详细步骤,确保用户清晰理解,易于学习和实践。

步骤 1:设计渐进式 rehash 数据结构

目标:

  • 为 ProgressiveHashMap 设计支持渐进式 rehash 的数据结构。

详细说明:
双表结构:

  • 维护两个数组:table0(旧表)和 table1(新表)。

  • rehashIdx:记录当前迁移的桶索引,初始为 -1(无 rehash)。

Entry 类:

  • 继承 CustomHashMap.Entry,新增 expiry 字段支持 TTL:
    key, value, hash, next(同前篇)。

  • expiry:过期时间,兼容 TTL。

状态管理:

  • size:总键值对数量(两表之和)。

  • capacity0, capacity1:旧表和新表容量。

  • loadFactor:负载因子(如 0.75)。

  • rehashStep:每次操作迁移的桶数(如 1)。

扩缩容触发:

  • 扩容:size > capacity0 * loadFactor 且无 rehash 进行。

  • 缩容:size < capacity0 * lowLoadFactor(如 0.1)且无 rehash 进行。

要求:

  • 定义 Entry 类,兼容 TTL。
  • 使用 table0 和 table1 支持渐进式迁移。
  • 维护 rehashIdx 和 rehashStep 控制迁移进度。

要求:

  • 异常处理(如内存不足)。
  • 为何如此设计:
  • 双表结构是 Redis 渐进式 rehash 的核心,允许分批迁移。
  • rehashIdx 跟踪进度,确保一致性。
  • 缩容支持内存优化,参考 Redis 字典设计。
    渐进式HashMap

步骤 2:实现渐进式 rehash 机制

目标:

  • 实现渐进式 rehash 逻辑,分摊扩缩容开销。

详细说明:
触发 rehash:

  • 扩容:size > capacity0 * loadFactor,创建 table1(容量翻倍)。

  • 缩容:size < capacity0 * lowLoadFactor,创建 table1(容量减半,保持 2 的幂)。

  • 设置 rehashIdx = 0,标记 rehash 开始。

渐进迁移:

  • 每次 get, put, remove 调用 progressiveRehash。
  • progressiveRehash 迁移 rehashStep 个桶(从 table0 到 table1)。
  • 迁移后更新 rehashIdx,当 rehashIdx >= capacity0 时,完成 rehash,table0 = table1,table1 = null。

查询逻辑:

  • get 和 remove 检查 table0 和 table1(若 rehash 进行中)。
  • 计算哈希索引:hash & (capacity0 - 1)(旧表)或 hash & (capacity1 - 1)(新表)。

TTL 兼容:

  • 检查 Entry.expiry,过期键直接移除。

要求:

  • 每次操作迁移少量桶(如 1-5 个),避免阻塞。
  • 查询检查双表,确保数据一致性。
  • 完成 rehash 后清理 table1 和 rehashIdx。
  • 异常处理空输入、无效 TTL。

为何如此设计:

  • 分批迁移降低单次操作延迟,适合高并发。

  • 双表查询保证数据不丢失。

  • Redis 字典使用类似机制,rehashStep 控制迁移速度。
    渐进式rehash流程

步骤 3:整合到缓存系统

目标:

  • 将 ProgressiveHashMap 整合到 ProgressiveCache,替换 CustomHashMap,兼容前篇功能。

详细说明:
继承结构:

  • ProgressiveCache 继承 HashMapCache。

  • 替换 CustomHashMap<String, Node> 为 ProgressiveHashMap<String, Node>。

功能兼容:

  • LFU:更新 freqMap 和 accessCount。
  • CLOCK:维护 referenceBit 和 clockHand。
  • FIFO:维护 insertionOrder。
  • TTL:检查 Node.expiry。
  • AOF:调用父类的 appendToAof 和 loadAof。

操作调整:

  • get:检查双表,触发 progressiveRehash,更新淘汰策略状态。
  • put:插入或更新节点,触发 rehash,同步更新链表和频率映射。
  • removeEvictedNode:从双表移除节点。

要求:

  • ProgressiveHashMap 接口与 CustomHashMap 一致。
  • 保留 LFU/CLOCK/FIFO、TTL 和 AOF 功能。
  • rehash 不影响淘汰策略和持久化。
  • 保持 O(1) 或近似 O(1) 性能。

为何如此设计:

  • 替换 CustomHashMap 验证渐进式 rehash 的正确性。
  • 兼容前篇功能,确保系统完整性。
  • 为最终章(第8篇,重构与扩展)铺路。
    ProgressiveCache整合

步骤 4:优化扩缩容性能

目标:

  • 优化渐进式 rehash 的性能,降低延迟和内存开销。
    详细说明:
    迁移步长:
  • 设置 rehashStep = 1(默认),每次操作迁移 1 个桶。
  • 可动态调整(如高负载时增加步长)。
    内存管理:
  • 迁移完成即释放 table1,避免内存浪费。
  • 缩容时确保新容量不低于最小值(如 16)。
    查询优化:
  • 若 rehashIdx = -1,仅查询 table0,减少开销。
  • 优先检查 table1(若迁移进度较高),降低双表查询成本。

并发考虑:

  • 当前实现非线程安全,第8篇引入锁或无锁优化。
  • rehash 过程中保持数据一致性。’

要求:

  • 控制 rehashStep,平衡性能和延迟。

  • 优化双表查询,优先检查可能存在的表。

  • 异常处理内存分配失败。

为何如此设计:

  • 小步长迁移适合高并发,参考 Redis 的 ht[0] 和 ht[1] 设计。
  • 内存释放和查询优化提升效率。
  • 为并发优化(第8篇)预留空间。
    渐进式与一次性再哈希延迟

步骤 5:添加测试用例

目标:

  • 通过 JUnit 测试验证 ProgressiveHashMap 和 ProgressiveCache 的功能,覆盖 rehash、扩缩容、淘汰、TTL 和 AOF。

详细说明:
测试场景:

  • ProgressiveHashMap:验证 put, get, remove,渐进式 rehash,扩缩容。

  • ProgressiveCache:验证 LFU/CLOCK/FIFO 淘汰、TTL 过期、AOF 恢复。

  • 异常处理:测试空键/值、无效 TTL、内存不足。

  • 性能:插入大量键,验证 rehash 进度和数据一致性。

实现细节:

  • 使用 @Before 和 @After 清理 AOF 文件。

  • 测试扩容(插入超过容量)和缩容(移除大量键)。

  • 通过 Thread.sleep 测试 TTL 过期。

  • 模拟重启验证 AOF 恢复。

用户学习点:

  • 提供详细测试代码,读者可直接运行。

  • 附带输出解释,展示 rehash 行为。

要求:

  • 测试覆盖所有功能和边界条件。

  • 验证 rehash 过程中数据一致性。

  • 提供清晰断言和错误信息。

为何如此设计:

  • 全面测试确保代码可靠性。

  • 模拟真实场景(扩容、缩容、重启)帮助理解 rehash。

  • 详细注释便于学习和调试。
    在这里插入图片描述

核心代码实现

以下是 ProgressiveHashMapProgressiveCache 的完整实现,存放在 ProgressiveHashMap.java 和 ProgressiveCache.java。

代码 1:ProgressiveHashMap

public class ProgressiveHashMap<K, V> {
    private static class Entry<K, V> {
        K key;
        V value;
        int hash;
        long expiry;
        Entry<K, V> next;
        Entry(K key, V value, int hash, long expiry) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.expiry = expiry;
        }
    }

    private Entry<K, V>[] table0;
    private Entry<K, V>[] table1;
    private int size;
    private int capacity0;
    private int capacity1;
    private final float loadFactor;
    private final float lowLoadFactor;
    private int rehashIdx;
    private final int rehashStep;
    private static final int INITIAL_CAPACITY = 16;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    private static final float LOW_LOAD_FACTOR = 0.1f;

    @SuppressWarnings("unchecked")
    public ProgressiveHashMap() {
        this.capacity0 = INITIAL_CAPACITY;
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        this.lowLoadFactor = LOW_LOAD_FACTOR;
        this.table0 = new Entry[INITIAL_CAPACITY];
        this.table1 = null;
        this.rehashIdx = -1;
        this.rehashStep = 1;
    }

    public V get(K key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        progressiveRehash();
        int hash = hash(key);
        V value = findInTable(table0, capacity0, hash, key);
        if (value == null && table1 != null) {
            value = findInTable(table1, capacity1, hash, key);
        }
        return value;
    }

    public void put(K key, V value, long ttlMillis) {
        if (key == null || value == null) throw new IllegalArgumentException("Key or value cannot be null");
        if (ttlMillis <= 0) throw new IllegalArgumentException("TTL must be positive");

        progressiveRehash();
        int hash = hash(key);

        // Check if rehash is needed
        if (rehashIdx == -1) {
            if (size >= capacity0 * loadFactor) {
                startRehash(capacity0 * 2);
            } else if (size < capacity0 * lowLoadFactor && capacity0 > INITIAL_CAPACITY) {
                startRehash(capacity0 / 2);
            }
        }

        // Try update in table1 first if rehashing
        if (table1 != null && updateInTable(table1, capacity1, hash, key, value, ttlMillis)) {
            return;
        }
        if (updateInTable(table0, capacity0, hash, key, value, ttlMillis)) {
            return;
        }

        // Insert new entry
        Entry<K, V>[] targetTable = table1 != null && rehashIdx > capacity0 / 2 ? table1 : table0;
        int targetCapacity = table1 != null && rehashIdx > capacity0 / 2 ? capacity1 : capacity0;
        int index = hash & (targetCapacity - 1);
        Entry<K, V> newEntry = new Entry<>(key, value, hash, System.currentTimeMillis() + ttlMillis);
        newEntry.next = targetTable[index];
        targetTable[index] = newEntry;
        size++;
    }

    public V remove(K key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        progressiveRehash();
        int hash = hash(key);
        V value = removeFromTable(table0, capacity0, hash, key);
        if (value == null && table1 != null) {
            value = removeFromTable(table1, capacity1, hash, key);
        }
        return value;
    }

    public int size() {
        return size;
    }

    private int hash(K key) {
        int h = key.hashCode();
        return (h ^ (h >>> 16)) & 0x7fffffff;
    }

    private V findInTable(Entry<K, V>[] table, int capacity, int hash, K key) {
        int index = hash & (capacity - 1);
        for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
            if (entry.hash == hash && key.equals(entry.key)) {
                if (System.currentTimeMillis() > entry.expiry) {
                    return null;
                }
                return entry.value;
            }
        }
        return null;
    }

    private boolean updateInTable(Entry<K, V>[] table, int capacity, int hash, K key, V value, long ttlMillis) {
        int index = hash & (capacity - 1);
        for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
            if (entry.hash == hash && key.equals(entry.key)) {
                entry.value = value;
                entry.expiry = System.currentTimeMillis() + ttlMillis;
                return true;
            }
        }
        return false;
    }

    private V removeFromTable(Entry<K, V>[] table, int capacity, int hash, K key) {
        int index = hash & (capacity - 1);
        Entry<K, V> prev = null;
        for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
            if (entry.hash == hash && key.equals(entry.key)) {
                if (prev == null) {
                    table[index] = entry.next;
                } else {
                    prev.next = entry.next;
                }
                size--;
                return entry.value;
            }
            prev = entry;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private void startRehash(int newCapacity) {
        if (rehashIdx != -1) return; // Avoid concurrent rehash
        try {
            table1 = new Entry[newCapacity];
            capacity1 = newCapacity;
            rehashIdx = 0;
        } catch (OutOfMemoryError e) {
            throw new RuntimeException("Failed to start rehash due to insufficient memory", e);
        }
    }

    private void progressiveRehash() {
        if (rehashIdx == -1 || table1 == null) return;
        for (int i = 0; i < rehashStep && rehashIdx < capacity0; i++, rehashIdx++) {
            Entry<K, V> entry = table0[rehashIdx];
            while (entry != null) {
                Entry<K, V> next = entry.next;
                if (System.currentTimeMillis() <= entry.expiry) {
                    int newIndex = entry.hash & (capacity1 - 1);
                    entry.next = table1[newIndex];
                    table1[newIndex] = entry;
                } else {
                    size--;
                }
                entry = next;
            }
            table0[rehashIdx] = null;
        }
        if (rehashIdx >= capacity0) {
            table0 = table1;
            capacity0 = capacity1;
            table1 = null;
            rehashIdx = -1;
        }
    }
}

代码 2:ProgressiveCache

import java.io.*;
import java.util.*;

public class ProgressiveCache extends HashMapCache {
    private static class Node {
        String key, value;
        long expiry;
        int accessCount;
        int referenceBit;
        long insertionOrder;
        Node prev, next;
        Node(String key, String value, long expiry) {
            this.key = key;
            this.value = value;
            this.expiry = expiry;
            this.accessCount = 1;
            this.referenceBit = 1;
            this.insertionOrder = insertionCounter++;
        }
    }

    private ProgressiveHashMap<String, Node> cache;
    private Node head, tail;
    private int capacity;
    private EvictionStrategy strategy;
    private TreeMap<Integer, List<Node>> freqMap;
    private Node clockHand;
    private static long insertionCounter;

    public ProgressiveCache(int capacity, EvictionStrategy strategy) {
        super(capacity, strategy);
        this.capacity = capacity;
        this.strategy = strategy;
        this.cache = new ProgressiveHashMap<>();
        this.freqMap = new TreeMap<>();
        this.head = new Node(null, null, 0);
        this.tail = new Node(null, null, 0);
        head.next = tail;
        tail.prev = head;
        this.clockHand = head;
        loadAof();
    }

    @Override
    public String get(String key) {
        if (key == null) throw new IllegalArgumentException("Key cannot be null");
        Node node = cache.get(key);
        if (node == null || System.currentTimeMillis() > node.expiry) {
            if (node != null) {
                removeNode(node);
                cache.remove(key);
                if (strategy == EvictionStrategy.LFU) updateFreqMap(node, -node.accessCount);
            }
            return null;
        }
        updateNodeAccess(node);
        return node.value;
    }

    @Override
    public void put(String key, String value, long ttlMillis) {
        if (key == null || value == null) throw new IllegalArgumentException("Key or value cannot be null");
        if (ttlMillis <= 0) throw new IllegalArgumentException("TTL must be positive");

        Node node = cache.get(key);
        if (node != null) {
            node.value = value;
            node.expiry = System.currentTimeMillis() + ttlMillis;
            updateNodeAccess(node);
        } else {
            if (cache.size() >= capacity) {
                removeEvictedNode();
            }
            node = new Node(key, value, System.currentTimeMillis() + ttlMillis);
            cache.put(key, node, ttlMillis);
            addToHead(node);
            if (strategy == EvictionStrategy.LFU) {
                freqMap.computeIfAbsent(1, k -> new ArrayList<>()).add(node);
            }
        }
        appendToAof("PUT", key, value, ttlMillis);
    }

    private void updateNodeAccess(Node node) {
        if (strategy == EvictionStrategy.LFU) {
            updateFreqMap(node, -node.accessCount);
            node.accessCount++;
            updateFreqMap(node, node.accessCount);
            moveToHead(node);
        } else if (strategy == EvictionStrategy.CLOCK) {
            node.referenceBit = 1;
        }
    }

    private void removeEvictedNode() {
        if (strategy == EvictionStrategy.LFU) {
            Map.Entry<Integer, List<Node>> entry = freqMap.firstEntry();
            if (entry != null) {
                List<Node> nodes = entry.getValue();
                Node node = nodes.remove(nodes.size() - 1);
                if (nodes.isEmpty()) freqMap.remove(entry.getKey());
                removeNode(node);
                cache.remove(node.key);
            }
        } else if (strategy == EvictionStrategy.CLOCK) {
            while (true) {
                clockHand = clockHand.next == tail ? head.next : clockHand.next;
                if (clockHand == head) continue;
                if (System.currentTimeMillis() > clockHand.expiry) {
                    Node toRemove = clockHand;
                    clockHand = clockHand.prev;
                    removeNode(toRemove);
                    cache.remove(toRemove.key);
                    return;
                }
                if (clockHand.referenceBit == 0) {
                    removeNode(clockHand);
                    cache.remove(toRemove.key);
                    return;
                }
                clockHand.referenceBit = 0;
            }
        } else if (strategy == EvictionStrategy.FIFO) {
            Node toRemove = tail.prev;
            if (toRemove != head) {
                removeNode(toRemove);
                cache.remove(toRemove.key);
            }
        }
    }

    private void addToHead(Node node) {
        node.next = head.next;
        node.prev = head;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(Node node) {
        removeNode(node);
        addToHead(node);
    }

    private void updateFreqMap(Node node, int count) {
        if (strategy != EvictionStrategy.LFU) return;
        freqMap.computeIfAbsent(count, k -> new ArrayList<>()).remove(node);
        if (freqMap.get(count) != null && freqMap.get(count).isEmpty()) {
            freqMap.remove(count);
        }
    }
}

测试代码

以下是 ProgressiveCacheTest.java,验证 ProgressiveHashMapProgressiveCache 功能。

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

import java.io.File;

public class ProgressiveCacheTest {
    private ProgressiveCache cache;
    private File aofFile = new File("appendonly.aof");

    @Before
    public void setUp() {
        if (aofFile.exists()) aofFile.delete();
    }

    @After
    public void tearDown() {
        if (cache != null) cache.shutdown();
        if (aofFile.exists()) aofFile.delete();
    }

    @Test
    public void testProgressiveHashMapBasic() {
        ProgressiveHashMap<String, String> map = new ProgressiveHashMap<>();
        map.put("key1", "value1", 5000);
        map.put("key2", "value2", 5000);
        assertEquals("value1", map.get("key1"));
        assertEquals("value2", map.get("key2"));
        assertEquals(2, map.size());
        map.put("key1", "newValue", 5000);
        assertEquals("newValue", map.get("key1"));
        map.remove("key2");
        assertNull(map.get("key2"));
        assertEquals(1, map.size());
    }

    @Test
    public void testProgressiveRehash() {
        ProgressiveHashMap<String, String> map = new ProgressiveHashMap<>();
        for (int i = 0; i < 20; i++) {
            map.put("key" + i, "value" + i, 5000);
        }
        assertEquals(20, map.size());
        assertEquals("value10", map.get("key10"));
        // Insert more to trigger rehash
        for (int i = 20; i < 30; i++) {
            map.put("key" + i, "value" + i, 5000);
        }
        assertEquals("value25", map.get("key25"));
    }

    @Test
    public void testProgressiveCache() throws InterruptedException {
        cache = new ProgressiveCache(3, MultiEvictionCache.EvictionStrategy.LFU);
        cache.put("key1", "value1", 5000);
        cache.put("key2", "value2", 5000);
        cache.put("key3", "value3", 5000);
        for (int i = 0; i < 5; i++) cache.get("key1");
        cache.put("key4", "value4", 5000); // 移除低频 key2
        assertNull(cache.get("key2"));
        assertEquals("value1", cache.get("key1"));

        cache.put("key5", "value5", 1000);
        Thread.sleep(1500);
        assertNull(cache.get("key5")); // 过期

        cache = new ProgressiveCache(3, MultiEvictionCache.EvictionStrategy.LFU);
        assertEquals("value1", cache.get("key1")); // AOF 恢复
    }

    @Test
    public void testShrink() {
        cache = new ProgressiveCache(16, MultiEvictionCache.EvictionStrategy.FIFO);
        for (int i = 0; i < 10; i++) {
            cache.put("key" + i, "value" + i, 5000);
        }
        for (int i = 0; i < 8; i++) {
            cache.remove("key" + i);
        }
        cache.put("newKey", "newValue", 5000); // 触发缩容
        assertEquals("newValue", cache.get("newKey"));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidInput() {
        cache = new ProgressiveCache(3, MultiEvictionCache.EvictionStrategy.LFU);
        cache.put(null, "value", 1000);
    }
}

运行方式:
确保 Maven 项目包含 JUnit 依赖(同前六篇)。

将 SimpleCache.java, ExpiryCache.java, AofCache.java, LruCache.java, MultiEvictionCache.java, HashMapCache.java, ProgressiveHashMap.java, ProgressiveCache.java 和 ProgressiveCacheTest.java 放入项目。

运行测试:mvn test 或通过 IDE 执行。

测试输出(示例):

	Cache full, removed key: key2

面试要点

问:Redis 的渐进式 rehash 如何实现?

  • 答:使用双表结构(ht[0] 和 ht[1]),每次操作迁移少量桶,rehashidx 跟踪进度。查询检查双表,确保一致性。本实现用 table0, table1 和 rehashIdx,每次迁移 1 个桶。

问:渐进式 rehash 与一次性 rehash 的优势?

  • 答:渐进式 rehash 分摊迁移开销,避免高延迟,适合高并发。一次性 rehash 可能阻塞,影响实时性。本实现通过 rehashStep 控制迁移速度。

问:缩容的触发条件和实现方式?

  • 答:当 size < capacity * lowLoadFactor(如 0.1),触发缩容,容量减半。使用双表迁移,类似扩容。本实现支持动态缩容,优化内存。

下一步预告

恭喜你已征服 Redis 的渐进式 rehash,成为哈希表优化大师!系列最终章(第八篇)即将来袭,我们将 重构代码并扩展功能,打造生产级 Redis 缓存,加入并发支持、红黑树优化和动态配置,让你的实现媲美真实 Redis!敬请期待,准备好迎接大厂 offer 吧!

总结

本篇作为“Java 从零手写 Redis”系列第七篇,成功实现了 Redis 的 渐进式 rehash 机制,通过 ProgressiveHashMap 和 ProgressiveCache 实现了动态扩缩容,分摊 rehash 开销,完美适配高并发场景。基于前六篇(SimpleCache、ExpiryCache、AofCache、LruCache、MultiEvictionCache、HashMapCache),我们将 CustomHashMap 升级为支持双表结构和渐进迁移的 ProgressiveHashMap,兼容 TTL、AOF 和 LFU/CLOCK/FIFO 淘汰策略。以下是核心亮点:

核心实现:

  • 实现 ProgressiveHashMap,支持渐进式 rehash,通过 table0 和 table1 分批迁移键值对。

  • 支持动态扩容(容量翻倍)和缩容(容量减半),优化内存使用。

  • 每次操作迁移少量桶(rehashStep = 1),降低延迟,参考 Redis 字典设计。

功能兼容:

  • 整合到 ProgressiveCache,无缝支持 TTL 过期、AOF 持久化和多种淘汰策略。

  • 保持 O(1) 或近似 O(1) 的存取性能,查询双表确保数据一致性。

局限性与展望:

  • 并发安全:当前实现非线程安全,第八篇将引入锁或无锁机制。

  • 冲突优化:仍使用链表处理冲突,第八篇可引入红黑树优化高冲突场景。

  • 动态步长:rehashStep 固定,第八篇可动态调整以适应负载变化。

技术债迟早要还,但今晚的积压必须现在就破!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wáng bēn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值