之前在一篇文章中提到過(guò),因?yàn)闃I(yè)務(wù)的集群限流需求,在每次請(qǐng)求都需要拿到當(dāng)前的日期,不過(guò)精確到天即可。上次給出的解決方案是,因?yàn)镃alendar的性能問(wèn)題,選擇更加直接粗暴的方式,就是下面這個(gè)。
private static final int DAY_MILLIS = 24 * 60 * 60 * 1000;
long day = System.currentTimeMillis() / DAY_MILLIS;
通過(guò)當(dāng)前的時(shí)間戳(毫秒級(jí)別),除以一天的毫秒數(shù),得到的結(jié)果就是從1970 到今天經(jīng)歷過(guò)的天數(shù)。
最近業(yè)務(wù)在使用限流功能時(shí),需要實(shí)現(xiàn)限制接口每天調(diào)用1w次。
只是,第一個(gè)版本的實(shí)現(xiàn)邏輯是這樣的,如果第一個(gè)請(qǐng)求是早上8點(diǎn)過(guò)來(lái)的,那么會(huì)在redis創(chuàng)建一個(gè)對(duì)應(yīng)的key,并設(shè)置24小時(shí)過(guò)期。所以從今天早上8點(diǎn)到明天早上8點(diǎn),只能調(diào)用1w次,多余的就拒絕,所以每天限制的時(shí)間段可能是不一樣的。
業(yè)務(wù)提出了新需求:必須按照0點(diǎn)到第二天0的時(shí)間段進(jìn)行限制。
對(duì)于這個(gè)需求,我表示會(huì)心一笑,上述的粗暴方案不是正好滿(mǎn)足么,三下五除二,就給加上了這個(gè)功能。
if (timeUnit == TimeUnit.DAYS) {
key = ruleId + TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis());
}
如果業(yè)務(wù)配置的限流周期是天,那么就在原始id上再加上當(dāng)前的天數(shù)當(dāng)前redis的key,那么每天的key都是不一樣的,新的一天,都會(huì)有一個(gè)新的key,如此的美好。
業(yè)務(wù)使用改進(jìn)后的版本上線了2天,甩過(guò)來(lái)一個(gè)監(jiān)控頁(yè)面,開(kāi)始diss我們了,為什么昨天觸發(fā)了限流,今天凌晨2點(diǎn)多還在限流??粗顸c(diǎn)數(shù)據(jù),發(fā)現(xiàn)這個(gè)鍋是甩不掉了,只能開(kāi)始翻代碼,無(wú)果。
后來(lái)通過(guò)redis的埋點(diǎn)數(shù)據(jù),發(fā)現(xiàn)2點(diǎn)多操作的redis key上還是昨天的天數(shù)。很奇怪,有木有?明明已經(jīng)2點(diǎn)了,怎么還是昨天的天數(shù),我已經(jīng)開(kāi)始懷疑服務(wù)器的時(shí)間是不是有問(wèn)題,居然鬼使神差的讓業(yè)務(wù)去驗(yàn)證服務(wù)器的時(shí)間,結(jié)果是我被打臉了。
到最后,我才把關(guān)注點(diǎn)放在了System.currentTimeMillis()這個(gè)返回值上,這個(gè)返回的時(shí)間戳是從1970開(kāi)始到現(xiàn)在格林威治時(shí)間的時(shí)間戳,而北京時(shí)間比格林威治整整快了8個(gè)小時(shí)。
所以真相是,通過(guò)暴力方案返回的天數(shù),在北京時(shí)間早上8點(diǎn)之前都算是之前一天,過(guò)了8點(diǎn),才算是新的一天,才會(huì)使用新的redis key進(jìn)行計(jì)數(shù)。
繼續(xù)修復(fù),繼續(xù)發(fā)版,下次在使用這種暴力方案時(shí),考慮一下時(shí)區(qū)問(wèn)題。