- Avro core中的基本代码结构
- Avro 之 可切分 & 可压缩
- Avro 与 大数据存储
- Avro在Kafka中的应用 之Confluent
- Avro在Kafka中的应用 之Flume&Flink
Avro core中的基本代码结构
首先说明一下core源码中的package目录
- Record类: Avro提供的 【三种Record操作方式】
- generic: 负责基于Schema(String)的映射操作
- specific: 负责基于代码生成类的映射操作
- reflect: 负责基于java反射的映射操作(这部分没看,不清楚)
- 序列化类:
- io: 负责基础数据类型 & 复合数据类型的io操作内容 【建议阅读源码】
- file: 负责Header & Codec的内容(可切分 & 可压缩的体现) 【建议阅读源码】
- 其他类: util & default(Schema & Protocol)
- Schema: 负责将avsc文件进行解析处理
- Protocol: 负责将avpr文件进行解析处理
Avro 之 可切分 & 可压缩
private static byte[] generateSync() {
// return md5(UUID + "@" + currentTime)
}
private void writeBlock() throws IOException {
if (blockCount > 0) {
bufOut.flush();
ByteBuffer uncompressed = buffer.getByteArrayAsByteBuffer();
DataBlock block = new DataBlock(uncompressed, blockCount);
block.setFlushOnWrite(flushOnEveryBlock);
block.compressUsing(codec);
block.writeBlockTo(vout, * sync *);
buffer.reset();
blockCount = 0;
}
}
@Override // 如何实现随机position从新定位
public void sync(long position) throws IOException {
seek(position);
// work around an issue where 1.5.4 C stored sync in metadata
if ((position == 0) && (getMeta("avro.sync") != null)) {
initialize(sin); // re-init to skip header
return;
}
try {
int i=0, b;
InputStream in = vin.inputStream();
vin.readFixed(syncBuffer); // 读取 sync长度 字节数
do {
int j = 0;
for (; j < SYNC_SIZE; j++) {
if (getHeader().sync[j] != syncBuffer[(i+j)%SYNC_SIZE])
break;
}
if (j == SYNC_SIZE) { // matched a complete sync
blockStart = position + i + SYNC_SIZE;
return;
}
b = in.read();
syncBuffer[i++%SYNC_SIZE] = (byte)b;
} while (b != -1);
} catch (EOFException e) {
// fall through
}
// if no match or EOF set start to the end position
blockStart = sin.tell();
//System.out.println("block start location after EOF: " + blockStart );
return;
}
以上源码内容说明:
- sync marker 生成:md5(UUID + “@” + currentTime)
- sync marker 写入 meta
- sync marker 写入 Block:对整个Block进行压缩再写入;并在尾部写入sync marker
- sync 定位:指定任意position,即可找到该position之后的,并最近的一个sync marker的位置
Avro 与 大数据存储
个人认为首先,Avro这种序列化方式在大数据存储中并不占有什么优势,因为考虑到大数据中主要的内容是 数据分析 ,所以,相对来说 列式存储 会更加的合适,而不是像Avro这样的行式存储。
列式存储中,像Parquet、ORC等都能在 空间利用率 上有良好的表现;不仅如此,列式存储还能为上游提供一定的 谓词下推 的语义。这些多是Avro这样的行式存储所不能提供的(准确的说,Avro的设计定位就不在这里)。
所以,Avro与Parquet和ORC等就没有对比的必要,而与之有对比价值的通常是JSON和Protobuf。
从序列化结果上看(不考虑序列化速度、序列化大小等因素),仅观察Schema与Value的映射关系,会有下面的发现:
- Json: Schema: Value = 1: 1
- Protobuf: Schema: Value = 0: M
- Avro: Schema: Value = 1: M
显然 相对于 结构化数据 的存储,Json会太过冗余,Protobuf则需要单独对Schema进行额外维护,Avro则非常合适。
虽然如此,但是我个人还是表示,在使用Avro进行数据存储时,最好还是需要在一下两个前提下:
- 数据的量级较大
- 数据结构不太复杂(如嵌套层次太多等)
Avro在Kafka中的应用 之Confluent
像上面说的一样,Avro中Schema: Value = 1: M,但是在Kafka中,Record是基本单位。也就是说,
- 如果在Kafka中,Record是JSON格式存储的,则Record的解析就不依赖于其他Context
- 如果在Kafka中,Record是Avro或者Protobuf格式存储的,则Record的解析就依赖于其他Context
又由于Kafka是大数据场景下的MQ,可想而知,数据的量级不小,所以存储问题是使用该服务时必要考虑的问题。所以在Kafka中使用JSON,不太合适。
其次如果在Kafka中使用Avro或者Protobuf,则不得不考虑元数据信息管理的问题,即Schema管理。那么如何管理?Confluent公司给出了他们的方案,同时他们的选择是Avro。
Confluent公司提供了一个开源的服务(包) – Schema Registry,同时为Kafka提供了定制化的Avro序列化工具包。
思路和过程如下:
- Schema Registry 是一个常驻服务,对外提供REST API,即外界使用无需依赖和被侵入。
- 向Kafka中写入Avro格式的数据时 会有这些相关操作
- 向Schema Registry发起Schema注册请求,并以ID作为响应(Schema Registry管理维护Schema与ID的映射关系)
- 将数据按照Avro的方式进行序列化,并在最开头填上ID标识
- 从Kafka中消费Avro格式的数据时 会有这些相关操作
- 首先,读取byte[] 的开头部分 – ID,并根据ID信息请求Schema Registry以获取对应的Schema信息
- 通过Schema解析剩余的Value内容
从以上过程可以看出,Confluent这样的设计,需要依赖于Avro的Generic的功能,即直接依赖于Schema String就可以进行序列化和反序列化的操作,而无需强制要求代码生成,这也是为何不使用Protobuf的原因。
总的来说,Confluent公司提供了以下关于Kafka存储Avro的工具:
- 核心部分:Schema Registry – 管理和维护Schema与ID之间的管理
- 序列化部分:联合与Schema Registry的 Avro Serializer & Avro Deserializer
- Connector部分:联合与Schema Registry的 包装Avro Deserializer的connect插件
Avro在Kafka中的应用 之Flume&Flink
近期需求 – 需要对IoT的数据进行实时分析。整体设计
简单说明:
- Flume:将格式化(tab符分割)的IoT日志文件按照Avro进行序列化采集
- Flink:数据计算引擎,以SqlClient方式进行使用,相对来说只需要SQL和配置文件定义即可
其中Flume中自定义Avro Interceptor了,即数据通过Source采集之后,立即按照指定的Schema进行序列化(Schema管理在Schema Manager),之后传向Channel,最后至Kafka Sink。
另外Flink中的SqlClient(Flink-1.6.1),当前对Connector的支持较为有限,对于HDFS的Connector虽然已经提供,但是目前还没有集成至SqlClient中(其实就是缺少了Factory),所以也进行了部分调整。
剩余其他都是原生支持模块,可以直接拼接使用。
目前来看,Flume的中Source端(Interceptor其实算Source端)可能存在性能瓶颈,表现形式为:Source,Channel,Sink均单线程下,各端的处理速度均约等于Source的速度。所以个人简单认为Source端的速度限制了Channel和Sink的速度(只是简单测试,所以不公布结果了)。