Redis为什么那么快-存储原理篇

前言:

Redis 是一个基于内存操作的高性能非关系型数据库,大多数人对于它的第一印象就是快,但是它咋就这么快嘞,这得益于他的几个特点

  • 单线程的处理机制
    一个主线程负责读写数据,其他附属的线程负责维护 Redis 服务的稳定,单线程的一个好处就是没有线程资源竞争的问题,采用多线程开发一般会引入同步原语来保护共享资源的并发访问,这也会降低系统代码的易调试性和可维护性。为了避免这些问题,Redis 直接采用了单线程模式。Redis 6.0 支持了多线程,具体大家可以自行了解。

  • io 的多路复用模型
    使用了 epoll 多路复用的模型,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果,本质上就是通过事件监听的方式,如果有个连接没有请求,那就进入休眠状态,让出时间片,等待被激活。

  • 优秀的底层数据结构
    一个好的数据存储结构决定了一半的性能,Redis 底层实现了多种数据结构,包括 简单动态字符串(SNS),压缩列表,双向链表,哈希表,跳表,整形数组等底层数据结构,当然它也同样支持我们将自己实现的数据结构也添加到底层中,Redis 会根据性能来判断选择使用哪一个数据结构作为存储,并作自动转换,除了字符串类型统一使用了SNS,其他的类型(list,hash, set ,sortset )均有两种底层结构的支持

  • 基于内存的读写
    Redis 是直接读写内存中的数据的,我们知道 只有内存中的数据才能够被程序使用,很多存储都有基于内存的优化策略,像 MySQL 的buffer机制,有效的利用内存,可以减少大量的 磁盘随机 io 读取,毕竟从文件中读取数据到内存是一个十分消耗性能的操作,为了数据的稳定,Redis 也提供了 RDB 和 AOF 机制,来保证数据的稳定性

一.Redis 的存储原理

1.1 存储结构

  Redis 的存储方式使用的是散列表的形式,我们都知道,数据结构的最底层物理结构只有数组和链表,而其他的比如说队列、树、图等数据结构都是基于这两个物理结构所实现的逻辑数据结构。
而数组和链表的区别主要在于读写的时间复杂度和内存的拓容上,因为数组要拓容的成本很高,因此大多数情况下会选用链表来实现各种逻辑数据结构。例如 PHP 的数组本质上就是一个使用拉链法的散列表。

在这里插入图片描述

(图片来源于geektime)

  在 Redis 的底层存储中,会维护一个全局的哈希表(本质上就是数组),它有无数个哈希桶(数组元素)组成,每一个哈希桶的存储了两个指针,一个是指向 key 的指针,另一个则是指向 value 的指针。目前 Redis 的 key 只有一种形式,就是字符串,而 value 却有多种支撑,包括 字符串类型,列表,哈希,集合,有序集合。我们知道数组取下标元素的时间复杂度为 O(1), 因为想要获得获取对应的哈希桶,实际上消耗的时间是哈希函数的计算,这个计算和哈希桶的数据量是没有关系的,一般来说哈希函数对应的时间复杂度为 O(1)。这种设计十分的巧妙,把资源放在其他地方,只保留对应资源的指针,在维护上会有很大的便捷性,这就好比说,我不需要把货物都放在房子里,我只需要一张纸,记录着我的每个货物的位置即可。这不经让我想起的MySQL 的表结构和数据分开存储的异曲同工之处。

1.2 rehash

学过数据结构的同学都知道,没有一个绝对无敌的哈希函数可以实现无冲突处理,而一般来说解决冲突的方式有两大类(注意这里是分的大类),一个是开放地址法,另一个是链地址法。而 Redis 采用的正是第二种方式。

在这里插入图片描述
(图片来于geektime)

当存在冲突的时候,会将对应的冲突实体放在一起搞成一个链表,此时哈希桶的指针会增加一个next 指针,用来指向下一个冲突实体,当数据量足够大的时候,这个链表可能会被拉的越来越长,因为链表的查询只能从头结点开始遍历下去,也就是 O(n) 的复杂度(n为链表长度),所以当链表过长时,会影响最终的查询速度。所以这时候不能袖手旁观。

