如何基于 BitMap 来作为底层的数据模型

Apache Doris BitMap去重实战:高基数场景下替代COUNT(DISTINCT)的性能优化方案 在数据仓库和实时分析领域,精确统计不重复元素数量是一个基础且关键的需求。传统COUNT(DISTINCT)方法在处理海量高基数数据时,常因内存消耗巨大和计算效率低下成为性能瓶颈。其原理涉及哈希表维护与全局聚合,易引发数据倾斜与查询失败。为应对此挑战,业界引入了基于位运算的Bitmap技术,它通过将元素映射到位图中的特定位来实现精确去重,具有常数级内存增长和极高计算效率的技术价值。该技术尤其适用于用户ID、设备ID等高基数去重场景,能显著提升查询性能。本文聚焦于Apache Doris数据库中的Bitmap函 阅读详情

作者:陈凯
GrowingIO 数据开发工程师,主要负责 SaaS 和 OP 产品数据平台的开发和设计,目前专攻于微服务、数仓建设方向。

GrowingIO 每天需要处理近千亿的用户行为数据,平台的「事件分析」模块是使用比较频繁的功能,简单且强大。在事件分析中,客户可以很灵活地使用多种维度组合去查看某个指标,并且查询的速度也十分可观。

本文抽取 GrowingIO 在事件分析中的通用数据模型,揭晓该功能背后的存储模型和实现原理。

在用户行为的数据分析中,无论是无埋点,还是埋点,对于某一条行为数据的表达形式往往是:「某人」于「某个时间」在「某个维度」下做了「某个动作」「多少次」。

所以在数据统计中,这种表达形式可以拆解成「指标量」和「维度」,指标量可以是用户量、页面浏览量、某个埋点的次数等,维度可以是时间、城市、浏览器、用户属性等。

在海量数据的背景下,如何比较高效地完成指标+维度的计算,一直是大数据分析领域比较热门的话题,下面将讲述在 GrowingIO ,我们是如何高效解决的。

1. 从一个数据需求说起📈

假设给定如下一组用户行为的原始数据:数据含义: 表示某个用户的某次访问记录。(这里仅列举了地区和设备维度,当然还会存在浏览器、平台、版本等维度,这里不一一列举了。)

1.1 使用 SQL 分析统计

🤔 现在业务想计算「过去7天」在「地区」维度下,「设备: Mac」的人数是多少?So Easy,一个 SQL 搞定在这里插入图片描述

使用 GrowingIO 平台的分析工具可以表示如下:
在这里插入图片描述

但是通过 SQL 这种现查的方式,随着数据量的越来越大,几十亿或上百亿的时候,对计算所需要的资源和响应时间也会线性地增长,此时客户在使用平台工具最直观的感受就是“菊花”转转转,图表一直加载不出来。

1.2 如何使查询更加高效

1.2.1 堆机器,加资源

最直接粗暴的方式,就是增加更多的计算资源,或者对查询的结果进行缓存、预热。但是对于 SaaS 产品来讲,在查询并发比较高的时候,再多的计算资源也会因为查询排队而遇到性能瓶颈。

1.2.2 数据分层

😼 在数仓的分层架构中,对于经常使用的查询结果,我们可以通过离线计算的方式生成了一个结果表「过去7天-地区-设备-指标表」,示例如下:在这里插入图片描述

这样在特性的查询场景中,只需要查询结果表就行,很大的减少了计算量,响应时间也短了不少。

😱 但是业务那边的需求往往是千变万化的,然后一堆统计的需求砸到了你的脑袋上

「过去 30 天」的「总用户的量」和「总访问量」是多少?
「昨天」「设备: Windows」的「用户量」是多少?
「获取 30 天」按「平台」拆分的「用户量」是多少?
「11 月- 11 日」来了哪些用户?请导出这个用户列表
其他更多漏斗、留存、画像等数不尽的需求让人头晕目眩。
…

😫 你看了看生成结果表,发现并不能完全解决这些问题,你觉得需要生成更多的结果表来满足更多的需求。然而到最后你发现有些表居然仅仅只使用了一次,数仓里面堆了一堆垃圾。

