Redis 核心原理串讲(上),从一条请求透视高性能的本质

news2024/11/24 9:47:58

文章目录

  • Redis 核心原理总览(全局篇)
  • 前言
  • 一、请求
  • 二、数据结构
    • 1. 有哪些?
    • 2. 为什么节省内存又高效?
  • 三、网络模型
    • 1、四种常见IO模型
      • 1.1 同步阻塞
      • 1.2 同步非阻塞
      • 1.3 IO多路复用
      • 1.4 异步IO
    • 2、事件驱动
      • 2.1 引子
      • 2.2 事件驱动模型
    • 3、Reactor 模型
      • 3.1 单线程模型
      • 3.2 多线程模型
      • 3.3 主从多线程模型
  • 四、线程模型
    • 1、单线程模型
    • 2、后台线程
    • 3、多线程模型
  • 总结


Redis 核心原理总览(全局篇)

正文开始之前,我们先思考下「如何造一个缓存组件?」

该片段是 Redis 原理知识地图,请仔细阅读!(基于 redis6.2

1)最小可用版:

  • 要快:缓存最核心的目的是支持快速访问,硬件层面一般选择「内存」
  • 远程访问:作为缓存组件,要支持单独部署,可以利用现有开源网络库,也可以自己实现。

大部分语言都提供了内存操作,条件 1 很容易满足,条件 2 要支持远程访问,就要和 TCP 连接打交道,我们可以利用开源的网络库,比如 C 版的 libc 等。

原理篇第一部分:将围绕一条请求探索 redis 高性能的核心原理。

2)进阶版:

第一步我们已经有了缓存组件最基本的雏形,并且已经达到了高性能的处理能力,这个时候我们可能有更多的诉求:

  • 稳定:即 尽可能不丢数据、无故障或故障后快速恢复、自动处理故障的能力
  • 可扩展:单机容量或者 QPS 达到上限,支持水平扩展能力。

原理篇第二部分:将围绕 redis 架构演进进行剖析。

全局知识地图:

在这里插入图片描述

前言

本文围绕「请求主线」透视 Redis 高性能的核心原理,在正文开始之前我们先思考几个问题:

  • Redis 为什么节省内存又高效?
  • 同步/异步、阻塞/非阻塞有什么区别?
  • 删除一个百万级数据的 hash 字典会阻塞吗?
  • Redis6.0 的多线程需要考虑并发问题吗?

如果你对以上问题了然于胸,这篇文章读起来很容易,权当帮你串联、回顾知识点,如果不是很清楚也没关系,且听我细细道来!


一、请求

我画了一条完整的请求处理流程,你可以参考下:

在这里插入图片描述

从用户发出请求到接收到响应这过程大概会经历以上7个阶段,你可以思考下,redis 服务端高性能主要处理哪些阶段?

你应该猜到了,是 3、4、5 阶段。

  • IO 模型:阶段 3、5
  • 线程模型、数据结构:阶段 4

我们先尝试回答这个问题:「Redis 高性能的原因?」

内存 + 优秀的数据结构/算法 + 高性能网络I/O

其中,最根本原因是基于「内存」,你可以想想你的应用,如果只操作内存而不是数据库,是不是非常快?

另外,我们还可以设计一些优秀的算法、数据结构让这些基于内存的操作 更快!

当整个 “业务” 模块已经非常高效时,前置模块 ------ 「网络 IO」可能成为瓶颈,我们得想办法让它达到高配模式,即 高性能网络 IO 模型

请求主线的整块内容就串起来了,我们小结下涉及到的核心知识点:

  • 数据类型 + 数据结构
  • 高性能网络I/O:I/O多路复用、事件驱动、Reactor模型
  • 线程模型:单线程模型、后台线程、多线程模型

值得一提的是,对于长链路优化终极秘法是「拆分」,然后逐个击破。

二、数据结构

1. 有哪些?

