首页 > 数据库 >Redis为什么这么快?

Redis为什么这么快?

时间:2023-09-11 22:02:15浏览次数:48  
标签:为什么 Pipeline 删除 SDS Redis 内存 key 这么

面试官:Redis为什么这么快? (qq.com)

”因为它是内存数据库,不用往硬盘上写,所以快啊“

“基于内存实现”这个原因就不详细展开了哈,毕竟地球人都懂。

图片

空间换时间 —— SDS数据结构

这里所说的空间为”内存空间“。

Redis是用C语言写的,但它的String数据类型,并没有直接用C语言中的char*字符数组字符串,而是通过简单动态字符串(Simple Dynamic String,SDS)的数据结构来实现的。

《Redis设计与实现》一书中,片段如下:

图片

图片

  • free属性的值为0,表示这个SDS没有分配任何未使用空间;
  • len 属性的值为 5,表示这个SDS保存了一个五字节长的字符串;
  • buf属性是char类型的字节数组,前五个字节保存了'y'、'i'、'k'、'u'、'n',最后一个字节保存了空字符 '\0';

相比较于C语言字符串,SDS记录了已使用和未使用字节的数量,目的有两点:

  1. 获取字符串长度时,无需遍历整个字符串,将操作复杂度从O(N)降低为O(1);
  2. 为减少修改字符串操作所带来的内存重分配次数,提供了数据结构上的支持;

第二点不太容易理解,我们详细解释一下。

C语言字符串修改场景的实现方式是:

  • 如果字符串在修改后变长了,会进行内存重分配操作,让其可以存下修改后的字符串大小;
  • 如果字符串在修改后变短了,则会进行内存释放操作;

这样做比较节省内存空间,但频繁地内存分配释放操作对性能是有影响的。

我们看看Redis SDS是如何进行优化的。

空间预分配

如果SDS在修改后变长了,Redis不仅会为SDS分配修改所需的空间, 还会为SDS分配额外的未使用空间。

如果修改后SDS长度小于1MB,Redis会分配和len属性同样大小的未使用空间,这时SDS的len和free属性的值是相同的;如果修改后SDS长度大于等于1M,Redis会分配1MB的未使用空间。

惰性空间释放

如果SDS在修改后变短了, Redis不会立即将内存进行回收,而是使用free属性将这些字节的数量记录起来,以备将来使用。当然,SDS也有真正释放SDS未使用空间的机制,不用担心该策略会带来内存浪费。

Redis SDS通过空间预分配和惰性空间释放的方式,减少操作内存次数,提升性能。

空间换时间 —— SkipList数据结构

Redis的ZSet数据类型的底层实现比较复杂。

默认情况下,ZSet使用ListPack做为存储结构,当集合中的元素大于等于512个,或是单个值的字节数大于等于64,存储结构会修改为SkipList + Hash。

其中,Hash用来支持高效单点查询,而SkipList的优势是能在 O(logN) 复杂度进行高效的范围查询。

SkipList是通过多层有序链表的方式实现的,上面每一层链表的节点个数,是下面一层的节点个数的一半,这样查找过程就非常类似于一个二分查找,使得查找的时间复杂度可以降低到 O(logn)。

相当于多耗费了链表上层存储的空间,但是通过快捷的二分查找节省了时间,即:用空间换时间。

图片

空间换时间 —— 过期key删除策略

Redis过期key删除策略为:定期删除和惰性删除两种策略配合使用。

定期删除

Redis会将每个设置了过期时间的key放入到一个独立的字典中,以后会定期遍历这个字典来删除到期的key,默认每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的key,而是采用了一种简单的贪心策略。

步骤如下:

  1. 从过期字典中随机20个key;
  2. 删除这20个key中已经过期的key;
  3. 如果过期key比率超过1/4,那就重复步骤1;

该线程不仅处理定时任务,还处理客户端请求,为了保证过期扫描不会出现循环过度,导致客户端请求堆积和处理缓慢的现象,算法还增加了扫描时间的上限,默认不会超过25ms。

惰性删除

除了定期遍历之外,它还会使用惰性策略来删除过期的key,在客户端访问这个key的时候,Redis对key的过期时间进行检查,如果过期了就立即删除。定期删除是集中处理,惰性删除是零散处理。

