脚本书写及执行注意事项

怎样写出健壮友好的脚本?

工作中经常会写一些脚本来批量处理一些数据,有时一个脚本的执行时间可能长达数小时,而且中间随时可能出错中断,这可能是脚本对某些情况考虑不够全面,也可能是脏数据的问题。那么,如何才能写出健壮的脚本,让整个任务一气呵成呢?以下就根据我丰(cai)富(guo)的(de)经(keng)验来谈一谈。

  1. 数据备份。避免脚本异常执行所导致数据无法恢复。
  2. 异常处理。当异常发生时,我们可以视情况决定是终止脚本,还是记录失败的数据继续执行,但无论哪种方式,都需要记录发生异常的位置和上下文信息,便于快速定位问题,并在修复问题后继续执行。尤其需要注意的是,有些数据二次执行会引发问题,因此正确记录异常位置,实现“断点续传”非常重要
  3. 日志记录。对于被执行的内容需要详细的记录,这样可以在发生异常或者在脚本结束时查看整个执行过程。需要注意的是,日志记录是追加而不是覆写,而且要注意日志的换行。
  4. 统计信息。记录脚本的开始、结束时间,内存消耗情况,以及执行统计信息,如:共需执行m条数据,成功执行n条,执行失败x条,执行总耗时y等信息,还可以根据统计信息在脚本健壮执行后再进一步优化
  5. 脚本测试。在真实数据执行前,一定要先在 mock 数据上验证一下脚本,有时数据量的不同也会导致脚本执行异常,所以 mock 数据量也要接近真实环境
  6. 结果通知。执行完成通过邮件等方式通知,不必一直盯着脚本
  7. Server 执行。服务器相比个人电脑性能和网络方面等更有优势,执行环境更快、更稳定。之前我一个脚本 Mac 执行20分钟,Server 执行2分钟😂
  8. 会话保持。使用screen或者tmux,远程登录服务器 ==> 创建会话 ==> 会话分离 ==> 退出服务器 ==> do somethin ==> 收到结果通知 ==> 重新登录服务器 ==> 会话恢复 ==> 查看执行结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?php
function refactorData() {
try {
$timeStart = microtime(true);
$startDesc = date('Y-m-d H:i:s') . ' 开始执行!' . PHP_EOL;
$logFile = __DIR__ . '/refactor_data.log';
echo $startDesc;

file_put_contents($logFile, $startDesc);

//数据初始化
$updateData = [];
$success = $failed = $exception = 0;
foreach ($data as $datum) {
if (false) {
$failed++;
continue;
file_put_contents(logFile, $datum['id'] . '由于xxx执行失败' . PHP_EOL, FILE_APPEND);
}

if (false) {
$exception++;
continue;
file_put_contents(logFile, $datum['id'] . '由于xxx无法执行' . PHP_EOL, FILE_APPEND);
}

//处理脚本...
file_put_contents($logFile, $datum['id'] . PHP_EOL, FILE_APPEND);
$success++;
}

$timeStop = microtime(true);
$endDesc = date('Y-m-d H:i:s') . '执行结束!' . PHP_EOL;
file_put_contents($logFile, $endDesc, FILE_APPEND);
echo $endDesc;

// 格式化执行耗时
$seconds = round($timeStop - $timeStart, 3);
switch ($seconds) {
case $seconds > 60 && $seconds < 3600:
$elapsed = intval($seconds / 60) . 'm' . ($seconds % 60) . 's';
break;
case $seconds > 3600 && $seconds < 86400:
$elapsed = intval($seconds / 3600) . 'h' . intval($seconds % 3600 / 60) . 'm' . ($seconds % 60) . 's';
break;
default :
$elapsed = $seconds . 's';
}

// 格式化内存单位
$formatStorageUnit = function ($scale) {
$gb = 1 << 30;
$mb = 1 << 20;
$kb = 1 << 10;
switch ($scale) {
case $scale >= $gb:
$humanUnit = round($scale / $gb, 2) . 'GB';
break;
case $scale >= $mb:
$humanUnit = round($scale / $mb, 2) . 'MB';
break;
case $scale >= $kb:
$humanUnit = round($scale / $kb, 2) . 'KB';
break;
default:
$humanUnit = $scale . 'B';
}

return $humanUnit;
};

$total = 999;
$summary = '=============统计摘要信息================' . PHP_EOL .
'本次共需处理' . $total . '条数据,成功' . $success . '条,失败' . $failed . '条,无法处理' .
$exception . '条。' . PHP_EOL . '执行耗时' . $elapsed . ', 内存消耗' . $formatStorageUnit(memory_get_usage()) .
',内存消耗峰值' . $formatStorageUnit(memory_get_peak_usage()) . PHP_EOL;

file_put_contents($logFile, $summary, FILE_APPEND);
echo $summary;

// 邮件通知执行结果
(new Mail())->sendMail([
'title' => 'xxx脚本执行完成',
'body' => $summary,
'receiver' => '[email protected]'
]);
} catch (Exception $e) {
// 邮件通知执行结果
(new Mail())->sendMail([
'title' => 'xxx脚本执行失败',
'body' => '执行失败,错误原因:' . $e->getMessage(),
'receiver' => '[email protected]'
]);
}
}

目前就是这些了,再有新的经验随时补充:)

因为热爱,所以执着。