cow数据库
1. 云计算核心技术Docker教程:Docker存储写入时复制(CoW)策略
【点击右上角加'关注',全国产经信息不错过】
写时复制是一种共享和复制文件的策略,可最大程度地提高效率。如果文件或目录位于映像的较低层中,而另一层(包括可写层)需要对其进行读取访问,则它仅使用现有文件。另一层第一次需要修改文件时(在构建映像或运行容器时),将文件复制到该层并进行修改。这样可以将I / O和每个后续层的大小最小化。这些优点将在下面更深入地说明。
共享可以提升较小的图像
当您用于docker pull从存储库中下拉映像时,或者当您从本地尚不存在的映像中创建容器时,每个层都会被分别下拉,并存储在Docker的本地存储区域中,该区域通常/var/lib/docker/在Linux主机上。在此示例中,您可以看到这些层被拉出:
$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
f476d66f5408: Pull complete
8882c27f669e: Pull complete
d9af21273955: Pull complete
f5029279ec12: Pull complete
Digest: sha256:
Status: Downloaded newer image for ubuntu:18.04
这些层中的每一层都存储在Docker主机的本地存储区域内的自己的目录中。要检查文件系统上的各层,请列出的内容/var/lib/docker/。本示例使用overlay2 存储驱动程序:
$ ls /var/lib/docker/overlay2
l
目录名称与层ID不对应(自Docker 1.10开始就是如此)。
现在,假设您有两个不同的Dockerfile。您使用第一个创建名为的图像acme/my-base-image:1.0。
# syntax=docker/dockerfile:1
FROM ubuntu:18.04
COPY . /app
第二acme/my-base-image:1.0层基于,但具有一些附加层:
# syntax=docker/dockerfile:1
FROM acme/my-base-image:1.0
CMD /app/hello.sh
第二个图像包含第一个图像的所有层,再加上带有CMD指令的新层,以及一个可读写容器层。Docker已经具有第一个映像中的所有层,因此不需要再次将其拉出。这两个图像共享它们共有的任何图层。
如果从两个Dockerfile构建映像,则可以使用docker image ls和 docker history命令来验证共享层的密码ID是否相同。
1.创建一个新目录cow-test/并更改到该目录中。
2.在中cow-test/,创建一个hello.sh具有以下内容的新文件:
#!/bin/sh
echo "Hello world"
保存文件,并使其可执行:
chmod +x hello.sh
3.将上面第一个Dockerfile的内容复制到一个名为的新文件中 Dockerfile.base。
4.将上面第二个Dockerfile的内容复制到一个名为的新文件中 Dockerfile。
5.在cow-test/目录中,构建第一个映像。不要忘记.在命令中包含final 。设置了PATH,它告诉Docker在哪里寻找需要添加到映像中的任何文件。
$docker build -t acme/my-base-image:1.0 -f Dockerfile.base .
6.建立第二张镜像。
$docker build -t acme/my-final-image:1.0 -f Dockerfile .
7.检查镜像的大小:
$docker image ls
8.检出构成每个镜像的图层:
$docker history bd09118bcef6
请注意,除了第二个图像的顶层以外,所有层都是相同的。所有其他层在两个图像之间共享,并且仅在中存储一次/var/lib/docker/。实际上,新层根本不占用任何空间,因为它不更改任何文件,而仅运行命令。
全国产经平台联系电话:010-65367702,邮箱:[email protected],地址:北京市朝阳区金台西路2号人民日报社
2. hudi流写入如何保证事务
方法如下:
1. 项目背景
传统数仓的组织架构是针对离线数据的OLAP(联机事务分析)需求设计的,常用的导入数据方式为采用sqoop或spark定时作业逐批将业务库数据导入数仓。随着数据分析对实时性要求的不断提高,按小时、甚至分钟级的数据同步越来越普遍。由此展开了基于spark/flink流处理机制的(准)实时同步系统的开发。
然而实时同步数仓从一开始就面临如下几个挑战:
小文件问题。不论是spark的microbatch模式,还是flink的逐条处理模式,每次写入HDFS时都是几M甚至几十KB的文件。长时间下来产生的大量小文件,会对HDFS namenode产生巨大的压力。
对update操作的支持。HDFS系统本身不支持数据的修改,无法实现同步过程中对记录进行修改。
事务性。不论是追加数据还是修改数据,如何保证事务性。即数据只在流处理程序commit操作时一次性写入HDFS,当程序rollback时,已写入或部分写入的数据能随之删除。
Hudi是针对以上问题的解决方案之一。以下是对Hudi的简单介绍,主要内容翻译自官网。
2. Hudi简介
2.1 时间线(Timeline)
Hudi内部按照操作时刻(instant)对表的所有操作维护了一条时间线,由此可以提供表在某一时刻的视图,还能够高效的提取出延后到达的数据。每一个时刻包含:
时刻行为:对表操作的类型,包含:
commit:提交,将批次的数据原子性的写入表;
clean: 清除,后台作业,不断清除不需要的旧得版本的数据;
delta_commit:delta 提交是将批次记录原子性的写入MergeOnRead表中,数据写入的目的地是delta日志文件;
compacttion:压缩,后台作业,将不同结构的数据,例如记录更新操作的行式存储的日志文件合并到列式存储的文件中。压缩本身是一个特殊的commit操作;
rollback:回滚,一些不成功时,删除所有部分写入的文件;
savepoint:保存点,标志某些文件组为“保存的“,这样cleaner就不会删除这些文件;
时刻时间:操作开始的时间戳;
状态:时刻的当前状态,包含:
requested 某个操作被安排执行,但尚未初始化
inflight 某个操作正在执行
completed 某一个操作在时间线上已经完成
Hudi保证按照时间线执行的操作按照时刻时间具有原子性及时间线一致性。
2.2 文件管理
Hudi表存在在DFS系统的 base path(用户写入Hudi时自定义) 目录下,在该目录下被分成不同的分区。每一个分区以 partition path 作为唯一的标识,组织形式与Hive相同。
每一个分区内,文件通过唯一的 FileId 文件id 划分到 FileGroup 文件组。每一个FileGroup包含多个 FileSlice 文件切片,每一个切片包含一个由commit或compaction操作形成的base file 基础文件(parquet文件),以及包含对基础文件进行inserts/update操作的log files 日志文件(log文件)。Hudi采用了MVCC设计,compaction操作会将日志文件和对应的基础文件合并成新的文件切片,clean操作则删除无效的或老版本的文件。
2.3 索引
Hudi通过映射Hoodie键(记录键+ 分区路径)到文件id,提供了高效的upsert操作。当第一个版本的记录写入文件时,这个记录键值和文件的映射关系就不会发生任何改变。换言之,映射的文件组始终包含一组记录的所有版本。
2.4 表类型&查询
Hudi表类型定义了数据是如何被索引、分布到DFS系统,以及以上基本属性和时间线事件如何施加在这个组织上。查询类型定义了底层数据如何暴露给查询。
| 表类型 | 支持的查询类型 | | :-------------------- | :----------------------------- | | Copy On Write写时复制 | 快照查询 + 增量查询 | | Merge On Read读时合并 | 快照查询 + 增量查询 + 读取优化 |
2.4.1 表类型
Copy On Write:仅采用列式存储文件(parquet)存储文件。更新数据时,在写入的同时同步合并文件,仅仅修改文件的版次并重写。
Merge On Read:采用列式存储文件(parquet)+行式存储文件(avro)存储数据。更新数据时,新数据被写入delta文件并随后以异步或同步的方式合并成新版本的列式存储文件。
| 取舍 | CopyOnWrite | MergeOnRead | | :----------------------------------- | :---------------------- | :-------------------- | | 数据延迟 | 高 | 低 | | Update cost (I/O)更新操作开销(I/O) | 高(重写整个parquet) | 低(追加到delta记录) | | Parquet文件大小 | 小(高更新(I/O)开销) | 大(低更新开销) | | 写入频率 | 高 | 低(取决于合并策略) |
2.4.2 查询类型
快照查询:查询会看到以后的提交操作和合并操作的最新的表快照。对于merge on read表,会将最新的基础文件和delta文件进行合并,从而会看到近实时的数据(几分钟的延迟)。对于 on write表,当存在更新/删除操作时或其他写操作时,会直接代替已有的parquet表。
增量查询:查询只会看到给定提交/合并操作之后新写入的数据。由此有效的提供了变更流,从而实现了增量数据管道。
读优化查询:查询会看到给定提交/合并操作之后表的最新快照。只会查看到最新的文件切片中的基础/列式存储文件,并且保证和非hudi列式存储表相同的查询效率。
| 取舍 | 快照 | 读取优化 | | :------- | :------------------------------------------------------ | :------------------------------------ | | 数据延迟 | 低 | 高 | | 查询延迟 | 高(合并基础/列式存储文件 + 行式存储delta / 日志 文件) | 低(原有的基础/列式存储文件查询性能) |
3. Spark结构化流写入Hudi
以下是整合spark结构化流+hudi的示意代码,由于Hudi OutputFormat目前只支持在spark rdd对象中调用,因此写入HDFS操作采用了spark structured streaming的forEachBatch算子。具体说明见注释。
4. 测试结果
受限于测试条件,这次测试没有考虑update操作,而仅仅是测试hudi对追加新数据的性能。
数据程序一共运行5天,期间未发生报错导致程序退出。
kafka每天读取数据约1500万条,被消费的topic共有9个分区。
几点说明如下
1 是否有数据丢失及重复
由于每条记录的分区+偏移量具有唯一性,通过检查同一分区下是否有偏移量重复及不连续的情况,可以断定数据不存丢失及重复消费的情况。
2 最小可支持的单日写入数据条数
数据写入效率,对于cow及mor表,不存在更新操作时,写入速率接近。这本次测试中,spark每秒处理约170条记录。单日可处理1500万条记录。
3 cow和mor表文件大小对比
每十分钟读取两种表同一分区小文件大小,单位M。结果如下图,mor表文件大小增加较大,占用磁盘资源较多。不存在更新操作时,尽可能使用cow表。
3. osg模型能在osgviewer中显示,不能在vs中显示
cow.osg模型具体放在什么路径下?
工程当前路径下,则可以直接viewer.setSceneData(osgDB::readNodeFile("cow.osg"));
模型文件cow.osg正常放在osg库的data文件夹下