华为云企业级redis揭秘第八期:用高斯redis进行计数-4008云顶国际网站

高斯redis官方博客 发表于 2021/04/20 15:40:48 2021/04/20
【摘要】 云数据库gaussdb(for redis)作为华为云旗下企业级redis,致力于为客户提供稳定可靠、超高并发,且能够极速弹性扩容的kv存储服务。


当我们打开手机刷微博时,就要开始和各种各样的计数器打交道了。我们注册一个帐号后,微博就会给我们记录一组数据:关注数、粉丝数、动态数;我们刷帖时,关注每天的热搜情况,微博需要为每个热搜记录一组搜索量。在这一串数据后面,是一个个计数器在工作。

计数器可以分为常规计数器和基数计数器,对于常规计数器,只需要对计数器进行简单的增减即可;对于基数计数器,需要对元素进行去重,比如统计搜索量时,需要保证每个用户的多次搜索只统计一次。对于这两种需求,redis 都有对应的数据类型进行统计。然而开源 redis 是一个弱一致性的数据库,在特定的场景下,弱一致的计数不能满足业务需求,为此,我们需要一个强一致的数据库进行计数。

gaussdb(for redis)(下文简称高斯redis),是华为自研的强一致、持久化 nosql 数据库,兼容 redis5.0 协议。本文将介绍常规计数器与基数计数器的应用场景及使用高斯 redis 实现计数。

2.1      如何使用 redis 进行常规计数

redis 实现常规计数器有两种数据类型适合:string hash

2.1.1        使用string 计数

当我们需要维护的计数器数目较少,比如统计网站的注册用户数时,适合使用 string 类型的计数器。redis 提供的 incr decr 命令分别对 string 类型的 key 值进行增一与减一操作:

127.0.0.1:6379> set counter 100
ok
127.0.0.1:6379> incr counter
(integer) 101
127.0.0.1:6379> decr counter
(integer) 100

incrdecr命令外,redis string 类型还提供 incrby decrby 命令,语法格式为:

  • incrbyincrby key count

key 增加 countcount 可正可负,返回 key 的结果:

127.0.0.1:6379> incrby counter 10
(integer) 10
127.0.0.1:6379> incrby counter -20
(integer) -10
  • decrbydecrby key count

key 减少 countcount 可正可负,返回 key 的结果:

127.0.0.1:6379> decrby counter 10
(integer) -10
127.0.0.1:6379> decrby counter -20
(integer) 10

2.1.2        使用hash计数

需要维护多个密切关联的计数器时,可以使用hash结构进行计数。比如说,当我们注册一个微博账号时,微博会给每个用户记录一些用户数据,如粉丝数、关注数等,这些数据都绑定到对应用户上,因此可以将这组计数器记录在同一个hash key中,使用 hincrby 命令,语法格式为:

  • hincrbyhincrby key filed count

hash key filed 增加 countcount 可正可负,返回对应 field 的结果:

127.0.0.1:6379> hget userid field
(nil)
127.0.0.1:6379> hincrby userid field 1
(integer) 1
127.0.0.1:6379> hincrby userid field -1
(integer) 0
127.0.0.1:6379> hget userid field
"0"

2.2      常规计数器使用场景

常规计数器的使用场景很广泛,对于社交产品,用户的粉丝数、关注数,帖子的点赞数、收藏数;对于视频网站,需要统计视频的播放次数(pv统计,page view);对于电商秒杀,需要统计商品数量并进行流量控制。在并发量高的情况下,redis 的性能优势明显,非常适合以上场景。

以电商秒杀业务为例,为了处理高并发读写,通常在mysql上层部署redis作为缓存。为了抗住大流量,使用计数器作限流。比如,当我们想控制每秒1万次请求时,可以初始化一个counter=10000,随后每次请求过来,都对counter减一,当counter 归零后,阻塞后续的请求。每隔一段时间,重置counter=10000,以此保证大流量不会冲击底层的mysql