1.2.3 数据预聚合

和数仓分层的理念类似,对数据进行预计算、预聚合,使用空间换时间的思想加快计算。这也是目前一些主流开源框架的解决思路,比如 SparkSQL 的物化视图、Kylin的 Cube、Druid 的 Segment 、Carbondata 的 MV 等。

下面使用一张图展示主要区别:
在这里插入图片描述

基于我们所追求的方式,我们首先需要寻找一种高效并且灵活的存储模型。

1.3 优化存储模型

基于上节中数据预聚合的思路,从预聚合的结果中,我们不难发现,其中有几个没有摆脱的点:
多个维度依然是横向存储在一起
维度和指标绑定在一条记录中
因为存在这种局限性,刚好限制了我们灵活进行「维度+指标」的任意组装和扩展

🤔 如何才能更好的让维度和指标随心所欲地组合呢?我们在预聚合结果的基础之上做了一些改进:
将每个维度纵向存储
将维度和指标分开存储

2. 基于 BitMap 的存储模型 💻

2.1 纵向存储维度(人数)

依然以开篇的那组数据为例,此时将维度进行纵向存储:在这里插入图片描述

此时想取「地区: 北京」和「设备: Mac」的「用户量」

在这里插入图片描述

OK!这样可以很灵活的解决各种维度组合起来的问题了,而且连用户的群体也能直接获取。

😇 但是从表格中发现,用户存储「用户集合」的数据结构还没有解决。那么既能以类似数组的方式存储整数值,还能使用交集(and)操作,还需要达到更好的数据压缩和计算。

此时你应该想到了 BitMap 这种数据结构

至于为何选用 BitMap 的数据结构,以及 BitMap 的功能和基本使用,这里不再探讨。可以参考 java 的实现 BitSet 以及优化的库 RoaringBitmap。

2.2 存储指标量(次数)

为了解决存储指标次数的存储问题,你需要用一个Map 的结构来存储「总的次数」: Map<Int, BitMap> (其中key为次数,value为符合访问次数的人)

在这里插入图片描述
访问量表示: 总共访问「1次」有哪些人,访问「2次」的有哪些人等等。

此时计算「地区: 北京」和「设备: Mac」的「访问量」

2.3 使用更优雅的方式存储次数

在 Map<Int, BitMap> 这个结构中,key 存储的是 10 进制的数字。这就会导致 Map 的 key 变得特别特别多,所以需要有一种方式来优化一下结构。

方式就是用将 10 进制转化为 2 进制的方式去存储次数,此时 Map 的 key 存储是 二进制为 1 的位置:

比如 2 的二进制是: 「10」,从右向左分别表示(下标i从0开始)「第0位是0」,「第1位是1」。所以将key为1的 bitmap 中存储这个人。

比如5的二进制是: 「101」,从右向左分别表示「第0位是1」,「第1位是0」,「第2位是1」。所以将 key 为 0 和 2 的 bitmap 中存储这个人。

然后将上节 2.2 中结果表示如下:

在这里插入图片描述
此时计算「地区: 北京」和「设备: Mac」的「访问量」

在这里插入图片描述

3. 多维度交叉的问题 ⚔️

理想是美好的,但是现实很残酷。在 2.1 小节的例子中,每个用户的维度组合只有一种,但是现实中往往一个用户行为可能会存在多种维度组合的情况。

那么什么是维度组合: 一条数据中唯一的所有维度值,称为一个组合。

PS: 如果你的系统中某个 ID 的维度组合只有一种。比如某个订单,一旦生成了,他的价格,商品,物流等信息基本都是固定的。那么之前的模型基本都能满足大多场景了。

3.1 面临的问题

🤔 那么会导致什么问题呢?此时回到起点,又来了一批用户行为的数据如下:

在这里插入图片描述
此时多了一个「用户1」在「杭州」使用了「Windows」。如果按照之前的模型存储如下:在这里插入图片描述

此时计算「地区: 北京」的用户:直接返回 [ 1 , 2 , 3 ],问题不大

此时计算「地区: 北京」和「设备: Windows」的用户
在这里插入图片描述
❌ 你会发现,得出的结果是错的,应该只有「用户 3 」满足才对。

