活動倒計時秒殺問題
我們知道電商裡有很多場景,就是活動倒計時的秒殺問題,比如:明天10點開始搶購,倒計時2分鐘等,那麼這個到底有多精確呢?是不是2分鐘時間剛剛到活動就準時開始呢?我們今天先來說下倒計時,改天說秒殺問題。
我說一下做法,這裡有三種方案,是我思考出來的,可能不對,也可能有更好的方案,僅供參考吧。
首先還有一張表,記錄著活動商品,裡面有活動的狀態、價格,標籤,最重要的是有活動的開始時間
1、新建一個快取,key為固定的,查詢活動商品表,把所有的活動待開始的商品,放進該快取裡。然後寫個定時任務,可以每5秒(如果要很精準的話,那就每秒去查詢該快取),去查詢該快取key,獲取到所有的待開始的商品list,遍歷這個list,拿每條資料的開始時間與當前時間相比,如果開始時間 <= new Date()時,表明該商品活動已經開始了,List<Integer> needUpdates = new ArrayList<>();那就用needUpdates.add(該商品ID),用來最後批量更新的,把這些商品的狀態由 未開始 批量更新為 進行中;更新DB成功後,要將剛剛活動已開始的商品,從快取中移除掉,由於redis沒有提供按照index來操作list中的某一條資料,那我們的做法是,jedis.del(key),然後再查詢DB,把所有的活動待開始的商品,放進該快取裡,全刪全增。
List<Dto> list = jedis.get(key);
for(Dto dto : list){
if(dto.getStartTime().getTime() <= new Date().getTime()){//商品已達到活動時間了
needUpdates.add(dto.getId());
contiune;
}
contiune;
}
if(Collentions.isNotEmpty(needUpdates)){
mapper.updateBatch(needUpdates);//活動狀態批量更新為 進行中
jedis.del(key);
jedis.set(key,"DB裡所有待開始的活動商品")
}
2、上面的做法,可以滿足需求,但是要全刪全增快取,為了避免這種做法,我們可以藉助redis中list型別裡的rpop和lpop來做。新建一個快取,key是固定的,查詢活動商品表,把所有的活動待開始的商品,按照活動開始時間進行從大到小排序,放進該快取裡。然後開啟一個定時任務,每5秒執行一次,List<Dto> list = jedis.get(key); 得到排序好的list。
List<Integer> needUpdates = new ArrayList<>();
for(Dto dto : list){
if(dto.getStartTime().getTime() <= new Date().getTime()){//商品已達到活動時間了
jedis.Lpop(key);//移出並獲取列表的第一個元素,從快取中移除該條資料
needUpdates.add(dto.getId());
contiune;
}else{//因為是按開始時間從大到小排序的,所以上面的條件不滿足,下面的資料也不需要判斷了
break;
}
}
if(Collentions.isNotEmpty(needUpdates)){
mapper.updateBatch(needUpdates);//活動狀態批量更新為 進行中
}
這樣的話,定時任務裡就不用全刪和全增資料了,但是在後臺,新增或更新活動商品的時候,還是要刪除快取,查詢資料並按開始時間排序,放進快取的,也需要全刪全增。
3、與第二種類似,只不過這裡藉助redis裡的有序集合,這樣的話,就不用我們自己按照時間排序了,讓redis來幫我們排序,直接上虛擬碼吧。
後臺新增或者更新活動商品的時候
ZADD key 開始時間的毫秒數1 dto1(資料)
ZADD key 開始時間的毫秒數2 dto2(資料)
ZADD key 開始時間的毫秒數3 dto3(資料)。。。
這樣存到redis裡,redis就會自動幫我們排序了,把 開始時間的毫秒數 最大的放在第一位
然後起一個定時任務,每5秒執行一次,List<Dto> list = jedis.get(key);
for(Dto dto : list){
if(dto.getStartTime().getTime() <= new Date().getTime()){//商品已達到活動時間了
zremrangebyrank key 0 0;//從快取裡移除掉第一個資料
needUpdates.add(dto.getId());
contiune;
}else{//因為是按開始時間從大到小排序的,所以上面的條件不滿足,下面的資料也不需要判斷了
break;
}
}
if(Collentions.isNotEmpty(needUpdates)){
mapper.updateBatch(needUpdates);//活動狀態批量更新為 進行中
}
這個方案比較第二種,就是少了手動記憶體排序這一步驟,提升效率。