Redis高性能原理:Redis为什么这么快?

目录

前言:

一、Redis知识系统观

 二、Redis为什么这么快?

 三、Redis 唯快不破的原理总结

四、Redis6.x的多线程


前言:

Redis 为了高性能,从各方各面都进行了优化。学习一门技术,通常只接触了零散的技术点,没有在脑海里建立一个完整的知识框架体系。这样会很吃力,而且会出现一看好像自己会,过后就忘记的懵逼情况。知识系统观其实是至关重要的,从某种程度上说,在解决问题时,拥有了系统观,就意味着你能有依据有章法地定位和解决问题。
————————————————

一、Redis知识系统观

Redis从应用维度有:缓存使用、集群运用、数据结构的巧妙使用;

Redis从系统维度有:可以归类为三高

  1. 高性能:线程模型、网络 IO 模型、数据结构、持久化机制;
  2. 高可用:主从复制、哨兵集群;
  3. 高拓展:Cluster 分片集群

Redis 为了高性能,从各方各面都进行了优化。

根据官方数据,Redis 的 QPS 可以达到约 100000(每秒请求数),有兴趣的可以参考官方的基准程序测试《How fast is Redis?》,官方地址:Redis benchmark | Docs

 

横轴是连接数,纵轴是 QPS。此时,这张图反映了一个数量级,希望大家在面试的时候可以正确的描述出来,不要问你的时候,你回答的数量级相差甚远!

 二、Redis为什么这么快?

  • Redis是基于内存操作,需要的时候需要我们手动持久化到硬盘中
  • Redis高效数据结构,对数据的操作也比较简单
  • Redis是单线程模型,从而避开了多线程中上下文频繁切换的操作
  • 使用多路I/O复用模型,非阻塞I/O
  • 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

1.1、基于内存的操作实现
Redis 是基于内存的数据库,不论读写操作都是在内存上完成的,完全吊打磁盘数据库的速度。Redis之所以可以使用单线程来处理,其中的一个原因是,内存操作对资源损耗较小,保证了处理的高效性。

如此宝贵的内存资源,Redis是怎么维护和管理的呢?

(1) 除了增删改查还有哪些维护性操作

命中率统计:在读取一个键之后,服务器会根据键是否存在来更新服务器的键空间命中次数或键空间不命中次数。

LRU时间更新:在读取一个键之后,服务器会更新键的LRU时间,这个值可以用于计算键的闲置时间。

惰性删除:如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行余下的其他操作。

键的dirty标识:如果有客户端使用WATCH命令监视了该键,服务器会将这个键标记为dirty,让事务程序注意到这个键已经被修改过。每次修改都会对dirty加一,用于触发持久化和复制。

数据库通知:如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。

(2) Redis数据过期策略

Reids中数据过期策略采用定期删除+惰性删除策略。内存和CPU资源都是宝贵的,Redis的过期键删除通过定期删除设定合理的执行时长和执行频率,配合惰性删除兜底的方式,来达到CPU时间占用和内存浪费之间的平衡。

定期删除策略:Redis启用一个定时器定时监视所有key,判断key是否过期,过期的话就删除。这种策略可以保证过期的key最终都会被删除,但是也存在严重的缺点:每次都遍历内存中所有的数据,非常消耗CPU资源,并且当key已过期,但是定时器还处于未唤起状态,这段时间内key任然可以使用。du

惰性删除策略:在获取 key 时,先判断 key 是否过期,如果过期则删除。这种方式存在一个缺点:如果这个 key 一直未被使用,那么它一直在内存中,其实它已经过期了,会浪费大量的空间。

定期删除+惰性删除策略是如何工作的?

这两种策略天然的互补,结合起来之后,定时删除策略就发生了一些改变,不在是每次扫描全部的 key 了,而是随机抽取一部分 key 进行检查,这样就降低了对 CPU 资源的损耗,惰性删除策略互补了为检查到的key,基本上满足了所有要求。

但是有时候就是那么的巧,既没有被定时器抽取到,又没有被使用触发惰性删除,是否会把内存撑爆?回答是不会,当内存不够用时,内存淘汰机制就会上场。

Redis 内存淘汰机制有以下几种策略:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。(Redis 默认策略)
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(推荐使用)
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。
  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。
  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。
  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。

