PySpark预计算ClickHouse Bitmap实践

news2024/11/24 17:40:15

1. 背景

ClickHouse全称是Click Stream,Data WareHouse,是一款高性能的OLAP数据库,既使用了ROLAP模型,又拥有着比肩MOLAP的性能。我们可以用ClickHouse用来做分析平台快速出数。其中的bitmap结构方便我们对人群进行交并。Bitmap位图的每一位表示一个数据(比如说一个用户)。假设5亿用户(一个用户4个字节),则需要5 * 10^8 * 4byte = 3GB,而压缩到32位的bitmap里只需要2^32bit = 512MB。压缩空间的同时,利用位运算还能快速处理人群集合。

常见的bitmap使用方法是将数据按维度打成窄表<user_id, attr_name, attr_value>,这里user_id已经mapping成int类型,然后明细存入ClickHouse中,并对attr_name or attr_value进行group by聚合,最后对id进行groupBitmapState (groupBitmap | ClickHouse Docs) 压缩成bitmap二进制格式存储到数据库里。导入方法可以是使用定时批量进行显式导入,或者使用ClickHouse物化视图的能力隐式导入。

总所周知,ClickHouse是一个MPP式的分布式数据库,引擎既负责计算又负责存储,因此ClickHouse的节点一般选择性能不错(也就是价格不便宜)的计算机。而且由于ClickHouse的任务和节点强绑定,因此节点数也不宜过多。在使用Replicated*引擎的时候(属于主从复制,利用ZooKeeper选出自己的主副本),节点过多会影响分布式性能。ClickHouse的Distributed表原则是谁执行谁负责,每个节点都负责把各个分片(shard)的数据发到其他分片上,节点过多则会增加传输故障的风险。简而言之,直接压缩bitmap对ClickHouse集群压力很大,而且消耗很高。

因此,我们希望可以预先计算bitmap的方式,用于分摊ClickHouse集群的压力。

2. 方案和实现

相对于MPP架构,还有另一种shared nothing架构,那就是hadoop生态的批处理架构。批处理架构和MPP架构的一大区别在于任务和节点分离。由于批处理架构有主从结构的存在,主节点主要做调配工作就可以了,如果有一台从节点变慢了那就给它分配更少的task。因此批处理架构可以使用大量廉价机器。如果我们可以利用hadoop生态事先对数据进行预处理,最后输出到ClickHouse,我们就即可以减少ClickHouse集群的负担而且可以加大结点数增加速度。

所以,这里要做的,就是将已经存入tdw的hive明细表,转成ClickHouse的bitmap类型。

本人更熟悉Python生态圈,因此选用了PySpark来完成这个任务(对于scala/java生态我也走通并复现了文章SparkSQL & ClickHouse RoaringBitmap使用实践_spark clickhouse bitmap-CSDN博客的细节,有需要的可以私下交流,这里也不铺开了)。我们解决问题的方式,是从简单到复杂到最后实现。

2.1 PySpark udf聚合bitmap

第一步要解决的问题,是如何将多个用户压缩到bitmap里,这里我们先假定已经有了一个bitmap类,add user进去就可以了。我们先按照传统做法,对user进行id映射,将string类型的id转化成int,然后将维度表打成窄表,类似于如下建表语句:

并读取hive表,做一些需要的过滤和处理操作,载入到spark dataframe里。

PySpark可以使用udf很方便地去实现自定义操作。它有两种udf,自带的udf和pandas_udf,但是按照本人的经验,pandas_udf虽然更方便也更快(默认提供了group by),但是不同的spark版本会有一些意想不到的bug,比如0之前会有偶发性的rdd位置错乱。所以在不确定公司内部具体spark情况,稳妥起见使用自带udf(pyspark.sql.functions.udf — PySpark 3.1.1 documentation, Python Aggregate UDFs in PySpark - Dan Vatterott )。

对于udf,我们可以使用collect_list,去实现group by后传入udf输出单列的功能。代码类似于如下:

udf函数的输入是一个list,返回了一个单值作为一个新的udf列。到这里,我们就完成了第一个部分。

2.2 bitmap二进制序列化

接下来第二部分是最麻烦的部分,就是如何实现上文的bitmap。这里,这个bitmap的特性是序列化成二进制后可以被ClickHouse读取使用。

我们需要知道ClickHouse怎么使用bitmap的。这里对照着ClickHouse的源码(AggregateFunctionGroupBitmapData.h)和ClickHouse遇见RoaringBitmap-CSDN博客这篇文章的解读,我们可以一览ClickHouse bitmap的设计和实现。

