redis为什么这个快?

一、什么是redis

Redis(Remote Dictionary Server)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被用作缓存系统,可以显著提高应用的性能。

Redis是一个基于Key-Value存储结构的Nosql开源内存数据库。它提供了5种常用的数据类型,String、Map、Set、ZSet、List。针对不同的结构,可以解决不同场景的问题。因此它可以覆盖应用开发中大部分的业务场景,比如top10问题、好友关注列表、热点话题等。

其次,由于Redis是基于内存存储,并且在数据结构上做了大量的优化所以IO性能比较好,在实际开发中,会把它作为应用与数据库之间的一个分布式缓存组件。

并且它又是一个非关系型数据的存储,不存在表之间的关联查询问题,所以它可以很好的提升应用程序的数据IO效率。

最后,作为企业级开发来说,它又提供了主从复制+哨兵、以及集群方式实现高可用在Redis集群里面,通过hash槽的方式实现了数据分片,进一步提升了性能。

二、Redis为什么这么快?

redis为什么这么快?将有以下几个方面揭晓:

1. 内存数据存储

Redis将所有数据存储在内存中,我们都知道内存存取速度远快于磁盘,因此Redis能提供极高的性能。==省去磁盘I/O的消耗==。虽然它也支持数据持久化,但这并不影响其读写速度,因为数据的读写都是直接与内存交互。

2. 高效的数据结构

Redis支持多种数据结构(如字符串、列表、集合、散列等),这些数据结构都被设计为能够高效地处理数据。这意味着Redis可以更快地执行复杂的操作。

先看看Redis的数据结构&内部编码:

* SDS 简单动态字符串

二进制安全:Redis 可以存储一些二进制数据,在 C 语言中字符串遇到'\0'会 结束,而 SDS 中标志字符串结束的是 len 属性。

* 字典

Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比如HashMap,通过key 就可以直接获取到对应的 value。而哈希表的特性,在O(1)时间复杂度就可以获得对应的值。

* 跳跃表

字符串长度处理:Redis 获取字符串长度,时间复杂度为 O(1),而 C 语言中,需要从头开始遍历,复杂度为 O(n);

空间预分配:字符串修改越频繁的话,内存分配越频繁,就会消耗性能,而SDS 修改和空间扩充,会额外分配未使用的空间,减少性能损耗。

惰性空间释放:SDS 缩短时,不是回收多余的内存空间,而是 free 记录下多余的空间,后续有变更,直接使用 free 中记录的空间,减少分配。
跳跃表是 Redis 特有的数据结构,就是在链表的基础上,增加多级索引提升查找效率。

跳跃表支持平均 O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。

3. 合理的数据编码

Redis 支持多种数据数据类型,每种基本类型,可能对多种数据结构。什么时候,使用什么样数据结构,使用什么样编码,是 redis 设计者总结优化的结果。

String:如果存储数字的话,是用 int 类型的编码;如果存储非数字,小于等于39 字节的字符串,是 embstr;大于 39 个字节,则是 raw 编码。

List:如果列表的元素个数小于 512 个,列表每个元素的值都小于 64 字节(默认),使用 ziplist 编码,否则使用 linkedlist 编码

Hash:哈希类型元素个数小于 512 个,所有值小于 64 字节的话,使用ziplist 编码,否则使用 hashtable 编码。

Set:如果集合中的元素都是整数且元素个数小于 512 个,使用 intset 编码,否则使用 hashtable 编码。

Zset:当有序集合的元素个数小于 128 个,每个元素的值小于 64 字节时,使用ziplist 编码,否则使用 skiplist(跳跃表)编码

4. 单线程模型

Redis使用单线程模型来处理命令,避免了常见的多线程或多进程环境下的并发问题和复杂的同步问题,这使得Redis能够更快地处理请求。

5. 优化的网络IO

Redis使用多路复用技术和非阻塞IO来处理并发连接,这使得Redis可以在单个线程内同时处理多个网络连接,从而提供更高的吞吐量。

I/O 多路复用:多路 I/O 复用技术可以让单个线程高效处理多个连接请求,而 Redis 使用用epoll 作为 I/O 多路复用技术实现。并且,Redis 自身事件处理模型将epoll 中连接、读写、关闭都转换为事件,不在网络 I/O 上浪费过多时间。

6. 虚拟内存机制

因此,通过优化内存存储、数据结构、线程模型和网络IO,Redis能够提供极高的性能。

7、简单高效的操作

Redis提供了简单、直观且高效的操作接口,使得开发者能够快速地进行数据操作。例如,通过使用Redis的原子性操作来实现分布式锁、计数器等常见的功能。

三、多方面深入的了解I/O多路复用

1、概念

IO多路复用机制,==核心思想是让单个线程去监视多个连接,一旦某个连接就绪,也就是触发了读/写事件==。

就通知应用程序,去获取这个就绪的连接进行读写操作。

也就是在应用程序里面可以使用单个线程同时处理多个客户端连接,在对系统资源消耗较少的情况下提升服务端的链接处理数量。

在IO多路复用机制的实现原理中,客户端请求到服务端后,此时客户端在传输数据过程中,为了避免Server端在read客户端数据过程中阻塞,服务端会把该请求注册到Selector复路器上,服务端此时不需要等待,只需要启动一个线程,通过selector.select()阻塞轮询复路器上就绪的channel即可,也就是说,如果某个客户端连接数据传输完成,那么select()方法会返回就绪的channel,然后执行相关的处理就可以了。

