Redis 核心篇:唯快不破的秘密(上)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 学习一个技术,通常只接触了零散的技术点,没有在脑海里建立一个完整的知识框架和架构体系,没有系统观。这样会很吃力,而且会出现一看好像自己会,过后就忘记,一脸懵逼。跟着「码哥字节」一起吃透 Redis,深层次的掌握 Redis 核心原理以及实战技巧。一起搭建一套完整的知识框架,学会全局观去整理整个知识体系。系统观其实是至关重要的,从某种程度上说,在解决问题时,拥有了系统观,就意味着你能有依据、有章法地定位和解决问题。

Redis 全景图


全景图可以围绕两个纬度展开,分别是:


应用维度:缓存使用、集群运用、数据结构的巧妙使用


系统维度:可以归类为三高


  1. 高性能:线程模型、网络 IO 模型、数据结构、持久化机制;


  1. 高可用:主从复制、哨兵集群、Cluster 分片集群;


  1. 高拓展:负载均衡


Redis 系列篇章围绕如下思维导图展开,这次从 《Redis 唯快不破的秘密》一起探索 Redis 的核心知识点。


image.png


唯快不破的秘密


65 哥前段时间去面试 996 大厂,被问到「Redis 为什么快?」


65 哥:额,因为它是基于内存实现和单线程模型


面试官:还有呢?


65 哥:没了呀。


很多人仅仅只是知道基于内存实现,其他核心的原因模凌两可。今日跟着「码哥字节」一起探索真正快的原因,做一个唯快不破的真男人!


Redis 为了高性能,从各方各面都进行了优化,下次小伙伴们面试的时候,面试官问 Redis 性能为什么如此高,可不能傻傻的只说单线程和内存存储了。


image.png


根据官方数据,Redis 的 QPS 可以达到约 100000(每秒请求数),有兴趣的可以参考官方的基准程序测试《How fast is Redis?》,地址:https://redis.io/topics/benchmarks


image.png


横轴是连接数,纵轴是 QPS。此时,这张图反映了一个数量级,希望大家在面试的时候可以正确的描述出来,不要问你的时候,你回答的数量级相差甚远!


完全基于内存实现


65 哥:这个我知道,Redis 是基于内存的数据库,跟磁盘数据库相比,完全吊打磁盘的速度,就像段誉的凌波微步。对于磁盘数据库来说,首先要将数据通过 IO 操作读取到内存里。


没错,不论读写操作都是在内存上完成的,我们分别对比下内存操作与磁盘操作的差异。


磁盘调用栈图


image.png


内存操作


内存直接由 CPU 控制,也就是 CPU 内部集成的内存控制器,所以说内存是直接与 CPU 对接,享受与 CPU 通信的最优带宽。


Redis 将数据存储在内存中,读写操作不会因为磁盘的 IO 速度限制,所以速度飞一般的感觉!


最后以一张图量化系统的各种延时时间(部分数据引用 Brendan Gregg)


image.png


高效的数据结构


65 哥:学习 MySQL 的时候我知道为了提高检索速度使用了 B+ Tree 数据结构,所以 Redis 速度快应该也跟数据结构有关。


回答正确,这里所说的数据结构并不是 Redis 提供给我们使用的 5 种数据类型:String、List、Hash、Set、SortedSet。


在 Redis 中,常用的 5 种数据类型和应用场景如下:


  • String: 缓存、计数器、分布式锁等。


  • List: 链表、队列、微博关注人时间轴列表等。


  • Hash: 用户信息、Hash 表等。


  • Set: 去重、赞、踩、共同好友等。


  • Zset: 访问量排行榜、点击量排行榜等。


上面的应该叫做 Redis 支持的数据类型,也就是数据的保存形式。「码哥字节」要说的是针对这 5 种数据类型,底层都运用了哪些高效的数据结构来支持。


65 哥:为啥搞这么多数据结构呢?


当然是为了追求速度,不同数据类型使用不同的数据结构速度才得以提升。每种数据类型都有一种或者多种数据结构来支撑,底层数据结构有 6 种。


image.png


Redis hash 字典


Redis 整体就是一个 哈希表来保存所有的键值对,无论数据类型是 5 种的任意一种。哈希表,本质就是一个数组,每个元素被叫做哈希桶,不管什么数据类型,每个桶里面的 entry 保存着实际具体值的指针。


image.png


整个数据库就是一个全局哈希表,而哈希表的时间复杂度是 O(1),只需要计算每个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的原因之一。


那 Hash 冲突怎么办?


当写入 Redis 的数据越来越多的时候,哈希冲突不可避免,会出现不同的 key 计算出一样的哈希值。


Redis 通过链式哈希解决冲突:也就是同一个 桶里面的元素使用链表保存。但是当链表过长就会导致查找性能变差可能,所以 Redis 为了追求快,使用了两个全局哈希表。用于 rehash 操作,增加现有的哈希桶数量,减少哈希冲突。


开始默认使用 hash 表 1 保存键值对数据,哈希表 2 此刻没有分配空间。当数据越来多触发 rehash 操作,则执行以下操作:


  1. 给 hash 表 2 分配更大的空间;


  1. 将 hash 表 1 的数据重新映射拷贝到 hash 表 2 中;


  1. 释放 hash 表 1 的空间。


