摘要: 概念 幂等这个词在软件研发中经常被提到。比如消息发送时不应该同时给同个用户推送多次相同的消息,针对同一笔交易的付款也不应该在重试过程中扣多次钱。曾见过一个案例,有个对于一个单据的确认模块没有考虑到幂等性,导致对应的单据有两条确认记录。
点此查看原文:http://click.aliyun.com/m/42758/
概念
幂等这个词在软件研发中经常被提到。比如消息发送时不应该同时给同个用户推送多次相同的消息,针对同一笔交易的付款也不应该在重试过程中扣多次钱。曾见过一个案例,有个对于一个单据的确认模块没有考虑到幂等性,导致对应的单据有两条确认记录。其实幂等这个词是个数学的概念,表示这个操作执行多次的结果和执行一次是完全一样的。严格的定义这里不展开讨论,有兴趣的可以到网上搜一下,会有很多介绍。通俗一些说,幂等表示这个操作可以多次重跑,不用担心重跑后到结果会乱掉。就赋值而言,i=1就是个幂等到操作,无论做多少次赋值,只要有做成功一次,i的值就是1。而i++就不是一个幂等的操作。如果多次执行这个操作,i的值会不断增加1。
从前面的例子也可以看出,幂等的优势是可以屏蔽重试带来的问题。在分布式的环境里,一般会通过消息中间件、异步调用等方式实现服务之间的解耦。在这过程中,如出现系统异常状况下的状态不明确的情况,一般会进行重试。如果应用不满足幂等的要求,则就会出现错误的结果。
离线计算与幂等
离线计算里的一个作业经常是非常重的,跑一个作业要较多时间。而且由于其特性,经常是凌晨开始计算,在OLTP业务调用量上来以前需要产出结果。如果发现问题,经常没有太多的时间留给技术人员去详细定位问题的原因,然后清理脏数据后重新进行计算。这时候我们需要计算能够进行任意次的重跑,也就是说计算需要满足幂等性。对于一个满足幂等性要求的作业,出现问题的时候,我们可以首先先重跑一下作业,以期能尽快恢复业务,后续再根据之前的日志慢慢定位问题。下面我们以MaxCompute+ DataWorks为例,从不同的角度里讨论离线计算的典型场景——离线数仓,看看都有哪些地方需要做到幂等以及如何做到。
计算
目前的离线计算,出于开发的效率考虑,一般都会考虑使用SQL进行代码开发。SQL里包含DDL和DML两种语句。除了SQL,计算引擎一般还支持MapReduce、Graph等计算模型。
DDL
DDL语法可以通过语句里的if exists/if not exists来确保幂等性。比如创建表可以用create table if not exists xxx,删除表可以通过drop table if exists xxx来保证不报错而且可以重复执行。当然创建表也可以先删除后再创建来事先幂等性。诚然,如果是建表这种“一次性”的操作可以在上线的时候手工做好,但是日常的分区创建/删除等操作就需要通过写进代码里,通过if exists/if not exists来保证可以重试。
DML
DML对数据有影响的是Insert操作。目前Insert有两种模式:Insert into和Insert overwrite。其中Insert into是把数据追加到原来的数据里,而Insert overwrite是把以前的数据直接覆盖。所以可以清楚得看到,Insert into是不满足幂等性要求的,而Insert overwrite是满足的。如果使用Dataworks的SQL节点跑一个Insert into的作业,会有提示
!!!警告!!!
在SQL中使用insert into语句有可能造成不可预料的数据重复,尽管对于insert into语句已经取消SQL级别的重试,但仍然存在进行任务级别重试的可能性,请尽量避免对insert into语句的使用!
我也曾去了解一些使用Insert into的用户,要使用这种数据更新方式的原因,除去数据订正,发现一般都是针对一些不会变化的数据(比如网站的日志、每天的统计结果等)每天需要追加到表里。其实这种更好的方法是创建一个分区表,把每天需要Insert into的数据改成Insert overwrite到每天的一个不同分区里。
MapReduce
MapReduce默认就是使用覆盖写入的模式的。如果确实有需要追加写入,可以使用com.aliyun.odps.mapred.conf.JobConf的setOutputOverwrite(boolean isOverwrite)来实现。
如果需要改成幂等的,可以使用前面SQL里提到的,把数据写入特定的分区里来实现。
ETL
ETL我们暂时不考虑数据清洗(一般数据清洗是通过计算来实现的),而这只讨论数据的同步。在Dataworks里,数据的同步通过数据集成模块来实现。在数仓中,数据同步包括数据导入到数仓和数据从数仓中导出两种场景。
数据导入的场景要实现幂等性比较容易。只需要设置导入的MaxCompute表的清洗规则为“写入前清理已有数据Insert Overwr”即可。这样数据在导入的过程中会先清空数据后再导入,从而实现幂等。
数据导出的场景,如果数据是全量导出的,也可以用类似数据导入的方法,配置“导入前准备语句”,把原来的数据全部删除后重新导入。另外如果数据源支持主键冲突设置时,可以通过“主键冲突”设置成“Replace Into”来实现数据的替换。
从上面的截图里可以看到,目前Dataworks本身就支持设置“出错重试”,如果同步作业满足幂等性要求的,可以大胆开启这个设置,从而降低运维成本提高稳定性。