「这是我参与11月更文挑战的第32天,活动详情查看:2021最后一次更文挑战」。
原创不易,望多多关注、多多点赞🙇👍
事故描述
公司的app客户端会上报一些用户数据到Java后台服务,其中有一个点击时间
的字段。今天在巡查日志的时候,发现了大量该保存该字段是的error日志。
如下:
Data truncation: Incorrect datetime value: '53884-04-07 04:09:44' for column 'clickTime' at row 1
伪代码
/**
* 根据日期格式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
方法验证一下。
现场还原
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"));
}
结果和预想的一样,果然是因为毫秒的问题。
解决问题
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
方法模拟一下:
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"));
}
发现,原来 SimpleDateFormat
的 format()
方法会对所有数字类型都进行格式化,这一点大家一定要注意了。
这次是真的解决了
把代码继续兼容优化:
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);
}
}
同时和客户端的同事沟通,统一时间单位。