-
Reactor网络模型处理请求
- 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果
-
优秀的底层数据结构
-
数据结构针对内存使用效率做了设计优化,如简单动态字符串(SDS)、压缩列表(ziplist)和整数集合(intset)
-
Redis 就给我们提供了两个优秀的设计思想:一个是使用连续的内存空间,避免内存碎片开销;二个是针对不同长度的数据,采用不同大小的元数据,以避免使用统一大小的元数据,造成内存空间的浪费。
-
-
基于内存
-
渐进式rehash
- 对于 Redis 键值数据库来说,Hash 表既是键值对中的一种值类型,同时,Redis 也使用一个全局 Hash 表来保存所有的键值对,能以 O(1) 的复杂度快速查询数据,这就使得数据操作非常快速。但是在实际应用 Hash 表时,当数据量不断增加,它的性能就经常会受到哈希冲突和 rehash 开销的影响。
- Redis 为我们提供了一个经典的 Hash 表实现方案。针对哈希冲突,Redis 采用了链式哈希,在不扩容哈希表的前提下,将具有相同哈希值的数据链接起来,以便这些数据在表中仍然可以被查询到;对于 rehash 开销,Redis 实现了渐进式 rehash 设计,渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。进而缓解了 rehash 操作带来的额外开销对系统的性能影响。
- 什么时候触发rehash:当负载因子达到指定值,并且当前不存在aof,rdb备份。
- dict采用哈希函数对key取哈希值得到在哈希表中的位置(桶的位置),采用拉链法解决hash冲突。
- 两张哈希表(ht[2]):只有在重哈希的过程中,ht[0]和ht[1]才都有效。而在平常情况下,只有ht[0]有效,ht[1]里面没有任何数据。上图表示的就是重哈希进行到中间某一步时的情况。
- 重哈希:跟HashMap一样当装载因子(load factor)超过预定值时就会进行rehash。dict进行rehash扩容,将ht[0]上某一个bucket(即一个桶上dictEntry链表)上的每一个链表移动到扩容后的ht[1]上(每次只移动一个链表,即渐进式rehash。原因是为了防止redis长时间的堵塞导致不可用,减少对主线程的阻塞),触发rehash的操作有查询、插入和删除元素。rehashidx会记录每次需要移动链表bucket桶的位置(后面会详细讲解)。
- 当 ht[0] 包含的所有键值对都迁移到了 ht[1] 之后 (ht[0] 变为空表),释放 ht[0] , 将 ht[1] 设置为 ht[0] , 并在 ht[1] 新创建一个空白哈希表, 为下一次 rehash 做准备。
- rehash 扩容扩多大:一般为原hash表不停地乘以 2,直到达到目标大小
- 为什么要渐进式,能解决什么问题:其实这是因为,Hash 表在执行 rehash 时,由于 Hash 表空间扩大,原本映射到某一位置的键可能会被映射到一个新的位置上,因此,很多键就需要从原来的位置拷贝到新的位置。而在键拷贝时,由于 Redis 主线程无法执行其他请求,所以键拷贝会阻塞主线程,这样就会产生 rehash 开销。简单来说,渐进式 rehash 的意思就是 Redis 并不会一次性把当前 Hash 表中的所有键,都拷贝到新位置,而是会分批拷贝,每次的键拷贝只拷贝 Hash 表中一个 bucket 中的哈希项。这样一来,每次键拷贝的时长有限,对主线程的影响也就有限了。
- 渐进式rehash的实现:
- 为ht[1]分配空间,让dict字典同时持有 ht[0] 和 ht[1] 两个哈希表。
- 在字典中维持一个索引计数器变量rehashidx,初始时值为-1,代表没有rehash操作,当rehash工作正式开始,会将它的值设置为0。
- 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在 rehashidx索引(table[rehashidx]桶上的链表)上的所有键值对rehash到ht[1]上,当rehash工作完成之后,将rehashidx属性的值+1,表示下一次要迁移链表所在桶的位置。
- 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有桶对应的键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。
-
单线程模型
- 按理来说单线程执行任务时串行的,为什么单线程反而快呢。这是由于是基于内存的redis进行数据处理相较于磁盘快上几个数量级,单个请求的响应速度达到纳秒级,没必要使用多线程,多线程对性能的提升微乎其微,不仅如此,redis在创建,维护,销毁线程中,必然会发生大量上下文切换,用户态与内核态之间的转换不仅对cup开销巨大,还增加对内存和磁盘的占用,同时允许多线程必然存在并发安全问题,还要引入类似于锁这种重量级的机制来确保数据不会因为线程并发竞争而发生错误,因此反而会降低处理速度,而单线程直接避免了多线程存在的问题。
简述redis为什么快
最新推荐文章于 2024-08-17 19:17:45 发布