Redis为什么这么快?Redis的线程模型与Redis多线程

本文详细介绍了Redis作为内存数据库的高速性能特点及其背后的原因,包括内存操作的优势、简单的数据结构设计、单线程模型、多路I/O复用机制及自行构建的VM机制等,并探讨了Redis6.0引入多线程的背景。

一、Redis有多快?

Redis是基于内存运行的高性能 K-V 数据库,官方提供的测试报告是单机可以支持约10w/s的QPS

​二、Redis为什么这么快?

(1)完全基于内存,数据存在内存中,绝大部分请求是纯粹的内存操作,非常快速,跟传统的磁盘文件数据存储相比,避免了通过磁盘IO读取到内存这部分的开销。

(2)数据结构简单,对数据操作也简单。Redis中的数据结构是专门进行设计的,每种数据结构都有一种或多种数据结构来支持。Redis正是依赖这些灵活的数据结构,来提升读取和写入的性能。

(3)采用单线程,省去了很多上下文切换的时间以及CPU消耗,不存在竞争条件,不用去考虑各种锁的问题,不存在加锁释放锁操作,也不会出现死锁而导致的性能消耗。

(4)使用基于IO多路复用机制的线程模型,可以处理并发的链接。

Redis 基于 Reactor 模式开发了自己的网络事件处理器,这个处理器被称为文件事件处理器 file event handler。由于这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型,但是它采用IO多路复用机制同时监听多个Socket,并根据Socket上的事件来选择对应的事件处理器进行处理。文件事件处理器的结构包含4个部分,线程模型如下图:

多个Socket IO多路复用程序 文件事件分派器 事件处理器(命令请求处理器、命令回复处理器、连接应答处理器)

​多个 Socket 可能会产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,将Socket产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。

多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,然后程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。 这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个客户端的网络IO连接请求(尽量减少网络 IO 的时间消耗)

(5)Redis直接自己构建了VM 机制 ,避免调用系统函数的时候,浪费时间去移动和请求

三、为什么Redis是单线程?

这里我们强调的单线程,指的是网络请求模块使用一个线程来处理,即一个线程处理所有网络请求,其他模块仍用了多个线程。

那为什么使用单线程呢?官方答案是:因为CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

但是,我们使用单线程的方式是无法发挥多核CPU 性能,不过我们可以通过在单机开多个Redis 实例来解决这个问题

四、Redis6.0 的多线程?

1、Redis6.0 之前为什么一直不使用多线程?

Redis使用单线程的可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。

2、Redis6.0 为什么要引入多线程呢?

因为Redis的瓶颈不在内存,而是在网络I/O模块带来CPU的耗时,所以Redis6.0的多线程是用来处理网络I/O这部分,充分利用CPU资源,减少网络I/O阻塞带来的性能损耗。

3、Redis6.0 如何开启多线程?

默认情况下Redis是关闭多线程的,可以在conf文件进行配置开启:

io-threads-do-reads yes io-threads 线程数 ## 官方建议的线程数设置:4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数,尽量不超过8个。

4、多线程模式下,是否存在线程并发安全问题?

如图,一次redis请求,要建立连接,然后获取操作的命令,然后执行命令,最后将响应的结果写到socket上。

​在redis的多线程模式下,获取、解析命令,以及输出结果着两个过程,可以配置成多线程执行的,因为它毕竟是我们定位到的主要耗时点,但是命令的执行,也就是内存操作,依然是单线程运行的。所以,Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行,也就不存在并发安全问题。

### Redis 高性能原理 Redis 的高性能主要得益于其内存操作特性。由于 Redis 将数据存储在内存中,因此读写数据时不受硬盘 I/O 速度的限制,从而实现了极高的吞吐量和低延迟响应[^3]。 除了内存存储的优势外,Redis 还通过单线程事件驱动模型来处理请求,这种设计减少了上下文切换带来的开销,进一步提升了性能。此外,Redis 支持多种数据结构(字符串、哈希、列表等),能够满足不同场景下的高效访问需求。 --- ### Redis 集群架构分析 为了支持大规模分布式环境下的高可用性和扩展性,Redis 提供了集群功能。Redis Cluster 是一种分布式的解决方案,它允许将数据分布在多个节点上,并提供自动分片能力。以下是 Redis 集群的主要特点: - **主从复制**:Redis 使用主从复制机制,在主节点发生故障时可以从节点接管工作负载,保障系统的高可用性。 - **分区策略**:Redis Cluster 基于槽位(slot)分配算法,将键空间划分为 16384 个槽位,每个槽位可以映射到不同的节点上,实现数据的水平扩展。 - **故障检测转移**:内置 Gossip 协议用于节点间通信,速发现并隔离失效节点;同时支持选举新主节点的过程自动化完成。 这些特性的组合使得 Redis 能够适应复杂的生产环境中对于弹性伸缩的要求[^1]。 --- ### 缓存雪崩应对策略 缓存雪崩指的是大量缓存同时到期导致数据库压力骤增的现象。针对这一问题,有如下几种常见解决办法: #### 方法一:设置随机过期时间 通过对同一类别的 key 设置带有一定范围波动的时间戳作为 TTL(Time To Live),避免集中刷新的情况出现。例如: ```python import random def set_key_with_random_ttl(key, value): ttl = base_ttl + random.randint(-delta, delta) # 动态调整TTL redis.setex(key, ttl, value) ``` 这种方法有效分散了热点key更新频率,减轻后台数据库瞬时负担[^4]。 #### 方法二:引入互斥锁控制并发加载逻辑 当某个特定资源未命中本地缓存时,先尝试获取全局唯一标志位再执行实际查询动作。只有第一个成功加锁的任务才会向底层发起真实调用,其余等待者可以直接复用已重建好的副本内容。 伪代码演示如下所示: ```java public Object getWithLock(String key){ String lockKey = "lock:" + key; if(redis.exists(lockKey)){ sleepAndRetry(); // 如果已经存在,则稍后再试 }else{ try { boolean acquired = redis.setnx(lockKey,"locked",expireInSeconds); if(acquired){ rebuildCacheIfNecessary(key); // 只有获得锁才能继续往下走 redis.del(lockKey); // 解除锁定状态 } } catch(Exception e){ log.error(e.getMessage()); } } return redis.get(key); } ``` 以上措施共同作用下可显著缓解因突发流量引发的服务瘫痪风险。 --- ### 缓存一致性实现机制 在分布式系统里保持各级之间的一致性是一项挑战较大的课题。下面列举了几种常用的技术手段用来达成目标: #### 方案 A : 双写模式 应用程序每次修改原始数据的同时也负责同步通知对应的缓存层做相应的变更操作。虽然简单易懂但容易造成额外维护成本上升以及潜在失败隐患等问题存在。 #### 方案 B :异步消息队列解耦合 利用 MQ 中转的方式间接传递增量改动信息给订阅方消费处理。相比前者更加灵活可控同时也降低了两者之间的依赖程度。 具体流程图示意如下: ``` Application -> Produce Update Event to Message Queue -> Consumer Listen Events and Invalidate/Update Cache Accordingly. ``` 此方法特别适合跨数据中心远距离传播场合使用。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值