数据类型:

  • 5 常见数据类型:string、list、set、hash、zset
  • 其他数据类型:stream、hyperloglog、bitmap、geo

数据结构:

  • sds、ziplist、quicklist、listpack、hash、skiplist、rax、intset

数据类型,属于更上层、直接提供对外使用的类型,而数据结构则是数据真正的组织方式。在 Redis 中,大家最好区分开来,避免混淆。

我们看看常见数据结构全景图:

在这里插入图片描述

注:参考版本 redis 6.2

2. 为什么节省内存又高效?

1)节省内存:

  • 压缩: 如ziplist、listpack …
  • 前缀树:rax
  • 概率算法:HyperLogLog

我们以 ziplist 压缩列表为例:

在这里插入图片描述

最开始 redis 使用双向链表(支持向前、向后遍历),每个节点都存储两个指针,也就是保存的是地址,一个指针占 4 字节,共 8 字节,如何更节省?

我们先去掉每个节点的两个指针,那如何后向遍历呢?使用 encoding 字段编码,可根据数据类型、长度选择 1、2、3 … 等编码字节表示数据的真实长度,后向遍历时挨个处理即可。

如何向前遍历?使用 prevlen 记录前一个元素的大小,也就能很方便定位前一个元素。

2)高效:

  • 空间换时间: 如跳表 …
  • SDS: 空间预分配、惰性释放…
  • 组合: 如 hash、zset …

三、网络模型

1、四种常见IO模型

1)同步、异步:

内核行为:如果内核主动发起数据拷贝(从内核到用户态)则为异步,反之为同步。

2)阻塞、非阻塞:

用户态行为:用户态发起系统调用,如果阻塞读,则为阻塞;如果立即返回(即使数据未准备就绪)则为非阻塞。

1.1 同步阻塞

在这里插入图片描述
用户态线程(进程)发起调用,此时内核数据未准备就绪,用户态会一直阻塞知道数据拷贝完成。

这个过程中,用户态主动发起调用,所以是「同步」,同时,用户态阻塞等待数据完成,因此,这个过程是「阻塞」的。

阻塞是啥意思?用户态线程在此期间,一直等待数据就绪,空闲状态、不做任何事情。

是不是很浪费资源?通常情况下,用户态应用会限制每个应用的资源数(线程),「干等」期间就是浪费资源!

1.2 同步非阻塞

在这里插入图片描述

为了解决「干等」浪费资源的情况,我们通过用户态轮询的方式来解决,具体是

  • 通过 read 系统调用,如果内核数据未就绪,直接返回,用户态控制过一会再来
  • 多轮操作,直到真正 read 数据

值得注意的是:数据准备就绪后,read 读取数据期间,该操作仍是阻塞。

1.3 IO多路复用

在这里插入图片描述

同步非阻塞 解决了「干等」问题,但效率还不算高,毕竟每个用户态线程都要亲自执行 「read 轮询」这个操作,100个并发连接就需要 100 个线程来完成。

为了解决这个问题,IO 多路复用登场了 …

IO 多路复用的目的是用 1 个线程解决 100、1000 … 个并发连接,具体怎么做的?

  • 首先,通过 select(epoll …) 操作,查询监听的 N 个连接,是否数据有准备就绪的。
  • 对数据准备就绪的连接,则挨个轮训调用其 read 方法读取对应数据。

为什么叫 I/O 多路复用?

因为一个专用线程轮询发起 select 调用可以向内核查「多个」数据通道(Channel)的状态,所以叫多路复用。

内核支持

I/O多路复用需要操作系统内核支持,比如 Windows下使用select、Linux下使用 poll/epoll、Mac下使用 kqueue。

值得一提的是,IO 多路复用是目前最为流行的 IO 模型,常见的 tomcat、nginx、kafka、netty 以及我们今天的 redis 都是这种 IO模型

1.4 异步IO

前面介绍了三种网络 IO 模型,你也看到了,不管是阻塞还是非阻塞,都需要用户态线程「主动」发起操作,有没有一种用户态被动接收的模型呢?

