如何基于MYSQL做实时计算?

本文介绍如何利用阿里云日志服务收集MySQL binlog,并通过流计算处理数据变更,实现数据多模式存储、业务实时监控等功能。
云栖社区 2017-12-24 20:26:57

有时候我们会有这样的场景,在某个接口中,数据已经很规范地存入到一张的MYSQL表中,现在想对这样的数据做一些实时或准实时处理,比如数据多模式存储、异步准实时业务流程、业务实时监控等。接口中处理流程如下:

如何基于MYSQL做实时计算?

最原始的方法,是改动业务代码,将这些额外的处理流程作为同步流程,在更新MYSQL数据之后同步执行。如下图:

如何基于MYSQL做实时计算?

但是这样的处理流程可能会越来越多,如果一直作为同步流程,整个接口会变得越来越庞大、并且耗时越来越长、出问题的风险越来越高。

所以我会考虑异步处理流程。如果可以改动一下代码,将数据额外写一份儿到队列里,再用flink、storm之类的去消费不就好了么。如下图:

如何基于MYSQL做实时计算?

但实际上,或许由于架构设计的不规范、或许由于业务场景的繁多,导致在代码中加一遍数据埋点,就如同重构一般的工作量。所以我们需要另一种方式,能实时感知到MYSQL中数据的变化。

MYSQL的binlog可以帮我们记录数据的变化,我们还需要一个工具来收集binlog,并转为我们能读懂的数据。阿里有一款叫canal的开源软件正是做这个用的,可以通过修改源码,增加监控、告警、投递队列功能来实现。但现在,阿里云的日志服务为我们集成了这一功能,我们可以用更短的时间、更少的资源来获得更稳定、更放心的服务。如下图:

如何基于MYSQL做实时计算?

日志服务收集binlog的功能还在内测中,不久之后将与大家见面。


比如有这样一个场景,我的MYSQL里有一张订单推送记录表,现在有一个需求,需要将这个表中的数据,按照一定格式再写入一份儿到表格存储TableStore中。

传统的实现方式,是在程序有写入到MYSQL的地方,再加一段代码,写入MYSQL成功后再写入到表格存储中。而现在,为完成这个需求,我选用的技术方案是:

日志服务SLS+流计算StreamCompute+表格存储TableStore

首先使用日志服务,配置对mysql中订单推送记录表所在实例的binlog的收集。

日志服务收集binlog的原理,与canal一致。具体配置这里暂不作叙述。

收集到的日志中,包含:数据库名、表名、事件(row_insert、row_update、row_delete)、全局事务ID、各个字段修改前的值、各个字段修改后的值。

根据场景,我们需要捕获到每个row_insert和row_update操作中,各个字段修改后的值,然后写入到表格存储中。所以我们在流计算中,先配置好日志服务的源表、和表格存储的目标表,中间的逻辑这样写:

INSERT INTO ots_result_order_push (pk, id, order_id, master_id, pull_status , offer_time, hire_time, push_time, limit_offer)SELECT concat(REVERSE(order_id),'|',master_id) as pk,id, order_id, master_id, pull_status, offer_time , hire_time, push_time, limit_offerFROM sls_stream_distribute_canalWHERE _db_ = 'distribute' AND (_event_ = 'row_insert' OR _event_ = 'row_update') AND _table_ = 'order_push';

即可完成此需求。


再比如有这样一个场景,我的MYSQL里有一张用户信息表,现在想要实时统计每日注册用户数,并通过大屏展示出来。

为完成这个需求,我选用的技术方案是:

日志服务SLS+流计算StreamCompute+表格存储TableStore+数据可视化DataV

首先使用日志服务,配置对mysql中user_info表所在实例的binlog的收集。

根据场景,我们需要捕获到每个row_insert操作的时间,并将时间截取到日期。统计每天有多少条往用户信息表中插入的操作记录。所以我们在流计算中,先配置好日志服务的源表、和表格存储的目标表,中间逻辑这样写:

insert into ots_result_user(pk,val)select concat('RegUser4General|t|',t.dt) as pk,t.valfrom (select from_unixtime(cast(create_time as bigint),'yyyyMMdd') as dt,count(_table_) as valfrom sls_stream_user_service_canalwhere _event_ = 'row_insert'and _table_ = 'user_info'group by from_unixtime(cast(create_time as bigint),'yyyyMMdd')) t;

即可完成需求。


异步获取MYSQL数据变化,触发异步流程,避免了多个同步流程可能造成的执行时间过长、或者由于网络原因卡住等等导致的风险。同时,异步流程也可以并行,总体上加快了业务流程的速度,使“一份儿数据、多种处理”变得更加方便快捷。

当然,对于上边作为例子的两个场景来说,文中给出的方案并不是唯一的解决办法,还可以使用函数计算代替流计算实现同样的效果

整套流程全部采用阿里云的服务化产品进行,使得本来全部独立开发需要几天的工作量,可以在几分钟之内搞定,方便快捷,且整套流程都有完善的监控、告警机制,安全放心。

<think>我们正在讨论如何使用Flink处理MySQL数据流。根据用户需求,需要实现实时计算。Flink提供了多种连接MySQL的方式,包括作为数据源(Source)和数据汇(Sink)。此外,FlinkSQL也可以用于实时计算。参考之前提供的引用[^1]中提到了JDBCAppendTableSink,这是用于写入MySQL的。但用户的问题更广泛,包括实时计算整个流程。步骤:1.从MySQL读取数据(作为数据源)2.对数据流进行转换和计算实时计算)3.将计算结果写入MySQL或其他存储(作为数据汇)Flink提供了不同的API:DataStreamAPI和TableAPI/SQL。这里我们主要展示DataStreamAPI的方式,因为引用中使用了DataStreamAPI。但是,Flink本身并不提供MySQL的流式数据源(CDC变更数据捕获),通常需要借助Debezium等工具捕获MySQL的binlog,然后通过Kafka传递给Flink。不过,Flink1.11+引入了CDC连接器,可以直接使用。由于问题中没有指定具体版本,我们以Flink1.12为例,介绍两种方式:方式一:使用传统的JDBC连接器读取MySQL,但这种方式是批处理,不是流处理。所以对于实时流,我们需要使用CDC方式。方式二:使用FlinkCDC连接器(开源项目:https://github.com/ververica/flink-cdc-connectors)来实现实时捕获MySQL变更。这里我们采用方式二(因为用户需要实时计算),但注意FlinkCDC连接器不是Flink官方发行版的一部分,需要额外添加依赖。步骤分解:1.设置环境,添加依赖(在项目中引入flink-sql-connector-mysql-cdc)2.创建流执行环境3.使用CDC连接器创建MySQL数据源4.对数据流进行转换计算5.将计算结果写入MySQL(使用JDBCSink)由于用户问题中提到了“实时计算”,我们假设需要计算一些指标,例如每5秒的订单总额(假设数据流是订单)。注意:以下代码示例基于Flink1.12和FlinkCDC1.2.0,实际使用请根据版本调整依赖。首先,添加Maven依赖(在pom.xml中):```xml<dependency><groupId>com.alibaba.ververica</groupId><artifactId>flink-connector-mysql-cdc</artifactId><version>1.2.0</version></dependency><!--FlinkJDBCConnector--><dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-jdbc_2.11</artifactId><version>1.12.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency>```然后,编写代码:示例:实时计算订单中每5秒的订单总金额,并将结果写入MySQL一张。步骤:1.创建MySQLCDC数据源2.转换数据流,提取订单金额和事件时间(假设中有金额字段和创建时间字段)3.设置时间特性和水位线4.按5秒时间窗口聚合5.将结果写入MySQL代码示例:```javaimportorg.apache.flink.api.common.eventtime.WatermarkStrategy;importorg.apache.flink.streaming.api.datastream.DataStream;importorg.apache.flink.streaming.api.environment.StreamExecutionEnvironment;importorg.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;importorg.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;importorg.apache.flink.streaming.api.windowing.time.Time;importorg.apache.flink.streaming.api.windowing.windows.TimeWindow;importorg.apache.flink.util.Collector;importcom.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;importcom.ververica.cdc.connectors.mysql.source.MySqlSource;importorg.apache.flink.connector.jdbc.JdbcSink;importjava.sql.PreparedStatement;importjava.sql.SQLException;importjava.time.Duration;publicclassFlinkMySQLCDCExample{publicstaticvoidmain(String[]args)throwsException{//创建执行环境StreamExecutionEnvironmentenv=StreamExecutionEnvironment.getExecutionEnvironment();env.setParallelism(1);//创建MySQLCDC源MySqlSource<String>mySqlSource=MySqlSource.<String>builder().hostname("localhost").port(3306).databaseList("testdb")//设置数据库.tableList("testdb.orders")//设置.username("root").password("password").deserializer(newJsonDebeziumDeserializationSchema())//使用JSON格式.build();//添加数据源DataStream<String>source=env.fromSource(mySqlSource,WatermarkStrategy.noWatermarks(),"MySQLSource");//解析JSON字符串(这里简化处理,实际应解析为POJO)DataStream<Order>orderStream=source.map(json->{//解析JSON,这里需要根据Debezium的JSON格式解析//假设我们解析出id,amount,create_time//由于解析JSON比较复杂,这里用伪代码代替//实际中可以使用Jackson或Gson解析//这里假设解析结果为:id,amount,create_time//创建Order对象returnparseOrderFromJson(json);}).assignTimestampsAndWatermarks(WatermarkStrategy.<Order>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner((event,timestamp)->event.getCreateTime().getTime()));//按5秒窗口计算订单总金额DataStream<OrderSummary>result=orderStream.keyBy(Order::getUserId)//假设按用户分组,如果没有则可以不分组(但通常会有分组).window(TumblingEventTimeWindows.of(Time.seconds(5))).process(newProcessWindowFunction<Order,OrderSummary,Integer,TimeWindow>(){@Overridepublicvoidprocess(Integerkey,Contextcontext,Iterable<Order>elements,Collector<OrderSummary>out){doublesum=0.0;for(Orderorder:elements){sum+=order.getAmount();}out.collect(newOrderSummary(key,context.window().getEnd(),sum));}});//将结果写入MySQLresult.addSink(JdbcSink.sink("INSERTINTOorder_summary(user_id,window_end,total_amount)VALUES(?,?,?)",(PreparedStatementps,OrderSummarysummary)->{ps.setInt(1,summary.getUserId());ps.setTimestamp(2,newjava.sql.Timestamp(summary.getWindowEnd()));ps.setDouble(3,summary.getTotalAmount());},newJdbcConnectionOptions.JdbcConnectionOptionsBuilder().withUrl("jdbc:mysql://localhost:3306/testdb").withDriverName("com.mysql.jdbc.Driver").withUsername("root").withPassword("password").build()));env.execute("FlinkMySQLCDCExample");}//解析JSON的辅助方法(伪代码)privatestaticOrderparseOrderFromJson(Stringjson){//解析逻辑,返回Order对象returnnewOrder();}//订单类(POJO)publicstaticclassOrder{privateintid;privateintuserId;privatedoubleamount;privatejava.util.DatecreateTime;//gettersandsetterspublicintgetUserId(){returnuserId;}publicdoublegetAmount(){returnamount;}publicjava.util.DategetCreateTime(){returncreateTime;}}//订单汇总结果类publicstaticclassOrderSummary{privateintuserId;privatelongwindowEnd;privatedoubletotalAmount;publicOrderSummary(intuserId,longwindowEnd,doubletotalAmount){this.userId=userId;this.windowEnd=windowEnd;this.totalAmount=totalAmount;}//getterspublicintgetUserId(){returnuserId;}publiclonggetWindowEnd(){returnwindowEnd;}publicdoublegetTotalAmount(){returntotalAmount;}}}```注意:上述代码中解析JSON部分需要根据Debezium产生的JSON格式进行解析,实际格式比较复杂,包含元数据和数据变更内容。通常,我们关心的是`after`字段(代更新后的数据)和`op`字段(操作类型)。因此,解析时需要提取`after`字段中的内容。外,FlinkCDC也提供了直接解析为RowData的方式,可以简化处理。但这里为了通用性,使用了JSON格式。如果使用TableAPI,可以更简洁地实现,特别是Flink1.12支持了DDL创建CDC。使用TableAPI的示例(SQL风格):```java//创建环境StreamTableEnvironmenttableEnv=StreamTableEnvironment.create(env);//创建MySQLCDCtableEnv.executeSql("CREATETABLEorders("+"idINT,"+"user_idINT,"+"amountDOUBLE,"+"create_timeTIMESTAMP(3),"+"PRIMARYKEY(id)NOTENFORCED"+")WITH("+"'connector'='mysql-cdc',"+"'hostname'='localhost',"+"'port'='3306',"+"'username'='root',"+"'password'='password',"+"'database-name'='testdb',"+"'table-name'='orders'"+")");//创建结果(用于写入MySQL)tableEnv.executeSql("CREATETABLEorder_summary("+"user_idINT,"+"window_endTIMESTAMP(3),"+"total_amountDOUBLE,"+"PRIMARYKEY(user_id,window_end)NOTENFORCED"+")WITH("+"'connector'='jdbc',"+"'url'='jdbc:mysql://localhost:3306/testdb',"+"'table-name'='order_summary',"+"'username'='root',"+"'password'='password'"+")");//执行查询并插入结果tableEnv.executeSql("INSERTINTOorder_summary"+"SELECTuser_id,TUMBLE_END(create_time,INTERVAL'5'SECOND)aswindow_end,SUM(amount)astotal_amount"+"FROMorders"+"GROUPBYuser_id,TUMBLE(create_time,INTERVAL'5'SECOND)");```以上两种方式(DataStreamAPI和TableAPI)都可以实现需求。TableAPI更简洁,但需要熟悉SQL语法。总结:Flink实时处理MySQL数据流的关键在于使用CDC连接器捕获MySQL的变更数据,然后进行流式处理,最后将结果写入MySQL或其他存储。注意事项:-FlinkCDC连接器需要额外依赖。-实时处理需要事件时间,因此数据中需要包含时间戳字段。-写入MySQL时,要确保结果存在,且主键设置正确以避免重复。根据用户需求,我们提供了两种实现方式。用户可以根据自己的熟悉程度和项目要求选择。</think>### Fllink 实时计算 MySQL 数据流处理指南 Flink 处理 MySQL 实时数据流通常包含三个核心环节:**数据源接入 → 流式处理 → 结果输出**。以下是完整实现方案: --- #### 一、数据源接入(MySQL → Flink) Flink 支持两种 MySQL 接入方式: 1. **CDC 变更捕获**(推荐实时场景) ```java // 使用 Flink CDC 连接器 import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema; import com.ververica.cdc.connectors.mysql.MySqlSource; MySqlSource<String> mySqlSource = MySqlSource.<String>builder() .hostname("localhost") .port(3306) .databaseList("your_db") .tableList("your_db.orders") // 监控 .username("user") .password("pass") .deserializer(new JsonDebeziumDeserializationSchema()) .build(); DataStreamSource<String> cdcStream = env.fromSource( mySqlSource, WatermarkStrategy.noWatermarks(), "MySQL CDC Source" ); ``` 2. **JDBC 批连接**(定时轮询) ```java // 创建 JDBC 输入格式 JdbcInputFormat jdbcInput = JdbcInputFormat.buildJdbcInputFormat() .setDrivername("com.mysql.jdbc.Driver") .setDBUrl("jdbc:mysql://localhost:3306/db") .setUsername("user") .setPassword("pass") .setQuery("SELECT * FROM orders WHERE update_time > ?") .setRowTypeInfo(rowTypeInfo) .finish(); DataStream<Row> jdbcStream = env.createInput(jdbcInput) .assignTimestampsAndWatermarks(...); ``` --- #### 二、流式数据处理 实现实时计算逻辑(示例:每5分钟订单总额统计): ```java // 1. 转换数据流 DataStream<Order> orders = cdcStream.flatMap(json -> { // 解析 JSON 变更数据 Order order = parseDebeziumEvent(json); return Collections.singletonList(order); }); // 2. 开窗聚合 DataStream<OrderSummary> result = orders .keyBy(Order::getProductId) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .aggregate(new OrderAggregator()); // 3. 自定义聚合函数 public static class OrderAggregator implements AggregateFunction<Order, OrderAccumulator, OrderSummary> { @Override public OrderAccumulator createAccumulator() { return new OrderAccumulator(); } @Override public OrderAccumulator add(Order order, OrderAccumulator acc) { acc.totalAmount += order.getAmount(); acc.count++; return acc; } @Override public OrderSummary getResult(OrderAccumulator acc) { return new OrderSummary(acc.productId, acc.totalAmount, acc.count); } // ... merge 方法实现 } ``` --- #### 三、结果写入 MySQL 使用 JDBC Sink 输出计算结果: ```java // 创建 JDBC Sink JDBCAppendTableSink sink = JDBCAppendTableSink.builder() .setDrivername("com.mysql.jdbc.Driver") .setDBUrl("jdbc:mysql://localhost:3306/analytics_db") .setUsername("flink") .setPassword("flink_pass") .setQuery("INSERT INTO order_summary (product_id, total_amount, count) VALUES (?,?,?)") .setParameterTypes(BasicTypeInfo.INT_TYPE_INFO, BasicTypeInfo.DOUBLE_TYPE_INFO, BasicTypeInfo.INT_TYPE_INFO) .build(); // 转换 DataStream 为 Table Table resultTable = tableEnv.fromDataStream(result); tableEnv.registerTableSink("mysql_sink", sink.getTableSchema(), sink); // 写入 MySQL resultTable.insertInto("mysql_sink"); ``` --- #### 四、执行完整任务 ```java public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.enableCheckpointing(60_000); // 启用检查点 // 1. 构建数据源 DataStream<Order> source = buildSource(env); // 2. 实时计算 DataStream<OrderSummary> result = processStream(source); // 3. 结果输出 result.addSink(buildJdbcSink()); env.execute("MySQL Real-Time Processing"); } ``` --- ### 关键配置建议 1. **容错机制** - 启用检查点:`env.enableCheckpointing(interval)` - JDBC Sink 开启事务:`.setBatchSize(100).setMaxRetries(3)` 2. **性能优化** - 并行度设置:`env.setParallelism(4)` - CDC 参数:`.serverId("5400-5404")`(避免集群冲突) 3. **状态管理** - RocksDB 状态后端:适合大状态场景 ```java env.setStateBackend(new RocksDBStateBackend("hdfs:///checkpoints")); ``` --- ### 典型应用场景 1. 实时订单分析仪盘 2. 用户行为实时统计 3. 库存变更实时预警 4. 金融交易实时风控[^1]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值