性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
在我们平时买电脑的时候,主要参考的是 CPU 和显卡,CPU线程核心数越多,性能越好。
同理,Redis这么快,应该也是设计成多线程的才对啊。
实际上恰恰相反,Redis却是单线程的。
Redis的单线程,是真的只使用一个线程吗
Redis在其核心数据操作上运行在单线程模式,但是这并不意味着Redis的整个服务器进程只使用一个线程。
在Redis 6.0版本之前,所有的数据读取、写入、处理操作确实是在单个线程上串行执行的,
但即便在那个时候,Redis也会使用其他辅助线程来处理比如数据持久化(AOF、RDB)、集群消息传递等后台任务。
从Redis 6.0版本开始,引入了多线程模型来处理客户端的网络请求,但是这里的多线程仅仅是用于辅助任务,
如读取客户端的请求和将回复写入网络,而不是用于数据的读写操作。Redis中的数据读写操作仍然是单线程的,这保持了Redis的简单性和性能优势,
因为这样就避免了线程切换和同步的开销,同时也避免了并发数据访问时的竞态条件和锁的问题。
![ ][nbsp]
Redis之所以能够在单线程模型下快速处理高并发请求,主要得益于以下几个方面:
1、纯内存操作
Redis 将所有数据都存储在内存中,内存的读写速度非常快,这是Redis能够高速读写的基础。
2、非阻塞I/O
Redis在网络操作上使用非阻塞I/O,在连接客户端时和读写网络套接字时都不会阻塞处理器。即使某个操作因为资源暂时不可用而无法立即完成,它也不会等待,而是会继续执行处理其他任务,直到资源可用再回来完成之前的操作。
这种事件驱动模型带来的优势是显而易见的:
3、高效的数据结构
Redis为了确保高效的数据操作和访问速度,内部实现了多种专门优化的数据结构。
这些数据结构不仅保证了操作的高效性,还能够提供对不同数据类型的支持。
下面我列出一些Redis中使用的关键数据结构:
SDS是Redis用来保存字符串值的结构,它比C语言的传统字符串类型安全、高效,可以快速进行长度的计算和修改,且避免了频繁的内存重分配。
Redis列表(List)类型的内部实现之一是双端链表,这种数据结构可以从两端快速添加或删除元素,适用于实现栈或队列这样的数据结构。
![ ][nbsp 2]
在Redis中,哈希表(Hashes)类型是通过字典这种数据结构实现的。字典使用了散列表(Hash Table),它是键值对的集合,可以提供O(1)时间复杂度的键的查找、添加和删除操作。
Redis的有序集合(Sorted Sets)在元素较多或者元素的成员较长时,使用跳表作为其内部实现之一。跳表是一种可以做到O(logN)时间复杂度搜索的有序数据结构,同时也支持快速的插入、删除、查找等操作。
![ ][nbsp 1]
当一个集合(Set)类型的所有元素都是整数且元素数量不多时,Redis会使用整数集合来存储这个集合。整数集合是一种内存使用效率极高的数据结构,只支持整数值的存储。
压缩列表是Redis为了节省内存而设计的一种数据结构,它将多个元素序列化存储在一块连续的内存中。当列表(List)类型存储的字符串较小或者长度较短时,Redis会使用压缩列表来存储。
快速列表是Redis 3.2中引入的,它是一种更为优化的列表数据结构,本质上是压缩列表和双端链表的结合体。快速列表可以很好地平衡内存和性能,特别是在列表元素较多或者元素大小不一时。
在Redis中,基数树(又称为radix tree)被用于实现Redis的Streams类型的内部数据结构。基数树是一种可以高效地存储键值对的树形结构,尤其适合存储具有大量共同前缀的键。
4、事件驱动模型
Redis使用了基于事件的编程模型,采用了多路复用技术,如epoll(在Linux系统上)来同时处理多个网络连接的I/O。
这意味着单个线程可以监听多个文件描述符,一旦某个描述符就绪(例如,一个TCP连接上有新数据可读),该线程就能立即处理它。
5、优化的命令执行
Redis中的命令大多数都非常简单并且执行快速,如GET和SET,这些操作的复杂度通常是O(1)。
6、单线程避免了多线程的问题
由于Redis是单线程的,它避免了多线程环境中常见的各种问题,如线程之间的上下文切换开销、竞态条件、死锁等,这也意味着它无需在内部使用锁来保护数据结构的完整性,进一步提高了效率。
多线程的引入使得Redis可以更好地利用多核服务器的网络I/O性能,同时保持其单线程的数据处理优势。即便如此,这些I/O线程的数量通常建议不要太多,因为Redis的瓶颈很少在网络I/O上,更多是在于内存速度和CPU处理命令的能力。
总的来说,Redis之所以快,是因为它主要在内存中操作,并且使用了高效的编程模型和数据结构。尽管它在6.0版本后引入了多线程处理网络I/O,但是Redis的核心优势仍然来源于它的单线程事件处理能力。
在Redis 6.0之前,它使用的是单线程模型,所有的客户端请求都是依次顺序处理的。
但这并不意味着Redis是完全不使用其他线程的。例如,Redis使用后台线程来进行持久化操作,如RDB快照和AOF文件写入。
从Redis 6.0开始,引入了多线程来处理网络I/O,但数据读写和命令执行仍然是单线程的。
在这个多线程模型中,主线程仍然负责执行所有命令请求,而多个I/O线程则可以在不同的客户端连接之间共享,并且负责读写网络套接字。
这里详细解释一下Redis的线程模型:
单线程主循环(事件循环)
核心数据结构的读写操作,如键值的设置、获取、过期等,都是在一个单线程中串行执行的。这意味着同一时间内只有一个数据操作在执行,确保了数据的一致性和操作的原子性。
这个单线程还负责处理客户端的请求、执行命令和发送响应。
文件事件处理
Redis使用了I/O多路复用技术(如epoll、kqueue、select等)作为其文件事件处理器的基础,
这使得单个线程可以高效地监听和处理多个网络连接上的事件。
epoll原理详解及epoll反应堆模型:
![ ][nbsp 3]
多线程I/O(从Redis 6.0开始)
在Redis 6.0及以后的版本中,虽然核心数据操作仍然是在单线程中执行的,但引入了多线程来优化网络I/O的处理。
这些I/O线程仅负责将客户端的请求读入队列中以及将响应写回客户端,不负责实际的命令执行。
这样可以减轻单个主线程的负担,并提高大量连接下的网络吞吐量。
辅助线程
对于某些耗时操作,如持久化(RDB、AOF)、同步(replication)以及某些计算密集型的命令(比如SORT),
Redis会使用辅助线程来处理,以避免阻塞主线程。
线程安全
由于核心数据操作是在单个线程中完成的,Redis内部通常不需要进行复杂的同步机制来保证数据一致性。
而多线程用于网络I/O操作时,也是通过无锁的数据结构设计来避免线程间的数据访问冲突。
下面是这个模型的简化描述:
这种组合使用单线程和多线程的模型,使得Redis可以同时保持高性能和数据一致性。
单线程执行命令的设计简化了并发控制,而多线程的使用又优化了网络通信效率。
这样的线程模型是Redis能够在高并发场景下也能保持快速响应的重要原因之一。
最后说一句(求关注,求赞,别白嫖我)
最近无意间获得一份阿里大佬写的刷题笔记和面经,一下子打通了我的任督二脉,进大厂原来没那么难。
这是大佬写的, 7701页的阿里大佬写的刷题笔记,让我offer拿到手软
求一键三连:点赞、分享、收藏
点赞对我真的非常重要!在线求赞,加个关注我会非常感激!@小郑说编程
[nbsp]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Fredis%2F240109%2Fwhy.png&pos_id=img-po2Ub09W-1704807031716)
[nbsp 1]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Fredis%2F240109%2Fskip_list.png&pos_id=img-N05VVGTT-1704807032141)
[nbsp 2]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Fredis%2F240109%2Fdouble_duan_list.png&pos_id=img-qUct4PgE-1704807031924)
[nbsp 3]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=http%3A%2F%2F%2Fwww.feiz.vip%2Fimages%2Fredis%2F240109%2Fepoll.png&pos_id=img-A1yxkTXI-1704807032347)