面试官:Redis 为什么这么快?除了基于内存操作还有其他原因吗?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 曾经有人这么告诉我:“在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。”那么瞬间、刹那、一弹指具体是多少时间呢?根据《摩诃僧祗律》记载:一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。那么,经过周密的计算,一瞬间为0.36 秒,一刹那有 0.018 秒.一弹指长达 7.2 秒。为了提升用户体验,提高网站响应速度,一般都会使用缓存,而通常的技术选型都是redis。今天我们就来聊聊,为什么redis这么快

前言

分割线.jpg


曾经有人这么告诉我:“在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。”

那么瞬间、刹那、一弹指具体是多少时间呢?

根据《摩诃僧祗律》记载:

一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。

那么,经过周密的计算,一瞬间为0.36 秒,一刹那有 0.018 秒.一弹指长达 7.2 秒。

为了提升用户体验,提高网站响应速度,一般都会使用缓存,而通常的技术选型都是redis。

今天我们就来聊聊,为什么redis这么快!


面试问题


“Redis 作为缓存大家都在用,那 Redis 一定很快咯?”,我问一个前来面试的同学。

“是的,因为Redis 完全是基于内存的操作,所以很快。”,面试的同学回答道。

“还有其他的原因吗?”,我一脸坏笑的追问道。对面的同学有点不知如何回答。


image.png


计算机的世界,缓存无处不在


浏览器有缓存,CPU有缓存,磁盘有缓存,CDN缓存,app应用有缓存,数据库有缓存。。。

为什么到处都是缓存?答案一个字:快!!!


image.png


浏览器、App有缓存,是为了能让用户更快的访问以前访问过的页面,提升用户体验;CPU、磁盘有缓存,是为了更快的处理数据,提高计算机的性能;数据库有缓存,是为了让业务服务器更快的处理业务。

作为java程序猿,一说到缓存,就想到了Redis。

官方文档介绍说,Redis的操作都是基于内存的,CPU不是 Redis性能瓶颈,,Redis的瓶颈是机器内存和*网络带宽*。

Reds是C语言写的,性能极高。单台redis情况下,官方提供的数据为:读的速度是110000次/s,写的速度是81000次/s 。

但是redis的线程模型表明,redis是单进程、单线程的。是不是很amazing!!!


Redis为什么这么快?


先说两个误区:一,高性能的服务器不一定都是多进程、多线程的;二、多线程不一定都不比单线程快,比如单核机器。

在我们通常的认知中,高性能都是通过多进程、多线程实现的。比如Nginx是多进程单线程的,Memcached是单进程多线程的。

在计算机的世界中,CPU的速度是远大于内存的速度的,同时内存的速度也是远大于硬盘的速度。redis的操作都是基于内存的,绝大部分请求是纯粹的内存操作,非常迅速,使用单线程可以省去多线程时CPU上下文会切换的时间,也不用去考虑各种锁的问题,不存在加锁释放锁操作,没有死锁问题导致的性能消耗。对于内存系统来说,多次读写都是在一个CPU上,没有上下文切换效率就是最高的!既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章的采用单线程的方案了(毕竟采用多线程会有很多麻烦)。

那么Redis的单进程单线程模型的具体细节是怎样的?


IO多路复用技术


首先说一下,什么是IO多路复用技术。

比如,现在我们模拟一个tcp服务器处理30个客户的socket,如何快速的处理掉这30个请求呢?

在不了解原理的情况下,我们类比一个实例:在课堂上让全班30个人同时做作业,做完后老师检查,30个学生的作业都检查完成才能下课。如何在有限的资源下,以最快的速度下课呢?

第一种:安排一个老师,按顺序逐个检查。先检查A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被耽误。这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。这种方式只需要一个老师,但是耗时时间会比较长。

第二种:安排30个老师,每个老师检查一个学生的作业。 这种类似于为每一个socket创建一个进程或者线程处理连接。这种方式需要30个老师(最消耗资源),但是速度最快。

