Redis 10亿数据量只需要100MB内存,是如何存储的?

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

来源:toutiao.com/i6767642839267410445

推荐:https://www.xttblog.com/?p=5062

本文主要和大家分享一下redis的高级特性:bit位操作。

力求让大家彻底学会使用redis的bit位操作并掌握其底层实现原理!主要包含以下内容:

  1. redis位操作命令示例

  2. 底层数据结构分析

  3. 为什么他的算法时间复杂度是O(1)?

  4. 10亿数据量需要多大的存储空间?

  5. redis位操作适合哪些应用场景?

本文redis试验代码基于如下环境:

操作系统:Mac OS 64位

版本:Redis 5.0.7 64 bit

运行模式:standalone mode

redis位操作

reids位操作也叫位数组操作、bitmap,它提供了SETBIT、GETBIT、BITCOUNT、BITTOP四个命令用于操作二进制位数组。

先来看一波基本操作示例:

SETBIT

语法:SETBIT key offset value

即:命令 key 偏移量 0/1

setbit命令用于写入位数组指定偏移量的二进制位设置值,偏移量从0开始计数,且只允许写入1或者0,如果写入非0和1的值则写入失败:

GETBIT

语法:GETBIT key offset

即:命令 key 偏移量

gitbit命令用于获取位数组指定偏移量上的二进制值:

BITCOUNT

语法:BITCOUNT key

即:命令 key

bitcount命令用于获取指定key的位数组中值为1的二进制位的数量,之前我们写入了偏移量0的值为1,偏移量10 的值为1,偏移量8的值为0:

BITOP

语法:BITOP operation destkey key [key…]

即:命令 操作 结果目标key key1 key2 …

bitop命令可以对多个位数组的key进行and(按位与)、or(按位或)、xor(按位异或)运算,并将运算结果设置到destkey中:

底层数据结构分析

SDS是redis中的一种数据结构,叫做简单动态字符串(Simple Dynamic String),并且它是一种二进制安全的,在大多数的情况下redis中的字符串都用SDS来存储。

SDS的数据结构:

struct sdshdr {
 #记录buff数组中已使用字节的数量
 #也是SDS所保存字符串的长度
 int len;
 #记录buff数组中未使用字节的数量
 int free;
 #字节数组,字符串就存储在这个数组里
 char buff\[\];
}

数据存储示例:

SDS的优点:

  1. 时间复杂度为O(1)

  2. 杜绝缓冲区溢出

  3. 减少修改字符串长度时候所需的内存重分配次数

  4. 二进制安全的API操作

  5. 兼容部分C字符串函数

关于SDS的详细介绍请大家参阅《redis设计与实现》一文。

redis中的位数组采用的是String字符串数据格式来存储,而字符串对象使用的正是上文说的SDS简单动态字符串数据结构。


大家都知道的是一个字节用的是8个二进制位来存储的,也就是8个0或者1,即一个字节可以存储十进制0~127的数字,也即包含了所有的数字、英文大小写字母以及标点符号。

1Byte=8bit

1KB=1024Byte

1MB=1024KB

1GB=1024MB

位数组在redis存储世界里,每一个字节也是8位,初始都是:

0 0 0 0 0 0 0 0

而位操作就是在对应的offset偏移量上设置0或者1,比如将第3位设置为1,即:

0 0 0 0 1 0 0 0
#对应redis操作即:
setbit key 3 1

在此基础上,如果要在偏移量为13的位置设置1,即:

setbit key 13 1
#对应redis中的存储为:
0 0 1 0 | 0 0 0 0 | 0 0 0 0 | 1 0 0 0

时间复杂度

GETBIT命令时间复杂度O(1)

STEBIT命令时间复杂度O(1)

BITCOUNT命令时间复杂度O(n)

BITOP命令时间复杂度O(n)、O(n2)

我们来看GETBIT以及SETBIT命令的时间复杂度为什么是O(1),当我们执行一个SETBIT key 10086 1的值的时候,reids的计算方式如下:

获取到要写入位数组中的哪个字节:10086÷8=1260,需要写入到位数组的下标1260的字节

