Redis實(shí)戰(zhàn)(9)-SortedSet實(shí)戰(zhàn)之再談?dòng)螒虺渲蹬判邪?如何處理歷史與異常的充值記錄)
作者:
修羅debug
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。
摘要:每當(dāng)我們談起緩存中間件Redis的應(yīng)用場景時(shí),我們一般都會(huì)根據(jù)其數(shù)據(jù)結(jié)構(gòu)聯(lián)想到對(duì)應(yīng)的應(yīng)用場景,有序集合SortedSet也不例外,“排行榜”一直都是與其緊密掛鉤、不得不談的其中一種實(shí)戰(zhàn)場景!本文我們將繼續(xù)再談“游戲充值排行榜”,介紹如何去處理歷史已經(jīng)存在的充值記錄 或者 在將充值記錄塞入緩存Cache失敗時(shí)如何開啟后續(xù)的補(bǔ)償處理措施!
內(nèi)容:在上篇文章中,我們已經(jīng)給各位小伙伴介紹了如何基于Spring Boot2.0 + 緩存Redis的SortedSet以實(shí)際的代碼實(shí)戰(zhàn)一種典型的業(yè)務(wù)場景“游戲充值排行榜”,在文中我們介紹了這一業(yè)務(wù)場景兩大典型的核心功能模塊,即“用戶充值”、“獲取充值排行榜”,各位小伙伴可以自行前往回顧!
然而,這世間本就沒有十全十美之物,“游戲充值排行榜”這一業(yè)務(wù)場景也不例外,雖然我們基本上已經(jīng)實(shí)現(xiàn)了該業(yè)務(wù)場景幾乎所有的功能模塊,但是我們卻忽略了其他兩種情況:
A.如果“充值排行榜”這一功能模塊是增量式的需求,那么上線時(shí)如何去處理歷史的用戶充值記錄呢?你總不能說我們的“充值排行榜”對(duì)于以往充值的用戶記錄不生效吧?(那樣豈不令人笑掉大牙?。?/span>
B.雖然我們的代碼看似完美,但是要知道Bug是無處不在的,這些Bug有的是能一眼被洞穿的,也有的是后知后覺的,“用戶充值的過程”便是如此,如果用戶充值后插入數(shù)據(jù)庫DB成功、但是插入緩存Cache失?。―B事務(wù)不回滾的前提),那毫無疑問,最終得出來的“充值排行榜”一定是不準(zhǔn)確的(因?yàn)槲覀兪侵苯訌木彺鍾edis中獲取的)!
帶著這兩大問題,我們給大家提供了一種并非十全十美的,但是卻能保證“最終一致性”的充值排行榜的解決方案,那就是萬能的定時(shí)任務(wù)調(diào)度!
既然是定時(shí)任務(wù)調(diào)度,那么這個(gè)定時(shí)任務(wù)是做啥的呢?沒錯(cuò),它要完成的任務(wù)就是開啟一個(gè)定時(shí)時(shí)鐘,基于數(shù)據(jù)庫DB中的“用戶充值記錄表”,借助數(shù)據(jù)庫提供的Order By、Group By等查詢得出目前為止所有有效用戶的“充值排行榜”,下面我們以實(shí)際的代碼進(jìn)行實(shí)戰(zhàn)。
(1)直接建立一個(gè)定時(shí)任務(wù)調(diào)度類PhoneFareScheduler,并開發(fā)相應(yīng)的方法實(shí)現(xiàn)具體的定時(shí)任務(wù)邏輯,其完整源代碼如下所示:
/**補(bǔ)償機(jī)制:手機(jī)號(hào)碼充值排行榜
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260**/
@Component
public class PhoneFareScheduler {
private static final Logger log= LoggerFactory.getLogger(PhoneFareScheduler.class);
@Autowired
private PhoneFareMapper phoneFareMapper;
@Autowired
private RedisTemplate redisTemplate;
//時(shí)間頻度設(shè)定為30min,當(dāng)然啦,具體的設(shè)定要根據(jù)實(shí)際情況而定
@Scheduled(cron = "0 0/30 * * * ?")
public void sortFareScheduler(){
log.info("--補(bǔ)償性手機(jī)號(hào)碼充值排行榜-定時(shí)任務(wù)");
this.cacheSortResult();
}
@Async("threadPoolTaskExecutor")
private void cacheSortResult(){
try {
ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet();
List<PhoneFare> list=phoneFareMapper.getAllSortFares();
if (list!=null && !list.isEmpty()){
redisTemplate.delete(Constant.RedisSortedSetKey2);
list.forEach(fare -> {
FareDto dto=new FareDto(fare.getPhone());
zSetOperations.add(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue());
});
}
}catch (Exception e){
log.error("--補(bǔ)償性手機(jī)號(hào)碼充值排行榜-定時(shí)任務(wù)-發(fā)生異常:",e.fillInStackTrace());
}
}
}
值得一提的是,在該定時(shí)任務(wù)調(diào)度中我們?cè)O(shè)定的時(shí)間頻率為 每30min進(jìn)行執(zhí)行一次任務(wù),實(shí)現(xiàn)“充值排行榜”的大洗盤!也就是說,如果前端“排行榜”頁面數(shù)據(jù)出現(xiàn)差錯(cuò),那么其恢復(fù)正確的等待時(shí)間是30min(因?yàn)槲覀兊亩〞r(shí)任務(wù)就是前往數(shù)據(jù)庫DB,查詢獲取得到排行榜,當(dāng)然啦,其前提是保證DB中的數(shù)據(jù)是正確無誤的?。?/span>
(2)其中,phoneFareMapper.getAllSortFares() 的作用就是前往數(shù)據(jù)庫Mysql,通過Group By、Order By和SUM等查詢得到排行榜,其完整的動(dòng)態(tài)SQL如下所示:
<!--基于數(shù)據(jù)庫的補(bǔ)償排名機(jī)制-->
<select id="getAllSortFares" resultType="com.boot.debug.redis.model.entity.PhoneFare">
SELECT
phone,
SUM(fare) AS fare
FROM
phone_fare
GROUP BY
phone
ORDER BY
fare DESC
</select>
除此之外,@Async("threadPoolTaskExecutor") 的作用便是采用“線程池-多線程的方式異步執(zhí)行定時(shí)任務(wù)”,故而我們需要作一個(gè)全局的Config,用于配置線程池-多線程的相關(guān)信息:
/**線程池-多線程配置
* @Author:debug (SteadyJack)
* @Link: weixin-> debug0868 qq-> 1948831260**/
public class ThreadConfig {
@Bean("threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setKeepAliveSeconds(10);
executor.setQueueCapacity(8);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
至此,我們已經(jīng)擼完了“游戲充值排行榜”這一完整業(yè)務(wù)的“補(bǔ)償機(jī)制”功能代碼了,在測試之前,我們先“偷偷”在數(shù)據(jù)庫表phone_fare中新增幾條充值記錄,代表“以前存在的歷史充值記錄”或者“插入DB成功,但插入緩存失敗的充值記錄”,如下圖所示:
最后我們基于Postman測試一波吧,下面一張圖足以說明一切了:
好了,本篇文章我們就介紹到這里了,建議各位小伙伴一定要照著文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談?wù)摺保?/span>
對(duì)Redis相關(guān)技術(shù)棧以及實(shí)際應(yīng)用場景實(shí)戰(zhàn)感興趣的小伙伴可以前往Debug搭建的技術(shù)社區(qū)的課程中心進(jìn)行學(xué)習(xí)觀看:https://www.fightjava.com/web/index/course/detail/12 !
其他相關(guān)的技術(shù),感興趣的小伙伴可以關(guān)注底部Debug的技術(shù)公眾號(hào),或者加Debug的微信,拉你進(jìn)“微信版”的真正技術(shù)交流群!一起學(xué)習(xí)、共同成長!
補(bǔ)充:
1、本文涉及到的相關(guān)的源代碼可以到此地址,check出來進(jìn)行查看學(xué)習(xí):
https://gitee.com/steadyjack/SpringBootRedis
2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學(xué)習(xí):https://www.fightjava.com/web/index/course/detail/12
3、關(guān)注一下Debug的技術(shù)微信公眾號(hào),最新的技術(shù)文章、課程以及技術(shù)專欄將會(huì)第一時(shí)間在公眾號(hào)發(fā)布哦!