当然有,这就是 异步IO 模型,工作原理大概是这样的:当我们首次发起 read 调用,这个操作类似于「注册」操作,内核收到这个操作后,先记下来,等到数据真正准备就绪的时候,然后找到这条记录,主动 发起拷贝(类似于回调)。

我画了张图,你可以参考下:
在这里插入图片描述

这种是真正做到了异步,将更多的动作交给了系统内核,也就是说需要操作系统来提供更多的支持,工作量其实不小,目前 Windows 操作系统提供了真正的异步能力,而 Linux 类则还没支持上。

2、事件驱动

2.1 引子

客户端、服务端通信的那张经典的模型图你肯定还记得,这里我就不再罗列了,我们聊聊服务端如何演进,一步步实现高性能的原因。

最初,我们的要求很简单,只要客户端、服务端能正常通行即可,于是,我们用一个线程来处理所有连接:

在这里插入图片描述

慢慢的发现这种串行处理太慢了,于是改进了一版,每一个新连接都用新开一个线程去处理,如下:

在这里插入图片描述

速度上快了很多,但线程数没法控制了,过多的线程消耗还会导致服务端资源枯竭、线程上下文频繁切换带来了严重的性能损耗。

你也想到了,我们可以继续改进,使用线程池:

在这里插入图片描述

看起来完美了,但仔细一想,还是有问题,对于一些长连接或者耗时久的连接还是会很快耗尽线程池资源,服务端能力同样受限。

问题出在哪里?大家想想,我们的业务操作(内存操作)和网络IO操作,谁更耗时?

问题就在这里,通常情况下,网络IO 才是瓶颈点,一方面,网络的不确定性,等待网络数据包的就绪实际也具有不确定性,另一方面,read/write 等调用涉及与内核的交互,进程上下文的切换等都是开销。

总结来说,我们的线程大部分时机都在「等待」网络数据包的就绪,也就是说,在等待中,浪费了我们线程池中的线程资源。

如何解决?拆分

2.2 事件驱动模型

事件驱动的核心是将 连接工作线程 分离,避免工作线程将过多的时间花在 I/O 等待上,以 事件 驱动工作线程,即,专人做专事(想想现代人的分工)

在这里插入图片描述

当有事件准备就绪时,再交给工作线程去处理,这样一来,我们的工作线程就节省了大部分的等待时间,全方位投入到实际工作中,彻底释放工作线程!!!

那负责处理连接的线程如何达到高性能?IO 多路复用,通常情况下,一个线程就能达到要求!

3、Reactor 模型

前面我们提了「事件驱动」,是一种抽象、指导思想,而 Reactor 模型则是一种具体的实现,换句话说,Reactor模型是事件驱动的一种实现

Reactor 模式由 Reactor 线程、Handlers 处理器两大角色组成:

  • Reactor 线程:主要负责连接建立、监听IO事件、IO事件读写以及将事件分发到Handlers 处理器
  • Handlers 处理器:非阻塞的执行业务处理逻辑

3.1 单线程模型

当我们的业务操作、网络IO 两部分能力都非常高效时,我们用一个线程是不是就可以搞定?是的,这就是单线程模型。

在这里插入图片描述

图中,acceptor 负责建立连接,利用「IO多路复用」可以达到一个线程应对成千上万连接的能力。

值得一提的是,本文的主角 — redis 就是采用这种模式,从 redis 单机几万的 QPS 也可以看出这种模式同样不可小觑,关键还得看场景!

3.2 多线程模型

不是所有的业务操作都是基于内存的,比如我们的 web 应用,很多可能需要操作 MySQL 这样的数据库,耗时就增加了,几十、上百毫秒都是很正常的事。

这样情况下,如果还是单线程处理业务,那这块的能力将会直线下降:

  • 我们可以考虑将业务这块的使用线程池
  • 同时,acceptor 也采用分离的线程处理

