笔记 - 《Redis深度历险》

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 《Redis深度历险》的笔记

总起

在公司内,一开始就可以方便地使用中间件Tair,对 Redis 也无了解要求,但这方面的了解,应该是进一步拓展技术视野的要求,所以购买了书籍进行了了解,本文也是对应的简单摘抄记录,可作为了解的一个索引。

书本链接: http://product.dangdang.com/25859315.html

image.png


Redis 是 “Remote Dictionary Service”(远程字典服务)的首字母缩写。

image.png


基础篇:数据结构

Redis 有5种基础数据结构

  • string 字符串
  • list 列表
  • hash 字典
  • set 集合
  • zset 有序集合

其中 list、hash、set、zset 共享2条通用原则:

  • create if not exists : 如果不存在,那就创建一个,再进行操作。
  • drop if no elements:如果容器里面的元素没有了,那么立即删除容器,释放内存。

Redis 的所有数据结构都可以设置过期时间。需要注意的是:

  • 过期是以对象为单位,而不是其中的某个子key。
  • 如果一个字符串设置了过期时间,修改它后,过期时间会消失。

string 字符串

string 字符串的特点

  • 是动态字符串,是可以修改的,结构类似 java 的 ArrayList。
  • 采用预分配冗余空间的方式减少内存的频繁分配。

list 列表

Redis 的列表相当于 Java 语言里面的 LinkedList。

  • 它是链表而不是数组,插入、删除快,索引定位慢。
  • 常用来做异步队列使用。
  • 底层是“快速列表”(quicklist):将多个采用连续内存的压缩列表(ziplist),用双向指针串起来使用。

hash 字典

Redis 的字典相当于Java 语言里面的 HashMap。

  • 是无序字典。
  • 实现结构:“数组+链表”二维结构。
  • Redis 为了性能,不阻塞服务,采用了渐进式 rehash 策略(后续的定时任务以及hash操作指令)。

set 集合

Redis 的集合相当于 Java 语言里面的 HashSet。

  • 内部的键值对是无序的、唯一的。
  • 内部实现相当于一个特殊的字典,字典的 value 都是一个值 NULL。

zset 有序列表

类似于Java 的 SortedSet 和 HashMap 的结合体。

  • 是一个set,保证内部value的唯一性。
  • 可以给每个 value 赋予一个 score, 代表这个 value 的排序权重。
  • 内部使用 “跳跃列表” 的数据结构。
  • 要支持随机的插入和删除:需要用链表,不宜用数组。
  • 有元素要插入的时候,要定位到特定位置的插入,期望二分法查找,所以会有多层次索引。

应用篇

分布式锁

基础逻辑

占坑:一般使用 setnx (set if not exists)指令,只允许被一个客户端占坑。

释放坑:调用 del 指令。

超时:为了避免中间异常,del指令没有调用,会通过 exipire 指令设置超时时间。

原子指令:set 增加了扩展参数支持 setnx 和 expire 组合在一起的原子指令。

超时问题

Redis 的分布式锁不能解决超时问题(执行的时间超过锁超时时间)。

因此:建议不要用于长时任务,真的超市化后可能需要人工介入。

可重入性

可重入性是指一个锁支持同一个线程的多次加锁,那么锁就是可重入的。

Redis 支持锁的可重入,一般需要结合使用 Threadlocal 记录锁。

锁冲突处理

3种处理策略

  • 直接抛出异常,让用户重试。
  • sleep 一会,然后再重试。
  • 将请求转移至延时队列,过一会再试。

延时队列

队列局限

Redis 的消息队列不是专业的消息队列,没有 ack 保证,对可靠性有极高要求的,不适合使用。

队列使用

  • 结合使用 list 列表的 rpush、lpop(lpush、rpop)操作。
  • 避免队列空造成频繁拉数据造成的 cpu 高。
  • 线程睡一会。
  • 采用阻塞读命令:blpop/ brpop。
  • 空闲链接自动断开:需要捕获 blpop/ brpop 抛出的异常。

延时队列