IO多路复用是一种同步的IO模型。利用IO多路复用模型,可以实现一个线程监视多个文件句柄;一旦某个文件句柄就绪,就能够通知到对应应用程序进行相应的读写操作;没有文件句柄就绪时就会阻塞应用程序,从而释放出CPU资源。

IO可以理解为,在操作系统中,数据在内核态和用户态之间的读、写操作,大部分情况下是指网络IO;

多路大部分情况下是指多个TCP连接,也就是多个Socket或者多个Channel;

复用是指复用一个或多个线程资源。IO多路复用意思就是说,一个或多个线程处理多个TCP连接。尽可能地减少系统开销,无需创建和维护过多的进程/线程。

graph TD A[开始] -->|创建文件描述符集合| B[创建文件描述符集合] B -->|将Redis服务器的监听套接字添加到集合| C[将Redis服务器的监听套接字添加到集合] C -->|设置超时时间| D[设置超时时间] D -->|等待I/O事件发生| E[等待I/O事件发生] E -->|有新连接| F[有新连接] E -->|有客户端可读事件| G[有客户端可读事件] E -->|有客户端可写事件| H[有客户端可写事件] E -->|客户端异常| I[客户端异常] F -->|接受新连接| E G -->|读取客户端请求| E H -->|向客户端发送响应| E I -->|关闭客户端连接| E E -->|结束| K[结束]

2、实现IO多路复用的三种模型

* Select模型

select模型,它的基本原理是,采用轮询和遍历的方式。也就是说,在客户端操作服务器时,会创建三种文件描述符,简称FD。分别是writefds(写描述符)、readfds(读描述符)和exceptfds(异常描述符)。

而select会阻塞监视这三种文件描述符,等有数据、可读、可写、出异常或超时都会返回;返回后通过遍历fdset,也就是文件描述符的集合,来找到就绪的FD,然后,触发相应的IO操;

它的==优点==是跨平台支持性好,几乎在所有的平台上支持。

它的==缺点==也很明显,由于select是采用轮询的方式进行全盘扫描,因此,随着FD数量增多而导致性能下降。

因此,每次调用select()方法,都需要把FD集合从用户态拷贝到内核态,并进行遍历。而操作系统对单个进程打开的FD数量是有限制的,一般默认是1024个。虽然,可以通过操作系统的宏定义FD_SETSIZE修改最大FD数量限制,但是,在IO吞吐量巨大的情况下,效率提升仍然有限。

* poll模型

poll模型的原理与select模型基本一致,也是采用轮询加遍历,唯一的区别就是poll采用链表的方式来存储FD。

所以,它的==优点==是没有最大FD的数量限制。

它的==缺点==和select一样,也是采用轮询方式全盘扫描,同样也会随着FD数量增多而导致性能下降。

* epoll模型

由于select和poll都会因为吞吐量增加而导致性能下降,因此,才出现了epoll模型。epoll模型是采用时间通知机制来触发相关的IO操作。它没有FD个数限制,而且从用户态拷贝到内核态只需要一次。它主要通过系统底层的函数来注册、激活FD,从而触发相关的IO操作,这样大大提高了性能。主要是通过调用以下三个系统函数:

1、epoll_create()函数,在系统启动时,会在Linux内核里面申请一个B+树结构的文件系统,然后,返回epoll对象,也是一个FD。

2、epoll_ctl()函数,每新建一个连接的时候,会同步更新epoll对象中的FD,并且绑定一个callback回调函数。

3、epoll_wait()函数,轮询所有的callback集合,并触发对应的IO操作
所以,epoll模型最大的==优点==是将轮询改成了回调,大大提高了CPU执行效率,也不会随FD数量的增加而导致效率下降。当然,它也没有FD数量限制,也就是说,它能支持的FD上限是操作系统的最大文件句柄数。一般而言,1G内存大概支持10万个句柄。分布式系统中常用的组件如Redis、Nginx都是优先采用epoll模型。

它的==缺点==是只能在Linux下工作。

3、I/O多路复用模型综合对比

select poll epoll
数据结构 数组 链表 B+树
最大连接数 1024 无上限 无上限
FD拷贝 每次调用select拷贝 每次调用poll拷贝 FD首次调用epoll_ctl拷贝 每次调用epoll_wait不拷贝
工作效率 轮询:O(N) 轮询:O(N) 轮询:O(1)
相关推荐
JAVA面经实录9174 小时前
Netty 全套系统化学习文档(零基础到高阶面试完整版)
java·后端
菜鸟‍4 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
GetcharZp4 小时前
C++ 程序员的终极减负:仅需一个头文件,优雅搞定 HTTP 客户端与服务端
后端
IT_陈寒4 小时前
Python的pickle让我半夜加班,这破玩意儿太坑了
前端·人工智能·后端
仙俊红4 小时前
SpringBoot启动原理
java·spring boot·后端
地铁潜行者5 小时前
加了幂等表,为什么消息重试反而不执行了?聊聊 MQ 消费幂等的边界
java·后端
退休倒计时5 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
地铁潜行者5 小时前
Kafka 只发了一条消息,为什么业务侧消费了两次?
后端
文心快码BaiduComate5 小时前
提升组织级AI Coding质量:电商搜索项目实践
前端·后端·程序员
用户8356290780515 小时前
Python 操作 Word 修订跟踪(Track Changes)
后端·python