根据官方数据,Redis 的 QPS 可以达到约 100000(每秒请求数),有兴趣的可以参考官方的基准程序测试《How fast is Redis?》,官方地址:
https://redis.io/topics/benchmarks
横轴是连接数,纵轴是 QPS
Redis为什么快?
- 内存存储:Redis将数据存储在内存中,避免了磁盘I/O的开销,大大提高了数据的访问速度。
- 单线程架构:Redis采用单线程模型,避免了多线程之间的线程切换、锁竞争等开销,同时也减少了线程间通信的开销。
- 异步非阻塞网络模型:Redis采用异步非阻塞的I/O模型,可以同时处理多个客户端请求,并且不会因为某一个客户端的请求阻塞而影响其他客户端请求的处理速度。
- 数据结构优化:Redis提供了多种数据结构,包括字符串、哈希表、列表、集合和有序集合等,这些数据结构针对不同的应用场景进行了优化,提高了数据访问的效率。
- 持久化策略:Redis支持多种持久化策略,包括RDB和AOF两种方式。RDB是指将内存中的数据定期写入磁盘,AOF是指将每个写操作以追加的方式写入磁盘。这些持久化策略可以保证数据的可靠性和一致性。
- 压缩算法:Redis采用多种压缩算法,包括LZF和Snappy等,可以有效地减少数据在内存中的占用空间,提高内存的利用率
Redis使用场景
Redis 一共有 5 种数据类型,
String、List、Hash、Set、ZSet(SortedSet)
在 Redis 中存储value,常用的 5 种数据类型和应用场景如下
:
String: 缓存、计数器、分布式锁等。
List: 链表、队列、微博关注人时间轴列表等。
Hash: 用户信息、Hash 表等。
Set: 去重、赞、踩、共同好友等。
Zset: 访问量排行榜、点击量排行榜等
一、基于内存Key/Value操作
Redis的操作都是基于内存的,CPU不是 Redis性能瓶颈,,Redis的瓶颈是机器内存和网络带宽。
在计算机的世界中,CPU的速度是远大于内存的速度的,同时内存的速度也是远大于硬盘的速度。redis的操作都是基于内存的,绝大部分请求是纯粹的内存操作,非常迅速
二、单线程模型
单线程指的是 Redis 键值对读写指令的执行是单线程
Redis 的单线程指的是 Redis 的网络 IO 以及键值对指令读写是由一个线程来执行的。 对于 Redis 的持久化、集群数据同步、异步删除等都是其他线程执行
官方答案:因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。原文地址
Redis FAQ | Redis
所谓单线程是指对数据的所有操作都是由一个线程按顺序挨个执行的,使用单线程好处:
-
不会因为线程创建导致的性能消耗;
-
避免上多线程上下文切换引起的 CPU开销;
-
避免了线程之间的竞争问题,比如添加锁、释放锁、死锁等,不需要考虑各种锁的问题。
-
代码更清晰,处理逻辑简单
三、异步非阻塞的I/O模型
-
I/O :网络 I/O
-
多路:多个 TCP 连接
-
复用:共用一个线程或进程
多路指的是多个 socket 连接,复用指的是复用一个线程。IO多路复用主要有三种技术:select,poll,epoll。epoll 是目前最新最好的IO多路复用技术
Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。
IO一般分为磁盘IO和网络IO,这里我们主要关注网络IO。一次完整的网络IO过程如下所示:
从上图可以看出,数据无论从网卡到用户空间还是从用户空间到网卡都需要经过内核
IO多路复用的基本原理是,内核不是监视应用程序本身的连接,而是监视应用程序的文件描述符。当客户端运行时,它将生成具有不同事件类型的套接字。在服务器端,I / O 多路复用程序(I / O 多路复用模块)会将消息放入队列(也就是 下图的 I/O 多路复用程序的 socket 队列),然后通过文件事件分派器将其转发到不同的事件处理器。
简单来说:Redis 单线程情况下,内核会一直监听 socket 上的连接请求或者数据请求,一旦有请求到达就交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的事件处理器。所以 Redis 一直在处理事件,提升 Redis 的响应性能
阻塞 I/O
当应用程序调用一个 IO 函数,其底层会委托操作系统的recvfrom()去完成,当数据还没有准备好时,revfrom会一直阻塞,等待数据准备好。当数据准备好后,从内核拷贝到用户空间,recvfrom 返回成功,IO函数调用完成。过程如下所示:
阻塞IO模型的优点是编程简单,但缺点是需要配合大量线程使用。应用进程没接收一个连接,就需要为此连接创建一个线程来处理该连接上的读写任务。
非阻塞 I/O
调用进程在等待数据的过程中不会被阻塞,而是会不断地轮询查看数据有没有准备好。当数据准备好后,将数据从内核空间拷贝到用户空间,完成IO函数的调用。等待数据的过程是非阻塞的,但数据拷贝时仍是阻塞的。过程如下所示
非阻塞io的优点在于可以实现使用一个线程同时处理多个连接的需求,减少线程的大量使用。缺点在于要不断地去轮询检查数据是否准备好,比较耗费CPU
IO复用只需要阻塞在select,poll或者epoll,可以同时处理和管理多个连接。缺点是当 select、poll或者epoll 管理的连接数过少时,这种模型将退化成阻塞 IO 模型。并且还多了一次系统调用:一次 select、poll或者epoll 一次 recvfrom。
1、select==>时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长,select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是: 单个进程可监视的fd数量被限制,即能监听端口的大小有限
2、poll==>时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的
3、epoll==>时间复杂度O(1)
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)),内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销
select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
四、高效的数据结构
我们知道MySQL为了提高检索速度使用了 B+ Tree 数据结构,所以 Redis 速度快应该也跟数据结构有关
Redis 一共有 5 种数据类型,String、List、Hash、Set、ZSet(SortedSet)
在 Redis 中存储value,常用的 5 种数据类型和应用场景如下
:
String: 缓存、计数器、分布式锁等。
List: 链表、队列、微博关注人时间轴列表等。
Hash: 用户信息、Hash 表等。
Set: 去重、赞、踩、共同好友等。
Zset: 访问量排行榜、点击量排行榜等
不同的数据类型底层使用了一种或者多种数据结构来支撑,目的就是为了追求更快的速度
SDS 简单动态字符串优势
C 语言字符串与 SDS
- SDS 中 len 保存这字符串的长度,O(1) 时间复杂度查询字符串长度信息。
- 空间预分配:SDS 被修改后,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间。
- 惰性空间释放:当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配
https://www.cnblogs.com/yxcn/p/14993512.html
Redis高性能原理:Redis为什么这么快?_Java后端何哥的博客-CSDN博客
redis为什么快?_redis为什么速度快_乱糟的博客-CSDN博客