Redis通过定期删除的25ms上限机制和惰性删除机制,也都是以一种空间换时间的策略,让客户端请求的响应时间得到保障。

单次改批量 —— Pipeline机制

正常情况下,客户端发送一个命令后等待Redis应答,Redis在接收到命令后处理应答,而下一个命令必须要等到第一个命令处理完才能进行。

图片

而Redis的Pipeline机制,允许客户端先将执行的命令写入到缓冲中,最后再一次性发送Redis服务器端,而不等待上一条命令执行的结果。

图片

Pipeline机制是一种通过同时发出多个命令(单次改批量)来提高性能的技术,而无需等待对每个单独命令的响应,相当于N个连续的读写操作总共只会花费一次网络来回。

btw:Pipeline机制并不是Redis服务器端提供的技术方案,而是其客户端提供的。

代码如下:

private void setKey(List<String> names, String key) {
    Pipeline pipeline = redis.pipelined();
    for (String name : names) {
        pipeline.zadd(key, name, score);
    }
    pipeline.expire(key, CACHE_EXPIRE_SECONDS);
    pipeline.sync();
}

我们执行10000次set命令,Pipeline机制和非Pipeline机制的执行时间对比如下:

网络环境 延迟 非Pipeline Pipeline
本机 0.17ms 573ms 134ms
同机房 0.41ms 1610ms 240ms
跨机房 7ms 78499ms 1104ms

由此可见,极端场景下,非Pipeline机制是Pipeline机制的执行时间的70多倍。

各取所长 —— QuickList数据结构

Redis 3.2 之前的版本,List数据类型实现方式为:

  • 当列表的长度小 512,并且所有元素的长度都小于64字节时,使用ziplist存储;
  • 否则使用 linkedlist 存储;

两者各有优缺点:

  • ZipList的优点是内存紧凑,访问效率高,缺点是更新效率低,且连锁更新会产生大量的内存复制;
  • LinkedList的优点是节点修改的效率高,但是需要额外的内存开销;

为了结合两者的优点,在Redis 3.2之后,List的底层实现变为快速列表QuickList。

QuickList是由多个节点组成的双向链表,每个节点都是一个ZipList,每个ZipList包含了多个元素,每个元素可以是一个整数或一个字节数组。

图片

通过各取所长,QuickList解决了ZipList连锁更新的大量内存复制,从而导致性能低下的问题,也解决了LinkedList产生内存碎片所导致的性能下降问题。

同步非阻塞 —— Reactor模型

之前我在介绍Netty的时候写过,其实Redis也是用的Reactor模型。

Reactor模型思想

分而治之 + 事件驱动,这种方式的优点为:把一个大任务拆分为若干个小任务,这样可以减少其阻塞时间。

分而治之

一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send(write)这几步。

Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。

事件驱动

相应的Task(accept、read、write)对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Acceptor和Handler执行。

Redis是基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器(file event handler)。

由于这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。采用IO多路复用机制同时监听多个 Socket,根据 socket 上的事件来选择对应的事件处理器来处理这个事件。

在Redis 6.0以前,Redis的核心网络模型选择用单线程来实现的。

图片

而在Redis 6.0的时候引入了多线程。因为单线程模式会导致系统消耗很多时间在网络IO上,从而降低吞吐量。

图片

Redis作者Antirez在RedisConf 2019分享时曾提到:Redis 6 引入的多线程IO特性对性能提升至少是一倍以上。

同步转异步 —— 解放主线程

Redis的del命令可以用来进行key删除的操作,它是一个同步阻塞的命令。

如果你要删除的是一个超大的键值对,里面有几百万个对象,这条命令会阻塞好几秒,而且事件循环是单线程的,会阻塞后面的其他命令,导致这些命令都变慢很多。

Redis 4.0 版本增加了del的异步版本 —— unlink命令,它不会同步删除数据,只是会将这个 key 的内存回收操作包装成一个任务,塞进异步任务队列,由后台线程去执行删除操作。

图片

除了unlink以外,flushall asyncflushdb async命令也是这种同步转异步的思想,去解放主线程,提升用户操作的性能。