延时队列可以通过 Redis 的 zset(有序列表)来实现。

  • 字符串作为 zset 的 value。
  • 到期处理时间作为 score。
  • 用多个线程轮询 zset 获取到期的任务。
  • zrem 命令是多线程争抢任务的关键,决定唯一的属主。
  • 优化竞争:通过 lua scripting 将 zrangebyscore 和 zrem 一同挪到服务端进行原子化操作。

位图

使用场景

一些 bool 型数据的存取,比如用户一年的签到记录(签了1,没签是0,记录365天)。

基本操作

  • 可以使用普通的 get/set 直接获取和设置整个位图的内容。
  • 可以使用位图操作 getbit/setbit 将 byte 数组看成 “位数组” 来处理。
  • 可以通过 bitcount 统计范围内的总数。
  • 可以通过 bitpos 查找开始的位置。
  • 可以通过 bitfield 一次进行多个位的操作。

HyperLogLog

使用场景

统计页面UV,用户量很大,需要进行去重,但是可以容忍一定概率误差。

基本概念

Redis 的 HyperLogLog 提供了不精确的去重计数方案,标准误差是 0.81%。

  • pfadd 增加计数。
  • pfcount 获取计数。
  • pfmerge 将多个 pf 计数值累加在一起形成一个新的 pf 值。

实现原理

抽象问题:给定一系列的随机整数,记录下低位连续零位的最大长度 K,通过这个 K 值可以估算出随机数的数量N。

数据占用

Q:pf 的内存占用为什么是12KB?

A:Redis 使用了 16384 个分桶, 也就是 2^14,每个桶需的maxbits 需要6个 bit 来存储,算出来是 12 KB。

布隆过滤器

使用场景

  • 推荐场景,判断是否推荐过,可以粗粒度处理,并且节约存储成本。
  • URL爬虫,初步判断页面是否查询过。
  • NOSQL 查询中,过滤掉大量不存在的 row 请求。
  • 邮箱系统的垃圾邮件过滤。

功能特点

  • 判断存在会有误判:当布隆过滤器说某个值存在时,这个值可能不存在;
  • 判断不存在很准确:当它说某个值不存在时,那就肯定不存在。

基本用法

  • bf.add 添加元素;
  • bf.exists 查询元素是否存在。

限流

使用场景

控制流量、控制用户行为等场景。

解决方案

1)简单限流 ,使用 zset 模拟,时间戳作为 score(排序依据),不适合量大的限流,计数会占用较多存储。

2)漏斗限流,提供了 Redis-Cell 模块,使用了漏斗算法(考虑容量、流速、剩余空间、上一次操作时间)。

GeoHash

使用场景

寻找附近的车辆、附近的餐馆等。

算法原理

GeoHash 算法将二维的经纬度数据映射到一维的整数,这样所有的元素都将挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。

基本用法

  • geoadd 增加位置
  • geodist 计算两个元素之间的距离
  • geopos 获取元素的经纬度坐标
  • geohash 获取元素的经纬度编码字符串

寻找key

使用场景

需要从 Redis 实例的成千上万个 key 中找出特定前缀的 key 列表。

相关指令

  • keys 用来列出所有满足特定正则字符串规则的 key,缺点:1)没有分页参数,2)遍历算法,复杂度是O(n),容易造成服务卡顿。
  • scan 提供了三个参数:第一个参数 cursor 是指针,第二个 key 是正则模式,第三个是 limit hint。

原理篇

线程 IO 模型

Redis 是个单线程程序,关键特点有:

  • 非阻塞 IO:读写 IO 不必阻塞,可以异步执行。
  • 事件轮询(多路复用):非阻塞场景下,解决数据到来的通知问题。如:select、epoll、kqueue、macosx。
  • 指令队列:客户端套接字关联到指令队列,先到先服务。
  • 响应队列:将返回结果放回关联的响应队列中。
  • 定时任务:定时任务存放在“最小堆”数据结构中,结合事件轮询的超时时间,确保能够及时执行。

通信协议

RESP 是 Redis 序列化协议(Redis Serialization Protocol)的简写。它是一种直观的文本协议,优势在于实现过程异常简单,解析性能极好。

