Redis系列8:Bitmap实现亿万级数据计算
Redis系列1:深刻理解高性能Redis的本质
Redis系列2:数据持久化提高可用性
Redis系列3:高可用之主从架构
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 集群模式
追求性能极致:Redis6.0的多线程模型
追求性能极致:客户端缓存带来的革命
1 前言
我们在第一篇 深刻理解高性能Redis的本质 的时候就介绍过Redis的几种基本数据结构,它是基于不同业务场景而设计的:
- 动态字符串(REDIS_STRING):整数(REDIS_ENCODING_INT)、字符串(REDIS_ENCODING_RAW)
- 双端列表(REDIS_ENCODING_LINKEDLIST)
- 压缩列表(REDIS_ENCODING_ZIPLIST)
- 跳跃表(REDIS_ENCODING_SKIPLIST)
- 哈希表(REDIS_HASH)
- 整数集合(REDIS_ENCODING_INTSET)
除了这常见数据类型,还有一些不常用的数据类型,如 BitMap、Geo、HyperLogLog 等等,他们在各自的领域为大数据量的统计,后面我们一一来介绍,学习下他们的实现原理和应用场景。
2 BitMap介绍
BitMap (位图)的底层数据结构使用的是String类型的的 SDS 数据结构来保存。因为一个字节8个bit位,为了有效的将字节的8个bit都利用到位,使用数组模式存储。
并且每个bit都使用二值状态表示,要么0,要么1。
所以,BitMap 是通过一个 bit 位来表示某个元素对应的值或者状态, 它的结构如下,key 对应元素本身;offset即是偏移量,固定整型,一般存数组下表或者唯一值;value存储的是二值(要么0要么1),一般用来表示状态,如性别、是否登录、是否打卡等。
从上面可以看出这边使用一个字节表示1行,每1行存储8个bit,就是可以存储8个状态位,极大的提高了空间利用。这也是BitMap的优势,我们可以使用很少的字节,存储大量的在线状态、打卡标记等状态信息,非常有效果。
我们可以使用 setbit, getbit, bitcount 等几个相关命令来管理BitMap。语法如下:
SETBIT key offset value
上面说过了,key是元素名称, offset 必须是数值类型,value 只能是 0 或者 1,如果我们存储一个用户的在线状态,用户,代码如下:
//设置在线状态
// $redis->setBit('online', $uid, 1);
$redis->setBit('online', 5, 1);
$redis->setBit('online', 9, 1);
则具体体现为:
byte | bit0 | bit1 | bit2 | bit3 | bit4 | bit5 | bit6 | bit7 |
---|---|---|---|---|---|---|---|---|
buf[0] | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
buf[1] | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
可以看出用户ID为5和9被打上1的标志,代表在线状态,其他未设置值默认为0,是离线状态。
除了Set之外,还有getBit、bitCount等语法,如下:
// 获取是否在线的状态
$isOnline = $redis->getBit('online', $uid);
// 获取在线人数 统计
$onlineNum = $redis->bitCount('online');
3 BitMap的主要应用场景
上面介绍了BitMap的原理和状态存储的优势。那我们存储了bit位,其实目的还是为了高效的计算,而不是简单的状态记录。
而在实际的应用场景中,他主要解决如下几个类型的需求:
3.1 状态统计
上面其实我们已经演示过了,这种场景最常见,因为值只能是1或者0,所以所有的二值状态的,所有存在是否对照关系的场景都可以使用。如在线(1) 离线(0),打卡(1) 未打卡(0),登录(1) 未登录(0),群聊消息已阅(1) 未阅(0) 等等。
我们以用户 离线/在线 为例子,看看如何使用 Bitmap 在海量的用户数据之中判断某个用户是否是在线状态。
假设我们使用一个 online_statu
来作为key,用来存储 用户登录后的状态集合,而用户的ID则为offset,online的状态就用1表示,offline的状态就用0表示。
- 如果1024用户登录系统,那么设置ID为1024的用户为在线的代码如下:
SETBIT online_statu 1024 1
- 如果想看1024的用户是否是在线状态(这边注意,key可能不存在,代表没有这个用户,这时候默认返回0),代码如下:
GETBIT online_statu 1024
- 如果1024的用户退出系统,则为他执行下线,代码如下:
SETBIT online_statu 1024 0
- 空间上的有效利用,1亿 人的状态存储只需要 100000000/8/1024/1024 = 11.92 M,简单的数据结构也保证了性能上的优势。
基于上面的讨论,我们可以总结出一个预评估公式,根据实际的数据量获取存储空间:( offset / 8 / 1024 / 1024 ) M
3.2 固定周期的签到情况统计(周/月/年)
固定周期可能是年/月/周,按照不同维度,可能有 365,31,7的bit位的统计周期。
假设这时候我们如果对于某个用户(如1024)全年的签到情况做统计,可以这么设计:
设计key 为 {bus_type}{uid}{yyyy} ,及业务类型+用户id+年份
比如 sign_1024_2022签到则执行对应代码
举例,1024用户在2022 年的第1天和最后一天如果有签到,那就是:
# 22年第一天
SETBIT sign_1024_2022 0 1
# 22年最后一天
SETBIT sign_1024_2022 364 1
- 判断某用户(1024)在某一天(150)是否有签到
GETBIT sign_1024_2022 150
- 统计某用户(1024) 全面的签到次数,使用 BITCOUNT 指令,统计给定的 bit 数组中,值 = 1 的所有bit位数量。
BITCOUNT sign_1024_2022
- 那如果你想限定范围了怎么办,比如原来设计的是一年的统计。但是你想获得某个月第一次打卡的数据,这时候就要使用BITPOS了。
通过 BITPOS key value [start] [end] 指令,返回数据表示 Bitmap 中第一个值为 给定value 的 offset 位置。
在默认情况下,命令会检测整个位图,但用户也可以通过可选的start参数和end参数指定要检测的范围。
比如第2个月的第3天是2月的第一次签到日,则下面的返回结果为30(第一个月31天)+ 3(二月第3天签到) = 33 :
$index = BITPOS sign_1024_2022 30
offset也是从0开始的,所以返回的值最好加个1,不会让用户看的晕头转向。
3.3 连续签到用户信息
如果一个平台有千万级别以上的大量用户,而我们需要统计每个用户连续签到的信息,那需要怎么设计呢?
- 可以把每天的日期当成位图(BitMap)的key,如 20221023
- 用户的唯一键当成(UserId)当成offset,如编号 1024 的用户
- 如果 1024 的用户在 2022.10.23 有签到,则位图的value为1,否则为0。
如果这时候我们要判断用户是否整周都有签到或者整个月都有签到就可以使用 【与】运算
只有指定周期内的所有值都是1(签到)的时候,结果才是1,否则是我们整周或者整个月都拿起来【与】运算,得到的结果是不是1就能确是否满勤。
# 与运算: 0&0=0;0&1=0;1&0=0;1&1=1
# 下面为伪代码,类似:
(20221022 1024) & ( 20221023 1024) & ...
Redis 提供了 BITOP operation destkey key [key ...]这个指令用于对一个或者多个 键 = key 的 Bitmap 进行 位元 操作。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
- BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
- BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
- BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
- BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
# 统计一周的值(7个BitMap,10.17 ~ 10.23 号)并将结果存入到新的BitMap (sign-result) 中
redis> BITOP AND sign-result 20221017 20221018 20221019 20221020 20221021 20221022 20221023
(integer) 1
# 新的BitMap 中,获取 1024的签到结果,如果为1,则本周全部签到
redis> GETBIT sign-result 1024
(integer) 1
可以理解下这张图的运算过程:
这边需要注意:当 BITOP 处理不同长度的字符串时,较短字符串所缺部分会被当作 0 对待。同样的,空 key 也被看作是 0 的字符串序列看待。
同理,类似HeapDump性能社区的用户签到统计,也可以用位图(BitMap)这种方式计算!
小结
1个byte等于8个bit,每个bit位只使用0或者1来表示,这样能够有效的降低存储空间,而Redis是存储在高速缓存中的,所以实际上是大大减少了内存占用。
很多场景都可以使用位图计算,比如我们上面说到的 是否登录、是否在线、是否签到、用户性别状态、IP黑名单、是否VIP用户统计 等等场景,但凡涉及到二值状态识别、海量统计的数据都可以考虑使用。
Redis系列8:Bitmap实现亿万级数据计算的更多相关文章
- Redis系列9:Geo 类型赋能亿级地图位置计算
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...
- Redis系列10:HyperLogLog实现海量数据基数统计
Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...
- Kafka对Java程序员有多重要?连阿里都再用它处理亿万级数据统计
一.了解淘宝Kafka架构 在ActiveMQ.RabbitMQ.RocketMQ.Kafka消息中间件之间,我们为什么要选择Kafka?下面详细介绍一下,2012年9月份我在支付宝做余额宝研发,20 ...
- 连阿里都在用它处理亿万级数据统计,论其对Java程序员的重要性!
一.了解淘宝Kafka架构 在ActiveMQ.RabbitMQ.RocketMQ.Kafka消息中间件之间,我们为什么要选择Kafka?下面详细介绍一下,2012年9月份我在支付宝做余额宝研发,20 ...
- Redis系列--内存淘汰机制(含单机版内存优化建议)
https://blog.csdn.net/Jack__Frost/article/details/72478400?locationNum=13&fps=1 每台redis的服务器的内存都是 ...
- Redis系列(九)--几道面试题
这里只是一点面试题,想了解更多,可以查看本人的Redis系列:https://www.cnblogs.com/huigelaile/category/1461895.html 1.Redis和Memc ...
- 【目录】redis 系列篇
随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...
- Redis系列(七)Redis面试题
Redis 系列: Redis系列(一)Redis入门 Redis系列(二)Redis的8种数据类型 Redis系列(三)Redis的事务和Spring Boot整合 Redis系列(四)Redis配 ...
- Redis系列之key操作命令与Redis中的事务详解(六)
序言 本篇主要目的有二: 1.展示所有数据类型中key的所有操作命令,以供大家学习,查阅,更深入的挖掘redis潜力. 2.掌握redis中的事务,让你的数据完整性一致性拥有更优的保障. redis命 ...
随机推荐
- php里的$this的 含义
$this 的含义是表示 实例化后的 具体对象! 我们一般是先声明一个类,然后用这个类去实例化对象! 但是,当我们在声明这个类的时候,想在类本身内部使用本类的属性或者方法.应该怎么表示呢? 例如 ...
- 创新能力加速产业发展,SphereEx 荣获“中关村银行杯”『大数据与云计算』领域 TOP1
8 月 9 日下午,2022 中关村国际前沿科技创新大赛"中关村银行杯"大数据与云计算领域决赛在北京市门头沟区中关村(京西)人工智能科技园·智能文创园落下了帷幕.SphereEx ...
- 10种有用的Linux Bash_Completion 命令示例
摘要:我们可以对这个 bash 补全进行加速,并使用 complete 命令将其提升到一个新的水平. 本文分享自华为云社区<有用的 Linux Bash_Completion 命令示例(Ster ...
- 使用 Less 混合(Mixins)时报语法错误
今天在尝试使用 less 的混合语法时,浏览器直接报了一个语法错误.下图是报错信息: 仔细地阅读了官方文档,和对比自己写的,并没有任何错误. .FlexLayout { .Start() { disp ...
- Java 断点下载(下载续传)服务端及客户端(Android)代码
原文: Java 断点下载(下载续传)服务端及客户端(Android)代码 - Stars-One的杂货小窝 最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一 ...
- RabbitMQ 入门系列:6、保障消息:不丢失:发送方、Rabbit存储端、接收方。
系列目录 RabbitMQ 入门系列:1.MQ的应用场景的选择与RabbitMQ安装. RabbitMQ 入门系列:2.基础含义:链接.通道.队列.交换机. RabbitMQ 入门系列:3.基础含义: ...
- 这三大特性,让 G1 取代了 CMS!
大家好,我是树哥. 之前我们聊过 CMS 回收器,但那时候我们说 CMS 回收器已经落伍了,现在应该是用 G1 回收器的时候了.那么 G1 回收器到底有什么魔力,它比 CMS 回收器相比强在哪里呢?今 ...
- Springboot连接数据库 (解决报错)
好家伙,来解决报错 1.新建项目时, 将SQL的" Spring Date 'jdbc' "点上 2.使用idea快速创建springboot项目时会出现连接不到服务器的情况 这里 ...
- Docker 入门指南
Docker 入门指南 目录 基础概念 安装教程 基本操作 常用安装 构建操作 容器编排 壹.基础概念 什么是Docker? Docker是基于Go开发的应用容器引擎,属于 Linux 容器的一种封装 ...
- 防止一台logstash机器上接入多个端口的日志会产生混乱
为了防止一台机器上多个接入会导致日志混乱所以地在各模块上添加type标识并作if判断! 不多比比直接上配置 [root@sf215 conf.d]# cat jddns-servers.conf in ...