ClickHouse并不是简单的压缩。在文档里也可以看到(https://clickhouse.com/docs/en/sql-reference/functions/bitmap-functions/):对于位图基数cardinality小于等于32的时候,使用了Set对象(源码里叫做SmallSet),对于大于32的时候,使用了RoaringBitmap对象。最后的输出,是将bitmap序列化成一个紧密排列的二进制对象。

这是写入方法:https://github.com/ClickHouse/ClickHouse/blob/master/src/AggregateFunctions/AggregateFunctionGroupBitmapData.h#L122-L138

我们先来看看小于等于32的情况,使用了smallset(ClickHouse使用内部实现的SmallSet<T, small_set_size> https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/HyperLogLogWithSmallSetOptimization.h#L28),它在数据量小的时候会更快。它分为3部分,1 byte占位符(这里是0,表示小于等于32),1 byte的基数(2^8次方,但这里基数最大是2^5=32),和byte array(这里注意,是一个数字占了4 byte)。PySpark代码如下:

这里对byte array用一个for循环将bitmap输入进去,最后用b""将整个二进制对象的数组拼接成一个完全的二进制对象。

接下来看大于32的情况,它也是3部分,1 byte占位符(这里是1,表示大于32),bitmap数的Valint表示,最后一部分是bitmap的序列化直接做字节。

这里补充一下,无论是Smallset还是RoaringBitmap,ClickHouse的bitmap都是使用了小端序,如果没有指定,可能会产生一些奇怪的结果。python的大小端序语法可参见通过python理解大端小端_小端是高位补零-CSDN博客。

第二部分使用Varint用来表示第三部分RoaringBitmap最后序列化后的字节数。在ClickHouse的源码可见(ClickHouse/src/IO/VarInt.h at master · ClickHouse/ClickHouse · GitHub)。

Varint的长度计算在ClickHouse/src/IO/VarInt.h at master · ClickHouse/ClickHouse · GitHub。不过python的bytes不需要,这里可以跳过,对java/scala的ByteBuffer就需要(需要预先预留字节位)。Varint的计算逻辑见Carl's Blog和ClickHouse源码阅读(0000 1101) —— ClickHouse是如何读写数据的(readVarUInt和writeVarUInt方法解析)_clickhouse writevaruint-CSDN博客 。讲得很详细,这里不赘述。

第三部分是整个ClickHouse bitmap的核心,这里使用了RoaringBitmap(GitHub - RoaringBitmap/RoaringBitmap: A better compressed bitset in Java: used by Apache Spark, Netflix Atlas, Apache Pinot, Tablesaw, and many others)。位图bitmap的问题是数据越稀疏,空间就越浪费。有很多算法尝试解决这个问题,压缩位图,比如WAH、EWAH、Concise等,RoaringBitmap是其中的佼佼者。它的主要思路是对32位无符号整数高低分桶。具体可见(高效压缩位图RoaringBitmap的原理与应用 - 简书 )

RoaringBitmap有不同的实现,对于ClickHouse,使用的是CRoaring(https://github.com/RoaringBitmap/CRoaring)。各种语言的api实现都共享同一种序列化格式,这让我们可以使用python/java/scala进行序列化,然后用c++(ClickHouse)进行读取。格式具体见(GitHub - RoaringBitmap/RoaringFormatSpec: Specification of the compressed-bitmap Roaring format),官方说法如下:

这里我们使用了python版本的api(https://pyroaringbitmap.readthedocs.io/en/stable),源码(https://github.com/Ezibenroc/PyRoaringBitMap)。pip安装,然后需要对整个spark集群都更新这个library。这个库底层仍是CRoaring,因为python很容易对c/c++做wrapper封装。

PySpark入ClickHouse的代码如下:

这里,get_statistics()["cardinality"]获取基数,sys.getsizeof(rb)可以获取rb序列后的字节数,见Expose roaring_bitmap_portable_size_in_bytes as __sizeof__. by urdvr · Pull Request #40 · Ezibenroc/PyRoaringBitMap · GitHub (类似的,java API里是rb.serializedSizeInBytes()),rb.serialize()按照RoaringBitmap的序列化规则进行序列化,这里是已经封装好了。

在这里其实本人尝试了很多方案,也绕了很多弯路,比如使用了bytearray去承接byte数组(bytearray的值最大是256),比如直接存python struct(https://docs.python.org/zh-cn/3/library/struct.html)的格式而不是string后的base64,比如to_bytes没指定大小端序,对源码结构不熟没指定正确的位数等等,太多就不赘述了。给个脑图:

在这一步的最后,我们需要对bitmap进行抽样检测。这个是从SparkSQL & ClickHouse RoaringBitmap使用实践_spark clickhouse bitmap-CSDN博客里学到的,我们对二进制对象,进行base64编码(encode)再转成string类型,可以做到快速可视化二进制并来校验我们的结果是否正确。

base64编码是使用64个可打印的ASCII字符将二进制字节序列数据编码成字符串。通过使用base64将二进制数据序列化为二进制字符串来实现将内存里的二进制字节固化到磁盘或者网络传输。具体资料可见 Base64编码及编码性能测试_base64性能-CSDN博客 。

简单使用一个ClickHouse SQL去执行一下,并编码成base64的字符串

结果是

同样地,我们打印在PySpark里的dataframe的值,可以看到

两者的ASCII码是一致的,说明我们的字节已经做到了兼容ClickHouse读取格式。

2.3 hive bitmap出库ClickHouse

最后一步,我们已经有了一个预处理好的兼容ClickHouse bitmap的hive表,我们需要hive出库到ClickHouse里。

由于公司的tdw工具没法往hive表里写入binary类型,我们只能考虑直接写入base64的string进去。这里对id聚合,然后打成bitmap再写入新的字段bmp_base64。建表语句如下:

利用ClickHouse自带的物化表达式(CREATE TABLE | ClickHouse Docs ),我们可以很容易新增一列bitmap列。MATERIALIZED关键字的含义接近于DEFAULT和ALIAS,也就是默认值。此外,当 SELECT 中有星号(select *)时,查询也不返回这一列。这是为了在把用SELECT * 得到的数据插到回表中的时候,不指定列名也不会改变数据。

ClickHouse建表语句如下:

本地表:

分布式表:

select attr_value, bitmapCardinality(bitmap) from user_attrbute_bmp_di_cluster可以得到如下样例:

至此,我们的Spark-ClickHouse的bitmap之旅到了一段落了。

3. 总结

  • 减少了ClickHouse集群计算压力,大大缩短了hive表出库时间(不需要出库明细数据,只需要出库对应的bitmap)。
  • 可以将明细长时间存于hive表里,而不用留在ClickHouse中(物化视图需要明细),降低存储成本。
  • 使用Python生态圈,减少数据同学的上手难度,同时也打通了其他Python分析工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1600680.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【muzzik 分享】关于 MKFramework 的设计想法

MKFramework是我个人维护持续了几年的项目&#xff08;虽然公开只有一年左右&#xff09;&#xff0c;最开始由于自己从事QP类游戏开发&#xff0c;我很喜欢MVVM&#xff0c;于是想把他做成 MVVM 框架&#xff0c;在论坛第一个 MVVM 框架出来的时候&#xff0c;我的框架已经快完…

uniapp--登录和注册页面-- login

目录 1.效果展示 2.源代码展示 测试登录 login.js 测试请求 request.js 测试首页index.js 1.效果展示 2.源代码展示 <template><view><f-navbar title"登录" navbarType"4"></f-navbar><view class"tips"><…

基于JSP的电器网上订购系统

本系统利用现在比较广泛的JSP结合后台SpringMybatisAjax编写程序的方式实现的。 在意见箱板块中&#xff0c;运用JSP通过JDBC技术和后台的数据库进行交互的方式将数据信息反馈给用户和管理员&#xff1b;在登录系统中&#xff0c;使用Ajax技术实现异步交互&#xff0c;在不更新…

VulBG: 构建行为图加强基于深度学习的漏洞检测模型

近年来&#xff0c;人们提出了基于深度学习&#xff08;DL&#xff09;的漏洞检测系统&#xff0c;用于从源代码中自动提取特征。这些方法在合成数据集上可以实现理想的性能&#xff0c;但在检测真实世界的漏洞数据集时&#xff0c;准确率却大幅下降。此外&#xff0c;这些方法…

基于Zookeeper 简单实现分布式任务协调组件

一、什么是 Zookeeper ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Google的Chubby一个开源的实现&#xff0c;是Hadoop和Hbase的重要组件。 它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维…

Java复习第二十天学习笔记(过滤器Filter),附有道云笔记链接

【有道云笔记】二十 4.8 过滤器Filter https://note.youdao.com/s/dSofip3f 一、为什么要使用过滤器 项目开发中&#xff0c;经常会用到重复代码的实现。 1、请求每个servlet都要设置编码 2、判断用户是否登录&#xff0c;只有登录了才有操作权限。 二、过滤器相关Api int…

【数据结构|C语言版】双向链表

前言1. 初步认识双向链表1.1 定义1.2 结构1.3 储存 2. 双向链表的方法&#xff08;接口函数&#xff09;2.1 动态申请空间2.2 创建哨兵位2.3 查找指定数据2.4 指定位置插入2.5 指定位置删除2.6 头部插入2.7 头部删除2.8 尾部插入2.9 尾部删除2.10 计算链表大小2.11 销毁链表 3.…

怎么样在外网登录访问CRM管理系统?

一、什么是CRM管理系统&#xff1f; Customer Relationship Management&#xff0c;简称CRM&#xff0c;指客户关系管理&#xff0c;是企业利用信息互联网技术&#xff0c;协调企业、顾客和服务上的交互&#xff0c;提升管理服务。为了企业信息安全以及使用方便&#xff0c;企业…

【K8s】:在 Kubernetes 集群中部署 MySQL8.0 高可用集群(1主2从)

【K8s】&#xff1a;在 Kubernetes 集群中部署 MySQL8.0 高可用集群&#xff08;1主2从&#xff09; 一、准备工作二、搭建nfs服务器2.1 安装 NFS 服务器软件包&#xff08;所有节点执行&#xff09;2.2 设置共享目录2.3 启动 NFS 服务器2.4 设置防火墙规则&#xff08;可选&am…

OpenHarmony南向开发实例:【游戏手柄】

介绍 基于TS扩展的声明式开发范式编程语言&#xff0c;以及OpenHarmony的分布式能力实现的一个手柄游戏。 完成本篇Codelab需要两台开发板&#xff0c;一台开发板作为游戏端&#xff0c;一台开发板作为手柄端&#xff0c;实现如下功能&#xff1a; 游戏端呈现飞机移动、发射…

计算机视觉——OpenCV Python基于颜色识别的目标检测

1. 计算机视觉中的颜色空间 颜色空间在计算机视觉领域的应用非常广泛&#xff0c;它们在图像和视频处理、物体检测等任务中扮演着重要角色。颜色空间的主要作用是将颜色以数值形式表示出来&#xff0c;这样计算机算法就能够对其进行处理和分析。不同的颜色空间有着不同的特点和…

Web3D智慧医院平台(HTML5+Threejs)

智慧医院的建设将借助物联网、云计算、大数据、数字孪生等技术&#xff0c;以轻量化渲染、极简架构、三维可视化“一张屏”的形式&#xff0c;让医院各大子系统管理既独立又链接&#xff0c;数据相互融合及联动。 建设医院物联网应用的目标对象&#xff08;人、物&#xff09;都…

基于afx透明视频的视觉增强前端方案

作者 | 青玉 导读 本文介绍了增长前端团队自研的Webview框架下透明视频视觉增强方案&#xff0c;该方案在保证对视觉进行高度还原的同时可投入更少的开发成本&#xff0c;还能获得更优的前端性能表现。文章首先分析了市面上动画方案的优缺点&#xff0c;然后详细介绍了透明视频…

CentOS下GitLab的安装部署_centos 安装部署 gitlab,2024年最新软件测试开发岗还不会这些问题

先自我介绍一下&#xff0c;小编浙江大学毕业&#xff0c;去过华为、字节跳动等大厂&#xff0c;目前阿里P7 深知大多数程序员&#xff0c;想要提升技能&#xff0c;往往是自己摸索成长&#xff0c;但自己不成体系的自学效果低效又漫长&#xff0c;而且极易碰到天花板技术停滞…

HTML段落标签、换行标签、文本格式化标签与水平线标签

目录 HTML段落标签 HTML换行标签 HTML格式化标签 加粗标签 倾斜标签 删除线标签 下划线标签 HTML水平线标签 HTML段落标签 在网页中&#xff0c;要把文字有条理地显示出来&#xff0c;就需要将这些文字分段显示。在 HTML 标签中&#xff0c;<p>标签用于定义段落…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单人脸检测/识别实战案例 之十二 简单人脸识别 一、简单介绍 二、简单人脸识别实现原理 三、简单人脸识别案例实现简…

基于小程序实现的餐饮外卖系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

如何在PPT中获得网页般的互动效果

如何在PPT中获得网页般的互动效果 效果可以看视频 PPT中插入网页有互动效果 当然了&#xff0c;获得网页般的互动效果&#xff0c;最简单的方法就是在 PPT 中插入网页呀。 那么如何插入呢&#xff1f; 接下来为你讲解如何获得&#xff08;此方法在 PowerPoint中行得通&#…

华为配置静态ARP示例

华为配置静态ARP示例 组网图形 图1 配置静态ARP组网图 静态ARP简介配置注意事项组网需求配置思路操作步骤配置文件相关信息 静态ARP简介 静态ARP表项是指网络管理员手工建立IP地址和MAC地址之间固定的映射关系。 正常情况下网络中设备可以通过ARP协议进行ARP表项的动态学习&…

差速机器人模型LQR 控制仿真——路径模拟

LQR路径跟踪要求路径中带角度&#xff0c;即坐标&#xff08;x,y,yaw&#xff09;&#xff0c;而一般我们的规划出来的路径不带角度。这里通过总结相关方法&#xff0c;并提供一个案例。 将点路径拟合成一条完整的线路径算法 将点路径拟合成一条完整的线路径是一个常见的问题…