获取要写入到这个字节的第几位:10086 mod 8 = 6,需要写入到这个字节的下标为6即第7位上去。

通过这两种计算方式大家可以清晰的看到,位操作的GETBIT和SETBIT都是常量计算,因此它的时间复杂度为O(1)。

而BITCOUNT命令需要对整个位数组的所有元素进行遍历算出值为1的有多少个,当然redis对于大数据了的bit执行bitcount命令会有一整套复杂的优化的算法,但是核心思路还是这个意思,无非是减少部分遍历查询次数。比如以128位为一次遍历,那么他的遍历次数就是所有的位数除以128。

BITTOP命令则是根据不同的操作有不同的执行方式。比如AND操作,则需要查看位值为1的即可。

存储空间计算

根据上面的介绍,相信大家已经知道了基于redis的位数组数据结构存储的数据占用内存大小是怎么计算的了。比如有100亿的数据,那么它需要的字节数组:

1000000000÷8÷1024÷1024≈119.21MB

也就是存储10亿的数据只需要119MB左右的内存空间,这对于现在动辄16G、32G集群版的redis,完全没有问题。

需要注意的是,如果你的数据量不大,那就不要把起始偏移量搞的很大,这样也是占空间的,比如我们只需要存储几百条数据,但是其中的偏移量却很大,这就会造成了很大的内存空间浪费。

应用场景

实际项目开发中有很多业务都适合采用redis的bit来实现。

用户签到场景

每天的日期字符串作为一个key,用户Id作为offset,统计每天用户的签到情况,总的用户签到数

活跃用户数统计

用户日活、月活、留存率等均可以用redis位数组来存储,还是以每天的日期作为key,用户活跃了就写入offset为用户id的位值1。

同理月活也是如此。

用户是否在线以及总在线人数统计

同样是使用一个位数组,用户的id映射偏移量,在线标识为1,下线标识为0。即可实现用户上下线查询和总在线人数的统计

APP内用户的全局消息提示小红点

现在大多数的APP里都有站内信的功能,当有消息的时候,则提示一个小红点,代表用户有新的消息。