3.2 使用维度组合编号的方式解决

其实问题出在将维度分开进行存储的时候,丢失了「维度组合关系」这个重要的衡量条件。「用户 1 」虽然在「北京」待过,也使用过「Windows」,但是他却没有同时满足这个条件,这就是问题所在。

所以需要一种方式来存储「维度组合关系」这一重要信息。

将每个人维度组合进行顺序编号,得到如下结果:
在这里插入图片描述

注意:编号是对应到每个人的,相同的维度组合,编号是一样的。

此时对应的存储结构也发生了变化:Map<Short, BitMap>( key 代表编号,value 代表人的集合在这里插入图片描述

此时我们再来计算「地区: 北京」和 「设备: Windows」维度的用户在这里插入图片描述

最后得到的结果就是「用户3」,算出来的数据就变准确了。

3.3 多维度情况下计算次数

其实稍微想一下就是两层的 Map 结构:Map<Int, Map<Short, BitMap>>,比如刚刚的那组数据表示如下:在这里插入图片描述

比如「用户1」这个用户,在「编号0」发生的 2 次,在「编号1」发生了 1 次
在这里插入图片描述
此时我们来计算「地区: 北京」和 「设备: Mac」维度的访问量

在这里插入图片描述

4. 简单的性能对比

环境准备:SparkSQL(local[16], 内存4G), BitMap 单线程计算(内存4G)

场景:简单的 2 ~ 3 个维度组合求人数、次数,按照值的降序取 Top 1000在这里插入图片描述

x轴含义: 数据量/用户量。
y轴含义: 计算时间, 单位毫秒。

可以看到随着数据量的不断递增,SparkSQL 的计算时间也在不断递增,但是 BitMap 的计算时间却相对比较稳定。

5. 总结

BitMap 是一个兼并计算和存储优】势的数据结构,在存储上百万甚至上千万的 ID 时,也能得到很好的计算效果。

并且当你使用 BitMap 存储的时候,就已经天然支持很多的业务场景,比如分群计算、标签计算、漏斗分析、留存分析、用户触达等,因为无需再重新计算人群。

本篇主要揭晓我们是如何基于 BitMap 来作为底层的数据模型,当然在实际应用过程中还有很多的挑战,由于篇幅原因,这里就不展开讲述了。

以下列出一些我们后续的工作内容和攻克方向:
bitmap 是以 int 值进行存储的,但是在实际生产中,你的 ID 数据可能是类似 UUID 的这种字符串,那么需要解决 string 转唯一 int 的问题。
如何使 bitmap 在分布式环境下达到更好的计算效果
如何解决高基维度所带来的挑战
如何在实时、图表分析、分群画像以及更多的业务场景中进行使用
如何设计一个类 SQL 语言来兼容这种数据模型
…

关于GrowingIO

GrowingIO 是基于用户行为数据的增长平台,国内领先的数据运营解决方案供应商。为产品、运营、市场、数据团队及管理者提供客户数据平台、获客分析、产品分析、智能运营等产品和咨询服务,帮助企业在数据化升级的路上,提升数据驱动能力,实现更好的增长。

如果对我们的产品有兴趣,欢迎点击此处,获取15天免费试用!

【Redis从入门到精通】第60篇:二进制位数组——Redis的BitMap有多好用 Redis的BitMap是一种高效的二进制位数组存储方式,它能将1亿用户的活跃状态压缩到仅12.5MB,一年的签到记录仅需46字节。BitMap本质是String类型的二进制视图,通过SETBIT/GETBIT操作单个位,BITCOUNT统计1的位数,BITPOS查找特定位,BITOP支持AND/OR/XOR/NOT位运算。其稀疏存储特性自动填充未设置的位为0,但需避免大offset导致内存浪费。典型应用场景包括用户签到、活跃统计和布隆过滤器,相比Set和HyperLogLog在特定场景下具有显著的空间和计 阅读详情

相关推荐

doris--Aggregate、UNIQUE、Duplicate、索引

doris--Aggregate、UNIQUE、Duplicate、索引

Y521316的博客 2434

【Starrocks + Hive 】BitMap + 物化视图 实战记录

一文搞定 starrocks bitmap+物化视图提速

weixin_39750695的博客 1014

Doris-07-索引的详细介绍(前缀索引、Ordinal索引、Zone Map索引、Bitmap索引、Bloom Filter索引、NGram BloomFilter索引、倒排索引)

Apache Doris 存储引擎采用类似LSM树的结构提供快速的数据写入支持。进行数据导入时,数据会先写入 Tablet 对应的 MemTable 中,当 MemTable 写满之后,会将 MemTable 里的数据刷写(Flush)到磁盘,生成一个个不超过 256MB 的不可变的 Segment 文件。MemTable 采用SkipList的数据结构,将数据暂时保存在内存中,SkipList 会按照 Key 对数据行进行排序,因此,刷写到磁盘上的 Segment 文件也是按 Key 排序的。

迷雾总会解 9408

growingio基本使用(总结)

  growingio基本使用 背景:方便分析图片报告的哪些报告受到用户喜欢,来调整报告的侧重点  设计师引流网站:http://huaban.com/  (国内,花瓣)                                  http://www.pinterest.com/ (国外) 一,准备工作: 注册一个GrowingIO账号 拥有自己的网站或APP 1,选择...

gaokcl的博客 3万+

SQL面试题——留存分析之使用bitmap 计算留存

因为使用工具的限制,所以我们实现方式也会有所不同,之前我们的实现方式比较通用,几乎所有的数据库软件都支持,今天我们看一下如何使用bitmap 来实现,首先这就需要你的数据库支持bitmap,这里我是用的是StarRocks,其实现在很多都支持了。其实这里看起来和我们常规的统计差不多,但是你要注意到visit_users 是个bitmap,也就是我们在没有改变统计方式的前提下,就实现了使用bitmap,其实这里我们也可以使用 bitmap_count函数。统计每个页面的 UV。

444

基于单片机的继电器参数测试系统设计

由于原有测试系统在参数设置上过于单一,在对继电器测试过程中需要多次进行器件连接,导致对其测试准确度下降,会造成继电器的二次损伤,基于单片机研究继电器参数测试系统的设计方法。在硬件设计上,构建二级模式集散控制框架,利用单片机进行数据采集和处理,满足实时继电器参数数据的同步控制;设计双极性反向比例输出电路,分别利用电极电阻对继电器两侧电压进行放大,实现继电器实时电压的检测。实验结果:以两组类型断电器为测试对象,分别连接示波器进行真实数据采集,在本文系统的应用下所得参数测试结果,与真实的数据相一致,具有实际应用效

weixin_45905610的博客 1439

Growing时代 | ​GrowingIO的数据驱动增长进行时

“对当前的企业来说,数字化转型中更重要的部分不是ERP的转型,也不是内部流程优化转型,而是针对消费者/用户数据的升级和转型。”在GrowingIO创始人兼CEO张溪梦看来,这不难理解,因为...

中国软件网 405

银河麒麟V10SP1实战:5步搞定AD域登录(附Samba+Winbind配置详解)

本文提供银河麒麟V10 SP1操作系统无缝集成Windows AD域的实战指南。文章深入解析了Samba、Winbind与Kerberos三大核心组件的协作原理,并详细阐述了从环境准备、软件包安装、配置文件定制到执行加域及排错的全流程。特别针对2403等新版本提供了关键配置差异与避坑要点,旨在帮助企业运维人员实现高效、稳定的域环境登录与管理。

weixin_29269441的博客 295

redis中bitmap位图,底层结构剖析,清晰明了,布隆过滤器原理

bitmap的底层数据结构就是0/1bit 的二进制数值,最大长度512Mb = 512 * 1024 * 1024 * 8 = 2^32 。使用场景举例: 用户登录活跃值记录 打卡签到记录,key为用户唯一键,offset是由日期换算得出的数值,1为已打卡,0为未打卡 热点活动记录,key为热点时间,offset是每个用户唯一键例如(id=12385123),如果该用户参与了此次活动,则在12385123的bit位设为1 … 1.位图常用命令 SETBIT命令 用来设置或者清除某一位上的值,其返

zzztimes的博客 4492

深入解析AutoConfigURL劫持:从注册表权限到一键修复

本文深入解析了AutoConfigURL劫持的原理,揭示其通过篡改注册表权限锁定浏览器设置的机制。文章提供了从手动修复注册表权限到使用一键修复工具的详细解决方案,并指导用户进行DNS检查与病毒查杀,以彻底根除此类顽固劫持问题。

weixin_29229989的博客 769

网络攻防技术——MD5碰撞试验

一、题目 本次实验主要是加深大家对MD5碰撞及其原理的理解,使用SEED实验环境中的工具及编程语言,完成以下任务: a)使用md5collgen生成两个MD5值相同的文件,并利用bless十六进制编辑器查看输出的两个文件,描述你观察到的情况; b)参考Lab3_task2.c的代码,生成两个MD5值相同但输出不同的两个可执行文件。 c)参考Lab3_task3.c的代码,生成两个MD5值相同但代码行为不相同的可执行文件。 d)回答问题:通过上面的实验,请解释为什么可以做到不同行为的两个可执行文件具有相同的M