修改内存淘汰机制只需要在 redis.conf 配置文件中配置 maxmemory-policy 参数即可。

值得一提的是,这里的lru和平常我们所熟知的lru还不完全一样,Redis使用的是采样概率的思想,省略了双向链表的内存消耗。Redis 会在每一次处理命令的时候判断是否达到了最大限制,如果达到则使用对应的算法去删除涉及到的Key,这时,我们前面所维护过键的LRU值就会派上用场了。

1.2、高效的数据结构
在 Redis 中存储value,常用的 5 种数据类型和应用场景如下:

  • String: 缓存、计数器、分布式锁等。
  • List: 链表、队列、微博关注人时间轴列表等。
  • Hash: 用户信息、Hash 表等。
  • Set: 去重、赞、踩、共同好友等。
  • Zset: 访问量排行榜、点击量排行榜等。

上面的应该叫做 Redis 支持的数据类型,也就是数据的保存形式。针对这 5 种数据类型,Redis底层都运用了哪些高效的数据结构来支持。

数据结构SDS的妙用举例

我们知道Redis的底层是用c语言来编写的,但是,数据结构确没有直接套用C的结构,而是根据Redis的定位自建了一套数据结构,比如说sds(简单动态字符串)。

 

Redis的String底层数据结构实现并没有直接使用C语言中的字符串,Redis中为了实现方便的扩展,考虑到安全和性能,自己定义了一个结构用来存储字符串,这个数据结构就是:简单动态字符串(Simple Dynamic String 简称sds),并将 SDS 用作 Redis 的默认字符串。

/*
 * redis中保存字符串对象的结构
 */
struct sdshdr {
    //用于记录buf数组中使用的字节的数目,和SDS存储的字符串的长度相等 
    int len;
    //用于记录buf数组中没有使用的字节的数目 
    int free;
    //字节数组,用于储存字符串
    char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的
};

 

(1)C语言中的字符串结构

C 语言中传统字符串是使用长度为 N+1 的字符数组来表示长度为 N 的字符串,并且字符串数组的最后一个元素总是空字符 '\0'。如下所示:

 

(2)SDS定义下的字符串结构

 

可以看到,相比于C语言来说,也就多了几个字段,分别用来标识空闲空间和当前数据长度,但简直是神来之笔:(这个\0对于用户来说是通用的,系统自动帮我们加上去)

  • 可以O(1)复杂度获取字符串长度:有len字段的存在,无需像C结构一样遍历计数。
  • 杜绝缓存区溢出:C字符串不记录已占用的长度,所以需要提前分配足够空间,一旦空间不够则会溢出。而有free字段的存在,让SDS在执行前可以判断并分配足够空间给程序
  • 减少字符串修改带来的内存重分配次数:有free字段的存在,使SDS有了空间预分配和惰性释放的能力。
  • 对二进制是安全的:二进制可能会有字符和C字符串结尾符 '\0' 冲突,在遍历和获取数据时产生截断异常,而SDS有了len字段,准确了标识了数据长度,不需担心被中间的 '\0' 截断。

(4)SDS 与 C 字符串区别

 

O(1) 时间复杂度获取字符串长度

C 语言字符串不记录长度信息,需要遍历整个字符串时间复杂度为 O(n),C 字符串遍历时遇到 '\0' 时结束。SDS 中 len 保存这字符串的长度,O(1) 时间复杂度。

空间预分配

SDS 被修改后,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间。

分配规则如下:如果对 SDS 修改后,len 的长度小于 1M,那么程序将分配和 len 相同长度的未使用空间。举个例子,如果 len=10,重新分配后,buf 的实际长度会变为 10(已使用空间)+10(额外空间)+1(空字符)=21。如果对 SDS 修改后 len 长度大于 1M,那么程序将分配 1M 的未使用空间。

惰性空间释放

当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。

二进制安全

在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。

二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 '\0',在 C 中遇到 '\0' 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性。

(5)Redis数据类型与底层数据结构

对于链表、hash表和跳表来说,Redis在设计数据结构的时候出发点是一致的,总结起来就是一句话:空间换时间。用牺牲存储空间和微小的计算代价,来换取数据的快速操作。

为了追求速度,每种数据类型使用不同的数据结构支撑,底层数据结构有 6 种:

 

