之前在一篇文章中提到过,因为业务的集群限流需求,在每次请求都需要拿到当前的日期,不过精确到天即可。上次给出的解决方案是,因为Calendar的性能问题,选择更加直接粗暴的方式,就是下面这个。
private static final int DAY_MILLIS = 24 * 60 * 60 * 1000;
long day = System.currentTimeMillis() / DAY_MILLIS;
通过当前的时间戳(毫秒级别),除以一天的毫秒数,得到的结果就是从1970 到今天经历过的天数。
最近业务在使用限流功能时,需要实现限制接口每天调用1w次。
只是,第一个版本的实现逻辑是这样的,如果第一个请求是早上8点过来的,那么会在redis创建一个对应的key,并设置24小时过期。所以从今天早上8点到明天早上8点,只能调用1w次,多余的就拒绝,所以每天限制的时间段可能是不一样的。
业务提出了新需求:必须按照0点到第二天0的时间段进行限制。
对于这个需求,我表示会心一笑,上述的粗暴方案不是正好满足么,三下五除二,就给加上了这个功能。
if (timeUnit == TimeUnit.DAYS) {
key = ruleId + TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
如果业务配置的限流周期是天,那么就在原始id上再加上当前的天数当前redis的key,那么每天的key都是不一样的,新的一天,都会有一个新的key,如此的美好。
业务使用改进后的版本上线了2天,甩过来一个监控页面,开始diss我们了,为什么昨天触发了限流,今天凌晨2点多还在限流。看着埋点数据,发现这个锅是甩不掉了,只能开始翻代码,无果。
后来通过redis的埋点数据,发现2点多操作的redis key上还是昨天的天数。很奇怪,有木有?明明已经2点了,怎么还是昨天的天数,我已经开始怀疑服务器的时间是不是有问题,居然鬼使神差的让业务去验证服务器的时间,结果是我被打脸了。
到最后,我才把关注点放在了System.currentTimeMillis()
这个返回值上,这个返回的时间戳是从1970开始到现在格林威治时间的时间戳,而北京时间比格林威治整整快了8个小时。
所以真相是,通过暴力方案返回的天数,在北京时间早上8点之前都算是之前一天,过了8点,才算是新的一天,才会使用新的redis key进行计数。
继续修复,继续发版,下次在使用这种暴力方案时,考虑一下时区问题。