学习记录 3622

Doris最全使用手册

doris常用的理论基础

数据挖掘+大数据研发+算法学习 1万+

大智慧L2实时api接口的逐笔委托功能执行代码分享

用过l2行情接口的投资者应该都了解大部分的接口都是有逐笔委托功能的,那么大智慧L2实时api接口也有着很强大的逐笔委托功能,投资者可以通过这个逐笔委托功能可以看到一个股票的每天出现的全部委托指令,那么在这个逐笔委托功能里包含了委托价格、委托时间、委托量和委托方向四个要素!那么接下来小编就分享一下大智慧L2实时api接口的逐笔委托功能执行代码!那么在这里提个醒:投资者可以在大智慧L2实时api接口进行设置过滤,在逐笔委托列表里,就仅仅只会显示超过设置委托量或委托额的委托单,而不再显示小单。

Qq_2037696191的博客 3733

Redis数据类型及底层实现

Redis数据类型及底层实现

野心与梦 1万+

单细胞专题(1) | 单细胞转录组研究样本准备指南(上)

单细胞RNA测序(scRNA-seq)是目前最前沿,能进行大规模实验的转录组研究技术,为理解细胞组成和动态生物学过程提供了很多新的见解。单细胞研究已经为我们理解发育过程,老化和疾病(如癌症)等提供了大量数据。在这里,小编主要就前期的样本准备与保存需注意问题给大家进行讲解,以备大家在不同的研究场景和研究背景下,走好单细胞转录组学研究的第一步。