因此,在这种情况下,Redis 会有另外一个线程专门去处理。我们称这种方式为 rehash ,和很多语言的数组底层一样,这种rehash 机制也是基于复制状态机的,说白了就是申请另一块空间(通常是原来的两倍),然后把数据信息复制过去,再释放原来的空间。通常来说程序不会快死的时候才想着救自己,当数值达到预警值时,就会开始自救,我们常常将其称之为装填因子,计算方式十分的简单,就是 use/total, 不同的存储对装填因子的判断规则不同,内容也比较底层,这里我们就不展开叙述了。

所以在 Redis 中,会存在另一张全局哈希表,其实它就是一个备胎,每次需要拓容的时候,这个备胎往往空间要比原配更大,rehash 线程会将原配的数据复制到备胎中,然后备胎就可以转正了。但是在复制的过程中,会有两个问题,一个是内存占用率会突然飙升,另一个就是Redis 阻塞的问题,复制的操作又耗时又耗空间,因此我们还需要更加聪明一点,能不能让一次的操作分成多步呢?温水煮青蛙听过没,如果每次来一个请求我就迁徙一点,这样的话,是不是慢慢的我就复制完了。这就是 Redis 的渐进式 rehash

1.3 渐进式 rehash

两个人的感情是需要慢慢的培养的,程序的处理也是可以慢慢来的,有些事情不非要一次性搞完的,干嘛要加班加点傻乎乎的通宵去做呢?慢工出细活,一天学一个小时专业知识,一万个小时不也就 417 * 24 天而已吗?好吧,不扯这个,看知识点。我们首先需要知道,Redis 是怎么去渐进式 rehash的,假设我要取 key 为 csdn 的 value ,而通过hash 算法得到的 索引位置为 1,但是该索引上有一个三个 entry, 此时处理的线程正常的去遍历这个链表拿到真正正确的值,此时 rehash进程 顺便把这个索引的 entry 从 ht0 复制到 ht1 中。并且释放 ht0 该索引的空间。大致流程如下图所示:
在这里插入图片描述
(图片来源于geektime)

这时候我们就会纠结一个问题了,如果有两种全局哈希表,那么Redis 的 crud 是怎么去操作的呢?

Redis的删除、查找、更新等操作会在两个全局哈希表上进行,比如说, 要在字典里面查找一个键的话, 程序会先在 ht0 里面进行查找, 如果没找到的话, 就会继续到 ht1 里面进行查找,并且新添加到Redis的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表,就完成了备胎转正流程。

下集预告:Redis 底层数据结构剖析

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Redis是一种基于内存的高性能键值存储系统。它以其速的读写能力和丰富的数据结构而闻名,被广泛应用于缓存、队列、计数器等场景。 Redis的核心原理包括以下几个方面: 1. 内存存储Redis将数据存储在内存中,以实现低延迟的读写操作。它使用了一种叫做"跳表"(Skip List)的数据结构来实现有序集合和有序哈希表,以及哈希表和字符串等其他数据结构。 2. 持久化:Redis支持两种持久化方式,分别是RDB(Redis Database)和AOF(Append-Only File)。RDB是一种照方式,将内存中的数据定期保存到磁盘上;AOF则是将写命令追加到一个日志文件中,通过重新执行这些命令来恢复数据。 3. 复制:Redis支持主从复制机制,在一个Redis服务器上配置一个或多个从服务器,从服务器会自动复制主服务器上的数据。主从复制可以提高系统的可用性和读取性能。 4. 高可用:Redis提供了哨兵(Sentinel)和集群(Cluster)两种方式来实现高可用。哨兵监控主服务器的状态,并在主服务器失效时自动将一个从服务器升级为主服务器;集群则将数据分散到多个节点上,每个节点负责一部分数据。 5. 事务:Redis支持简单的事务操作,通过MULTI、EXEC、WATCH和UNWATCH等命令来实现。事务中的命令在执行EXEC命令时原子地被提交,保证了相关操作的一致性。 这些是Redis的一些核心原理,它们共同构成了Redis的高性能和可靠性。当然,Redis还有很多其他的特性和功能,比如发布订阅、Lua脚本等,可以根据具体需求进行使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MClink

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

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

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

打赏作者

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

抵扣说明:

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

余额充值