基数计数(cardinality counting是指在一个数据集合中,统计不重复元素的个数,是实际应用中一种常见的场景。比如统计一段时间内访问某个网站的用户数,网络游戏的日活用户数量等。

在数据量较小情况下,我们可以把所有数据保存下来进行去重统计。redis 中,可以使用 set zset 将数据保存下来,然后统计集合中的元素数量。而当数据量较大时,该方法会消耗较大的存储空间,需要考虑其它的算法。

考虑一种情况,当我们登录微博时,微博会记录我们的登录情况,并统计每天有多少活跃用户。很显然,我们不需要也不应该记录活跃用户的id,并且,少量误差对活跃用户数量的统计使用影响不大,这种场景下,我们可以使用 hyperloglog 进行计数。hyperloglog 是一种使用极少内存实现巨量统计的计数算法,非常适合大数据场景的基数估计,在 redis 中被实现为一种数据类型。

3.1      hyperloglog 原理介绍

从伯努利试验到基数计数

hyperloglog 是一种基数估计算法,其思想来自于伯努利过程。

简单来说,伯努利过程就是一个抛硬币的过程。抛一次硬币,结果为正面或者反面的概率都是1/2。记正面为1,反面为0,如果抛硬币多次,直到出现第一次正面时停止,记为一次投掷试验,并且得到一个投掷结果的序列,比如“001”,我们可以知道,这个序列出现的概率是 。

反过来,如果我们持续进行投掷试验,当出现第一次“001”序列时,我们可以简单估算出,我们投掷试验次数为8(事实上,这是一个极大似然估计)。

hyperloglog 的原理就是将每个元素视为一次投掷试验,通过记录试验的最大投掷次数对元素的数量进行估计。当我们向集合中每插入一个元素,视为做了一次投掷试验,相同的元素对应一个投掷结果的序列。为了将每一个元素转化成一个“01”序列,我们可以使用一个哈希函数进行转换:


这里,我们有了一个简单的估计算法。我们只需要记录哈希结果中第一个“1”出现的位置的最大值即可,但很明显,当数据量较小时,这样一个估计值误差会很大,而且单个元素的对估计值的影响不平滑。


分桶平均减小误差

为了减小单一估计量的影响,hyperloglog 使用分桶多次试验的方法减小误差。方法是将哈希后的bitmap中前若干位当成桶的编号,剩余位当成试验结果。


对于每个桶中的结果,计算其调和平均值获取基数估计值(相比算术平均,调和平均数能够有效改善基数较小情况下极值影响过大的问题):






3.2      redis 中的 hyperloglog

redis hyperloglog 实现成一种数据类型,对于每个元素,redis将其hash64位的二进制串,用低14位用来表示桶的下标(所以桶的个数为1<<14=16384),剩余的位用来模拟伯努利分布,每个桶需要6bit;最多能够对264次方个元素进行统计,内存占用约12 k;其标准误差为 0.81%

redis 支持的 hyperloglog 命令只有3个,pfaddpfcountpfmerge,其语法如下:

  • pfadd:将所有元素参数添加到 hyperloglog 数据结构中

语法:pfadd key element1 [element2…]

如果至少有一个元素被添加返回1,否则返回0

如果没有指定 element,则创建 hyperloglog key

127.0.0.1:6379> pfadd key1 ele1 ele2
(integer) 1
127.0.0.1:6379> pfadd key1
(integer) 0
127.0.0.1:6379> pfadd key2
(integer) 0
  • pfcount:返回给定的hyperloglog的基数估计值

语法:pfcount key1 [key2 … ]

返回对应 hyperloglog 的基数值,多个key时,返回多个key的合并后的基数值。

127.0.0.1:6379> pfcount key1
(integer) 0
127.0.0.1:6379> pfadd key1 ele1 ele2
(integer) 1
127.0.0.1:6379> pfadd key2 ele1 ele3
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 2
127.0.0.1:6379> pfcount key1 key2
(integer) 3
  • pfmerge:将多个 hyperloglog 合并为一个

语法:pfmerge destkey sourcekey1 [sourcekey2 …]

sourcekey destkey 合并,当 destkey 不存在时,会创建 destkey

返回ok

127.0.0.1:6379> pfadd key1 ele1 ele2
(integer) 1
127.0.0.1:6379> pfadd key2 ele1 ele3
(integer) 1
127.0.0.1:6379> pfcount key3
(integer) 0
127.0.0.1:6379> pfmerge key3 key1 key2
ok
127.0.0.1:6379> pfcount key3
(integer) 3

3.3      hyperloglog 的适用场景

hyperloglog 作为一种计算大数据量的基数统计算法,在统计注册用户数,每日访问ip数,实时统计在线用户数等场景可以大显神威。

  • 统计网站的uv(unique visitor)

对于一个网页,我们想要知道这个网页的受关注程度,可以统计一下有多少用户(ip)点击了这个网页。为此,我们给每个时间段设置一条记录,比如,127.0.0.1这个ip2021111点的时候访问了网页:

pfadd key_prefix_2021010101 "127.0.0.1"

当需要统计这一天0-1点这一个小时一共有多少ip访问了这个网页时:

pfcount key_prefix_2021010101

需要统计上午812点的网页访问情况:

pfcount key_prefix_2021010109 …… key_prefix_2021010112

一天结束了,需要统计并保存这一天访问情况:

pfmerge key_prefix_2021010101 ...... key_prefix_2021010124

对于一个热门的网页,这样一个计数的方式显然能够极大的节约存储空间。

  • 用户画像

用户画像是根据用户在互联网上留下的各种数据,给用户贴上一系列的标签,比如用户的性别,年龄,爱好等。在进行数据分析时,可以使用 hyperloglog 进行数据的保存与分析。



  1. 对于每个标签,创建hyperloglog key值保存数据,如:man, woman, basketball…等,对于每个需要记录的值,都需要创建一个key进行记录。
  2. 每多一个用户时,向所有记录的key里使用pfadd 添加元素。
  3. 进行数据分析时,使用 pfcount 将需要分析的数据进行统计。

4.1      开源 redis 的问题

生产环境中,为避免单点故障,增强数据库可用性,redis 通常将数据复制多个副本,保存在不同的服务器上;在大量并发请求过来时,为了尽可能利用主从节点的服务器资源,可以采用主写从读的方式。由于 redis 的主从同步是异步的,当主节点写入数据后,从节点不保证立刻更新数据,如果此时读取数据,读到的就是过期的旧数据,产生数据不一致问题。

当主节点故障宕机后,数据不一致的问题会更严重。主节点故障后,哨兵节点会将从节点提升为主,原主节点上堆积的数据 buffer 就彻底丢失了。在电商秒杀业务中,如果发生主节点复制buffer堆积,导致从节点与主节点的 counter 偏大很多,一旦此时主节点宕机,发生主备倒换后,容易导致流量压力超出阈值,大量数据可能会将 mysql 压垮,导致系统不可用。



4.2      高斯 redis 如何解决

高斯 redis 借助高斯品牌的“存算分离”架构,将全量数据下沉到强一致存储层(dfv pool),彻底摒弃了开源 redis 的异步复制机制;计算层将海量数据进行分片,在故障场景下,自动进行接管,实现了服务的高可用。



存储层 dfv pool 是华为内部自研的公司级data lake,是分布式、强一致、高性能的先进架构。底层实现3副本强一致的存储,保证了在任何时间点的数据强一致,故障情况下数据不丢失,对于秒杀等业务满足计数的绝对精确。此外,借助存算分离架构,高斯redis还拥有低成本、大容量、秒扩容等优势:

高斯 redis 在社区版 redis 的基础上,结合华为自研强一致存储dfv pool,具有强一致、秒扩容、超可用、低成本等优势,保证了计数的准确性、可靠性。


本文作者:华为云数据库gaussdb(for redis)团队

杭州/西安/深圳简历投递:yuwenlong4@huawei.com

gaussdb(for redis)产品4008云顶国际网站主页:

更多技术文章,关注gaussdb(for redis)官方博客:

https://bbs.huaweicloud.com/community/usersnew/id_1614151726110813

【4008云顶国际集团的版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。