一文带你看懂 Redis BitArray 如何实现高性能的位操作

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: Redis 作为当代互联网行业无可替代的 Key-Value 数据库,在我们日常的工作中占据主要的角色,对于常用的命令相信大家都很熟悉。今天给大家分享一个平时可能用到的少,但是也很重要的一个类型 BitArray。我们先通过简单的命令使用,了解该命令的用法,然后再给大家介绍一下底层的实现原理,帮助大家更好的了解。

Redis 作为当代互联网行业无可替代的 Key-Value 数据库,在我们日常的工作中占据主要的角色,对于常用的命令相信大家都很熟悉。今天给大家分享一个平时可能用到的少,但是也很重要的一个类型 BitArray。我们先通过简单的命令使用,了解该命令的用法,然后再给大家介绍一下底层的实现原理,帮助大家更好的了解。

简单使用

我们先看下什么是BitArray 位数组。Redis 使用字符串对象来存储位数组,一个 Byte 字节有 8 个 bit 位,通过控制每一个 bit 位为 0 或者 1来表示某个元素对应的值或者状态。通过使用 8 个 bit 位可以对复杂操作节省很多的空间。BitArray 相关的操作命令有 SETBIT,GETBIT,BITCOUNT,BITOP。下面我们依次看下命令的使用,最后再看下实现的原理。

首先我们在本地启动一个 Redis 实例,再启动一个客户端去链接如下图,

6.jpg

通过redis-cli 链接客户端,执行相应的命令,接下来使用一下 BitArray 相关的命令,

7.jpg

通过setbit test 2 1 命令我们创建了一个名为 testbitarray 并将其第二位设置成 1,再使用getbit test 2 获取对应位的值。setbit命令功能是将对应的 key 指定 offset 的位置设置为 1 或 0,getbit 命令是获取指定 offset 位置的值。test 是一个位数组通过上面的命令值变成0000 0010

接下来我们再创建一个名为test2的位数组,并且通过多次使用 setbit 命令和 bitcountbitcount 命令的作用是用来统计位数组中 1 的个数,通过下面我们看到第一次使用 bitcount test2 命令时结果为 1,当使用了 setbit test2 1 1 命令后再次使用 bitcount 命令我们发现结果已经变成 2 了。其中test2 的刚开始是0000 0100 后面变成0000 0101

8.jpg

bitop 命令相信大家都能理解,都是一些与,或,异或,非的运算,就不赘述了,具体使用可以看上图。

原理

前面说到 Redis 是通过字符串对象来实现位数组的,所以字符串对象有的功能,在位数组上面都是有的,在Redis 底层位数组的存储结构也是基于 SDS (简单动态字符串)的,如下:

9.jpg

其中 len 字段表示包含的 buf 数组的个数,buf[i] 表示的是第i个字节数组里面具体的数值,buf[len] 是末尾的分隔符\0 。上图中的buf[0] 是一个字节,其中有 8 个 bit 位,在使用了 setbit 命令后初始值为0000 0000,buf[1] 中就是分隔符\0

SETBIT

当我们执行setbit key offset value  命令时,我们分两步:

  1. 计算出创建多少个字节数组(offset / 8) + 1;
  2. 判断是否长度不够需要进行扩容;
  3. 计算出 offset 对应的字节位置 byte = offset / 8;
  4. 计算出 offset 对应的 bit 位,bit = (offset mod 8) + 1;
  5. 根据 offset 找到对应的位置将此处的值改成value 并返回旧值;

假设我们执行的命令时setbit test2 3 1,第一步先计算字节个数 (3 / 8) + 1 = 1,计算出来我们只需要一个字节;第二步跟原始 len 进行比较,发现不需要扩容;3. 根据 offset 计算存放的字节 3 / 8 = 0 则,存放的 buf[0] 中;第四部计算 bit,( 3 mod 8) + 1 = 4,表示的是第四个 bit 位。经过一轮 test2 就变成了0000 1000

setbit 命令执行的操作都是常数级别的,时间复杂度为 O(1)。

GETBIT

