Redis第十章 Redis HyperLogLog与事务、Redis 7.0前瞻

HyperLogLog是一种基数算法,用于在有限内存中统计独立元素数量,常用于大流量场景的UV统计。Redis提供了HyperLogLog数据结构和pfadd、pfcount、pfmerge等命令。此外,文章还介绍了Redis的简单事务功能,以及Pipeline和事务的区别。在复制方面,文章讨论了主从复制的全量同步和增量同步,以及Redis7.0中为解决复制缓存区问题引入的共享复制缓存区设计。
摘要由CSDN通过智能技术生成

HyperLogLog

HyperLogLog(Hyper[ˈhaɪpə®])并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过 HyperLogLog 可以利用极小的内存空间完成独立总数的统计,数据集可以是 IP、Email、ID 等。
如果你的页面访问量非常大,比如一个爆款页面几千万的 UV(登录的用户数),你需要一个很大的 set 集合来统计,这就非常浪费空间。

这就是 HyperLogLog 的用武之地,Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,Redis 官方给出标准误差是 0.81%,这样的精确度已经可以满足上面的 UV 统计需求了

操作命令

HyperLogLog 提供了 3 个命令: pfadd、pfcount、pfmerge。
例如 08-15 的访问用户是 u1、u2、u3、u4,
08-16 的访问用户是 u-4、u-5、u-6、u-7

  1. pfadd
 pfadd key element [element …]

pfadd 用于向 HyperLogLog 添加元素,如果添加成功返回 1:
在这里插入图片描述
2. pfcount

pfcount key [key …]

pfcount 用于计算一个或多个 HyperLogLog 的独立总数,例如 08-15:u:id 的独立总数为 4:
在这里插入图片描述
如果此时向插入 u1、u2、u3、u90,结果是 5:
在这里插入图片描述
如果我们继续往里面插入数据,比如插入 100 万条用户记录。内存增加非常少,但是 pfcount 的统计结果会出现误差。
在这里插入图片描述

  1. pfmerge
pfmerge destkey sourcekey [sourcekey ... ]

pfmerge 可以求出多个 HyperLogLog 的并集并赋值给 destkey
在这里插入图片描述
原理概述
HyperLogLog 基于概率论中伯努利试验并结合了极大似然估算方法,并做了分桶优化。
实际上目前还没有发现更好的在大数据场景中准确计算基数的高效算法,因此在不追求绝对准确的情况下,使用概率算法算是一个不错的解决方案。概率算法不直接存储数据集合本身,通过一定的概率统计方法预估值,这种法可以大大节省内存,同时保证误差控制在一定范围内。目前用于基数计数的概率算法包括:
Linear Counting(LC):早期的基数估计算法,LC 在空间复杂度方面并不算优秀;
LogLog Counting(LLC):LogLog Counting 相比于 LC 更加节省内存,空间复杂度更低;
HyperLogLog Counting(HLL):HyperLogLog Counting 是基于 LLC 的优化和改进,在同样空间复杂度情况下,能够比 LLC 的基数估计误差更小。
在这里插入图片描述
k 是每回合抛到 1(硬币的正面)所用的次数,我们已知的是最大的 k 值,也就是 Mark 老师告诉 Fox 老师的数,可以用 k_max 表示。由于每次抛硬币的结果只有 0 和 1 两种情况,因此,能够推测出 k_max 在任意回合出现的概率 ,并由 kmax 结合极大似然估算的方法推测出 n 的次数 n = 2^(k_max) 。概率学把这种问题叫做伯努利实验。
所以这种预估方法存在较大误差,为了改善误差情况,HLL 中引入分桶平均的概念。

分桶平均的基本原理是将统计数据划分为 m 个桶,每个桶分别统计各自的k_max, 并能得到各自的基数预估值,最终对这些基数预估值求平均得到整体的基数估计值。LLC 中使用几何平均数预估整体的基数值,但是当统计数据量较小时误差较大;HLL 在 LLC 基础上做了改进,采用调和平均数过滤掉不健康的统计值。

什么叫调和平均数呢?举个例子
求平均工资:A 的是 1000/月,B 的 30000/月。采用平均数的方式就是:(1000 + 30000) / 2 = 15500
采用调和平均数的方式就是: 2/(1/1000 + 1/30000) ≈ 1935.484
可见调和平均数比平均数的好处就是不容易受到大的数值的影响,比平均数的效果是要更好的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

事务

