关于数据采集分发系统的一些思考

·  阅读 394
  • 采集侧流程
  • 分发侧流程
  • 怎么保证数据不重不丢
  • canal的高可用需要注意的问题

采集侧基于什么实现?定制化开发实现了canal那部分?

canal部署架构 125.png

1.开发了一个定时任务,检查canal接口的采集实例是否存在 如果没有会一直尝试部署实例,保证采集数据不丢

2.增强了canal监控埋点

3.定制化了canal的globalId,开发的过程中涉及以下问题:

  • 1、binlog globalid/group_id 含义

  • 2、binlog pos 含义

  • 3、binlog采集不会丢。怎么保障的?(canal-server的幂等性恢复,再次启动的时候从第一个binlig开始恢复)

  • 4、如何保证采集高可用

  • Q &A 问题一

canal文档:github-wiki-see.page/m/alibaba/c…

globalid:基于binlog中提取的executeTime时间戳加上canal服务器系统的微秒时间戳得来, 这样的globid会是一个高精度,起到可以区分binlog日志先后执行顺序的作用; 将globid与日志数据包装好发送到kafka,最后落于中间表用于merge逻辑排序。

如果要改动globalId,那么需要涉及到canal解析链路的改动

canal的核心设计

  • canal server:代表了我们部署的一个 canal 应用。
  • canal instance:代表了一个 canal server 中的多个 MySQL instance。
    • 一般情况下,一个 canal instance 对应一个 MySQL instance 这里说的不是我们在 MySQL 服务器上创建的一个数据库,而指的就是一个 MySQL 服务器实例
    • 由此可以看出,一个 canal server 可以搜集多个数据库实例的数据, 在 canal 中把 instance 叫 destination。
    • 每个 canal instance 由多个组件构成。 在 conf/spring/xxx-instance.xml 中配置了这些组件。
    instance 模块(组件):
        ① eventParser(数据解析):模拟 MySQL slave 与 MySQL master 进行交互,负责 binary log 解析。
        ② eventSink(数据归集):Parser 和 Store 之间的链接器,负责对 eventParser 解析后的数据 进行过滤、加工、分发。类似于 ETL 中的 数据清洗。
        ③ eventStore(数据存储):用于存储 eventSink 处理后的数据。
        ④ metaManager:增量订阅 & 消费信息管理器。
复制代码
  • Q&A 问题2

canal采集位点流程图: www.processon.com/view/link/6…

在主从切换的时候,从库会向Mysql Master按照日志文件名(binlog文件名) 发送dump命令,当binlog的事件事件大雨待查找的时间戳 有两种选择 一种是开启事务,一种是从最近最新的消费位点开始消费

  • Q&A 问题3

假设我们现在有一张表A,建表A与binlog如下 (一共有7个binlog)

CREATE TABLE t (
a INT(11) NOT NULL AUTO_INCREMENT,
b VARCHAR(20) DEFAULT NULL,
c INT,
PRIMARY KEY (a)
);
CREATE TABLE t1 LIKE t;
CREATE TABLE t2 LIKE t;
RESET MASTER;

INSERT INTO t(a,b,c) VALUES(1,’A’,100);
FLUSH LOGS;

binlog 2
INSERT INTO t(a,b,c) VALUES(2,’A’,200);
FLUSH LOGS;

binlog 3
INSERT INTO t(a,b,c) VALUES(3,’C’,100);
FLUSH LOGS;

binlog 4
DELETE FROM t WHERE b=’A’;
FLUSH LOGS;

....

复制代码

解析如下

mysqlbinlog –skip-gtids -v mysql-bin.000001 > 1.sql
mysqlbinlog –skip-gtids -v mysql-bin.000002 > 2.sql
mysqlbinlog –skip-gtids -v mysql-bin.000003 > 3.sql
mysqlbinlog –skip-gtids -v mysql-bin.000004 > 4.sql
mysqlbinlog –skip-gtids -v mysql-bin.000005 > 5.sql
mysqlbinlog –skip-gtids -v mysql-bin.000006 > 6.sql
mysqlbinlog –skip-gtids -v mysql-bin.000007 > 7.sql


复制代码

数据未恢复前

123.png

执行恢复命令如下

FLUSH LOGS;\
INSERT INTO t2 SELECT * FROM t;\
DELETE FROM t;\
INSERT INTO t SELECT * FROM t1;
复制代码

数据恢复后

124.png

上面的binlog我们是从第5个binlog开始备份的。下面从第二个binlog开始执行恢复。 会多出了一条记录

原因在于:mysql-bin.000004里面删除delete from t where b=’A’;包
括两条记录(a=1,a=2),在一个事务里面。要么成功,要么失败。

而从mysql-bin.000002开始执行的时候,由于表里面没有a=2的记录,
所以a=2插入成功,但是删除的时候,a=1没有,事务不成功。导致多了一条记录


答案:从基于备份时间点之前,应用binlog来恢复,数据是不一致的。但是如果从第一个binlog起, 执行多次不会有问题。
复制代码

canal更新zk offset的频率控制是什么样的。

这个问题显然是一个伪命题且属于一个平行问题,关于canal更新zk信息的解释如下:

  • 对于平行问题一

    基于zk的canal部署显然是高可用的,这里的高可用。不是针对server级别,而是针对采集实例(instance)级别的高可用.假设现在有这样一个场景:


canal_A1和canal_A2为一组,
conf目录下配置instance1,instance2,instance3,instance4
canal_B1和canal_B2为一组,
conf目录下配置instance5,instance6,instance7,instance8
相应的消费端同理,一组只消费1,2,3,4,另一组只消费5,6,7,8
zk保存的数据,即为instace节点的状态

可以连上zk,手动在/otter/canal/destinations
    目录下找到canal的所有实例,
    实例下有running节点,用set命令改active的值就行了
复制代码
  • 对于平行问题二

offset的频率控制是什么样的。 这里我们一般说的是binlog文件当前写入的pos为止 加本次写入数据的偏移量(offset),以下参数可以配置

#canal 持久化数据到zk上的更新频率,单位毫秒
canal.zookeeper.flush.period = 1000
#canal持久化数据到file上的目录,默认和instance.properties为同一目录
canal.file.data.dir = ${canal.conf.dir}
#canal持久化数据到file上的更新频率,单位毫秒
canal.file.flush.period = 1000
复制代码
  • Q&A 问题4

canal的高可用部署

canal的数据传输有两块,一块是进行binlog订阅时, binlog转换为我们所定义的Message, 第二块是client与server进行TCP交互时,传输的TCP协议。

对于第一块而言是在mysql订阅instance消息的时候进行传输 对于第二块而言是在client与canalserver上的instance交互的时候进行传输

1. 启动两个监听example1的canal client,
启动两个监听example2的canal client:

在example1或example2对应的数据发生变化时,
两个canal client只有一个消费消息。

当两个监听同一个队列的canal client有一个宕掉时,
再有数据变化时,剩下的一个canal client就会开始消费数据。

这就验证了canal client的HA机制:为了保证有序性,一
份instance同一时间只能由一个canal client进行get/ack/rollback
操作,否则客户端接收无法保证有序.

 

2. 启动两个canal server并在zk上注册

当停掉其中一个canal server时,当产生数据变化时,整个canal server集群仍可以正常对外提供服务。

这就验证了canal server的HA机制:为了减少对mysql dump的请求,不同server上的instance要求同一时间只能有一个处于running,其他的处于standby状态.

3. 在canal server切换过程中,canal client存在重复消费数据的问题

这点需要在消费端自行进行处理
复制代码

2)分发侧基于什么实现 数据分发过程

  • 广播流如何使用

用来获取动态规则实时推送规则

用法:

public class EtlBroadcastProcessFunction extends BroadcastProcessFunction<String,Map<String, JobConfig>,Tuple2<Integer, Map<String, Object>>> {
    
    //解析规则
    private JobConfig jobConfig;

     /**
     * 处理数据流
     * @param record
     * @param ctx
     * @param out
     * @throws Exception
     */
    @Override
    public void processElement(String record, ReadOnlyContext ctx, Collector<Tuple2<Integer, Map<String, Object>>> out) throws Exception {
        //record为要处理的数据,可利用获取到的规则jobConfig来检测收到的数据
    }

    /**
     * 获取规则流并缓存
     * @param value
     * @param ctx
     * @param out
     * @throws Exception
     */
    @Override
    public void processBroadcastElement(Map<String, JobConfig> value, Context ctx, Collector<Tuple2<Integer, Map<String, Object>>> out) throws Exception {
        //value中为获取到的规则数据
        //缓存规则数据
        this.jobConfig = value.get(“jobConfig“);
    }
}
复制代码

原理:

  • 用户必须创建一个 MapStateDescriptor,才能得到对应的状态句柄。 这保存了状态名称, 状态所持有值的类型,并且可能包含用户指定的函数

  • checkpoint的时候也会checkpoint broadcast state

  • Broadcast State只在内存有,没有RocksDB state backend

  • Flink 会将state广播到每个task,注意该state并不会跨task传播,对其修改仅仅是作用在其所在的task

  • 下游tasks接收到broadcast event的顺序可能不一样,所以依赖其到达顺序来处理element的时候要小心

  • flink checkpoint参数如何配置

Flink 的 Checkpoint 包括如下几个部分:

  • JM trigger checkpoint
  • Source 收到 trigger checkpoint 的 PRC,自己开始做 snapshot,并往下游发送 barrier
  • 下游接收 barrier(需要 barrier 都到齐才会开始做 checkpoint)
  • Task 开始同步阶段 snapshot
  • Task 开始异步阶段 snapshot
  • Task snapshot 完成,汇报给 JM
checkpointDir=hdfs://ns3/flink-checkpoints
enableCheckPointing=30000
enableExternalizedCheckPoints=RETAIN_ON_CANCELLATION
failOnCheckPointingErrors=false
maxConcurrentCheckpoint=1
minPauseBetweenCheckpoints=30000
parallelism=2
useSnapshotCompression=false
复制代码
  1. flink线上环境算子链并发度如何分配?

沙盒环境当前数据看:主要瓶颈点在join 算子, 处理数据的qps 和 并发数成正比。 sink doris 并发数在16时,处理数据的qps 11w,提高并发到20, 处理速度仍为11w.并行度 大于 kafka 的partition 数时,吞吐不能提高的, 多的task ,分配不到数据。

线上环境
- 1、source 30并发, join、flatemap  50并发 、 sink 10并发    
整体处理qps :20w 。 单个并发需要的内存8g

- 2、source 30并发, join、flatemap  50并发 、 sink 50并发     
整体处理qps :40w 。 单个并发需要的内存2g

- 3、source 30并发, join、flatemap  60并发 、 sink 60并发     
整体处理qps :47w 。 单个并发需要的内存2g

- 4、source 30并发, join、flatemap  60并发 、 去除sink算子     
整体处理qps :60w 。 单个并发需要的内存2g
复制代码

2)如何保证到达的数据不乱序

因为我们的系统里 绝对不允许因为数据延迟到达,而造成乱序的情况

所以我们将上游从mysql数据库采集到的数据存到中继kafka, 每个topic作为库级别的数据存储

然后定制化一个入库平台,用户配置各种分发规则后

  • 流处理这一侧

库级别topic的每个分区的数据,分发到下游表级别的topic

  • 批处理的这一侧

库级别的topic按照小时级别入cosn。由于我们在写入cosn的时候 写入超时,引发了如下问题: juejin.cn/post/697791…

3.kafka在入库分发的时候性能怎么样?

kafka 50台 flink190台

Kafka集群的网络没做好,产生了竞争, 导致Kafka的Producer发送慢了,然后发不出去的消息一直堆在Producer缓冲区, 最后放不下了报错,因为是EXACTLY_ONCE, 并且Flink Producer没有设置logFailuresOnly=true,所以任务会挂掉

4.flink内存怎么计算分配

参考:cloud.tencent.com/developer/a…

  1. Flink Memory = Totoal Memory - JVM Metaspace - JVM Overhead

2)Total Memory :flink job 启动时,按照参数: -ytm 6144 指定的内存大小(单位M)。如果不指定,按照yarn最小Container分配的内存大小。

yarn.scheduler.minimum-allocation-mb 2048

3)Network = Flink Memory *0.1(默认值)

4)Managed Memory = Flink Memory * 0.4(默认值)

5)Task off-heap 默认为0;

6)framework off-heap :默认为128M

5.flink的solt怎么分配?

taskmangaer数量 = 全局并行度/solt数量

根据算子的并行度算需要多少个taskmanager 哪些算子使用soltsharegroup作为一个单独的算子链 具体情况具体分析

假设并行度为1000,

方式一:设置100个TM,每个TM设置10个slot,每个设置5g内存,5core;

方式二:设置200个TM,每个TM设置5个slot,每个设置2.5g内存,2.5core(忽略小数的问题)。

先说答案:没有绝对的好坏,更分散会增加TM 之间数据交换开销, 更集中的话,如果对状态访问较多,会导致对磁盘压力太大

缺点:状态访问较多,会导致对磁盘压力太大 打个比方: 如果只有50台机器的话,那么每台机器上使用方式一的2个TM或者方式二的4个TM 对磁盘来说压力是一样的,但实际不会这么理想 在集群有很多任务的情况下,假设100个TM大概会分布在10台机器上 这个时候对单机的磁盘压力就体现出来了,如果磁盘资源确实紧张 就考虑平衡taskmanager个数与solt个数/tm的关系

一般设置内存:0.5gslot个数/TM,core:0.5slot个数/TM

Continue..QwQ待更新

分类:
后端