Redis 协议将传输的数据结构数据分为 5 种最小单元类型,单元结束时统一加上回车换行符合 \r\n。

  1. 单行字符串以“+”符号开头,如“+hello world\r\n”。
  2. 多行字符串以“$”符合开头,后跟字符串长度,如“$11\r\nhello world\r\n”。
  3. 整数值以“:”符号开头,后面跟整数的字符串形式,如“:1024\r\n”。
  4. 错误消息以“-”符合开头,如“-WRONGTYPE Operation\r\n”。
  5. 数组以“*”符合开头,后跟数组的长度,如数组[1,2,3] “*3\r\n:1\r\n:2\r\n:3\r\n”。

持久化

技术背景

Redis 的数据全部存在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。有2种机制,一种是快照,一种是AOF日志。

快照

快照是一次全量备份,是内存的二进制序列化形式,在存储上非常紧凑。

  • 使用操作系统的多进程 COW(Copy On Write)机制来实现快照持久化机制。
  • Redis 在持久化时会 fork 产生一个子进程,快照持久化完全交给子进程来处理。

AOF日志

AOF日志是连续的增量备份,记录的是内存数据修改的指令记录文本。需要定期进行AOF重放,进行日志瘦身。

  • 先执行指令,才将日志存盘。
  • AOF 重写:开辟子进程,对内存进行遍历,转换为一些列操作指令,序列化到一个新的AOF日志文件中,然后将操作期间的增量 AOF 日志追加到这个新的文件中。
  • 写数据时实际写到了内存缓存中,内核会异步将数据刷回到磁盘,在宕机时可能会丢。Redis 通常每隔一定时间执行一次 fsync 指令,强制刷到磁盘。

运维

文件操作是一个比较费时的事情,所以 Redis 的主节点不会进行持久化操作,持久化操作注意在从节点进行。从节点是备份节点,没有来自客户端的请求的压力,它的操作系统资源往往比较充沛。

混合持久化

问题:重启Redis时,很少使用快照恢复内存状态,因为会丢失大量数据;通常使用AOF日志重放,但是慢很多。

方案:Redis 4.0 带来了新的持久化特性:混合持久化。将快照的内容和增量的AOF日志存在一起,AOF不再是全量日志,是增量日志。在重启时,先加载快照,再重放增量AOF日志,效率大幅提升。

管道

Redis 管道(Pipeline)本身不是 Redis 服务器直接提供的技术,这个技术本质上是由客户端提供的。客户端通过对管道中的指令列表改变读写顺序就可以大幅节省IO时间。

事务

基本用法

包含指令 multi、exec、discard。

  • multi 指示事务的开始;
  • exec 指示事务的执行;
  • discard 指示事务的丢弃。

因为 Redis 的单线程特性,不用担心在执行队列的时候被其他指令打搅,可以保证“原子性”执行。

“原子性”

当指令执行失败之后,后面的指令还会继续执行,所以Redis的事务根本不具备“原子性”,而仅仅满足了实物的“隔离性”中的串行化——当前执行的事务有着不被其他事务打断的权利。

watch 机制

出现并发问题的时候,一种是采用分布式锁的悲观锁方案,Redis 提供了一种了 watch 的乐观锁机制。

watch  会在事务开始之前盯住变量,当事务执行时,也就是服务端收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后是否被修改了。

Redis 禁止在 multi 和 exec 之间执行 watch 命令,必须在 multi 之前盯住关键变量。

PubSub

为了支持消息多播,Redis 单独提供了 PubSub 模块来支持消息多播,PubSub 也就是 PublisherSubscriber(发布者 / 订阅者模式)。

内存相关

Redis 是一个非常耗费内存的数据库,它的所有数据都放在内存里。为了优化数据结构的内存占用,增加了非常多的优化点。

  • 32bit or 64bit : 如果使用32bit进行编译,数据结果所使用的指针空间占用会减少一半。
  • 小对象压缩存储 ziplist : 如果 Redis 内部管理的集合数据很小,它会使用经凑存储形式压缩存储。
  • 内存回收机制:Redis 并不是将空闲内存立即归还给操作系统,它会重新使用那些尚未回收的空闲内存,原因是操作系统以页为单位来回收内存的。
  • 内存分配算法:Redis 将内存分配的细节丢给了第三方内存分配库去实现。可以使用 jemalloc(facebook)库,也可以使用 tcmalloc(google)库。