Redis 事务
事务表示一组动作,要么全部执行,
要么全部不执行。例如在社交网站上用户 A 关注了用户 B,那么需要在用户 A 的关注表中加入用户 B,并且在用户 B 的粉丝表中添加用户 A,这两个行为要么全部执行,要么全部不执行,否则会出现数据不一致的情况。
Redis 提供了简单的事务功能,将一组需要一起执行的命令放到 multi 和 exec两个命令之间。multi 命令代表事务开始,exec命令代表事务结束,如果要停止事务的执行,可以使用 discard 命令代替 exec 命令即可。
它们之间的命令是原子顺序执行的,例如下面操作实现了上述用户关注问题。
在这里插入图片描述
可以看到 sadd 命令此时的返回结果是 QUEUED,代表命令并没有真正执行,而是暂时保存在 Redis 中的一个缓存队列(所以 discard 也只是丢弃这个缓存队列中的未执行命令,并不会回滚已经操作过的数据,这一点要和关系型数据库的Rollback 操作区分开)。
如果此时另一个客户端执行

sismember user1 1

在这里插入图片描述
只有当 exec 执行后
在这里插入图片描述
才会存入进去
在这里插入图片描述
如果事务中的命令出现错误,Redis 的处理机制也不尽相同。
1、命令错误
例如下面操作错将 set 写成了 sett,属于语法错误,会造成整个事务无法执行,key 和 counter 的值未发生变化:
在这里插入图片描述
2.运行时错误
例如误把sadd命令(针对集合)写成了zadd命令(针对有序集合),这种就是运行时命令,因为语法是正确的:
在这里插入图片描述
可以看到 Redis 并不支持回滚功能,sadd user4 2 命令已经执行成功,开发人员需要自己修复这类问题。

有些应用场景需要在事务之前,确保事务中的 key 没有被其他客户端修改过,才执行事务,否则不执行(类似乐观锁)。Redis 提供了 watch 命令来解决这类问题。

客户端1在这里插入图片描述
客户端2在这里插入图片描述
客户端 1 继续:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Pipeline 和事务的区别
1、pipeline 是客户端的行为,对于服务器来说是透明的,可以认为服务器无法区分客户端发送来的查询命令是以普通命令的形式还是以 pipeline 的形式发送到服务器的;

2 而事务则是实现在服务器端的行为,用户执行 MULTI 命令时,服务器会将对应这个用户的客户端对象设置为一个特殊的状态,在这个状态下后续用户执行的查询命令不会被真的执行,而是被服务器缓存起来,直到用户执行 EXEC 命令为止,服务器会将这个用户对应的客户端对象中缓存的命令按照提交的顺序依次执行。

3、应用 pipeline 可以提服务器的吞吐能力,并提高 Redis 处理查询请求的能力。
但是这里存在一个问题,当通过 pipeline 提交的查询命令数据较少,可以被内核缓冲区所容纳时,Redis 可以保证这些命令执行的原子性。然而一旦数据量过大,超过了内核缓冲区的接收大小,那么命令的执行将会被打断,原子性也就无法得到保证。因此 pipeline 只是一种提升服务器吞吐能力的机制,如果想要命令以事务的方式原子性的被执行,还是需要事务机制,或者使用更高级的脚本功能以及模块功能。

4、可以将事务和 pipeline 结合起来使用,减少事务的命令在网络上的传输时间,将多次网络 IO 缩减为一次网络 IO。

Redis 提供了简单的事务,之所以说它简单,主要是因为它不支持事务中的回滚特性,同时无法实现命令之间的逻辑关系计算,当然也体现了 Redis 的“keep it simple”的特性。

Redis 7.0 前瞻

Redis 主从复制原理
Redis 主从复制的基本原理。Redis 的主从复制主要分为两种情况:

  • 全量同步
    主库通过 fork 子进程产生内存快照,然后将数据序列化为 RDB 格式同步到从库,使从库的数据与主库某一时刻的数据一致。
  • 命令传播(增量同步)
    当从库与主库完成全量同步后,进入命令传播阶段,主库将变更数据的命令发送到从库,从库将执行相应命令,使从库与主库数据持续保持一致。

