Redis的实现五:二叉堆的数据结构和TTL、c,c++的实现

简介: 这篇文章详细探讨了二叉堆的数据结构及其在C和C++中的实现,特别强调了二叉堆在Redis中实现TTL(生存时间)功能的重要性,并通过代码示例展示了如何在Redis中使用二叉堆来管理键的过期时间。
    **概述:**Redis的主要用途是作为缓存服务器,管理缓存大小的一种方法是通过显式设置ttl(生存 时间)。ttl可以使用计时器来实现。不幸的是,上一章的计时器是固定值(使用链表);因 此,需要一种排序数据结构来实现任意可变的超时;而堆数据结构是一种流行的选择。 与我们之前使用的AVL树相比,堆数据结构的优势在于占用的空间更少。

   快速回顾一下堆数据结构:

   1. 堆是一个二叉树,被打包成一个数组;而树的布局是固定的。父子关系是隐式的, 指针不包含在堆元素中。

  2. 这棵树上唯一的约束是父母不能比他们的孩子大。

  3. 元素的值可以更新。如果值改变了: •它的值比之前大:它可能比它的子元素大,如果是这样,就把它和最小的子 元素交换,这样亲子约束就再次得到满足。现在,其中一个孩子比以前大, 继续这个过程,直到达到一个休假。 •它的值变小了:同样地,与它的父节点交换,直到到达根节点。   

 4. 新元素以叶子的形式添加到数组的末尾。如上所述保持约束。

 5. 当从堆中删除一个元素时,将其替换为数组中的最后一个元素,然后维护约束, 就好像它的值被更新了一样。

二叉堆的完整例子:

#include <stdio.h>

#define MAX_HEAP_SIZE 100

// 定义最小堆结构
struct MinHeap {
    int arr[MAX_HEAP_SIZE];
    int size;
};

// 函数声明
void initHeap(struct MinHeap* heap);
void insertElement(struct MinHeap* heap, int value);
void deleteMin(struct MinHeap* heap);
void printHeap(struct MinHeap* heap);

int main() {
    struct MinHeap heap;
    initHeap(&heap);

    // 添加元素
    insertElement(&heap, 5);
    insertElement(&heap, 3);
    insertElement(&heap, 8);
    insertElement(&heap, 1);

    // 打印堆
    printf("堆的元素:");
    printHeap(&heap);

    // 删除最小元素
    deleteMin(&heap);

    // 打印堆
    printf("删除最小元素后的堆:");
    printHeap(&heap);

    return 0;
}

// 初始化堆
void initHeap(struct MinHeap* heap) {
    heap->size = 0;
}

// 向堆中插入元素
void insertElement(struct MinHeap* heap, int value) {
    if (heap->size == MAX_HEAP_SIZE) {
        printf("堆已满\n");
        return;
    }

    // 将新元素添加到末尾
    heap->arr[heap->size] = value;
    heap->size++;

    // 上移操作,维护堆的性质
    int currentIndex = heap->size - 1;
    while (currentIndex > 0) {
        int parentIndex = (currentIndex - 1) / 2;
        if (heap->arr[currentIndex] < heap->arr[parentIndex]) {
            // 交换元素,保持堆的性质
            int temp = heap->arr[currentIndex];
            heap->arr[currentIndex] = heap->arr[parentIndex];
            heap->arr[parentIndex] = temp;
        } else {
            break; // 堆的性质已满足
        }
        currentIndex = parentIndex;
    }
}

// 删除堆中的最小元素
void deleteMin(struct MinHeap* heap) {
    if (heap->size == 0) {
        printf("堆为空\n");
        return;
    }

    // 将最后一个元素替换到根节点
    heap->size--;
    heap->arr[0] = heap->arr[heap->size];

    // 下移操作,维护堆的性质
    int currentIndex = 0;
    while (1) {
        int leftChild = 2 * currentIndex + 1;
        int rightChild = 2 * currentIndex + 2;
        int minIndex = currentIndex;

        if (leftChild < heap->size && heap->arr[leftChild] < heap->arr[minIndex]) {
            minIndex = leftChild;
        }
        if (rightChild < heap->size && heap->arr[rightChild] < heap->arr[minIndex]) {
            minIndex = rightChild;
        }

        if (minIndex != currentIndex) {
            // 交换元素,保持堆的性质
            int temp = heap->arr[currentIndex];
            heap->arr[currentIndex] = heap->arr[minIndex];
            heap->arr[minIndex] = temp;
            currentIndex = minIndex;
        } else {
            break; // 堆的性质已满足
        }
    }
}

// 打印堆的元素
void printHeap(struct MinHeap* heap) {
    for (int i = 0; i < heap->size; i++) {
        printf(" %d", heap->arr[i]);
    }
    printf("\n");
}
struct HeapItem {
   uint64_t val = 0;
   size_t *ref = NULL;
};
// the structure for the key
struct Entry {
   struct HNode node;
   std::string key;
   std::string val;
   uint32_t type = 0;
   ZSet *zset = NULL;
   // for TTLs
   size_t heap_idx = -1;
};
 堆用于对时间戳排序,Entry与时间戳相互链接。heap\_idx是对应HeapItem的索引, ref指向Entry。我们再次使用了侵入式数据结构;ref指针指向heap\_idx字段。

父子关系是固定的:

