【当心】一次日期格式转化的线上事故

「这是我参与11月更文挑战的第32天,活动详情查看:2021最后一次更文挑战」。

原创不易,望多多关注、多多点赞🙇‍👍

事故描述

公司的app客户端会上报一些用户数据到Java后台服务,其中有一个点击时间的字段。今天在巡查日志的时候,发现了大量该保存该字段是的error日志。

如下:

Data truncation: Incorrect datetime value: ‘53884-04-07 04:09:44’ for column ‘clickTime’ at row 1

伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码/**
* 根据日期格式DateTime转String
*/
public static String dateTimeMillisToString(long time, String pattern) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(time);
return (new SimpleDateFormat(pattern)).format(calendar.getTime());
}

/**
* 保存客户端上报的用户数据
*/
public void save(User user) {
// 注意这里要将 时间*1000 转换成毫秒数
String time = dateTimeMillisToString(user.getClickTime() * 1000, "yyyy-MM-dd HH:mm:ss");
user.setCreateTime(time);
save(user);
}

猜想

根据异常日志和源代码,我们猜想可能是因为有些客户端没有按原定的以为单位来上报,而是使用的毫秒为单位。

为了验证猜想,决定写个main方法验证一下。

现场还原

1
2
3
4
5
6
java复制代码public static void main(String[] args) {
long time1 = 1638263956L;
long time2 = 1638263956000L;
System.out.println(dateTimeMillisToString(time1 * 1000, "yyyy-MM-dd HH:mm:ss"));
System.out.println(dateTimeMillisToString(time2 * 1000, "yyyy-MM-dd HH:mm:ss"));
}

结果和预想的一样,果然是因为毫秒的问题。

image.png

解决问题

1
2
3
4
5
6
7
8
9
10
java复制代码String time = user.getClickTime();
if (StringUtils.isNotBlank(time)) {
if (time.length() == 10) {
// 10位,表示该时间以秒为单位
time = dateTimeMillisToString(time * 1000, YYYYMMDD_HHMMSS);
} else if (time.length() == 13) {
// 13位,表示该时间以毫秒为单位
time = dateTimeMillisToString(time, YYYYMMDD_HHMMSS);
}
}

你以为这样就结束了吗?

修复好发生产后,却爆发了更多的异常,量级是原来的十多倍,我一下子慌了神,赶紧找运维大佬回滚版本。

这次的异常日志如下:

Data truncation: Incorrect datetime value: ‘0’ for column ‘clickTime’ at row 1

Data truncation: Incorrect datetime value: ‘1’ for column ‘clickTime’ at row 1

原来客户端还上报了数量庞大的 0 和 1。

当该字段长度不为10或13时,程序中是不做任何处理,直接插入到数据库的,数据库表结构中该字段为 datetime 类型的,所以当保存 0 或 1 时会报错。

那我们之前将 0 或 1, 转换后保存的究竟时什么呢? 再次通过 main 方法模拟一下:

1
2
3
4
5
6
java复制代码public static void main(String[] args) {
System.out.println(dateTimeMillisToString(0 * 1000, "yyyy-MM-dd HH:mm:ss"));
System.out.println(dateTimeMillisToString(1 * 1000, "yyyy-MM-dd HH:mm:ss"));
// 9位长度的时间戳
System.out.println(dateTimeMillisToString(163826395 * 1000, "yyyy-MM-dd HH:mm:ss"));
}

image.png

发现,原来 SimpleDateFormatformat() 方法会对所有数字类型都进行格式化,这一点大家一定要注意了。

这次是真的解决了

把代码继续兼容优化:

1
2
3
4
5
6
7
8
9
java复制代码String time = user.getClickTime();
if (StringUtils.isNotBlank(time)) {
// 大于10位长度,则不再将 时间*1000
if (time.length() > 10) {
time = dateTimeMillisToString(time, YYYYMMDD_HHMMSS);
} else {
time = dateTimeMillisToString(time * 1000, YYYYMMDD_HHMMSS);
}
}

同时和客户端的同事沟通,统一时间单位。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%