Redis 复制缓存区相关问题分析
多从库时主库内存占用过多
OutputBuffer 拷贝和释放的堵塞问题
Redis 为了提升多从库全量复制的效率和减少 fork 产生 RDB 的次数,会尽可能的让多个从库共用一个 RDB,从代码(replication.c)上看:
在这里插入图片描述
当已经有一个从库触发 RDB BGSAVE 时,后续需要全量同步的从库会共享这次 BGSAVE 的 RDB,为了从库复制数据的完整性,会将之前从库的OutputBuffer 拷贝到请求全量同步从库的 OutputBuffer 中。
其中的 copyClientOutputBuffer 可能存在堵塞问题,因为 OutputBuffer 链表上的数据可达数百 MB 甚至数 GB 之多,对其拷贝可能使用百毫秒甚至秒级的时间,而且该堵塞问题没法通过日志或者 latency 观察到,但对 Redis 性能影响却很大。
同样地,当 OutputBuffer 大小触发 limit 限制时,Redis 就是关闭该从库链接,而在释放 OutputBuffer 时,也需要释放数百 MB 甚至数 GB 的数据,其耗时对 Redis 而言也很长。

ReplicationBacklog 的限制
复制积压缓冲区 ReplicationBacklog 是 Redis 实现部分重同步的
基础,如果从库可以进行增量同步,则主库会从 ReplicationBacklog 中拷贝从库缺失的数据到其 OutputBuffer。拷贝的数据量最大当然是 ReplicationBacklog 的大小,为了避免拷贝数据过多的问题,通常不会让该值过大,一般百兆左右。但在大容量实例中,为了避免由于主从网络中断导致的全量同步,又希望该值大一些,这就存在矛盾了。
而且如果重新设置 ReplicationBacklog 大小时,会导致 ReplicationBacklog 中的内容全部清空,所以如果在变更该配置期间发生主从断链重连,则很有可能导致全量同步。

Redis7.0 共享复制缓存区的设计与实现
每个从库在主库上单独拥有自己的 OutputBuffer,但其存储的内容却是一样的,一个最直观的想法就是主库在命令传播时,将这些命令放在一个全局的复制数据缓冲区中,多个从库共享这份数据,不同的从库对引用复制数据缓冲区中不同的内容,这就是『共享复制缓存区』方案的核心思想。实际上,复制积压缓冲区(ReplicationBacklog)中的内容与从库 OutputBuffer 中的数据也是一样的,所以该方案中,ReplicationBacklog 和从库一样共享一份复制缓冲区的数据,也避免了 ReplicationBacklog 的内存开销。

『共享复制缓存区』方案中复制缓冲区 (ReplicationBuffer) 的表示采用链表的表示方法,将 ReplicationBuffer 数据切割为多个 16KB 的数据块(replBufBlock),然后使用链表来维护起来。为了维护不同从库的对ReplicationBuffer 的使用信息,在 replBufBlock 中存在字段:
refcount:block 的引用计数
id:block 的唯一标识,单调递增的数值
repl_offset:block 开始的复制偏移
在这里插入图片描述
ReplicationBuffer 由多个 replBufBlock 组成链表,当 复制积压区 或从库对某个 block 使用时,便对正在使用的 replBufBlock 增加引用计数,上图中可以看到,复制积压区正在使用的 replBufBlock refcount 是 1,从库 A 和 B 正在使用的 replBufBlock refcount 是 2。当从库使用完当前的 replBufBlock(已经将数据发送给从库)时,就会对其 refcount 减 1 而且移动到下一个 replBufBlock,并对其 refcount 加 1。

堵塞问题和限制问题的解决
多从库消耗内存过多的问题通过共享复制缓存区方案得到了解决,对于
OutputBuffer 拷贝和释放的堵塞问题和 ReplicationBacklog 的限制问题是否解决了呢?
首先来看 OutputBuffer 拷贝和释放的堵塞问题问题, 这个问题很好解决,因为 ReplicationBuffer 是个链表实现,当前从库的 OutputBuffer 只需要维护共享 ReplicationBuffer 的引用信息即可。所以无需进行数据深拷贝,只需要更新引用信息,即对正在使用的 replBufBlock refcount 加 1,这仅仅是一条简单的赋值操作,非常轻量。
OutputBuffer 释放问题呢?在当前的方案中释放从库
OutputBuffer 就变成了对其正在使用的 replBufBlock refcount 减 1,是一条赋值操作,不会有任何阻塞。
对于 ReplicationBacklog 的限制问题也很容易解决了,因为
ReplicatonBacklog 也只是记录了对 ReplicationBuffer 的引用信息,对
ReplicatonBacklog 的拷贝也仅仅成了找到正确的 replBufBlock,然后对其refcount 加 1。这样的话就不用担心 ReplicatonBacklog 过大导致的拷贝堵塞问题。而且对 ReplicatonBacklog 大小的变更也仅仅是配置的变更,不会清掉数据。

ReplicationBuffer 的裁剪和释放
在这里插入图片描述
数据结构的选择
在这里插入图片描述

选择记录

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

friover

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值