bioyigene的博客 4232

ABB变频器使用PROFINET IO通信模块时的激活方法

ABB变频器使用PROFINET IO通信模块时的激活方法

Robot_PLC_HMI_Automation 2736

【Doris基础】Apache Doris数据模型全面解析:选择最适合你的数据组织方式

Duplicate模型,顾名思义,是允许数据重复的模型了,在这种模型下,Doris不会对导入的数据进行任何形式的去重或聚合处理,而是完整保留原始数据的所有细节。Aggregate模型是Doris中最具特色的数据模型,它通过在数据加载时进行预聚合,显著提高聚合查询的性能,这种模型适合有大量聚合分析需求的场景。Unique模型确保主键唯一性,相同主键的数据会自动进行覆盖(类似于UPSERT操作),这种模型适合需要保证数据唯一性且有更新需求的场景。:在复杂系统中,可以同时使用多种模型。:选择Unique模型。

IT成长日记的博客 1259

用 ADT 的 RAP Generator 把 RAP 栈一键搭起来:从表到可预览的 OData V4 应用

摘要 SAP RAP Generator 工具能显著提升开发效率,通过一键生成从数据库表到完整 OData UI Service 的全套对象(包括 CDS、Behavior、Service Definition 等)。该工具特别适用于原型验证和标准化应用开发,可节省 60% 以上的基础编码时间。使用时需注意: 表设计需包含关键管理字段(如 last_changed 时间戳) 向导配置决定分层架构和命名规范 生成结果包含可立即预览的 Fiori Elements 界面 后续可专注于业务逻辑增强(校验、权限等)

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1281

