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实现亿万级数据计算的更多相关文章

  1. Redis系列9:Geo 类型赋能亿级地图位置计算

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  2. Redis系列10:HyperLogLog实现海量数据基数统计

    Redis系列1:深刻理解高性能Redis的本质 Redis系列2:数据持久化提高可用性 Redis系列3:高可用之主从架构 Redis系列4:高可用之Sentinel(哨兵模式) Redis系列5: ...

  3. Kafka对Java程序员有多重要?连阿里都再用它处理亿万级数据统计

    一.了解淘宝Kafka架构 在ActiveMQ.RabbitMQ.RocketMQ.Kafka消息中间件之间,我们为什么要选择Kafka?下面详细介绍一下,2012年9月份我在支付宝做余额宝研发,20 ...

  4. 连阿里都在用它处理亿万级数据统计,论其对Java程序员的重要性!

    一.了解淘宝Kafka架构 在ActiveMQ.RabbitMQ.RocketMQ.Kafka消息中间件之间,我们为什么要选择Kafka?下面详细介绍一下,2012年9月份我在支付宝做余额宝研发,20 ...

  5. Redis系列--内存淘汰机制(含单机版内存优化建议)

    https://blog.csdn.net/Jack__Frost/article/details/72478400?locationNum=13&fps=1 每台redis的服务器的内存都是 ...

  6. Redis系列(九)--几道面试题

    这里只是一点面试题,想了解更多,可以查看本人的Redis系列:https://www.cnblogs.com/huigelaile/category/1461895.html 1.Redis和Memc ...

  7. 【目录】redis 系列篇

    随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...

  8. Redis系列(七)Redis面试题

    Redis 系列: Redis系列(一)Redis入门 Redis系列(二)Redis的8种数据类型 Redis系列(三)Redis的事务和Spring Boot整合 Redis系列(四)Redis配 ...

  9. Redis系列之key操作命令与Redis中的事务详解(六)

    序言 本篇主要目的有二: 1.展示所有数据类型中key的所有操作命令,以供大家学习,查阅,更深入的挖掘redis潜力. 2.掌握redis中的事务,让你的数据完整性一致性拥有更优的保障. redis命 ...

随机推荐

  1. 如何自定义一个Collector

    Collectors类提供了很多方便的方法,假如现有的实现不能满足需求,我们如何自定义一个Collector呢?   Collector接口提供了一个of方法,调用该方法就可以实现定制Collecto ...

  2. 定制化JDK升级引发的离奇事件

    1.背景 由于Oracle对外宣称Oracle JDK停止免费用于商用.公司法务部门评估之后担心后续会惹上光司,于是就开始了JDK升级-将所有服务Oracle修改为OpenJDK.上周开始微服务JDK ...

  3. 深入解析Flutter下一代渲染引擎Impeller

    作者 魏国梁:字节 Flutter Infra 工程师, Flutter Member,长期专注 Flutter 引擎技术 袁    欣:字节 Flutter Infra 工程师, 长期关注渲染技术发 ...

  4. vue-router4 |name的作用|query传参|parmas传参|动态路由参数|命名视图|别名alias|前置路由守卫|路由过渡效果|滚动行为

    vue-router4 出现 No match found for location with path "/" #### router/index.ts文件 import { c ...

  5. 创建swarm集群并自动编排

    1.基础环境配置 主机名 master node1 node2 IP地址 192.168.***.1 192.168.***.2 192.168.***.3 角色     管理节点 工作节点 工作节点 ...

  6. Android平台RTMP/RTSP播放器开发系列--解码和绘制

    本文主要抛砖引玉,粗略介绍下Android平台RTMP/RTSP播放器中解码和绘制相关的部分(Github). 解码 提到解码,大家都知道软硬解,甚至一些公司觉得硬解码已经足够通用,慢慢抛弃软解了,如 ...

  7. JTS TopologyException 问题

    计算2个几何相交结果时候,报错了: val geometry = polygon.intersection(lineString) 日志如下 org.locationtech.jts.geom.Top ...

  8. CentOS7_SSH_安装总结

    在使用ssh 连接自己的centos 虚拟机时,发现连接不上,于是有了这个安装过程 (以下是在root用户下执行的,没权限的话就sudo) 1.首先判断是否有这个服务 systemctl list-u ...

  9. crictl 命令 - Kubernetes 管理命令详解

    描述:crictl 是 CRI 兼容的容器运行时命令行对接客户端, 你可以使用它来检查和调试 Kubernetes 节点上的容器运行时和应用程序.由于该命令是为k8s通过CRI使用containerd ...

  10. Docker 部署 RocketMQ Dledger 集群模式( 版本v4.7.0)

    文章转载自:http://www.mydlq.club/article/97/ 系统环境: 系统版本:CentOS 7.8 RocketMQ 版本:4.7.0 Docker 版本:19.03.13 一 ...