在这里插入图片描述

acceptor 还是利用 IO 多路复用处理连接,当监听到有就绪的 IO 事件时,就交给业务线程池去处理,分工明确、各司其职。

3.3 主从多线程模型

说到底,还是要在「网络IO」「业务操作」两部分进行 PK,瓶颈在哪里,就优化哪里。

当我们的业务是纯内存操作时,那一般 「网络 IO 」就是瓶颈,怎么优化?还得搞多线程,只不过这里还有些讲究。

网络IO 涉及两部分:建立新连接、监听/处理已建立连接的IO事件,这两部分我们也可以分开处理:

  • 建立新连接:这里叫主 reactor,负责建立新的连接,然后交给子 reactor 处理
  • 监听/处理IO事件:也叫子 reactor,负责从建立的连接中监听IO事件,将就绪读取的事件交给业务线程池去处理。

在这里插入图片描述

网络这块说了这么多,相信你也清楚了,优化思路是:对于长的链路,先拆分、然后逐个击破

四、线程模型

你可能会经常听到这几个问题:

  • redis 是单线程的?
  • keys 这类命令要避免在线上使用?
  • 删除一个百万级的 hash 字典有阻塞风险?
  • redis6.0 已经有了多线程了,需要考虑并发问题吗?

通过这个模块,你会对这几个问题了然于胸~

1、单线程模型

redis 是单线程的,这个问题从某种层面来看是没有任何问题的,到目前为止(redis6.2),redis 的 命令执行仍然是单线程串行处理

Redis 是典型的事件驱动服务,内部有文件事件和时间事件两类:

  • 文件事件:对外 - 处理用户请求,即 连接、I/O读写以及命令处理等
  • 时间事件:对内 - 定时任务、各类指标/监控,比如内存驱逐、过期key、关闭超时客户端等

在这里插入图片描

redis 服务端的运行就依赖于主线程轮询交替执行「文件事件」和「时间事件」,换句话说,整个 redis 服务端大厦由主线程来驱动完成。

值得注意的是,redis 服务端大部分工作都是由主线程来完成,地位不可撼动

前面我们也提到过,redis 属于 Reactor 模型中的单线程模型,也就是说 「网络IO」和「命令执行」都是一个线程完成。

我们这里的单线程模型也主要指正常请求中的 网络IO + 命令处理。

:单线程模型一定要有自己的认识,要知道,redis 也在不断对网络IO模块进行优化,升级一下版本、或者调整一些参数,实际运行的模型可能就变了,需要你辩证的来看!!!

2、后台线程

删除一个百万级的 hash 字典有阻塞风险?姿势用对了还真就可以!!!redis 提供了一些后台线程,来专门应对这种 慢操作

截止到目前,redis 共有三个后台线程,分别是 close_file、aof_fsync和lazy_free:

  • close_file 表示关闭相应文件描述符对应的文件(释放套接字、数据空间等)
  • aof_fsync 表示 AOF 刷盘
  • lazy_free 表示惰性释放空间

在这里插入图片描述

3、多线程模型

Redis 在6.0版本新增多线程实现,主要针对IO读、写使用多线程,命令执行仍然是单线程处理:

在这里插入图片描述

命令执行为什么不使用多线程?

  • redis 性能主要在于网络和内存,而不是 CPU
  • 另外,使用单线程处理命令可以避免并发控制问题。

总结

我们从一条请求出发,然后深入剖析其背后的原理,也清楚的看到了哪些关键点能影响到 redis 的高性能。

redis 是基于内存的,这是其 的核心原因,我们先介绍了 redis 的数据结构、类型以及一些典型的算法,透视 redis 对数据的组织、对内存的掌控以及对空间/时间的权衡。

然后进入网络模块,这是重中之重,是任何需要网络交互并且渴望拥有高性能的组件必须考虑的问题,redis 实现了自己的轻量级网络库,采用的是 单线程版的 Reactor 模型。