集群篇

主从同步

CAP 原理

  • C:Consistent,一致性
  • A:Availability,可用性
  • P:Partition tolerance,分区容忍性

网络断开的场景叫做网络分区。当网络分区发生事,一致性和可用性两难全。

最终一致性

  • Redis 的主从数据是异步同步的,所以并不满足一致性要求。
  • 即使在主从网络断开的情况,主节点依旧可以对外提供服务,满足可用性。
  • Redis 保证最终一致性,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态保持一致。

Redis 同步支持 主从同步与同同同步。

增量同步

主节点会将指令记录在本地内存的buffer中,然后异步将指令同步到从节点。当网络情况不好的时候,buffer可能被后续的指令覆盖而造成丢失。

快照同步

快照同步是一份耗费资源的操作:在主节点上将内存数据全部快照到磁盘中,然后再将快照文件全部传达到从节点。从节点接受完毕后,立即执行一次全量加载,加载完毕后通知主节点继续进行增量同步。

增加从节点

增加从节点时,需先进行一次快照同步,然后再继续进行增量同步。

无盘复制

指主服务器直接通过套接字将快照内容发送到从节点,不需要落盘之后再发送。从节点还是和之前一样。

wait指令

Redis 的复制是异步进行的,wait 指令可以让异步复制变身同步复制,确保系统的强一致性(不严格)。

Sentinel

背景

需要有一个高可用方案来抵抗节点故障,当故障发生时可以自动进行主从切换,程序可以不用重启,Redis 官方提供了方案 Redis Sentinel(Sentinel 的含义是哨兵)。

工作原理

Sentinel 负责持续监控主从节点的健康,当住节点挂掉时,自动选择一个最优的从节点切换成为主节点。客户端来连接集群时,会首先连接 Sentinel,通过 Sentinel 来查询主节点地址。

消息丢失

Redis 主从采用异步复制,意味着当主节点挂掉时,从节点可能没有收到全部的同步消息,这部分为同步的消息就会丢失了。

Sentinel 无法保证消息完全不丢失,但是尽量保证消息少丢失,如:可以定义一定场景下,停止对外写服务,丧失可用性。

Codis

产生背景

在大数据并发场景下,单个 Redis 实例往往会捉襟见肘:

  • 内存不宜过大,过大会导致快照文件过大,主从同步时时间过长。
  • 云环境下单个实例内存大小往往都是受限的。
  • CPU 利用率上,单个 Redis 实例只能利用单个核心。

在这样的大数据高并发的需求下,Redis 集群方案应运而生。它可以将众多小内存的 Redis 实例整合起来,将分布在多台机器上的众多 CPU 核心的计算能力聚集到一起,完成海量数据存储和高并发读写操作。

基本介绍

Codis 是 Redis 集群方案之一,来自前豌豆荚中间件团队。使用 Go 语言开发,它是一个代理中间件,和 Redis 一样使用 Redis 协议对外提供服务。当客户端向 Codis 发送指令时,Codis 负责将指令转发到后面的 Redis 实例来执行。

  • 分片原理:默认将 key 划分为 1024 个槽位(slot)。
  • 槽位同步:需要一个分布式配置存储数据库专门用来持久化槽位关系,使用 zookeeper、etcd。
  • 当扩容时,Codis 可使用 SLOTSSCAN 指令扫描出待迁移槽位的所有 key。
  • 代价:key 分散在不同的 Redis 实例中,不能再支持事务了,事务只能在单个 Redis 实例中完成。
  • 优点:将分布式问题交给三方(zookeeper等)负责,相比 Redis Cluster 官方集群方案要简单很多。

Cluster

