作者简介
中国工商银行软件开发中心广州技术部
小明是一家大型公司的运维支持经理,凌晨三点他收到了批量中断的报警信息,他不得不拖着疲惫的身体来到电脑前,点开报警信息,又是熟悉的数据库异常,他点了点鼠标,重新启动批量作业处理。经常需要半夜处理的批量中断使他感到非常沮丧,他想这种情况能否有所改变?
开发的小伙伴决定提升自动化运维水平,当遇到批量中断时,系统能自动重新启动批量作业。当然,并非所有批量中断都可以自动重新启动的,例如粗心的开发人员写错了代码,重提也只能继续中断,甚至可能会引发重复入账等更严重的后果。一般只有环境抖动之类的问题,才适合自动重新启动批量作业。那么看看批量运行依赖的环境和资源以及可能发生的异常。
批量作业主要依赖的外部环境和资源有:批量运行框架、数据库、文件服务器、分布式消息,下表梳理了可能发生的异常及应对措施:
又对 MySQL 数据库的错误进行了细化分析,认为以下几个错误码可进行重提:
另外对数据库访问语句的 SocketTimeoutException 异常,也可进行重提,例如:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 3,008 milliseconds ago. The last packet sent
successfully to the server was 3,006 milliseconds ago.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:989)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3556)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3456)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3897)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2524)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2677)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2545)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2503)
at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1369)
at Test.main(Test.java:36)
Caused by: java.net.SocketTimeoutException: Read timed out
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3005)
at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3466)
... 8 more
经过对异常的分析,开发的小伙伴要开始动手改造批量作业了。但是他们又遇到更棘手的问题。面对成千上万个批量作业,每个代码都要添加自动重提的功能,开发人员又得干几个通宵了。
还好,批量作业是由批量运行框架统一调度的,可以通过对批量运行框架进行改造,提供统一的作业重提机制。
批量运行框架分为批量控制器和批量执行器两个角色:批量控制器负责作业的调度,即根据控制器数据库中的作业排程信息,给执行器发送作业开始指令,并接收执行器的作业执行结果;批量执行器由应用开发,负责调用业务逻辑,且业务逻辑的实现基于同一个接口。
由此看出,要实现自动重新启动批量作业,涉及批量控制器和批量执行器的改造。
批量控制器支持重提的改造要点:
控制器新增支持执行器返回重提状态(原来只有成功、失败两种状态)。当控制器收到重提状态后,若作业已重提次数小于排程中定义的自动重提最大次数(防止无限次重试),则将作业状态设置为待重提状态,否则将作业置为失败状态。
环境抖动问题,一般是等待一段时间,环境恢复后再重试,因此控制器启动后台定时任务,每隔一定时间扫描作业,如果作业状态为待重提,则给批量执行器发送开始指令,并将作业状态更新为执行中。
批量执行器支持重提的改造要点:
执行器基于 Spring 开发,负责运行批量作业。每个批量作业是一个JAVA类,并且都是实现同一个接口。Spring 切面技术真是个好东西,可以减少对业务代码的侵入,也避免了每个批量作业程序进行修改。切面在 process 方法执行结束后进行加工处理,如果作业发生了异常,并判断该异常可以重提的话,则打印异常,并给框架返回重提状态。
批量运行异常自动重提的改造方案投入生产后,可以自动识别并快速处理异常的作业,降低对业务的影响,同时避免支持经理人工干预,减少人工出错的可能性。公司信息系统的自动化运维水平得到了极大的提升。
运维支持经理小明很少需要在半夜处理批量中断,良好的休息提升了工作效率,为公司创造更多价值。
近期好文: