Redis的高性能怎么做到的?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Redis的高性能怎么做到的?Redis这个NOSQL数据库在计算机界可谓是无人不知,无人不晓。只要涉及到数据那么就需要数据库,数据库类型很多,但是NOSQL的kv内存数据库也很多,redis作为其中一个是怎么做到行业天花板的呢?是怎么做到高性能的呢?怎么做到高可用的呢?今天这篇八股文我就整理一些redis的设计写写,本篇还是偏关于高性能这一块。

Redis的高性能怎么做到的?

Redis这个NOSQL数据库在计算机界可谓是无人不知,无人不晓。只要涉及到数据那么就需要数据库,数据库类型很多,但是NOSQLkv内存数据库也很多,redis作为其中一个是怎么做到行业天花板的呢?是怎么做到高性能的呢?怎么做到高可用的呢?今天这篇八股文我就整理一些redis的设计写写,本篇还是偏关于高性能这一块。

高效数据结构

Redis的数据库相比传统的关系数据库,在数据结构上也是比较特殊的,它的所有数据类型都可以看做是一个map的结构,key作为查询条件。

网络异常,图片无法展示
|

基本数据结构

Redis基于KV内存数据库,它内部构建了一个哈希表,根据指定的KEY访问时,只需要O(1)的时间复杂度就可以找到对应的数据,而value的值又是一些拥有各种特性的数据结构,这就给redis在数据操作的时候提供很好的性能了。

基于内存存储

相比传统的关系数据库,数据文件可能以lsm tree 或者 b+ tree形式存在硬盘上,这个时候读取文件要有io操作了,而redis在内存中进行,并不会大量消耗CPU资源,所以速度极快。

网络异常,图片无法展示
|

存储金字塔

内存从上图可以看到它介于硬盘和cpu缓存中间的,相比硬盘查找数据肯定是快的,当然这里笔者个人见解上,如果关系型数据库把一些平凡操作的数据库也放置在内存中缓存,也会得到一些性能的提升,像操作系统里面缺页异常一样处理,把数据片段通过一些特殊算法缓存在内存里面,减少文件io的开销。

io多路复用

传统对于并发情况,假如一个进程不行,那搞多个进程不就可以同时处理多个客户端连接了么?多进程是可以解决一些并发问题,但是还是有一些问题,上下文切换开销,线程循环创建,从PCB来回恢复效率较低。随着客户端请求增多,那么线程也随着请求数量直线上升,如果是并发的时候涉及到数据共享访问,有时候涉及到使用锁来控制范围顺序,影响其他线程执行效率。(进程在Linux也可以理解为线程,每个进程只是有一个线程,当然这里我上面写的进程,别纠结这些。。。)

线程是运行在进程上下文的逻辑流,一个进程可以包含多个线程,多个线程运行在同一进程上下文中,因此可共享这个进程地址空间的所有内容,解决了进程与进程之间通信难的问题,同时,由于一个线程的上下文要比一个进程的上下文小得多,所以线程的上下文切换,要比进程的上下文切换效率高得多。

像redis和Nginx这种应用就是单线程的程序,为什么他们能做到这么强的性能?首先看一个例子:

  1. Blocking IO

中午吃饭,我给餐厅老板说要一碗‘热干面’,然后我就在那边一直等着老板做,老板没有做好,我就一直在哪里等着什么也不做,直到‘热干面’做好。

这个流程就是我们常说的Blocking I/O如图:

网络异常,图片无法展示
|

blocking io

同步阻塞 IO模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

  1. Non Blocking IO

切换一下常见:

同样你中午吃饭,给餐厅老板说要一碗‘热干面’,然后老板开始做了,你每隔几分钟向老板问一下‘好了吗?’,直到老板说好了,你取到‘热干面’结束。

网络异常,图片无法展示
|

非阻塞io

同步非阻塞 IO模型中,应用程序会一直发起read调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间,通过轮询操作,避免了一直阻塞,取回热干面的过程就是内核把准备好的数据交换到用户空间过程。

综上两种模型,缺点都是差不多,都是在等待内核准备数据,然后阻塞等待,同样逃不开阻塞这个问题,应用程序不断进行I/O系统调用轮询数据是否已经准备好的过程是十分消耗CPU资源的。

  1. I/O Multiplexing

还是之前那个例子:

中午吃饭,给餐厅老板说要一碗‘热干面’,然后老板安排给下面的厨子做,具体哪个厨子做不知道,有好几个厨子,然后老板每隔一段时间询问下面的厨子有木有做好,如果做好了,就通知我来去取餐。

网络异常,图片无法展示
|

多路复用

IO多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read调用,read 调用的过程(数据从内核空间->用户空间)还是阻塞的。

网络异常,图片无法展示
|

Reactor模式

Reactor通过 I/O复用程序监控客户端请求事件,收到事件后通过任务分派器进行分发。针对建立连接请求事件,通过 Acceptor 处理,并建立对应的handler 负责后续业务处理,针对非连接事件,Reactor会调用对应的handler 完成 read->业务处理->write 处理流程,并将结果返回给客户端,整个过程都在一个线程里完成。

网络异常,图片无法展示
|

redis里面模型

Redis 是基于 Reactor 单线程模式来实现的,IO多路复用程序接收到用户的请求后,全部推送到一个队列里,交给文件分派器。对于后续的操作,和在 reactor 单线程实现方案里看到的一样,整个过程都在一个线程里完成,因此 Redis 被称为是单线程的操作。

我们平时说的Redis单线程快是指它的请求处理过程非常地快!在单线程中监听多个Socket的请求,在任意一个Socket可读/可写时,Redis去读取客户端请求,在内存中操作对应的数据,然后再写回到Socket中。

单线程的好处:

  • 没有了访问共享资源加锁的性能损耗
  • 开发和调试非常友好,可维护性高
  • 没有多个线程上下文切换带来的额外开销,不是没有,是减少了

单线程不是没有缺点,其实缺点也是很明显的,如果前一个请求发生耗时比较久的操作,那么整个Redis就会阻塞住,其他请求也无法进来,直到这个耗时久的操作处理完成并返回,其他请求才能被处理到,但是redis使用的Reactor 单线程模式来实现的可以缓解这种情况。

Redis 4.0之后的版本,引入多线程,而这个多线程是只的异步释放内存,它主要是为了解决在释放大内存数据导致整个redis阻塞的性能问题,单机redis如果处理大数据请求时还是会出现瓶颈,但是redis有集群高可用解决方案可以解决,主节点只负责写,从节点负责读,io复用先写到这里,集群高可用我会另外在出一篇文章。

写时拷贝

有了高效的数据结构和io多路模型,目前能解决数据访问效率问题,但是redis为了保证了数据不丢失有快照机制,说到快照那么会操作磁盘,redis怎么解决的在数据操作的时候并且还能保证数据记录完整性的?不影响数据访问效率的呢?

答案是用了写时复制技术,什么是写时复制?如果你是一个科班的或者你的操作系统学的不错的话,这个问题很清楚。

在操作系统设计中进程的内存可分为 虚拟内存物理内存,什么是虚拟内存?你可以去看我上一篇文章Virtual Memory。redis会从主进程中通过fork()系统调用,创建一个子进程,将父进程的 虚拟内存物理内存 映射关系复制到子进程中,并将设置内存共享的,子进程只负责将内存里面数据写入到rdb进行持久化操作,如果在操作的时候主进程对内存修改了,使用写时拷贝技术,将对应的内存创建一个副本然后进行写入持久化。

网络异常,图片无法展示
|

示意图

如上图主进程则提供服务,只有当有人修改当前内存数据时,才去复制被修改的内存页,用于生成快照。

管道通讯

除了本地服务器内存和数据结构的操作影响客户端读写效率的还有网络原因。redis的通讯协议是用一种文件协议,有兴趣自己去研究研究吧,我这里不打算写。每次客户端操作的时候,命令和元数据都被打包成redis协议进行传输到服务器上。

按照这样那每个命令的执行时间:客户端发送时间 + 服务器处理和返回时间 + 一个网络来回的时间。

网络异常,图片无法展示
|

数据包来回

从上图可以看出来如果每操作一条命令,那么就要执行一次网络io,如果客户端频繁操作数据那么就频繁网络操作,这个过程也是非常耗时的,影响性能的。redis在客户端程序中做了一些优化引入了一个管道(pipelining)概念。

管道会把多条无关命令批量执行,以减少多个命令分别执行带来的网络交互时间,在一些批量操作数据的场景。

小结

简单始于复杂!,别看客户端就几个简单api call的事情,这后面还有很多设计值得去学习,看完这篇八股文你或许对redis高性能有新的认识了,不要小看某些细节优化和解决方案选型,有时候可以带来明显性能提升。当然这篇文章没有把redis设计写完,例如还有aof的内核文件描述符映射,异步写数据到硬盘上,零拷贝技术等等。。。。后续文章将会更新redis高可用是怎么做到的?

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
NoSQL Unix Linux
Redis核心技术与实践 03 | 高性能IO模型:为什么单线程Redis能那么快?
Redis核心技术与实践 03 | 高性能IO模型:为什么单线程Redis能那么快?
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
78 6
|
4月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
82 0
|
7月前
|
存储 消息中间件 缓存
Redis的高性能使得它非常适合用于实时分析场景
【5月更文挑战第15天】Redis在Python Web开发中扮演关键角色,常用于缓存系统,提高数据读取速度;会话管理,存储用户信息;分布式锁,确保数据一致性;排行榜和计数,利用有序集合和哈希结构;消息队列,基于列表结构实现异步处理;实时分析,高效处理实时数据。其丰富的数据结构和高性能使其在多种场景下应用广泛。
346 3
|
3月前
|
存储 缓存 NoSQL
Redis的高性能之谜
Redis的高性能之谜
43 5
|
3月前
|
存储 消息中间件 NoSQL
Redis的单线程设计之谜:高性能与简洁并存
Redis的单线程设计之谜:高性能与简洁并存
45 1
|
存储 消息中间件 NoSQL
深入了解Redis:高性能的内存数据库
深入了解Redis:高性能的内存数据库
|
6月前
|
存储 缓存 NoSQL
Redis是一种高性能的内存数据库,常用于高并发环境下的缓存解决方案
【6月更文挑战第18天】**Redis摘要:** 高性能内存数据库,擅长高并发缓存。数据存内存,访问迅速;支持字符串、列表等多元数据类型;具备持久化防止数据丢失;丰富命令集便于操作;通过节点集群实现数据分片与负载均衡,增强可用性和扩展性。理想的缓存解决方案。
85 1
|
6月前
|
存储 运维 NoSQL
Redis 分区:构建高性能、高可用的大规模数据存储解决方案
Redis 分区:构建高性能、高可用的大规模数据存储解决方案
|
6月前
|
NoSQL Redis
Redis的单线程和高性能
Redis 的单线程主要是指 Redis 的网络 I0 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。 但Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
29 0