我们知道的setbit 命令是如何实现的,那么getbit 命令也就知道如何计算了,过程是类似的。

  1. 找到对应的字节数组 byte = offset / 8;
  2. 计算出对应的 bit 位bit =  (offset mod 8) + 1;

经过上面的计算我们可以知道当执行命令 getbit test2 3  的时候,先算出 3 / 8 = 0 ,找到 buf[0],再使用(3 mod 8) + 1  = 4,找到 bit 位。

看到这里细心的小伙伴就会有疑问,会说不对啊,根据这个计算返回的值应该是 0 啊,因为上面 setbit命令执行完的结果是0000 1000 啊。

能发现这个问题的小伙伴说明很用心在看了,这里就要跟大家说下了,虽然 setbit 命令执行完结果是0000 1000 但是在 「buf[0] 中存储的确实反过来的,即为0001 0000。采用的是逆序的方式来保存位数组的。

之所以采用逆序保存位数组是为了减少位数组的移动,提高性能,感兴趣的小伙伴可以自行研究一下。

BITCOUNT 命令

bitcount 命令是用来计算一个位数组中 1 的个数,说起来比较简单,但是实现起来却很有讲究。我们设想一下,统计一个位数组中 1 的个数有多少个,最简单的办法就是遍历,依次累加。但是当我们的位数组很大的时候,整个效率就会变得非常慢,因为遍历是跟长度正相关的,当存放 100MB 的位数组整个遍历需要八亿次。而当达到 500MB 时整个遍历就达到了四十亿次!

在 Redis 中采用的是查表和 variable-precision SWAR 算法,查表是指当位数组长度小于 128 时,直接根据预设的映射表找到对应 1 的个数,直接返回。而variable-precision SWAR 算法相对比较复杂,阿粉也还要再研究研究,今天就先不分享了。

BITOP 命令

bitop 命令相对简单一点,因为 Redis 底层是基于 C 语言实现的,C语言本身就支持相关的逻辑运算。因为本身就是二进制位数组,所以对应的逻辑运算会简单很多就不赘述了,相信大家都能理解。

相关实践学习
基于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
相关文章
|
NoSQL Unix Linux
Redis核心技术与实践 03 | 高性能IO模型:为什么单线程Redis能那么快?
Redis核心技术与实践 03 | 高性能IO模型:为什么单线程Redis能那么快?
|
2月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
78 6
|
4月前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
85 0
|
22天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
36 5
|
7月前
|
存储 消息中间件 缓存
Redis的高性能使得它非常适合用于实时分析场景
【5月更文挑战第15天】Redis在Python Web开发中扮演关键角色,常用于缓存系统,提高数据读取速度;会话管理,存储用户信息;分布式锁,确保数据一致性;排行榜和计数,利用有序集合和哈希结构;消息队列,基于列表结构实现异步处理;实时分析,高效处理实时数据。其丰富的数据结构和高性能使其在多种场景下应用广泛。
346 3
|
3月前
|
存储 缓存 NoSQL
Redis的高性能之谜
Redis的高性能之谜
44 5
|
3月前
|
存储 消息中间件 NoSQL
Redis的单线程设计之谜:高性能与简洁并存
Redis的单线程设计之谜:高性能与简洁并存
46 1
|
存储 消息中间件 NoSQL
深入了解Redis:高性能的内存数据库
深入了解Redis:高性能的内存数据库
|
6月前
|
存储 缓存 NoSQL
Redis是一种高性能的内存数据库,常用于高并发环境下的缓存解决方案
【6月更文挑战第18天】**Redis摘要:** 高性能内存数据库,擅长高并发缓存。数据存内存,访问迅速;支持字符串、列表等多元数据类型;具备持久化防止数据丢失;丰富命令集便于操作;通过节点集群实现数据分片与负载均衡,增强可用性和扩展性。理想的缓存解决方案。
85 1
|
6月前
|
存储 运维 NoSQL
Redis 分区:构建高性能、高可用的大规模数据存储解决方案
Redis 分区:构建高性能、高可用的大规模数据存储解决方案