android layerlist bitmap设置宽高

Android 中LayerList Bitmap设置宽高的实用指南 在Android开发中,LayerList是一种非常实用的Drawable类型,它能够将多个Drawable层叠在一起,从而创建出复杂的视觉效果。在使用LayerList时,我们可能需要对位图(Bitmap)的显示宽高进行控制。本文将详细讲解如何设置La...

weixin_36995880的博客 679

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

别再乱选数据模型了!StarRocks四种模型实战对比,教你根据业务场景快速决策

本文深度解析StarRocks的四种数据模型(Duplicate、Aggregate、Unique和Primary Key),提供从理论到实战的选型指南。通过真实业务场景对比,帮助开发者根据数据更新模式、查询访问模式和数据一致性要求快速决策,避免常见误用陷阱,优化分区和分桶策略,提升数据库性能。

weixin_30772105的博客 610

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Doris 知识树

Doris 知识树

weixin_41687172的博客 274

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Apache Druid 底层存储设计(列存储与全文检索)

导读:首先你将通过这篇文章了解到 Apache Druid 底层的数据存储方式。其次将知道为什么 Apache Druid 兼具数据仓库,全文检索和时间序列的特点。最后将学习到一种优雅的...

码哥字节 411

【Redis入门系列】:Stream、GEO、HyperLogLog、Bitmap 与 BITFIELD:从消息流、地理位置到概率统计与位级存储

文档主要介绍Redis除了五种基本类型外的其他能力:Stream、GEO、HyperLogLog、Bitmap、BITFIELD,并重点以Stream为例讲解其作为消息队列的特性,包括逻辑结构、XADD命令等,强调建立心智模型而非死记命令。摘要需简洁概括核心要点。</think>Redis除五种基本类型外,还提供Stream、GEO、HyperLogLog、Bitmap、BITFIELD等能力。Stream作为消息队列,通过XADD追加

努力努力再努力wz的博客 370

Apache Druid 底层的数据存储

导读:首先你将通过这篇文章了解到 Apache Druid 底层的数据存储方式。其次将知道为什么 Apache Druid 兼具数据仓库,全文检索和时间序列的特点。最后将学习到一种优雅的底层数据文件结构。 今日格言:优秀的软件,从模仿开始的原创。 了解过 Apache Druid 或之前看过本系列前期文章的同学应该都知道 Druid 兼具数据仓库,全文检索和时间序列的能力。那么为什么其可以...

码哥字节 1903

03-Redis 快的秘密藏在这里:数据类型与底层数据结构

本文从 Redis 五大数据类型出发,讲清 String、List、Hash、Set、ZSet 背后的底层编码与数据结构,包括 SDS、listpack、quicklist、hashtable、intset、skiplist 等核心实现,并结合编码切换阈值、电商业务场景和常见避坑,帮助读者真正理解 Redis 为什么快、怎么选型、如何避免大 key 和内存浪费。

m0_66500660的博客 498

Redis十大数据类型深度解析:从核心原理到实战应用

在分布式系统和高性能服务架构中,内存数据库是提升应用响应速度和处理能力的关键组件。其核心原理在于利用内存的高速读写特性,通过高效的数据结构组织数据,从而实现对海量请求的快速响应。Redis作为内存数据库的典型代表,其技术价值不仅在于极致的性能,更在于提供了丰富的数据结构模型,能够直接映射复杂的业务场景,如排行榜、社交关系、实时统计等。这些数据结构包括基础的字符串、列表、哈希、集合、有序集合,以及进阶的位图、基数统计、地理空间等类型,每种结构都针对特定场景进行了深度优化。例如,**Sorted Set(有序集

weixin_34293141的博客 404
上一篇: 3 大步骤 + 2 大策略,提升电商首页营收贡献效率
下一篇: 百年巨头 IBM 如何用「增长黑客」重塑自我,超 10 倍增长!
GrowingIO_
博客等级 码龄8年 56粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值