第三种:安排一个老师,站在讲台上,谁解答完谁举手。这时C、D举手,表示他们作业做完了,老师下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。这种方式可以在最小的资源消耗的情况下,最快的处理完任务。

第三种就是IO复用模型(Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。)


Redis线程模型


Redis基于Reactor模式开发了自己的网络事件处理器,被称为文件事件处理器,由套接字、I/O多路复用程序、文件事件分派器(dispatcher),事件处理器四部分组成。


image.png

I/O多路复用程序、文件事件分派器


I/O多路复用程序会同时监听多个套接字,当被监听的套接字准备好执行accept、read、write、close等操作时,与操作相对应的文件事件就会产生,I/O多路复用程序会将所有产生事件的套接字都压入一个队列,然后以有序地每次仅一个套接字的方式传送给文件事件分派器,文件事件分派器接收到套接字后会根据套接字产生的事件类型调用对应的事件处理器。


事件的处理器


image.png

(1)连接应答处理器:


当Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AEREADABLE事件关联起来,当有客户端用sys/socket.h/connect函数连接服务器监听套接字的时候,套接字就会产生AEREADABLE事件,引发连接应答处理器执行,并执行相应的套接字应答操作。


(2)命令请求处理器:


当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AEREADABLE事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生AEREADABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作;

在客户端连接服务器的整个过程中,服务器都会一直为客户端套接字的AE_READABLE事件关联命令请求处理器。


(3)命令回复处理器:


当服务器有命令回复需要传送给客户端的时候,服务器会将客户端套接字的AEWRITABLE事件和命令回复处理器关联起来,当客户端准备好接收服务器传回的命令回复时,就会产生AEWRITABLE事件,引发命令回复处理器执行,并执行相应的套接字写入操作。

当命令发送完毕后,服务器会解除命令回复处理器与客户端套接字的AE_WRITABLE事件之间的关联。

注意1:只有当上一个套接字产生的事件被所关联的事件处理器执行完毕,I/O多路复用程序才会继续向文件事件分派器传送下一个套接字,所以对每个命令的执行时间是有要求的,如果某个命令执行过长,会造成其他命令的阻塞。所以慎用O(n)命令,Redis是面向快速执行场景的数据库。

注意2:命令的并发性。Redis是单线程处理命令,命令会被逐个被执行,假如有3个客户端命令同时执行,执行顺序是不确定的,但能确定不会有两条命令被同时执行,所以两条incr命令无论怎么执行最终结果都是2。


客户端与redis通信过程


1、假设一个Redis服务器正在运作,那么这个服务器的监听套接字的 AE_READABLE 事件应该正处于监听状态之下, 而该事件所对应的处理器为连接应答处理器。

2、如果这时有一个Redis客户端向服务器发起连接,那么监听套接字将产生 AEREADABLE事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AEREADABLE事件与命令请求处理器进行关联,使得客户端可以向主服务器发送命令请求。

3、之后,假设客户端向主服务器发送一个命令请求,那么客户端套接字将产生 AE_READABLE 事件,引发命令请求处理器执行,处理器读取客户端的命令内容,然后传给相关程序去执行。

4、执行命令将产生相应的命令回复, 为了将这些命令回复传送回客户端, 服务器会将客户端套接字的 AEWRITABLE 事件与命令回复处理器进行关联。当客户端尝试读取命令回复的时候, 客户端套接字将产生 AEWRITABLE 事件, 触发命令回复处理器执行, 当命令回复处理器将命令回复全部写入到套接字之后, 服务器就会解除客户端套接字的 AE_WRITABLE 事件与命令回复处理器之间的关联。


压力测试


redis安装完成后,有一个命令叫redis-benchmark。

这个命令是官方自带的压力测试工具。下面我们就是用这个命令的默认参数简单的做一下压测。


可以看出

一共发了10万个set请求,一共耗时0.79秒

一共发了10万个get请求,一共耗时0.81秒


$ ./redis-benchmark
====== PING_INLINE ======
100000requests completedin0.85seconds
50parallel clients
3bytes payload
keep alive:1
99.98%<=1milliseconds
100.00% <=1milliseconds
117924.53requestspersecond
======PING_BULK======
100000requestscompletedin0.81seconds
50parallelclients
3bytespayload
keepalive:1
100.00% <=0milliseconds
123915.74requestspersecond
======SET======
100000requestscompletedin0.79seconds#一共发了10万个set请求,一共耗时0.79秒
50parallelclients#一共使用50个客户端并发请求
3bytespayload#每个请求set3个字节
keepalive:1#使用一个redis服务器,单机测试
99.90% <=1milliseconds#99.9%的请求的耗时是小于等于1毫秒的
99.95% <=2milliseconds
99.96% <=3milliseconds
100.00% <=3milliseconds
127388.53requestspersecond#每秒处理127388.53个请求
======GET======
100000requestscompletedin0.81seconds#一共发了10万个get请求,一共耗时0.81秒
50parallelclients#一共使用50个客户端并发请求
3bytespayload#每个请求get3个字节
keepalive:1#使用一个redis服务器,单机测试
99.99% <=1milliseconds#99.99%的请求的耗时是小于等于1毫秒的
100.00% <=1milliseconds
123456.79requestspersecond#每秒处理123456.79个请求

也可以设置并发连接数-c、请求数-n、字节数-d参数等。


总结


redis是纯内存操作:数据存放在内存中,内存的响应时间大约是100纳秒,这是Redis每秒万亿级别访问的重要基础。

非阻塞I/O:Redis采用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接,读写,关闭都转换为了时间,不在I/O上浪费过多的时间。

单线程避免了线程切换和竞态产生的消耗。

Redis采用单线程模型,每条命令执行如果占用大量时间,会造成其他线程阻塞,对于Redis这种高性能服务是致命的,所以Redis是面向高速执行的数据库

完成,收工!










相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
13天前
|
存储 缓存 NoSQL
【Java面试题汇总】Redis篇(2023版)
Redis的数据类型、zset底层实现、持久化策略、分布式锁、缓存穿透、击穿、雪崩的区别、双写一致性、主从同步机制、单线程架构、高可用、缓存淘汰策略、Redis事务是否满足ACID、如何排查Redis中的慢查询
【Java面试题汇总】Redis篇(2023版)
|
4天前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
|
4天前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
|
29天前
|
存储 NoSQL 算法
Redis内存回收
Redis 基于内存存储,性能卓越,但单节点内存不宜过大,以免影响持久化或主从同步。可通过配置 `maxmemory` 限制最大内存。内存达到上限时,Redis采用两种策略:内存过期策略和内存淘汰策略。过期策略包括惰性删除和周期删除,后者分为 SLOW 和 FAST 模式。内存淘汰策略有八种,如 LRU、LFU 和随机淘汰等,用于在内存不足时释放空间。官方推荐使用 LFU 算法。
Redis内存回收
|
1月前
|
存储 缓存 NoSQL
Redis内存管理揭秘:掌握淘汰策略,让你的数据库在高并发下也能游刃有余,守护业务稳定运行!
【8月更文挑战第22天】Redis的内存淘汰策略管理内存使用,防止溢出。主要包括:noeviction(拒绝新写入)、LRU/LFU(淘汰最少使用/最不常用数据)、RANDOM(随机淘汰)及TTL(淘汰接近过期数据)。策略选择需依据应用场景、数据特性和性能需求。可通过Redis命令行工具或配置文件进行设置。
42 2
|
13天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
1月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
【Azure Redis 缓存】Redission客户端连接Azure:客户端出现 Unable to send PING command over channel
|
29天前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
58 0
|
1月前
|
缓存 NoSQL Redis
【Azure Redis 缓存】使用StackExchange.Redis,偶发ERROR - Timeout performing HSET (15000ms)
【Azure Redis 缓存】使用StackExchange.Redis,偶发ERROR - Timeout performing HSET (15000ms)