最后是其线程模型,命令处理始终沿用单线程串行执行,随着版本迭代,引入了后台线程处理慢操作,redis6.0 还提供了多线程来解决网络 IO 瓶颈。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Windows】win10家庭版无法被远程桌面(mstsc)连接的解决方案

🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向)🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd🌐系列专栏:善假于物&#…

前端知识学习

一、html的学习 1.1 html的基本结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body></body> </html>1. <!DOCTYPE html> 告诉浏…

网页版chatGPT,国内直接打开就用的chatgpt

先看效果&#xff1a; 文件就是一个网页文件&#xff0c;直接打开就可以网页使用了。 使用的前提是需要有chatGPT的账号去获取apikey,然后把拿到的apikey放在下面代码中 然后网页的代码如下&#xff1a; <script src"https://unpkg.com/vue3/dist/vue.global.js&qu…

MySQL事务日志 (redo log)

MySQL事务日志 &#xff08;redo log) 事务的隔离性由 锁机制 实现。 而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。 REDO LOG 称为 重做日志 &#xff0c;提供再写入操作&#xff0c;恢复提交事务修改的页操作&#xff0c;用来保证事务的持久性。 …

如何使用YonBuilder进行报表分析?

报表是基于业务元数据、业务模型、数据模型等数据来源展示与分析业务的重要工具&#xff0c;在YonBuilder中可以通过简单拖拽、选择&#xff0c;快速生成报表分析&#xff0c;提升报表开发效率。本期通过员工信息数据对YonBuilder中报表的基本配置进行介绍。 01、创建报表 首…

深入理解kafka-1

kafka快速入门1、kafka简介1.1 kafka是什么1.2 kafka基础架构1.3 kafka模块概述2、kafkka结构剖析2.1 kafka工作流程2.2 kafka文件存储2.2.1 顺序写2.2.2 分片&#xff0c;索引2.2.3 页缓存2.2.4 零拷贝2.3 broker集群2.3.1 Controller控制器及选举机制2.4 生产者2.4.1 生产者分…

MCU-51:定时器

目录一、定时器介绍1.1 定时器的功能1.2 定时器的结构1.3 定时器框图二、定时器控制2.1 工作模式寄存器TMOD2.2 控制寄存器TCON三、中断系统3.1 中断系统介绍3.2 中断程序流程3.3 STC89C52中断资源四、应用4.1 定时器控制LED闪烁4.2 基于定时器按键控制LED流水灯4.3 定时器时钟…

C进阶 :征服指针之指针与数组强化笔试题练习(1)

目录 &#x1f63c;&#x1f638;一.彻底明白 sizeof 操作符 &#xff0c;数组名&#xff0c;strlen 函数 &#x1f405;1.数组名的意义 &#x1f406;2. sizeof 详解 &#x1f40b;3.strlen详解 &#x1f996;3.数组名意义详细图解演示 &#x1f431;&#x1f640;二.关于…

使用JDBC+javafx写一个简单功能齐全的图书管理系统

目录 1、JDBC的使用 2、对应包和Java文件的层级关系及对应的含义 3、数据库 4、相关代码 1&#xff09;、bookmanager包 Ⅰ、main函数 Ⅱ、utils包 Ⅲ、bean包 Ⅳ、controller包 2)resources(为资源文件包&#xff0c;可以看链接文章了解) Ⅰ、book包 Ⅱ、 login包…

嘘!P站数据分析年报;各省市疫情感染进度条;爱奇艺推出元宇宙App;You推出AI聊天机器人;GitHub今日热榜 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f3a1;AI应用与工具大全 | &#x1f514;公众号资料下载 | &#x1f369;韩信子 &#x1f4e2; 『The 2022 Year in Review』P站2022年度报告 Pornhub 发布了第 9 次年度报告&#xff0c;数据科学家们绘制了多张彩色可视化图表&#xff0c;回顾…

Spring注册Bean系列--方法3:@Import+@Bean