1.3、单线程模型
Redis6.x之前,一直在说单线程如何如之何的好。那么,具体单线程体现在哪里,又是怎么完成数据读写工作的呢?关于新版本的多线程模型在后面小节单独说,这里先说单线程。

(1)单线程模型

我们要明确的是:Redis 的单线程指的是 Redis 的网络 IO 以及键值对指令读写是由一个线程来执行的。 对于 Redis 的持久化、集群数据同步、异步删除等都是其他线程执行。

所谓单线程是指对数据的所有操作都是由一个线程按顺序挨个执行的,使用单线程好处:

不会因为线程创建导致的性能消耗;

避免上多线程上下文切换引起的 CPU开销;

避免了线程之间的竞争问题,比如添加锁、释放锁、死锁等,不需要考虑各种锁的问题。

代码更清晰,处理逻辑简单。

单线程是否没有充分利用 CPU 资源呢?

官方答案:因为 Redis 是基于内存的操作,使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于服务器内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理10万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。

然而,使用了单线程的处理方式,就意味着到达服务端的请求不可能被立即处理。那么怎么来保证单线程的资源利用率和处理效率呢?

(2) IO多路复用模型和性能优良的事件驱动

Redis 采用 I/O 多路复用技术,并发处理连接。采用了 epoll + 自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。

Redis服务端,从整体上来看,其实是一个事件驱动的程序,所有的操作都以事件的方式来进行。

 

如图所示,Redis的事件驱动架构由套接字、I/O多路复用、文件事件分派器、事件处理器四个部分组成:

套接字(Socket),是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。

I/O多路复用,通过监视多个描述符,当描述符就绪,则通知程序进行相应的操作,来帮助单个线程高效的处理多个连接请求。

Redis为每个IO多路复用函数都实现了相同的API,因此,底层实现是可以互换的。

Reids默认的IO多路复用机制是epoll,和select/poll等其他多路复用机制相比,epoll具有诸多优点:

 

 

事件驱动,Redis设计的事件分为两种,文件事件和时间事件,文件事件是对套接字操作的抽象,而时间事件则是对一些定时操作的抽象。

文件事件:

客户端连接请求(AE_READABLE事件)

客户端命令请求(AE_READABLE事件)和事

服务端命令回复(AE_WRITABLE事件)

时间事件: 分为定时事件和周期性时间;redis的所有时间事件都存放在一个无序链表中,当时间事件执行器运行时,需要遍历链表以确保已经到达时间的事件被全部处理。

可以看到,Redis整个执行方案是通过高效的I/O多路复用件驱动方式加上单线程内存操作来达到优秀的处理效率和极高的吞吐量。

1.4、IO多路复用模型
多路指的是多个 socket 连接,复用指的是复用一个线程。IO多路复用主要有三种技术:select,poll,epoll。epoll 是目前最新最好的IO多路复用技术。

IO多路复用的基本原理是,内核不是监视应用程序本身的连接,而是监视应用程序的文件描述符。当客户端运行时,它将生成具有不同事件类型的套接字。在服务器端,I / O 多路复用程序(I / O 多路复用模块)会将消息放入队列(也就是 下图的 I/O 多路复用程序的 socket 队列),然后通过文件事件分派器将其转发到不同的事件处理器。

简单来说:Redis 单线程情况下,内核会一直监听 socket 上的连接请求或者数据请求,一旦有请求到达就交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的事件处理器。所以 Redis 一直在处理事件,提升 Redis 的响应性能。

Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性

1.5、高效的的 Gossip 通信协议
Redis 采用了 Gossip 协议作为通信协议。Gossip 是一种传播消息的方式,可以类比为瘟疫或者流感的传播方式,使用 Gossip 协议的有:Redis Cluster、Consul、Apache Cassandra 等。Gossip 协议类似病毒扩散的方式,将信息传播到其他的节点,这种协议效率很高,只需要广播到附近节点,然后被广播的节点继续做同样的操作即可。当然这种协议也有一个弊端就是:会存在浪费,哪怕一个节点之前被通知到了,下次被广播后仍然会重复转发。

 三、Redis 唯快不破的原理总结

纯内存操作:一般都是简单的存取操作,线程占用的时间很少,时间的花费主要集中在 IO 上,所以读取速度快。

采用单线程模型:保证了每个操作的原子性,也减少了线程的上下文切换和竞争。

