概叙
Redis 作为一款流行的开源数据库,每个版本都在不断的迭代和升级,新增了丰富的特性和功能,解决了更多实际应用场景中的问题,提高了 Redis 的性能和可靠性。
总体来说,Redis 的演化之路可以分为以下几个阶段:
-
初期版本阶段(2009 - 2011):在 Redis 最初的版本中,开发人员主要关注将 Redis 构建为一个高效的键值存储系统,支持基本的字符串和列表操作功能。
-
版本 2.0 及更新版本的阶段(2011 - 2012):Redis 2.0 带来了一些重要的特性,如虚拟内存和 Lua 脚本,这些特性让 Redis 变成一个更加强大的数据存储系统。此后,Redis 每个版本都会推出新特性。
-
高可用阶段(2012 - 2015):Redis 2.4 中实现了主从复制,引入了用于分布式系统的标准工具包 Sentinel。2015年,Redis 的 Cluster 模式发布,让 Redis 可以像分布式数据库一样进行横向扩展。
-
发展阶段(2015 - 2018):Redis 社区持续稳定地改进性能并增加新特性,如支持 Geo 搜索的 GEO 数据类型、对 Bitmaps 的支持,以及针对数据安全的增强功能。
-
数据分析阶段(2018 至今):Redis 对数据分析的支持越来越强大,Redis Labs 发布了 RediSearch、RedisAI 和 RedisTimeSeries 等模块,让 Redis 用于数据科学和 AI 技术的开发变得更加容易。
Redis 的演化之路可以归纳为制定协议、节约内存、支持多种数据类型、引入集群支持、实现高可用、提供扩展功能等几个阶段。Redis 的不断发展和更新,使其成为当前最流行和实用的 NoSQL 数据库之一。
Redis一直都是多线程模型
常说的Redis单线程是指「接受客户端请求->解析请求->进行数据读写->返回数据给客户端」这个过程由一个线程(主线程)来完成。
但Redis程序并不是单线程,启动时会启动后台线程(BIO_CLOSE_FILE、BIO_AOF_FSYNC、BIO_LAZY_FREE),所以从整体来说整个redis是多线程的:
- Redis在2.6,会启动2个后台线程,分别处理关闭文件、AOF刷盘任务
- Redis在4.0之后,新增1个 lazyfree 后台线程,用来异步释放Redis内存,会把一部分删除操作(例如 unlink key、flushdb async、flushall async等)交给后台线程来执行,不会导致主线程的卡顿。因此,删除一些大key的时候,使用unlink命令来异步删除,避免使用del命令卡顿主线程。
- Redis在6.0之后将主线程做了拆分:工作线程=主业务线程(单线程完成命令执行)+读写线程(负责网络连接处理)
关闭文件、AOF刷盘、释放内存这些任务用后台线程执行是因为比较耗时,主线程执行的话很容易阻塞;
后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者轮询队列,拿到任务就去执行,分别对应三个任务队列(BIO_CLOSE_FILE、BIO_AOF_FSYNC、BIO_LAZY_FREE)。
Redis 共有三个后台线程,分别是:
- close_file 用于完成关闭文件描述符对应的文件(释放套接字、数据空间等)的操作;
- aof_fsync 用于完成 AOF 刷盘操作;
- lazy_free 用于完成惰性释放内存空间操作。
Redis的一些主要版本
Redis(Remote Dictionary Server)是一个基于内存的开源数据结构存储系统。从1.0到7.0共经历了12个比较大的版本,每一个版本都有不同的特性和功能。
Redis 1.0 版本特性
Redis 1.0 是 Redis 数据库的第一个正式版本,于2009年3月发布。Redis 1.0 采用 C 语言编写,支持 Linux、Mac OS X 和 Solaris 等多个操作系统,提供了一种新的键值存储模式,并引入了一些基本的数据结构和操作命令。Redis 1.0 的发布为分布式数据库和缓存系统带来了全新的选择。
以下是 Redis 1.0 中一些重要的特性:
-
内存中存储:Redis 1.0 基于内存存储数据,性能远高于传统的数据库,特别适合对延迟和响应速度有高要求的应用。
-
支持多种数据类型:Redis 提供了多种数据类型,包括字符串、哈希、列表、集合和有序集合,使得开发人员可以根据具体业务需求灵活选择合适的数据存储结构。
-
支持持久化:Redis 1.0 中首次引入了 RDB(Redis 数据库快照)机制,可以在一定时间间隔内自动将内存中的数据写入磁盘并保存,保证数据的持久化和可靠性。
-
提供命令行界面:Redis 1.0 提供了命令行界面,使得用户可以方便地进行交互式操作和控制 Redis 服务器。
-
Redis 1.0 作为 Redis 数据库发展的早期版本,其特性还相对比较简单,但它所提供和实现的基础功能,奠定了 Redis 数据库的基础,并为 Redis 的未来发展奠定了坚实的基础。
Redis 2.0 版本特性
Redis 2.0 于2010年4月发布。Redis 2.0 引入了一些重要的新特性,如虚拟内存机制、实现 Redis 的 Lua 脚本语言、支持哈希类型的直接操作以及提供慢查询日志等功能。Redis 2.0 的发布为 Redis 数据库提供了更加强大和灵活的功能支持。
以下是 Redis 2.0 中一些重要的特性:
-
引入虚拟内存:Redis 2.0 第一次实现了虚拟内存,支持将部分不常用的数据保存到磁盘中,保证了 Redis 在物理内存不足时仍然能够正常工作。
-
实现 Lua 脚本语言:Redis 2.0 引入了 Lua 脚本语言,可用于开发 Redis 数据源的无缝集成、数据过滤、数据平滑、数据分析以及数据交换等操作。
-
支持哈希哈希操作:Redis 2.0 实现了哈希类型的操作,允许用户直接对哈希表进行添加、删除和更新操作,并支持在哈希元素上执行命令。
-
提供慢查询日志:Redis 2.0 引入了慢查询日志记录机制,使得开发人员可以有效地识别性能瓶颈和调优 Redis 的应用程序。
除了以上的变化和新增的功能外,Redis 2.0 也大幅提升了性能和稳定性。Redis 2.0 是 Redis 数据库发展的一个重要里程碑,也为后续版本的改进和优化提供了坚实的基础。
Redis 2.2 版本特性
Redis 2.2 于2011年5月发布。该版本引入了一些重要的新特性,包括支持脚本操作、支持有序集合(sorted set)和增强了调试功能。以下是 Redis 2.2 中一些重要变化和新增功能:
-
支持 Redis 脚本:Redis 2.2 中首次引入了 Lua 脚本,并增加了 EVAL/EVALSHA 命令,使得用户可以根据具体业务需求,编写代码对 Redis 中的数据进行处理和操作。
-
引入有序集合(sorted set):在 Redis 2.2 中,有序集合被引入,该数据结构类似于无序集合(set),但每个元素都有一个关联的分数,可以用于分数排名等场景。
-
强化调试功能:Redis 2.2 中引入了调试命令,如 OBJECT 和 DEBUG,为开发人员提供了诊断和调试 Redis 服务器问题的工具。
-
新增命令:Redis 2.2 中增加了一些新命令,如 ZRANK、ZREVRANK、ZCOUNT 等。
除了以上的变化和新增的功能外,Redis 2.2 还引入了在 Redis 中支持大数值的整型、增加了对 IPv6 的支持。Redis 2.2 在性能和可靠性方面得到了大幅改进,为 Redis 的发展奠定了坚实的基础。
Redis 2.4 版本特性
Redis 2.4 于2011年9月发布。Redis 2.4 引入了一些重要的新特性,如主从复制支持、发布订阅支持和事务支持等。Redis 2.4 的发布为 Redis 数据库提供了更加高可用、可靠和灵活的功能支持。
以下是 Redis 2.4 中一些重要的特性:
-
引入主从复制:Redis 2.4 实现了主从复制机制,使得 Redis 可以在多个服务器之间进行数据复制和同步,提高了 Redis 数据库的可伸缩性和可用性。
-
实现发布订阅模式:Redis 2.4 引入了发布订阅模式,允许多个客户端订阅指定的频道,并实时接收有关数据更新和变化的通知。
-
支持事务:Redis 2.4 实现了事务机制,使用 MULTI/ EXEC 命令将多个操作封装成一个事务,并通过 WATCH 命令实现乐观锁机制,保证数据的一致性和完整性。
-
提供管道功能:Redis 2.4 提供了管道技术,可以批量地执行命令并返回结果,减少网络带宽和延迟,提高了 Redis 的性能。
除了以上的变化和新增的功能外,Redis 2.4 还对性能、稳定性和错误处理能力等方面进行了大幅优化和改进,为 Redis 数据库的发展奠定了坚实的基础。
Redis 2.6 版本特性
Redis 2.6 于2012年8月发布。Redis 2.6 引入了一些重要的新特性,如虚拟内存、持久化和内存优化等。Redis 2.6 的发布为 Redis 数据库提供了更加强大和灵活的功能支持。
以下是 Redis 2.6 中一些重要的特性:
-
引入虚拟存储:Redis 2.6 引入了虚拟存储机制,支持将不常用的数据自动保存到磁盘中,减少了 Redis 服务器的内存占用。
-
支持持久化:Redis 2.6 中首次引入了 AOF(Append Only File)持久化机制,可以将每次写操作追加到文件中,以保证数据的可靠性和持久化。
-
内存优化机制:Redis 2.6 引入了内存优化机制,通过压缩列表(ziplist)和增量哈希表,减少了 Redis 对内存的占用,提高了 Redis 的性能和稳定性。
-
增加新的数据类型:Redis 2.6 增加了一种新的数据类型——到期键(EXPIRE),可以指定键的生存时间,并在过期后删除键,具有很强的应用价值。
Redis 2.6 除了以上的变化和新增的功能外,还对性能和错误处理能力进行了大幅优化和改进,提高了 Redis 的可靠性和稳定性。Redis 2.6 为 Redis 数据库的发展奠定了坚实的基础。
Redis 2.8 版本特性
Redis 2.8 于2014年4月发布。Redis 2.8 引入了一些重要的新特性,如对 Lua 脚本、集群和二进制流支持的提升,以及支持 SSL/TLS 加密等功能。Redis 2.8 的发布为 Redis 数据库提供了更加强大、高效和安全的功能支持。
以下是Redis 2.8 中一些比较重要的:
-
增强的 Lua 脚本支持:Redis 2.8 引入了支持 Redis 事务的 Lua 脚本,并且在 Lua 语言层面加入了支持 SHA1 校验和,为 Redis 更多应用场景提供了自定义扩展功能。
-
新的 Redis Sentinel 实现:Redis 2.8 引入了 Redis Sentinel,一个分布式系统的监视器,支持 Redis 高可用性和自动故障转移。
-
多种新的命令和数据结构:Redis 2.8 引入了多条新命令,例如 BITCOUNT、MSETNX 等等。同时也增加了新的数据结构,包括带有 TTL 的有序集合(ZSET)和哈希表(HASH)。
-
优化增量重写:Redis 2.8 改进了 RDB(Redis 数据库快照)机制中的增量重写,使得 Redis 数据库的备份和恢复更加高效和可靠。
-
更好的性能:Redis 2.8 引入了多种性能优化,例如对 Redis 原生虚拟内存的支持、对新的数据结构的支持、对网络I/O的优化等等,加快了 Redis 的处理速度和响应速度。
总体来说,Redis 2.8 在性能、可扩展性、容错性和灵活性等方面都有很大提升,为 Redis 数据库的应用和发展提供了更加全面、高效和可靠的功能支持,使得 Redis 数据库在实时数据存储和快速数据处理场景得到了更广泛的应用和支持。
Redis 3.0 版本特性
Redis 3.0 于2015年4月发布。Redis 3.0 引入了一些重要的新特性,如更加高效和可靠的集群方案、更加安全和可靠的持久化机制,以及增强了对 Lua 脚本、哈希表和流数据的支持。Redis 3.0 的发布为 Redis 数据库提供了更加全面、高效和安全的功能支持。
以下是 Redis 3.0 中一些重要的特性:
-
多种新的命令和数据结构:Redis 3.0 引入了多条新命令,例如 GEOADD、BITFIELD 等等。同时也增加了新的数据结构,包括 GEO 数据类型和 HyperLogLog 数据类型。
-
增强的 Sentinal 系统:Redis 3.0 增强了 Sentinal 系统,提高了 Redis 高可用性和自动故障迁移能力。
-
多种新的集群模式:Redis 3.0 提供了多种新的集群模式,例如 Redis Cluster,提高了 Redis 数据库的可用性和可扩展性。
-
新的持久化机制:Redis 3.0 提供了 RDB(Redis 数据库快照)和 AOF(Append Only File)两种持久化机制,并对这两种机制进行了优化和增强。
-
更好的 Lua 脚本支持:Redis 3.0 改进了 Lua 脚本支持,提高了 Redis 数据库的可扩展性和灵活性。
-
多种新的配置选项:Redis 3.0 引入了多种新的配置选项,使得 Redis 数据库更加灵活可配置。
总体来说,Redis 3.0 在性能、可扩展性和容错性等方面都有很大提升,为 Redis 数据库的应用和发展提供更加全面、高效和可靠的功能支持,使得 Redis 数据库在实时数据存储和快速数据处理场景得到了更广泛的应用和支持。
Redis 3.2 版本特性
Redis 3.2 于2016年8月发布。Redis 3.2 引入了一些重要的新特性,如更加强大的集群方案、增强的 Lua 脚本和哈希表支持、更加安全和可靠的持久化机制等。Redis 3.2 的发布为 Redis 数据库提供了更加完善、全面和高效的功能支持。
Redis 3.2 引入的一些重要特性包括:
-
实用的集群架构:Redis 3.2 引入了对 Redis 集群的完整支持,支持高可用性、数据复制和分片。
-
流数据类型:Redis 3.2 引入了流(stream),可以用于有序、事件型数据的存储和处理。
-
Lua 脚本增强:Redis 3.2 提供了更多 Lua 脚本的操作和支持,可以用于更加复杂和灵活的应用程序开发。
-
增强的持久化机制:Redis 3.2 通过改进 RDB(Redis 数据库快照)和 AOF(Append Only File)两种持久化机制,提供更加高效和可靠的数据备份和恢复。
-
新增命令和数据结构:Redis 3.2 增加了多个新的功能,包括 GEO 数据类型、针对 HyperLogLog 和 Bloom 过滤器的命令,以及一些有关时间序列的新命令和数据结构。
-
带有模式识别的键过期:Redis 3.2 允许使用通配符进行键匹配并设置带有模式识别的键过期时间。
总体来说,Redis 3.2 版本为 Redis 数据库提供了更加全面、高效和可靠的功能支持,增强了 Redis 数据库应用在实时数据处理和实时存储场景中的能力,并大幅提升了 Redis 数据库的可用性和可扩展性,当然这些新特性在提高了性能的同时也增加了学习成本。
Redis 4.0 版本特性
Redis 4.0 于2017年9月发布。Redis 4.0 引入了一些重要的新特性,如更加强大和高效的集群功能、更加安全和可靠的持久化机制、新的数据类型、事务和流水线批处理等功能。Redis 4.0 的发布为 Redis 数据库提供了更加全面、高效和可靠的功能支持。以下是其中一些比较重要的:
-
更强大、高效的集群功能:Redis 4.0实现了自动分片和无中心式的集群控制平面,提高了Redis数据库的可用性和伸缩性。
-
增加新的数据类型:Redis 4.0引入了模块化数据类型,如快照文件、复制和持久化机制,可以自由地扩展和定制Redis数据库的功能。
-
事务和批处理等功能增强:Redis 4.0实现了多命令执行的事务机制和批量处理机制,提高了Redis数据库的性能和可用性。
-
增强持久化机制:Redis 4.0改进了RDB和AOF机制,提供更加高效和可靠的数据备份和恢复方案。
-
流水线集群:Redis 4.0引入了新的流水线集群协议,可以同时执行多个命令,提高了Redis数据库的性能。
-
增强的集合:Redis 4.0增加了多个对集合类型的操作指令,增强了集合的功能。
-
增强的有序集合:Redis 4.0也增加了多个对有序集合类型的操作指令,增强了有序集合的功能。
-
客户端缓存:Redis 4.0实现了客户端缓存(client-side caching)机制,可以在客户端上缓存一些热点数据,提高应用程序的性能和可用性。
-
Redis Graph:Redis 4.0引入了Redis Graph模块,提供图形数据库功能,支持图形数据存储和处理。
-
Secured Redis:Redis 4.0增加了对安全性的支持,可以对Redis访问进行认证和授权,提高了Redis数据库的安全性。
总体来说,Redis 4.0在功能和性能方面都得到了很大的提升,为Redis数据库的应用和发展带来更广泛的机遇。
Redis 5.0 版本特性
Redis 5.0 于2018年10月发布。Redis 5.0 引入了一些重要的新特性,如更加强大和高效的模块化架构,更加安全和可靠的持久化机制、新的数据类型、多种集群模式、新增命令等等。以下是Redis 5的一些更突出的新特性:
-
内置的模块化架构:Redis 5.0 引入了一个内置的模块化架构,使得开发人员可以更加灵活地定制和扩展 Redis 功能。
-
更加安全和可靠的持久化机制:Redis 5.0 针对 AOF(Append Only File)和 RDB(Redis 数据库快照)机制进行了优化,提供了更加高效和可靠的数据存储和备份方案。
-
新增数据类型:Redis 5.0 引入了相对时间类型(Reliable Timer),支持延迟任务调用和持续计时等场景,也添加了一种新的数据类型:流带有客户端组支持, 以便开发人员可以轻松地在 Redis 中存储和查询有序事件测试。
-
多种集群模式:Redis 5.0 提供了更多的集群部署模式,包括在不同机房之间进行数据中心复制和分片,提高了 Redis 数据库的应用容错性和可伸缩性。
-
新增多条命令:Redis 5.0 增加了多个新的命令,例如 RPOPLPUSH 命令和 ZPOPMAX/ZPOPMIN 命令等等,这些操作可以更好地支持具有高吞吐量的实时应用程序。
总的来说,Redis 5.0 在模块化、安全性和可靠性、集群和性能优化等方面都有显著的提升,在支持实时数据存储和处理方面有越来越广泛的应用和支持。Redis 5.0的发布将继续推动Redis数据库的应用和发展,为Redis数据存储和管理提供更加完整的功能支持。
Redis 6.0 版本特性
Redis 6.0 是 Redis 数据库的一个重要版本,于2020年4月发布。Redis 6.0 带来了许多新特性,以下是其中一些比较重要的:
-
新的命令和数据类型的增加:Redis 6.0 引入了多条新命令,例如 XPEEK、XGROUP、XACK 等等。同时也增加了模块化数据类型 LISTPACK 这个数据结构用于列表。
-
增强的集群功能:Redis 6.0 加强了对集群的支持,包括更好的负载均衡、更快的故障转移和更强的容忍度,这使得 Redis 集群更加可靠和稳健。
-
更快的性能和更低的延迟:Redis 6.0 引入了很多性能优化,包括 CRC32C 校验和、优化的数据结构、更好的虚拟内存管理等等,这些变化相比之前版本更加高效。
-
更好的内存管理:Redis 6.0 引入了新的内存分配器 jemalloc,这个内存分配器能够有效地减少内存碎片,并且提高 Redis 的内存利用率和性能。
-
更好的可伸缩性:Redis 6.0 改善了对大规模数据和高并发流量应用场景的支持,使得 Redis 数据库更加可伸缩和可扩展。
-
更好的安全性:Redis 6.0 提供了更好的安全性支持,包括加强的密码和 SSL/TLS 加密支持,保障 Redis 在分布式网络中的安全通信。
总体来说,Redis 6.0 在性能、安全性、可伸缩性和集群支持等方面都有很大提升,为 Redis 数据库的应用和发展提供更加全面、高效和可靠的功能支持,让 Redis 数据库更好地支持实时数据存储和处理的需求。
可以看到,网络I/O和命令处理部分都是单线程。
Redis基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。
- 文件事件处理器使用I/O多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器
- 当被监听的套接字准备好连接、读取、写入、关闭等操作时,与操作相对应的文件事件就会产生,这是文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件
Redis初始化时,会做如下事情:
- 首先,调用 epoll_create() 创建一个 epoll 对象、调用 socket() 创建一个服务端 socket
- 其次,调用bind() 绑定端口、调用 listen() 监听该 socket
- 然后,调用 epoll_ctl() 将监听的 socket 加入到 epoll,同时注册连接事件处理函数
初始化完成后,主线程进入一个事件循环函数:
- 先调用处理发送队列函数,发送队列如果有任务,则通过write函数将客户端发送缓存区里的数据发送出去,如果这一轮没有发送完,就注册写事件处理函数,等待 epoll_wait 发现可写后再处理
- 然后,调用 epoll_wait 函数等待事件的到来
个人理解:
I/O多路复用(epoll):实质上通过一个线程同时监听来自客户端的大量连接,并将客户端的请求创建相应的文件事件并分发给相应的文件事件处理器
Redis的处理过程(1-2为Redis初始化,3-4为客户端连接,5-9为处理客户端命令):
- Redis初始化时,首先创建 epoll 对象以及服务端 socket A
- 将该 socket A 添加到 epoll 的监听列表中,并为该 socket A 注册 连接事件处理器
- epoll 监听到 socket A 有来自客户端的连接请求,创建连接事件并分发给A之前注册的连接事件处理器
- 连接事件处理器:接受客户端 socket B 的连接,将B添加到epoll的监听列表,并为B注册 读事件处理器
- 客户端请求执行命令,epoll 监听到 socket B 可以执行读取(read),创建读事件并分发给之前注册的读事件处理器
- 读事件处理器:调用 read 读取 socket B 的内容,解析并执行来自客户端的命令,然后将客户端对象添加到发送队列,并将执行结果写到发送缓存区等待发送
- 主线程的事件循环除了等待来自 epoll 的事件,还会检查发送队列是否有任务,如果有,就调用 write 将发送缓存区的数据发送到相应客户端的 socket,如果不可写或者本轮没有发送完,就为该客户端B注册一个 写事件处理器
- epoll 监听到 socket B 可以执行写入(write),创建写事件并分发给之前注册的写事件处理器
- 写事件处理器:调用 write 将发送缓存区的数据发送到相应客户端的 socket,本轮没有发送完,就继续注册写事件处理器,等待 epoll 发现可写后再处理
❝主线程与 IO 多线程是如何实现协作呢?
如下图:
Redis多线程与IO线程
主要流程:
-
主线程负责接收建立连接请求,获取
socket
放入全局等待读处理队列; -
主线程通过轮询将可读
socket
分配给 IO 线程; -
主线程阻塞等待 IO 线程读取
socket
完成; -
主线程执行 IO 线程读取和解析出来的 Redis 请求命令;
-
主线程阻塞等待 IO 线程将指令执行结果回写回
socket
完毕; -
主线程清空全局队列,等待客户端后续的请求。
思路:将主线程 IO 读写任务拆分出来给一组独立的线程处理,使得多个 socket 读写可以并行化,但是 Redis 命令还是主线程串行执行。
Redis 7.0 版本特性
Redis7.2 是 Redis 的最新版本,包含了一系列广泛的新特性,以及对 AI 的支持提供了重大的投资.
redis7新特性
- multi-AOF: 7之前的版本AOF只有一个文件,现在有多个处于同一目录的AOF文件
- RDB文件格式更新,不兼容老版本的RDB格式
- redis function: 用来替代lua脚本的,暂时用处不多
- ACL: 更细粒度的ACL,能通过selector选择命令集合,对命令集合进行鉴权
1.1、redis7单线程
Redis是单线程
主要是指Redis的网络IO和键值对读写是由一个线程来完成的
Redis在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。
这也是Redis对外提供键值存储服务的主要流程。
但Redis的其他功能,比如持久化RDB、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。
Redis命令工作线程是单线程的,但是,整个Redis来说,是多线程的;
1.2、redis7单线程快
基于内存操作:redis的所有数据都存在内存中,因此所有的运算都是内存级别的,性能比较高
数据结构简单:redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1)
多路复用和非阻塞I/O:redis使用I/O多路复用功能来监听多个socket连接客户端,这样就可以使用一个线程来处理多个请求,减少线程切换带来的开销。同时也避免了I/O阻塞操作
避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,省去了多线程切换带来的时间和性能上的消耗,而且单线程不会导致死锁问题的发生
简单来说,Redis4.0之前一直采用单线程的主要原因有以下三个:
1 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;
2 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻塞IO;
3 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU。
2、为啥加入多线程
1、单线程的问题
正常情况下使用 del 指令可以很快的删除数据
而当被删除的 key 是一个非常大的对象时,例如时包含了成千上万个元素的 hash 集合时,那么 del 指令就会造成 Redis 主线程卡顿。
这就是redis3.x单线程时代最经典的故障,大key删除的头疼问题,
由于redis是单线程的,del bigKey .....
等待很久这个线程才会释放,类似加了一个synchronized锁,你可以想象高并发下,程序堵成什么样子?
2、解决办法
使用惰性删除可以有效的避免redis卡顿的问题
比如当我(Redis)需要删除一个很大的数据时,因为是单线程原子命令操作,这就会导致 Redis 服务卡顿,
于是在 Redis 4.0 中就新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据效率比较低的问题的。
unlink key
flushdb async
flushall async
把删除工作交给了后台的小弟(子线程)异步来删除数据了。
因为Redis是单个主线程处理,redis之父antirez一直强调"Lazy Redis is better Redis".
而lazy free的本质就是把某些cost(主要时间复制度,占用主线程cpu时间片)较高删除操作,
从redis主线程剥离让bio子线程来处理,极大地减少主线阻塞时间。从而减少删除导致性能和稳定性问题。
在redis4.0就引入了多个线程来实现数据的异步惰性删除等功能
但是其处理读写请求的仍然只有一个线程,所以仍然算是狭义上的单线程
3、redis6/7多线程特性和IO多路复用
3.1、redis主要的性能瓶颈是内存或者网络带宽而非CPU
3.2、redis6/7真正多线程登场
在Redis6/7中,非常受关注的第一个新特性就是多线程。
这是因为,Redis一直被大家熟知的就是它的单线程架构,虽然有些命令操作可以用后台线程或子进程执行(比如数据删除、快照生成、AOF重写)。
但是,从网络IO处理到实际的读写命令处理,都是由单个线程完成的。
随着网络硬件的性能提升,Redis的性能瓶颈有时会出现在网络IO的处理上,也就是说,单个主线程处理网络请求的速度跟不上底层网络硬件的速度,
为了应对这个问题:
采用多个IO线程来处理网络请求,提高网络请求处理的并行度,Redis6/7就是采用的这种方法。
但是,Redis的多IO线程只是用来处理网络请求的,对于读写操作命令Redis仍然使用单线程来处理。
这是因为,Redis处理请求时,网络处理经常是瓶颈,通过多个IO线程并行处理网络操作,可以提升实例的整体处理性能。
而继续使用单线程执行命令操作,就不用为了保证Lua脚本、事务的原子性,额外开发多线程互斥加锁机制了(不管加锁操作处理),这样一来,Redis线程模型实现就简单了
3.3、主线程和IO线程是协作完成请求处理
3.4、Unix网络编程中的五种IO模型
Blocking IO 阻塞IO
NoneBlocking IO 非阻塞IO
IO Multiplexing IO多路复用
signal driven IO 信号驱动IO
asynchronous IO 异步IO
IO多路复用
一个服务端进程可以同时处理多个套接字描述符
实现IO多路复用的模型有3种:select-->poll-->epoll三个阶段来描述
模拟一个tcp服务器处理30个客户socket。
假设你是一个监考老师,让30个学生解答一道竞赛考题,然后负责验收学生答卷,你有下面几个选择:
第一种选择(轮询):按顺序逐个验收,先验收A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被耽误,你用循环挨个处理socket,根本不具有并发能力。
第二种选择(来一个new一个,1对1服务):你创建30个分身线程,每个分身线程检查一个学生的答案是否正确。这种类似于为每一个用户创建一个进程或者线程处理连接。
第三种选择(响应式处理,1对多服务),你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。
此时E、A又举手,然后去处理E和A。。。
这种就是IO复用模型。Linux下的select、poll和epoll就是干这个的。
将用户socket对应的文件描述符(FileDescriptor)注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。
此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,
收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor反应模式。
在单个线程通过记录跟踪每一个Sockek(I/O流)的状态来同时管理多个I/O流. 一个服务端进程可以同时处理多个套接字描述符。
目的是尽量多的提高服务器的吞吐能力。
大家都用过nginx,nginx使用epoll接收请求,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。
redis类似同理,这就是IO多路复用原理,有请求就响应,没请求不打扰。
3.5、简单说明
redis工作线程是单线程的
但是整个redis来说,是多线程的
4、redis7如何开启多线程
1.设置io-thread-do-reads配置项为yes,表示启动多线程。
2。设置线程个数。
关于线程数的设置,官方的建议是如果为 4 核的 CPU,建议线程数设置为 2 或 3,如果为 8 核 CPU 建议线程数设置为 6,线程数一定要小于机器核数,线程数并不是越大越好。
5、总结
Redis自身出道就是优秀,基于内存操作、数据结构简单、多路复用和非阻塞 I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;
但对于大数据的 key 删除还是卡顿厉害,因此在 Redis 4.0 引入了多线程unlink key/flushall async 等命令,主要用于 Redis 数据的异步删除;
而在 Redis6/7中引入了 I/O 多线程的读写,这样就可以更加高效的处理更多的任务了,Redis 只是将 I/O 读写变成了多线程
而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis 不会出现线程安全的问题。
Redis 无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis 变得越来越快。