gcc skiplist.c sl-timer.c -I./ -o sl-timer
./sl-timer
g++ test.cc -o test
./test
1)红黑树
2)时间轮
3)跳表
zset 数据>=128时,数据用skiplist来存储。
redis有哪些事件需要定时器处理?
key expire
redis的是双向 链表的实现(无序的)。
优化:
1.有序
2.跳表 CRUD的时间复杂度: 大概率的趋向于O(Logn)
理想: 是O(Logn)
rank: 需要支持反向查找。
跳表: 第一个节点就是最近要过期的节点。
查找和插入,删除(任意节点和头结点) O(Logn)
过期时间,只需要跟第一个节点比较。
============================
最小堆(Mark不太认可): 特征: 父节点比子节点小. 子节点之间无顺序性能.
二叉树: boost.asio
四叉树: go
=======================定时器的作用
1.超时任务
2.定时任务
===================具备的数据特性
需要有序性。
查找和插入数据速度要快(时间复杂度)。
同时要保证结构有序性。
===================开发
第一步: 简单可用,持续优化.
==================学习源码:需要去看数据结构,自己构架框架的时候,可以直接从源码中获取=============
做高性能中间件,框架的时候:
时间和空间
=======定时器的接口====
init_timer: 初始化定时器 ==》初始化 初始化一个跳表结构
add_timer: 添加定时器 ==》添加定时器 添加一个节点。 同时,根据当前时间+超时时间,插入一个节点。
del_timer: 删除定时器 ==》删除定时器 删除一个节点。
expire_timer: 超时 ==》定时器过期函数 死循环:每次找最小的,第一个都没过期,则跳出循环。第一个过期,则回调函数,并删除节点。
=======
实际过程,是epoll_wait阻塞的, 先处理网络事件,再调用超时。 是用IO多路复用去阻塞的。
---------------------------------红黑树的实现
nginx中,红黑树: 稳定的O(Logn)。 跳表是:不稳定的O(Logn)。 最小堆:子节点是无序的,因此不合适。 时间轮在游戏中是最好的。
二叉树的问题: 删除的时候,容易将二叉树退化为单链表==》
AVL: 为了解决这个问题,出现了AVL这种平衡的二叉树,左右子树的高度相差为1。但是平衡操作太重度了。
红黑树: 每次插入都是以红色节点来插入的。 只需要维护一个 ‘黑高度’ ,让黑高度一致即可。只用做简单的旋转,就做到了平衡,是一种简化版本的AVL。
红黑树也是一种有序的数据结构。
从左往右依次增大。
红黑树,只用找最左边的节点。因为它最小。 但是成千、上百万的定时器的话。 性能不如:跳表。
哨兵节点: 黑高度。 任意一个子节点触发到最上层节点的高度。
红黑树 + 哨兵
skynet: 10ms
值相同的话,是由插入函数决定的: 是更新呢? 还是新建一个节点。
stl: map、set 也是红黑树, 但是key相同时,是更新。 而实现定时器,新加节点。
相邻的节点不能都是红节点,否则会:触发旋转操作。
--------------------时间轮------------
跳表 和 红黑树 在多线程环境下,应该如何加锁?
互斥锁和自旋锁的区别:
一个需求:
假设检测连接超时,根据心跳包来检测,10s钟没收到,就断开连接。
redis的主从节点、哨兵节点、zookeeper 都是10s。
openresty nginx+lua==》 红黑树来检测
客户端每5s发送一次。
一般的做法: map<fd, conn*>, 每s轮训这个结构,检查所有的连接是否超时。
收到心跳包就记录下时间戳。
问题:效率非常差。 每次对所有的连接都检查。
分治的思路:只检测快要过期的链接,不检测。 数组+链表,hash,设置为10还是16?
相同秒数过期的,则加成链表链接起来。
redis hash: 为什么设置为2的N次方。 取余数的时候,设置为2的n次方,取余数是很快的。
时间定时器:
先确定精度:10ms 改为1ms, skynet源码 质量非常高的定时器。
每个时间轮,里面都是链表。
缺点: 因为总是移动的,不容易删除(如: 引用计数删除. 第二种:不处理)。
像时钟一样运行,确保每个精度运行1次。
优点:锁的粒度小。