Redis 使用IO多路复用模型:非阻塞 IO,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,Redis 采用自己实现的事件分离器,效率比较高。

Redis高效数据结构,对数据的操作也比较简单:

        (1)整个 Redis 就是一个全局哈希表:他的时间复杂度是 O(1),而且为了防止哈希冲突导致链表过长,Redis 会执行 rehash 操作,扩充 哈希桶数量,减少哈希冲突。并且防止一次性重新映射数据过大导致线程阻塞,采用 渐进式 rehash。巧妙的将一次性拷贝分摊到多次请求过程后总,避免阻塞。

        (2)Redis 全程使用 hash 结构:读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。可以根据实际存储的数据类型选择不同编码。

四、Redis6.x的多线程

Redis 6.0.1 于 2020 年 5 月 2 日正式发布了,如 Redis 作者 antirez 所说,这是迄今为止最“企业”化的版本,也是有史以来改动最大的一个 Redis 版本。这个版本提供了诸多令人心动的新特性及功能改进,比如新网络协议RESP3,新的集群代理,ACL等,其中关注度最高的应该是“多线程”了。

Redis的多线程模型,不是传统意义上的多线程并发,而是把socket解析回写的这部分操作并行化,以解决IO上的时间消耗带来的系统瓶颈。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/704789.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用ChatGPT 4o画漂亮的燃尽图代码

把代码给ChatGPT,然后他就会帮我生成出来了。 而且图是动态的,可以调整颜色文字之类的内容 # Given data for Sprint 5 Progress data_sprint_5 {User Story: [BEAN-40, BEAN-42, BEAN-41, BEAN-22, BEAN-33, BEAN-44, BEAN-10, BEAN-26, BEAN-37, BEA…

【学术小白成长之路】03三方演化博弈(基于复制动态方程)均衡点与稳定性分析

从本专栏开始,笔者正式研究演化博弈分析,其中涉及到双方演化博弈分析,三方演化博弈分析,复杂网络博弈分析等等。 先阅读了大量相关的博弈分析的文献,总结了现有的研究常用的研究流程,针对每个流程进行拆解。…

Sqlite3入门和c/c++下使用

1. SQLite3基本介绍 1.1 数据库的数据类型 1.2 基本语法 1. 创建数据表格 create table 表名(字段名 数据类型, 字段名 数据类型); create table student(id int, name varchar(256), address text, QQ char(32)); 2. 插入数据 insert into 表名 valu…

手机流畅运行470亿参数大模型,上交大发布PowerInfer-2推理框架,性能提升29倍

苹果一出手,在手机等移动设备上部署大模型迅速成为行业焦点。 目前,移动设备上运行的模型相对较小(苹果的是3B,谷歌的是2B),并且消耗大量内存,这在很大程度上限制了其应用场景。 即使是苹果&…

运营商二要素核验-手机号机主姓名核验接口-运营商二要素核验接口

通过电信运营商验证手机号码与姓名是否一致。广泛用于实名注册、风控审核等场景,如电商、游戏、直播、金融等需要用户实名认证的场景。支持携号转网核验。 更新周期:联通T1 电信T3 移动T3~5 均为工作日 接口地址: https://www.wapi.cn/api_de…

RabbitMQ系列-rabbitmq无法重新加入集群,启动失败的问题

当前存在3个节点:rabbitmq5672、rabbitmq5673、rabbitmq5674 当rabbitmq5673节点掉线之后,重启失败 重启的时候5672节点报错如下: 解决方案 在集群中取消失败节点 rabbitmqctl forget_cluster_node rabbitrabbitmq5673删除失败节点5673的…

木头姐预测:2029年特斯拉股价将达2600美元,市值8.2万亿美元

ARK预计特斯拉将在未来两年内推出robotaxi服务,并估计到2029年特斯拉近90%的市值和盈利将归功于robotaxi业务。此外研究表明,FSD模式下的特斯拉比人类驾驶的特斯拉安全约5倍,比道路上的普通汽车安全约16倍。 北京时间12日晚,木头姐…

【课程总结】Day9(上):深度学习基本流程

前言 在上一篇课程《【课程总结】Day7:深度学习概述》中,我们了解到: 模型训练过程→本质上是固定w和b参数的过程;让模型更好→本质上就是让模型的损失值loss变小;让loss变小→本质上就是求loss函数的最小值&#xf…