原文网址&#xff1a;Spring注册Bean系列--方法3&#xff1a;ImportBean_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Spring注册Bean的方法&#xff1a;ImportBean。 注册Bean的方法我写了一个系列&#xff0c;见&#xff1a;Spring注册Bean(提供Bean)系列--方法大全_IT利刃出鞘…

Redis-SDS

本文你能得到&#xff1a; 1 SDS基本介绍 。 2 SDS与 C语言传统字符串的区别&#xff0c;为什么使用SDS。 3 SDS的结构和策略详解。 1 SDS 是什么&#xff1f;用来做什么&#xff1f; 1.1 ​ Redis没有直接使用C语言传统的字符串表示&#xff08;以空字符结尾的字符数组&a…

[网络工程师]-STP

生成树协议&#xff08;Spanning Tree Protocol&#xff0c;STP&#xff09;是一种链路管理协议&#xff0c;为网络提供路径冗余&#xff0c;同时防止产生环路。交换机之间使用网桥协议数据单元&#xff08;Bridge Protocol Data Unit&#xff0c;BPDU&#xff09;来交换STP信息…

C语言中单井号(#)和双井号(##)在宏语句中的应用

在阅读Linux内核代码过程中&#xff0c;特别是一些预处理指令宏的时候&#xff0c;会看到宏语句里会包含一些# 或者是连着的## 符号&#xff0c;刚接触的时候觉得很一头雾水&#xff0c;但这些宏语句有时候绕不开&#xff0c;所以为了更好地读懂这些代码&#xff0c;很有必要仔…

头豹研究院发布《2022年腾讯安全威胁情报能力中心分析报告》:助力企业掌握安全防御主动权

12月23日&#xff0c;头豹研究院发布了《2022年腾讯安全威胁情报能力中心分析报告》&#xff08;以下简称《报告》&#xff09;&#xff0c;深度研究了腾讯安全威胁情报能力建设、威胁情报能力应用、威胁情报价值实践方面的现状及成果&#xff0c;从专业视角分析腾讯安全威胁情…

全网首发!华为云UCS正式商用

日前&#xff0c;华为云UCS正式商用。华为云UCS是业界首个分布式云原生服务&#xff0c;支持对华为云集群、伙伴云集群、多云集群、本地集群和附着集群的统一管理&#xff0c;覆盖中心Region、专有Region、边缘云、客户数据中心和第三方云场景&#xff0c;提供无处不在的云原生…

深度学习02:损失函数总结

目录 nn.L1Loss: nn.NLLLoss: nn.MSELoss: nn.CrossEntropyLoss: 损失函数是用来估量模型的预测值与真实值的不一致程度&#xff0c;它是一个非负实值函数。我们训练模型的过程&#xff0c;就是通过不断的迭代计算&#xff0c;使用梯度下降的优化算法&#xff0c;使得损失函…

Day 05- Vue3 Vue2响应式原理

Vue2的响应式 核心&#xff1a;通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持&#xff1b; 数据劫持 --> 给对象扩展属性 --> 属性设置 实现原理&#xff1a; 对象类型&#xff1a;通过Object.defineProperty()对属性的读取、修改进行拦截…

风控黑名单库的使用与判断指南

反欺诈策略中有一类策略是专门针对黑名单用户的&#xff0c;我们称之为黑名单命中策略。 一、黑名单定义 黑名单是对严重逾期、骗贷、失联、诈骗等系列高风险客群构建的名单&#xff0c;即为明确非意向客群的名单。黑名单也是反欺诈的第一道防线&#xff0c;可以有效识别黑产客…

近些年大火的零信任,落地情况怎么样?

疫情以来      居家办公、远程工作需求激增      企业IT环境变得更加复杂      在此背景下      以“持续验证,永不信任”为核心的      零信任火爆出圈      引得国内外企业机构纷纷布局      然而根据戴尔2022全球数据保护指数(GDPI),尽管91%的组…