值得注意的是,将 hash 表 1 的数据重新映射到 hash 表 2 的过程中并不是一次性的,这样会造成 Redis 阻塞,无法提供服务。


而是采用了渐进式 rehash,每次处理客户端请求的时候,先从 hash 表 1 中第一个索引开始,将这个位置的 所有数据拷贝到 hash 表 2 中,就这样将 rehash 分散到多次请求过程中,避免耗时阻塞。


SDS 简单动态字符


65 哥:Redis 是用 C 语言实现的,为啥还重新搞一个 SDS 动态字符串呢?


字符串结构使用最广泛,通常我们用于缓存登陆后的用户信息,key = userId,value = 用户信息 JSON 序列化成字符串。


C 语言中字符串的获取 「MageByte」的长度,要从头开始遍历,直到 「\0」为止,Redis 作为唯快不破的男人是不能忍受的。


C 语言字符串结构与 SDS 字符串结构对比图如下所示:


image.png


SDS 与 C 字符串区别


O(1) 时间复杂度获取字符串长度


C 语言字符串布吉路长度信息,需要遍历整个字符串时间复杂度为 O(n),C 字符串遍历时遇到 '\0' 时结束。


SDS 中 len 保存这字符串的长度,O(1) 时间复杂度。


空间预分配


SDS 被修改后,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间。


分配规则如下:如果对 SDS 修改后,len 的长度小于 1M,那么程序将分配和 len 相同长度的未使用空间。举个例子,如果 len=10,重新分配后,buf 的实际长度会变为 10(已使用空间)+10(额外空间)+1(空字符)=21。如果对 SDS 修改后 len 长度大于 1M,那么程序将分配 1M 的未使用空间。


惰性空间释放


当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。


二进制安全


在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。


二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 '\0',在 C 中遇到 '\0' 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性。


zipList 压缩列表


压缩列表是 List 、hash、 sorted Set 三种数据类型底层实现之一。


当一个列表只有少量数据的时候,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么 Redis 就会使用压缩列表来做列表键的底层实现。

ziplist 是由一系列特殊编码的连续内存块组成的顺序型的数据结构,ziplist 中可以包含多个 entry 节点,每个节点可以存放整数或者字符串。


ziplist 在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表占用字节数、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。


struct ziplist<T> {
    int32 zlbytes; // 整个压缩列表占用字节数
    int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
    int16 zllength; // 元素个数
    T[] entries; // 元素内容列表,挨个挨个紧凑存储
    int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
}


image.png


如果我们要查找定位第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其他元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N)



相关实践学习
基于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
相关文章
|
存储 缓存 NoSQL
【Redis】Redis核心知识
Redis可谓后端程序员技术栈中的重中之重了,逢考必问,整理一波。
310 0
【Redis】Redis核心知识
|
运维 NoSQL 小程序
SpringBoot配置文件加密jasypt【数据库配置加密、redis配置加密、核心参数加密】
SpringBoot配置文件加密jasypt【数据库配置加密、redis配置加密、核心参数加密】
381 0
|
存储 消息中间件 缓存
掌握9个核心知识点轻松玩转Redis
掌握9个核心知识点轻松玩转Redis
125 0
|
存储 NoSQL 关系型数据库
Redis 面霸篇:高频问题横扫核心知识点 (一)
从高频面试问题跟大家一起横扫 Redis 核心知识点,从根本上理解 Redis ,不做八股文的工具人,做扭转乾坤的大神。
219 0
Redis 面霸篇:高频问题横扫核心知识点 (一)
|
NoSQL Redis
Redis 核心数据结构和应用(下)
常用 5 种数据类型 string, list, set, hash, zset
126 0
Redis 核心数据结构和应用(下)
|
存储 缓存 NoSQL
Redis 核心数据结构和应用(上)
常用 5 种数据类型 string, list, set, hash, zset
136 0
Redis 核心数据结构和应用(上)
|
缓存 运维 NoSQL
重磅下载 | Redis最佳实践与实战指南 源代码核心贡献者带你学习Redis关键技术
本书由七天玩转Redis实训营课程内容整理而成,不仅系统性地介绍Redis的整体架构及在多种场景下的最佳实践经验,而且揭秘阿里云Redis开发规范和运维解法,更有基于Redis的开发实操教程。
37158 0
重磅下载 | Redis最佳实践与实战指南 源代码核心贡献者带你学习Redis关键技术
|
存储 缓存 监控
Redis 在互金核心账务系统中的场景实践
Redis是一款开源的、网络化的、基于内存的、可进行数据持久化KEY-VALUE的存储系统。Redis通过KEY映射VALUE的方式来建立字典来保存数据,支持多类型存储包括STRING、LIST,SET,SORT SET和HASH等,可以在这些数据类型上做很多原子性操作。Redis将数据存储在内存里面,而且它发送给Redis的命令请求不需要经过典型的查询分析器(PARSER)或查询优化器(OPTIMIZER)处理,所以Redis对自身存储的数据执行随机读写的速度是非常快速的。
423 0
Redis 在互金核心账务系统中的场景实践