Java中ArrayList(顺序表)的自我实现(如果想知道Java中怎么自我实现ArrayList,那么只看这一篇就足够了!)

前言:在 Java 编程中,ArrayList 是一种非常常用的数据结构,它提供了动态数组的实现方式,可以方便地存储和操作数据。相比于传统的数组,ArrayList 具有更多的灵活性和便利性,可以根据需要动态地调整大小&…

构建 deno/fresh 的 docker 镜像

众所周知, 最近 docker 镜像的使用又出现了新的困难. 但是不怕, 窝们可以使用曲线救国的方法: 自己制作容器镜像 ! 下面以 deno/fresh 举栗, 部署一个简单的应用. 目录 1 创建 deno/fresh 项目2 构建 docker 镜像3 部署和测试4 总结与展望 1 创建 deno/fresh 项目 执行命令…

嵌套查询(一)-谓词IN、量词ANY、量词ALL

一、在多个表之间进行数据查询,除了可以使用连接查询之外,也可以使用嵌套查询,那么什么是嵌套查询呢?如何使用嵌套查询呢? 1、将一个SELECT-FROM查询,嵌套在另一个SELECT查询语句中,那么这个SE…

响应式企业网站建站系统源码 模版丰富+一站式建站 全开源可二次开发 带源码包+搭建部署教程

系统概述 在数字化转型的浪潮中,企业官网作为品牌展示、产品推广及客户服务的重要窗口,其建设质量直接影响着企业的线上形象与市场竞争力。响应式企业网站建站系统源码的出现,为企业提供了一种高效、灵活且成本可控的建站解决方案。 代码示…

【安装笔记-20240612-Linux-内网穿透服务之cpolar极点云】

安装笔记-系列文章目录 安装笔记-20240612-Linux-内网穿透服务之 cpolar 极点云 文章目录 安装笔记-系列文章目录安装笔记-20240612-Linux-内网穿透服务之 cpolar 极点云 前言一、软件介绍名称:cpolar极点云主页官方介绍 二、安装步骤测试版本:openwrt-…

日本旅游回忆录Day1-02三千院

中午回到京都站吃拉面啦,这边的图片由小宝补充,整体味道是不错的啦。时间关系,我不展开了,由小宝补充。 拉面: 下午目的地是三千院。 我们是坐公交车去的,刚刚上车就坐到了靠窗的位置,往深山里…

华为防火墙技术

防火墙技术综合介绍1 时代的认知:这是一个快鱼吃慢鱼的时代,是技术能够成就梦想是时代。 防火墙的认知:网络安全产品;位于网络的边界(企事业单位的出口位置与ISP运营商进行连接并接入外网(公网的&#xff…

MySQL(5)

聚合函数 GROUP BY 的使用 需求:查询各个部门的平均工资,最高工资SELECT department_id,AVG(salary),SUM(salary)FROM employeesGROUP BY department_id;需求:查询各个job_id的平均工资SELECT job_id,AVG(salary)FROM employeesGROUP BY jo…

rocketmq-5.1.2的dleger高可用集群部署

1、背景 原先为5.0.0版本,因检查出有漏洞,升级到5.1.2版本。 【Rocketmq是阿里巴巴在2012年开发的分布式消息中间件,专为万亿级超大规模的消息处理而设计,具有高吞吐量、低延迟、海量堆积、顺序收发等特点。在一定条件下&#xf…

牧原发布年度低碳报告,看行业“一哥”如何数字化减碳!

此前,牧原信息化负责人何秋梅在接受绿研院的专题访谈时提到:“在销售、采购等业务上,都涉及到大量的合同和文件,传统的纸质合同保存和管理繁琐,需要档案柜存储,且成本高昂。使用电子签不仅节省了打印、盖章…

优雅迷人的小程序 UI 风格

优雅迷人的小程序 UI 风格

什么是DMZ?路由器上如何使用DMZ?

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 DMZ 📒🚀 DMZ的应用场景💡 路由器设置DMZ🎈 注意事项 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在网络管理中,DMZ(Demilitarized Zone,隔离区)是一个特殊的网络区域,常用于将公共访问和内部网络隔离开来。DMZ功能允许…