Redis Cluster 是 Redis 的“亲儿子”,是 Redis 作者提供的 Redis 集群化方案。

  • 是去中心化的,节点间通过特殊的二进制协议交互集群信息;
  • 将所有数据划分为 16384 个槽位;
  • 客户端需要缓存槽位关系,并有纠正机制,可直接定位到目标节点。

拓展篇、源码篇

总结

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
相关文章
|
6月前
|
canal 缓存 NoSQL
【Redis系列笔记】双写一致性
本文讨论了缓存不一致问题及其后果,如价格显示错误和订单计算错误。问题主要源于并发和双写操作的异常。解决方案包括使用分布式锁(但可能导致性能下降和复杂性增加)、延迟双删策略(通过延迟删除缓存来等待数据同步)以及异步同步方法,如通过Canal和MQ实现数据的最终一致性。面试中,可以提及这些策略来确保数据库和缓存数据的一致性。
520 1
【Redis系列笔记】双写一致性
|
6月前
|
缓存 NoSQL 安全
【Redis系列笔记】缓存三剑客
缓存穿透是指请求一个不存在的数据,缓存层和数据库层都没有这个数据,这种请求会穿透缓存直接到数据库进行查询。它通常发生在一些恶意用户可能故意发起不存在的请求,试图让系统陷入这种情况,以耗尽数据库连接资源或者造成性能问题。 缓存击穿发生在访问热点数据,大量请求访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。 缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
167 2
|
6月前
|
存储 缓存 NoSQL
蚂蚁金服P7私藏的Redis原理与实践内部笔记
Redis 是完全开源免费的,是一个高性能的key-value类型的内存数据库。整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
108 1
|
6月前
|
NoSQL Redis
Redis原理之网络通信协议笔记
1. RESP协议 ​2. 自定义Socket连接Redis
|
6月前
|
NoSQL Linux Redis
Redis原理之网络模型笔记
Redis采用单线程模型,这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O(Blocking I/O)、非阻塞I/O(Non-blocking I/O)、I/O多路复用(I/O Multiplexing)、信号驱动I/O(Signal-driven I/O)以及异步I/O(Asynchronous I/O)。
100 1
|
6月前
|
NoSQL Java 关系型数据库
【Redis系列笔记】分布式锁
分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁,只要大家使用的是同一把锁,那么我们就能锁住线程,不让线程进行,让程序串行执行,这就是分布式锁的核心思路
661 2
|
5月前
|
存储 NoSQL Java
Redis系列学习文章分享---第八篇(Redis快速入门之达人探店--发布探店笔记 + 查看探店笔记 + 点赞功能 + 点赞排行榜)
Redis系列学习文章分享---第八篇(Redis快速入门之达人探店--发布探店笔记 + 查看探店笔记 + 点赞功能 + 点赞排行榜)
31 0
|
6月前
|
存储 消息中间件 缓存
Redis - 笔记
Redis是开源的内存数据结构存储系统,兼备数据库、缓存和消息中间件功能。它支持字符串、哈希、列表、集合、有序集合等数据结构,以及地理空间、基数统计和位图等特殊类型。Redis具备复制、LUA脚本、LRU事件、事务、持久化、哨兵和集群等高级特性,以实现高可用性。应用场景包括计数器、存储用户信息、消息队列、共同关注等。字符串最大容量为512M。
|
6月前
|
存储 负载均衡 NoSQL
【Redis系列笔记】Redis集群
集群是一种在多个计算机或服务器之间分配和管理任务的方式。它们被广泛应用于大型计算任务、数据处理、网络服务和高性能计算等领域。在Redis中,主要有以下三种集群,分别是主从集群,哨兵集群,分片集群。
194 18
|
6月前
|
缓存 NoSQL Java
【Redis系列笔记】Redis入门
本文介绍了Redis常用命令,以及SpringBoot集成Spring Data Redis和Spring Cache。Spring Data Redis 提供了对 Redis 的操作方法,而 Spring Cache 则提供了基于注解的缓存功能,可以方便地将方法的返回值缓存到 Redis 中,以提高性能和减少对数据源的访问次数。这样的集成可以帮助开发者更便捷地利用 Redis 来管理应用程序的数据和缓存。
176 4
下一篇
无影云桌面