static size_t heap_parent(size_t i) {
    return (i + 1) / 2 - 1;
}
static size_t heap_left(size_t i) {
   return i * 2 + 1;
}
static size_t heap_right(size_t i) {
   return i * 2 + 2;
}

当孩子比父母小时,与父母交换。注意heap_idx是在交换时通过ref指针更新的。

static void heap_up(HeapItem *a, size_t pos) {
    HeapItem t = a[pos];
  while (pos > 0 && a[heap_parent(pos)].val > t.val) {
    // swap with the parent
    a[pos] = a[heap_parent(pos)];
    *a[pos].ref = pos;
    pos = heap_parent(pos);
}
    a[pos] = t;
    *a[pos].ref = pos;
}

和最小的孩子交换也是类似的。

static void heap_down(HeapItem *a, size_t pos, size_t len) {
    HeapItem t = a[pos];
while (true) {
   // find the smallest one among the parent and their kids
   size_t l = heap_left(pos);
   size_t r = heap_right(pos);
   size_t min_pos = -1;
   size_t min_val = t.val;
 if (l < len && a[l].val < min_val) {
   min_pos = l;
   min_val = a[l].val;
}
if (r < len && a[r].val < min_val) {
  min_pos = r;
}
if (min_pos == (size_t)-1) {
  break;
}
   // swap with the kid
   a[pos] = a[min_pos];
   *a[pos].ref = pos;
   pos = min_pos;
}
   a[pos] = t;
  *a[pos].ref = pos;
}

heap_update是用于更新位置的堆函数。它用于更新、插入和删除。

void heap_update(HeapItem *a, size_t pos, size_t len) {
  if (pos > 0 && a[heap_parent(pos)].val > a[pos].val) {
    heap_up(a, pos);
} else {
   heap_down(a, pos, len);
  }
}

将堆添加到我们的服务器:

// global variables
static struct {
   HMap db;
   // a map of all client connections, keyed by fd
   std::vector<Conn *> fd2conn;
   // timers for idle connections
   DList idle_list;
   // timers for TTLs
   std::vector<HeapItem> heap;
} g_data;

更新、添加和删除一个定时器到堆。只需在更新数组元素后调用heap_update即可。

// set or remove the TTL
static void entry_set_ttl(Entry *ent, int64_t ttl_ms) {
  if (ttl_ms < 0 && ent->heap_idx != (size_t)-1) {
   // erase an item from the heap
   // by replacing it with the last item in the array.
   size_t pos = ent->heap_idx;
   g_data.heap[pos] = g_data.heap.back();
   g_data.heap.pop_back();
  if (pos < g_data.heap.size()) {
   heap_update(g_data.heap.data(), pos, g_data.heap.size());
}
  ent->heap_idx = -1;
}  else if (ttl_ms >= 0) {
  size_t pos = ent->heap_idx;
  if (pos == (size_t)-1) {
  // add an new item to the heap
  HeapItem item;
  item.ref = &ent->heap_idx;
  g_data.heap.push_back(item);
   pos = g_data.heap.size() - 1;
}
  g_data.heap[pos].val = get_monotonic_usec() + (uint64_t)ttl_ms * 1000;
  heap_update(g_data.heap.data(), pos, g_data.heap.size());
 }
}

后面懒得写了。

目录
相关文章
|
3月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
203 6
|
2月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
235 86
|
4月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
2月前
|
存储 消息中间件 NoSQL
Redis数据结构:别小看这5把“瑞士军刀”,用好了性能飙升!
Redis提供5种基础数据结构及多种高级结构,如String、Hash、List、Set、ZSet,底层通过SDS、跳表等实现高效操作。灵活运用可解决缓存、计数、消息队列、排行榜等问题,结合Bitmap、HyperLogLog、GEO更可应对签到、UV统计、地理位置等场景,是高性能应用的核心利器。
|
2月前
|
存储 缓存 NoSQL
Redis基础命令与数据结构概览
Redis是一个功能强大的键值存储系统,提供了丰富的数据结构以及相应的操作命令来满足现代应用程序对于高速读写和灵活数据处理的需求。通过掌握这些基础命令,开发者能够高效地对Redis进行操作,实现数据存储和管理的高性能方案。
98 12
|
2月前
|
存储 消息中间件 NoSQL
【Redis】常用数据结构之List篇:从常用命令到典型使用场景
本文将系统探讨 Redis List 的核心特性、完整命令体系、底层存储实现以及典型实践场景,为读者构建从理论到应用的完整认知框架,助力开发者在实际业务中高效运用这一数据结构解决问题。
|
2月前
|
存储 缓存 NoSQL
【Redis】 常用数据结构之String篇:从SET/GET到INCR的超全教程
无论是需要快速缓存用户信息,还是实现高并发场景下的精准计数,深入理解String的特性与最佳实践,都是提升Redis使用效率的关键。接下来,让我们从基础命令开始,逐步揭开String数据结构的神秘面纱。
|
4月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
123 0
|
6月前
|
存储 缓存 NoSQL
Redis中的常用命令-get&set&keys&exists&expire&ttl&type的详细解析
总的来说,这些Redis命令提供了处理存储在内存中的键值对的便捷方式。通过理解和运用它们,你可以更有效地在Redis中操作数据,使其更好地服务于你的应用。
419 17

热门文章

最新文章