Doris Bitmap介绍及企业应用

Bitmap是什么

Bitmap是一种经典的数据结构,用于高效地对大量的二进制数据进行压缩存储和快速查询。

可以把bitmap看作是一个由位(bit)组成的数组,每个位上只有两个可能的状态,0或1。当需要对某个元素进行标记时,可以将对应的位的值设为1,否则为0。因此,bitmap可以非常高效地表示一个元素是否出现过,而不需要使用更为复杂的数据结构。当需要查询某个元素是否出现时,只需要查找该元素对应的二进制位,判断其值是否为1即可

Doris Bitmap

Doris中也有Bitmap数据类型,表示一个整数集合,需要注意的是,它是一个集合set类型,每个元素都是唯一的。集合中只能存放整数,更准确地说是非负整数,字符串可以通过Hash算法转为数字保存,Doris提供了相关的函数bitmap_hash(),如果是Doris 1.2以上的版本,可以用bitmap_hash64(),以减少数据碰撞。

需要注意的是:Doris Bitmap元素最大支持到2^64 - 1,并且只能配合bitmap_union()函数,用在 Aggregate 模型中。

让我们直观地感受一下:

将数字转为bitmap类型

select to_bitmap(1);
+
| to_bitmap(1) |
+
|              |
+

你会发现没有输出,这是因为Bitmap是二进制类型,要转为字符串才能输出

将数字转为bitmap类型

mysql> select bitmap_to_string(to_bitmap(3));
+
| bitmap_to_string(to_bitmap(3)) |
+
| 3                              |
+


将字符串转为bitmap类型

select bitmap_to_string(to_bitmap("1234"));
+
| bitmap_to_string(to_bitmap('1234')) |
+
| 1234                                |
+

计算Bitmap中有多少个元素:

select bitmap_count(bitmap_from_string("2"));
+
| bitmap_count(bitmap_from_string('2')) |
+
|                                     1 |
+

select bitmap_count(bitmap_from_string("1,2,3,4"));
+
| bitmap_count(bitmap_from_string('1,2,3,4')) |
+
|                                           4 |
+

bitmap_count()这个函数可以统计bitmap中 1 的个数,当要做数据去重的时候,比如用户去重,可以把数据写里面,count一下就知道有多少个用户了。

Doris还提供了很多Bitmap的计算函数,可以在官网找到,这里不再赘述。

Flink SQL读写Doris Bitmap

Bitmap类型无法被直接读取,如果使用了Doris Bitmap类型,在工作中难免会遇到需要用Flink读写的情况,下面是我们测试可行的方案。

由于我们Flink版本的原因,没法直接读Doris,这里是用了JDBC连接读取的,直接读也是同理。创建一个Doris的视图表,读的时候读取视图即可。

Doris 视图:

CREATE VIEW db_ads.use_tag_view (user_id,tags)
AS
SELECT user_id,bitmap_to_string(tag)
from dor_user_tags_rt;

Flink JDBC表:

CREATE TABLE rtime_db_dim.dim_dor_user_tags_test (
      user_id STRING,
      tags string
) WITH (
    'connector' = 'jdbc',
    'url' = 'jdbc:mysql://xxxx/db_ads?serverTimezone=Asia/Shanghai&autoReconnect=true',
    'table-name' = 'use_tag_view',
    'username' = 'xxx',
    'password' = 'xxx'
);

Flink SQL好像是没有Bitmap类型的,因此也没法直接往Doris中写Bitmap数据,我们可以给Doris传String类型的数据,让Doris自己完成转换

create table rtime_db_ads.dor_user_tags (
    user_id string,
    tag string
) with (
    'connector' = 'doris',
    'fenodes' = 'xxx',
    'table.identifier' = 'db_ads.dor_user_tags_bm_rt',
    'username' = 'xxx',
    'password' = 'xxx',
    'sink.properties.format' = 'json',
    'sink.properties.strip_outer_array' = 'true',
    'sink.properties.fuzzy_parse' = 'true',
    'sink.properties.columns' = 'user_id,tag,tag=bitmap_from_string(tag)',
    'sink.batch.interval' = '30s',
    'sink.batch.size' = '100000'
);

sink.properties.columns表示要写的字段与Doris表字段的映射关系,在这里可以做计算,用bitmap_from_string()方法将字符串转为Bitmap类型。注意,这里写入的是Doris的Bitmap原表,而不是视图表。

应用

我理解,目前官方对于bitmap的设计还是在于利用bitmap本身高效的存储计算去做一些集合之间的计算,官网上也有很多应用案例。很重要的一个点是bitmap目前只能往里面添加数据,而无法删除。但实际业务中难免会有需要删除的场景存在,这时候你会发现很难利用bitmap实现。