<think>好的,我需要介绍Redis支持的数据类型及其使用场景。首先,我得回忆一下Redis的几种基本数据类型。常见的包括字符串(String)、列表(List)、哈希(Hash)、集合(Set)、有序集合(Sorted Set)。还有后来新增的如位图(Bitmap)、HyperLogLog、地理空间索引(GEO)和流(Stream)等。 接下来,每个数据类型的结构和使用场景需要详细说明。比如字符串常用于缓和计数器,列表适合队列,哈希适合存储对象,集合用于去重和交集运算,有序集合适合排行榜。位图适合二值统计,HyperLogLog用于基数估算,GEO处理地理位置,流用于消息队列。 用户可能希望了解每种类型的具体应用例子,以及为什么选择这种类型而不是其他。比如为什么用哈希而不是字符串存储对象?可能需要解释内存效率和字段单独操作的优势。 还要注意是否有最新的数据类型或更新,比如Stream是Redis 5.0引入的,用于更复杂的消息流处理。需要确认信息的准确性,避免过时的内容。 可能需要分点列出每个类型,结构清晰。使用场景部分需要具体,比如有序集合的排行榜,可以举例子游戏得分排名。同时,要提到每种类型的操作命令,比如ZADD用于有序集合,HGETALL用于哈希。 还要考虑性能方面的优缺点,比如集合运算的时间复杂度,大数据量下的表现。比如HyperLogLog在估算基数时的高效性,但在误差率。 需要确保语言简洁明了,避免技术术语过多,但又要准确。比如解释位图时,可以说明其底层是字符串,但通过位操作实现节省空间。 最后,总结一下各种类型的适用场景,帮助用户根据需求选择合适的类型。可能还需要指出常见的使用错误,比如误用列表作为频繁插入删除的队列时的性能问题,或者使用字符串存储大对象导致的内存浪费。</think>Redis支持**9种核心数据类型**,每种类型针对特定场景设计,以下是详细解析: --- ### 一、基础数据类型 1. **String(字符串)** - **数据结构**:二进制安全字符串,最大512MB - **典型操作**:`SET/GET/INCR/DECR` - **使用场景** - 缓热点数据(用户会话、商品信息) - 计数器(文章阅读量:`INCR article:123:views`) - 分布式锁(`SET lock:order NX EX 30`) 2. **List(列表)** - **数据结构**:双向链表,元素可重复 - **典型操作**:`LPUSH/RPOP/LRANGE` - **使用场景** - 消息队列(生产者`LPUSH`,消费者`BRPOP`) - 最新消息排行(`LTRIM`保持固定长度) - 历史记录(用户最近浏览商品) 3. **Hash(哈希表)** - **数据结构**:键值对集合,类似Java的HashMap - **典型操作**:`HSET/HGET/HGETALL` - **使用场景** - 存储对象(用户信息:`HSET user:1001 name "张三" age 25`) - 商品属性聚合(颜色、库、价格等字段独立更新) 4. **Set(集合)** - **数据结构**:无序唯一元素集合 - **典型操作**:`SADD/SMEMBERS/SINTER` - **使用场景** - 标签系统(文章标签:`SADD article:2024:tags tech redis`) - 共同好友(`SINTER user:A:friends user:B:friends`) - 抽奖去重(`SADD lottery:2024 user123`) 5. **ZSet(有序集合)** - **数据结构**:元素关联score值实现自动排序 - **典型操作**:`ZADD/ZRANGE/ZREVRANK` - **使用场景** - 实时排行榜(游戏积分:`ZADD leaderboard 3500 "player1"`) - 延迟队列(score执行时间戳,定时扫描) - 范围检索(价格区间商品:`ZRANGEBYSCORE products 100 500`) --- ### 二、高级扩展类型 6. **Bitmap(位图)** - **本质**:String的位操作封装 - **典型操作**:`SETBIT/GETBIT/BITCOUNT` - **使用场景** - 用户签到(每天1位:`SETBIT sign:202405:user1001 1 1`) - 活跃用户统计(按日存储,`BITOP OR`计算月活跃) 7. **HyperLogLog** - **特性**:12KB内存估算上亿级唯一值,误差率0.81% - **典型操作**:`PFADD/PFCOUNT/PFMERGE` - **使用场景** - UV统计(`PFADD page:uv:20240501 user123`) - 大规模去重计数(无需存储完整元素) 8. **GEO(地理空间)** - **底层**:ZSet扩展,存储经纬度 - **典型操作**:`GEOADD/GEORADIUS` - **使用场景** - 附近的人(`GEORADIUS users:locations 116.40 39.90 5 km`) - 配送范围计算(实时检索周边门店) 9. **Stream** - **数据结构**:持久化消息队列,支持消费者组 - **典型操作**:`XADD/XREADGROUP/XACK` - **使用场景** - 订单事件流(`XADD orders * user_id 1001 product "A1"`) - 日志采集(多消费者并行处理) --- ### 三、选型对比表 | 数据类型 | 时间复杂度 | 典型场景 | 内存优化技巧 | |------------|-------------------|--------------------------|--------------------------| | String | O(1) | 简单键值存储 | 使用数字类型替代字符串 | | Hash | O(1)单字段 | 对象属性聚合 | 控制字段数量,避免过度嵌套 | | ZSet | O(logN) | 带权重的排序场景 | 优先使用整数score值 | | HyperLogLog| O(1) | 海量数据去重计数 | 严格接受误差时使用 | | Stream | O(1)添加消息 | 消息队列与事件溯源 | 设置最大长度防止无限增长 | --- ### 四、最佳实践原则 1. **避免大Key** - String值不超过10KB,集合元素不超过5000个 - 拆分策略:`user:1001:cart`拆分为多个Hash 2. **选择最优结构** - 需要范围查询 → ZSet - 需要精确去重 → Set - 频繁修改部分字段 → Hash 3. **内存效率优化** - 使用`ziplist`编码优化小规模Hash/List(需配置`hash-max-ziplist-entries`) - 优先使用整数而非字符串存储数值 Redis通过多样化的数据结构设计,使其在缓、队列、统计等场景中展现出极高性能,正确选择数据类型可使内存占用减少50%以上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值