Redis之线程IO模型

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis之线程IO模型

Redis是一个单线程的应用程序,NodeJs、Nginx都是单线程,它们都属于服务器高性能的典范。

Redis之所以是单线程还能这么快的原因,

其一是因为它所有的数据都在内存当中,所有的运算都是内存级别的运算,所以使用redis时,要注意时间复杂度为O(n)的指令,因为是单线程的,如果数据量太大,会让其他指令被阻塞等待,

其二是因为redis使用非阻塞IO与多路复用处理大量的客户端连接。

非阻塞IO

当我们使用套接字的读写方法时,默认是阻塞的,

即调用read方法传递一个参数n,表示最多读取n个字节后返回,如果一个字节都没有,线程就会在read方法这里持续等待,直到有数据过来或者连接被关闭,read方法此时返回,线程才能执行下面的逻辑,

write方法一般不会阻塞,除非内核为套接字分配的写缓冲区满了,write方法才会阻塞,一直到缓存区中有空间闲出来。

下图是套接字读写的细节流程。

image.png

非阻塞IO在使用套接字时提供了一个选项Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少,

能读多少取决与内核为套接字分配的读缓冲区的数据字节数,能写多少取决于内核为套接字写缓冲区分配的数据字节数,

读写方法都会通过返回值告诉程序读写了多少字节数。

非阻塞IO意味着读写时,线程不必再被阻塞着,读写可以瞬间完成,线程可以继续往下做别的事情。

多路复用(事件轮询)

非阻塞IO虽然很快,但是也带来一个问题,线程读数据,读了一部分就返回了,没有读完,剩下的数据何时继续读?,写数据,缓冲区满了,没有写完,剩下的数据何时继续写?

当可以继续读或者可以继续写时,应该给应用程序一个通知,告诉应用程序可以继续读或者继续写,事件轮询API就是用来处理这个问题的。

select

操作系统提供了一个select函数给用户程序,输入是读写描述符列表 read_fds & write_fds,输出是与之对应的可读可写事件,

同时还提供了timeout参数,线程最多等待timeout的时间,在这期间有事件过来,方法立刻返回,线程往下处理,如果超过timeout时间,方法也会返回,

如果拿到事件了,线程即可挨个处理相应的事件,处理完了以后继续调用 select api 轮询,所以该线程其实是一个死循环,不停的 select,不停的处理,来回这样,这个死循环被称之为事件循环,一个循环即一个周期。

image.png

事件循环伪代码:

while True
    read_events, write_events = select(read_fds, write_fds, timeout)
    for event in read_events:
        handle_read(event.fd)
    for event in write_events:
        handle_write(event.fd)
    handle_others() # 做其他的逻辑处理,处理定时任务等等

通过select函数我们可以处理多个通道描述符的读写事件,所以将select这类的系统函数调用称之为多路复用API,

现代操作系统的多路复用API已经不使用select系统调用,改用epoll(linux)和kqueue(FreeBSD、macosx),

select的性能在描述符变多时会变得很差,epoll与select使用起来略有差异,不过都可以用上面的伪代码理解,都是当描述符发生事件时,循环对描述符的事件做出处理,

serversocket对象的读操作是指调用accept接受客户端新连接,何时有连接来临,也是通过select调用的读事件通知的。

Java中的NIO技术就是事件轮询,其他语言也有这个技术。

指令队列

Redis为每一个客户端套接字关联一个指令队列,客户端发来的指令通过队列进行先进先出的顺序处理。

响应队列

同样Redis返回的结果也通过为每个客户端关联的一个队列返回,如果队列为空,则暂时不需要去获取写事件,

此时会将该客户端描述符从write_fds里移除,等队列有数据的时候,再将描述符放进去,这样可以避免select系统调用返回写事件时,发现没数据可写,造成空轮询、无用轮询,对机器CPU的消耗。

定时任务

服务器不单要响应IO事件,有些其他的事情也需要处理,例如应用程序自身的定时任务,如果线程阻塞在select调用上,等待select的返回,这会造成有些定时任务到期了,却没有执行,

Redis的定时任务记录在一个称为 最小堆 的数据结构中,这个堆中,最快要执行的任务排在最上方,每个循环周期里,redis会对堆中已经到时间点的任务进行处理,

处理完毕后,将堆中即将要执行的任务还需要的时间记录下来,再次调用select时,这个时间就是timeout的值,在这期间内不会有其他任务需要执行了,redis可以放心的最多阻塞这么久,然后到时间后进行相应的处理。

NodeJs和Nginx的事件处理原理和Redis也是类似的形式。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
28天前
|
监控 NoSQL 安全
如何在 Redis 中正确使用多线程?
【10月更文挑战第16天】正确使用 Redis 多线程需要综合考虑多个因素,并且需要在实践中不断摸索和总结经验。通过合理的配置和运用,多线程可以为 Redis 带来性能上的提升,同时也要注意避免可能出现的问题,以保障系统的稳定和可靠运行。
38 2
|
28天前
|
存储 NoSQL Redis
Redis 新版本引入多线程的利弊分析
【10月更文挑战第16天】Redis 新版本引入多线程是一个具有挑战性和机遇的改变。虽然多线程带来了一些潜在的问题和挑战,但也为 Redis 提供了进一步提升性能和扩展能力的可能性。在实际应用中,我们需要根据具体的需求和场景,综合评估多线程的利弊,谨慎地选择和使用 Redis 的新版本。同时,Redis 开发者也需要不断努力,优化和完善多线程机制,以提供更加稳定、高效和可靠的 Redis 服务。
33 1
|
23天前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
1月前
|
网络协议 前端开发 Java
网络协议与IO模型
网络协议与IO模型
网络协议与IO模型
|
25天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
16 1
|
1月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
41 1
|
1月前
|
缓存 Java Linux
硬核图解网络IO模型!
硬核图解网络IO模型!
|
1月前
|
安全 调度 C#
STA模型、同步上下文和多线程、异步调度
【10月更文挑战第19天】本文介绍了 STA 模型、同步上下文和多线程、异步调度的概念及其优缺点。STA 模型适用于单线程环境,确保资源访问的顺序性;同步上下文和多线程提高了程序的并发性和响应性,但增加了复杂性;异步调度提升了程序的响应性和资源利用率,但也带来了编程复杂性和错误处理的挑战。选择合适的模型需根据具体应用场景和需求进行权衡。
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
4月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用