场景:根据标签筛选用户,标签是int类型,一个用户有多个标签,一个标签有多个用户,两者是多对多的关系,用户身上的标签可以增加、删除

这时候可以这么设计Doris表:

CREATE TABLE db_ads.dor_user_tags_rt (
    `user_id` varchar(64) NOT NULL COMMENT "用户id",
    `tags` bitmap bitmap_union COMMENT "标签"
) ENGINE = OLAP 
AGGREGATE KEY(user_id`) 
COMMENT "用户标签" 
DISTRIBUTED BY HASH(`user_id`) 
BUCKETS 6 
PROPERTIES (
    "replication_allocation" = "tag.location.default: 3",
    "in_memory" = "false",
    "storage_format" = "V2"
);

一个用户一行数据,查询用户下的所有标签:

select user_id,bitmap_to_string(tags)
from db_ads.dor_user_tags_rt
where user_id='xxx';

查询包含某些标签的用户:

select user_id
from db_ads.dor_user_tags_rt
where bitmap_and_count(tags,bitmap_from_string("64,32"))>0;

这个方案查询速度很快,很完美。但唯一的缺点是,bitmap是union聚合的,这意味着每一条写入的数据都会按照key聚合起来,相同的key只能往里面增加,无法删除。然而用户标签是需要可删除的,因此只能舍弃。

一开始想了两天之后,我们是放弃用bitmap了的,因为限制就摆在那里,没法删除确实没法删除,加软删除标志也不行,逻辑上有问题,而且Aggregate模型也不支持update数据。

不过后来一个新的想法又让我们看到了希望的曙光,新方案是这样的,在建表的时候加上版本标志:

CREATE TABLE db_ads.dor_user_tags_rt (
    `user_id` varchar(64) NOT NULL COMMENT "用户id",
    `version` int NOT NULL COMMENT "标签版本",
    `tags` bitmap BITMAP_UNION NULL COMMENT "用户标签"
) ENGINE = OLAP
AGGREGATE KEY(`user_id`, `version`) 
COMMENT "用户标签" 
DISTRIBUTED BY HASH(`user_id`) 
BUCKETS 6 
PROPERTIES (
    "replication_allocation" = "tag.location.default: 3",
    "in_memory" = "false",
    "colocate_with" = "app_group",
    "storage_format" = "V2"
);

写入的时候把版本+1,这样子每个用户会有多条数据,如下:

用户   版本   标签
u1    1      123
u1    2      23
u2    1      23
u2    2      123
...

查询的时候只需要找到最大版本的数据即可,但这一点也挺难的,因为查找最大版本的过程就很耗时,我们尝试了两个写法,一个是自己Join自己,一个是用开窗函数(如下),但两者都很慢,最少需要6秒,这就还不如不用bitmap了。

select * from(
select user_id,
  bitmap_to_string(tags),
  rank() over(
    partition by user_id
    order by version desc
  ) n
from (
    select *
    from db_ads.dor_user_tags_rt
    where bitmap_and_count(tags, bitmap_from_string("51527,48961,54812")) > 0
  ) s)fs where n=1;

思考再三,我们引入了一张维度表:

CREATE TABLE db_ads.dor_user_tags_version_rt (
    `user_id` varchar(64) NOT NULL COMMENT "用户id",
    `version` int NOT NULL COMMENT "标签版本"
) ENGINE = OLAP 
UNIQUE KEY(`user_id`, `version`) 
COMMENT "用户标签版本" 
DISTRIBUTED BY HASH(`user_id`) 
BUCKETS 6 
PROPERTIES (
    "replication_allocation" = "tag.location.default: 3",
    "in_memory" = "false",
    "colocate_with" = "app_group",
    "storage_format" = "V2"
);

维度表使用UNIQUE模型,之所以维度表不用Aggregate模型,是考虑到Aggregate的话还要对version去max()聚合,性能应该比不上unique模型。

写入的时候往用户标签表写的同时也把标签版本写到这张表来,查询时两张表做Inner Join即可:

with t_tags as (
    select user_id,
        version,
        bitmap_to_string(tags)
    from db_ads.dor_user_tags_rt
    where bitmap_and_count(tags, bitmap_from_string("51527,48961,54812")),
        t_version as (
            select user_id,
                version
            from db_ads.dor_user_tags_version_rt
        )
    select user_id
    from t_tags
        join t_version on t_tags.user_id = t_version.user_id
        and t_tags.version = t_version.version;

这两张表的分桶列和分桶数是相同的,这样可以把它们放在同一个Colocation Group,得益于Doris向量化计算和Colocation Join的方式,上面这个SQL执行速度很快,能够满足业务需求。

以上就是Doris Bitmap的基本介绍以及在实际业务场景中的应用,如果你有其他想法,或者想了解更多东西,到公众号“大数据小屋”后台留言、一起交流,也欢迎关注~~
在这里插入图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值