最近我们有一些涉及到数据库的单元测试,本身很简单:
final ZonedDateTime beforeUpdate = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
repository.upsert(entity);
ZonedDateTime updatedAt = repository.findById(entity.getId()).getUpdatedAt();
ZonedDateTime afterUpdate = ZonedDateTime.now().truncatedTo(ChronoUnit.SECONDS);
assertFalse(updatedAt.isBefore(beforeUpdate));
assertFalse(afterUpdate.isBefore(updatedAt));
updatedAt是数据库的updated_at字段,beforeUpdate/afterUpdate是跑upsert前/后的系统时钟。因为数据库的DATETIME只精确到秒,所以beforeUpdate/afterUpdate也截断到秒。整个测试就是确保updatedAt是发生在beforeUpdate和afterUpdate之间。
这个测试运行时需要连接跑在本地Docker里的一个临时MariaDB server。在Linux上跑一直没问题,在Mac上跑就会有非常小的概率会随机在最后一行fail,也就是说数据库的时钟比系统时钟更快一点。
因为Mac版Docker其实是跑了一个Linux VM,再在VM里跑container,所以怀疑是VM的系统时钟和Mac本机不一致。查了一下果然是:
Addressing Time Drift in Docker Desktop for Mac - Docker Blogwww.docker.com原因是不论Host还是VM,读硬件实时时钟(RTC)都是很慢的操作,所以VM内一般用RDTSC来读CPU的time stamp counter(TSC),作为时钟来源。但因为种种原因,这个counter并不可靠,如果不加校正的话,就会出现下面这种情况——每25分钟累积大约1秒的差异。
Docker Mac版想了各种办法来定时纠正这个差异,最后采取的办法是每30秒纠正一次差异,从落后host 15毫秒变成比host快5毫秒:
而这20毫秒的差异,已经足够使我们的测试在小概率下失败了。