追问:这么快咋不用来做主数据库呢?

内存资源相较于磁盘较小

数据存储在内存中如果崩溃或者断电来不及及时备份,虽然redis支持rdb和aof

访问控制不足

事务也比较简单,跨多个键就不行。

标签:为什么,Pipeline,删除,SDS,Redis,内存,key,这么
From: https://blog.51cto.com/coderge/7438895

相关文章

  • 【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error
    问题描述使用Redis-cli连接Redis服务,因为工具无法直接支持TLS6380端口连接,所以需要使用stunnel配置TLS/SSL服务。根据文章(LinuxVM使用6380端口(SSL方式)连接AzureRedis(redis-cli&stunnel):https://www.cnblogs.com/lulight/p/14188279.html),配置stunnel后,始终无法连接成......
  • Redis 缓存击穿,缓存穿透,缓存雪崩原因+解决方案
    缓存击穿,缓存穿透,缓存雪崩的原因缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存......
  • 为什么linux配置了环境变量,却找不到位置
    为什么linux配置了环境变量,却找不到位置例如我刚在linux中配置环境变量HBASE_HOME,通过cd$HBASE_HOME去不到对应路径,我出现这个问题的原因很简单,就是忘记加载环境变量了通过source/etc/profile可以加载环境变量(这个路径对应编写环境变量的路径,比如说我是在/etc/profile进行环......
  • 通配符ssl证书的作用有哪些?为什么通配符ssl证书如此受欢迎?
    随着互联网安全问题日益突出,加之主流浏览器声明网站部署SSL证书,有助于提升网站排名,因此SSL证书成为了企业网络安全防护不可缺少的一环。SSL证书种类多样,其中通配符SSL证书颇受当下企业青睐,申请比例逐年上升。为何通配符SSL证书如此受欢迎呢?请跟着小编一起往下看。什么是通配符SSL证......
  • redis连接失败
     本次连接redis失败记录,密码全为0,不加“”认证会失败springboot yml配置 密码为0时,要加“”很经典的错误,记录一下......
  • 程序员,为什么一定要了解Kepler无代码?
    软件开发行业的未来在哪?可以毫不夸张地说,无无代码正成为人类社会发展的必然趋势。为什么这么说呢?历史上,人类一直被“懒惰”所驱动,汽车、洗衣机的发明是最好佐证。现在,企业需要数字化转型,生活被数字化覆盖,学习课程和工具被数字化填满。大量开发的需求围绕四周,但很明显传统模式开发的......
  • 面向对象这么久了,还没找到对象?
    写代码的小伙伴们真幸福啊,想要对象了?没问题,new一个就好了。但是,new太多对象,对象也会生气的哦。你瞧,她来了从两段代码发现端倪我们来计算一个矩形的面积,看看这两段代码有什么区别呢?第一段:constheight=3;constwidth=5;letaaa=123;letbbb=456;letccc=789;//定......
  • 日志是你的朋友:为什么每个开发者都应该写日志
    大家好,我是小米,一个热衷于技术分享的程序员。今天我想和大家聊一聊一个在编写代码时常常被忽视,却极为重要的话题——为什么要写有意义的日志。在日常的编程工作中,我们经常听到“日志”这个词,但是有些人可能并不理解为什么要在代码中写入日志,或者觉得这只是一种不必要的繁琐。但是,当......
  • 为什么EDI工作流中围绕XML做EDI报文数据解析/生成?
    经常有客户问起,为什么在处理EDI文件时不一次到位,而需要使用多个端口来分次进行处理呢,是不是想要多占用几个端口好多卖钱呀?实际上,在一开始的知行EDI产品中,功能还没有这么完善,当时只支持EDI常见的传输协议,那个时候我们在做报文翻译时,还不能仅通过简单的配置来实现,需要手写代码,去读......
  • redis集群
    Redis集群本章是基于CentOS7下的Redis集群教程,包括:单机安装RedisRedis主从Redis分片集群1.单机安装Redis首先需要安装Redis所需要的依赖:yuminstall-ygcctcl然后将课前资料提供的Redis安装包上传到虚拟机的任意目录:例如,我放到了/tmp目录:解压缩:tar-xzfredis-6......