概述:本系列博文所涉及的相關內容來源於debug親自錄製的實戰課程:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰),感興趣的小夥伴可以點擊自行前往學習(畢竟以視頻的形式來掌握技術 會更快!) ,文章所屬專欄:緩存中間件Redis技術入門與實戰
摘要:每當我們談起緩存中間件Redis的應用場景時,我們一般都會根據其數據結構聯想到對應的應用場景,有序集合SortedSet也不例外,“排行榜”一直都是與其緊密掛鉤、不得不談的其中一種實戰場景!本文我們將繼續再談“遊戲充值排行榜”,介紹如何去處理歷史已經存在的充值記錄 或者 在將充值記錄塞入緩存Cache失敗時如何開啓後續的補償處理措施!
內容:在上篇文章中,我們已經給各位小夥伴介紹瞭如何基於Spring Boot2.0 + 緩存Redis的SortedSet以實際的代碼實戰一種典型的業務場景“遊戲充值排行榜”,在文中我們介紹了這一業務場景兩大典型的核心功能模塊,即“用戶充值”、“獲取充值排行榜”,各位小夥伴可以自行前往回顧!
然而,這世間本就沒有十全十美之物,“遊戲充值排行榜”這一業務場景也不例外,雖然我們基本上已經實現了該業務場景幾乎所有的功能模塊,但是我們卻忽略了其他兩種情況:
A.如果“充值排行榜”這一功能模塊是增量式的需求,那麼上線時如何去處理歷史的用戶充值記錄呢?你總不能說我們的“充值排行榜”對於以往充值的用戶記錄不生效吧?(那樣豈不令人笑掉大牙!)
B.雖然我們的代碼看似完美,但是要知道Bug是無處不在的,這些Bug有的是能一眼被洞穿的,也有的是後知後覺的,“用戶充值的過程”便是如此,如果用戶充值後插入數據庫DB成功、但是插入緩存Cache失敗(DB事務不回滾的前提),那毫無疑問,最終得出來的“充值排行榜”一定是不準確的(因爲我們是直接從緩存Redis中獲取的)!
帶着這兩大問題,我們給大家提供了一種並非十全十美的,但是卻能保證“最終一致性”的充值排行榜的解決方案,那就是萬能的定時任務調度!
既然是定時任務調度,那麼這個定時任務是做啥的呢?沒錯,它要完成的任務就是開啓一個定時時鐘,基於數據庫DB中的“用戶充值記錄表”,藉助數據庫提供的Order By、Group By等查詢得出目前爲止所有有效用戶的“充值排行榜”,下面我們以實際的代碼進行實戰。
(1)直接建立一個定時任務調度類PhoneFareScheduler,並開發相應的方法實現具體的定時任務邏輯,其完整源代碼如下所示:
/**補償機制:手機號碼充值排行榜 * @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; //時間頻度設定爲30min,當然啦,具體的設定要根據實際情況而定 @Scheduled(cron = "0 0/30 * * * ?") public void sortFareScheduler(){ log.info("--補償性手機號碼充值排行榜-定時任務"); 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("--補償性手機號碼充值排行榜-定時任務-發生異常:",e.fillInStackTrace()); } } }
值得一提的是,在該定時任務調度中我們設定的時間頻率爲 每30min進行執行一次任務,實現“充值排行榜”的大洗盤!也就是說,如果前端“排行榜”頁面數據出現差錯,那麼其恢復正確的等待時間是30min(因爲我們的定時任務就是前往數據庫DB,查詢獲取得到排行榜,當然啦,其前提是保證DB中的數據是正確無誤的!)
(2)其中,phoneFareMapper.getAllSortFares() 的作用就是前往數據庫Mysql,通過Group By、Order By和SUM等查詢得到排行榜,其完整的動態SQL如下所示:
<!--基於數據庫的補償排名機制--> <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") 的作用便是採用“線程池-多線程的方式異步執行定時任務”,故而我們需要作一個全局的Config,用於配置線程池-多線程的相關信息:
/**線程池-多線程配置 * @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; } }
至此,我們已經擼完了“遊戲充值排行榜”這一完整業務的“補償機制”功能代碼了,在測試之前,我們先“偷偷”在數據庫表phone_fare中新增幾條充值記錄,代表“以前存在的歷史充值記錄”或者“插入DB成功,但插入緩存失敗的充值記錄”,如下圖所示:
最後我們基於Postman測試一波吧,下面一張圖足以說明一切了:
好了,本篇文章我們就介紹到這裏了,建議各位小夥伴一定要照着文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!對Redis相關技術棧以及實際應用場景實戰感興趣的小夥伴可以咱們51cto學院 debug親自錄製的課程進行學習:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰)
補充:
1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:https://gitee.com/steadyjack/SpringBootRedis
2、目前debug已將本文所涉及的內容整理錄製成視頻教程,感興趣的小夥伴可以前往觀看學習:https://edu.51cto.com/course/20384.html