概述:本系列博文所涉及的相關內容來源於debug親自錄製的實戰課程:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰),感興趣的小夥伴可以點擊自行前往學習(畢竟以視頻的形式來掌握技術 會更快!) ,文章所屬專欄:緩存中間件Redis技術入門與實戰
摘要:緩存中間件Redis的數據結構~有序集合SortedSet在實際項目開發中還是比較常見的,特別是在一些諸如“排行榜”的業務場景更是經常可以見到其身影!本文我們將以項目中實際的業務場景“遊戲充值排行榜”爲案例,一起來踐行有序集合SortedSet的“有序 + 唯一”的特性,感受感受其在實際項目中是如何得到應用的!
內容:“排行榜”,通俗地講,就是一份榜單,我們小時候每次考試之後學校貼出來的成績榜其實就是“排行榜”的一種。顧名思義就是將某些對象/實體,比如“某個人”、“某個手機號”按照某個值“從大排到小”、“從高排到低”或者“從小到排到大”、“從低排到高”而出來的一種結果。
站在程序的角度上看,“排行榜”亦可以說是某種“排序算法”運行出來的結果,典型、常見的業務場景包括:手機充值排行榜、商城積分排行榜、遊戲充值排行榜等等…其最終的效果如下圖所示(以下手機號碼爲虛構的):
由於“排行榜”涉及到“排名”,故而在“放榜”的那一刻,會有很多小夥伴一擁而上前往觀看,這就類似於在某一瞬間,許許多多、併發產生的線程 請求 查看“排行榜”,而排行榜的數據一般是存儲在DB數據庫中的,如果每個請求過來時都走一遍數據庫查詢、排序,那無疑是需要付出很大的代價的,比如最爲明顯的就是某一瞬間DB負載會變高、壓力變大,更誇張的可能會壓垮DB。
因此,我們將想辦法將那些跟排行榜相關的業務數據轉移到緩存Cache中,並在緩存中實現業務數據的排行,最終將得到的排行榜返回給到每個發起請求的用戶!
在這裏我們使用的緩存Cache便是Redis,並使用其中的數據結構:有序集合SortedSet加以實現!SortedSet這種數據結構延伸了集合Set的“元素唯一/不重複”的特性,卻額外增添了不同於集合Set的另外一個特性:“有序性”,正是這個“有序性”,才使得我們的“排行榜”業務可以得到很好的實現!
值得一提的是,有序集合SortedSet “有序性”的實現是通過 “在添加成員時附帶一個double類型的參數:分數”實現的,在接下來的代碼實戰中,各位小夥伴將會看到這個“分數”參數的無窮魅力!
接下來我們以“遊戲充值排行榜”爲案例,一起來踐行有序集合SortedSet在實際業務場景的應用。對於“遊戲充值排行榜”這一業務而言,無非包含兩個核心模塊,一個用戶充值模塊,一個是用戶獲取排行榜模塊!下面我們將重點來介紹並實戰這兩大核心功能模塊
一、用戶遊戲充值模塊
對於用戶充值模塊,玩過遊戲的小夥伴估計都曉得其大概的業務流程,其實無非就是輸入手機號/遊戲賬號以及金額,然後點擊支付即完成充值的整個過程,如下圖所示爲該模塊的核心業務流程圖:
下面,我們進入代碼實戰環節!
(1)同樣的道理,工欲善其事,必先利其器,我們先建立一張用於記錄 用戶歷史充值記錄的“用戶充值表”,其DDL如下所示:
CREATE TABLE `phone_fare` ( `id` int(11) NOT NULL AUTO_INCREMENT, `phone` varchar(50) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '手機號碼', `fare` decimal(10,2) DEFAULT NULL COMMENT '充值金額', `is_active` tinyint(4) DEFAULT '1' COMMENT '是否有效(1=是;0=否)', PRIMARY KEY (`id`), KEY `idx_phone` (`phone`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='手機充值記錄';
採用Mybatis逆向工程或者代碼生成器生成該數據庫表的實體類Entity、Mapper操作接口以及對應的用於寫動態SQL的Mapper.xml,在這裏就不貼出來了,各位小夥伴可以前往文末提供的源碼地址進行下載觀看!
(2)緊接着我們需要開發一個SortedSetController,用於前端用戶發起“充值”的請求,其完整的源代碼如下所示:
/**@Author:debug (SteadyJack) weixin-> debug0868 qq-> 1948831260 **/ @RestController @RequestMapping("sorted/set") public class SortedSetController extends AbstractController { @Autowired private SortedSetService sortedSetService; @RequestMapping(value = "put/v2",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) public BaseResponse putv2(@RequestBody @Validated PhoneFare fare, BindingResult result){ String checkRes= ValidatorUtil.checkResult(result); if (StrUtil.isNotBlank(checkRes)){ return new BaseResponse(StatusCode.Fail.getCode(),checkRes); } BaseResponse response=new BaseResponse(StatusCode.Success); try { response.setData(sortedSetService.addRecordV2(fare)); }catch (Exception e){ response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage()); } return response; } }
其中,實體類PhoneFare的代碼如下所示:
@Data @EqualsAndHashCode public class PhoneFare implements Serializable { private Integer id; @NotBlank(message = "手機號碼不能爲空!") private String phone; @NotNull(message = "充值金額不能爲空!") private BigDecimal fare; private Byte isActive = 1; }
(3)而sortedSetService.addRecordV2(fare) 要做的事情就是“如何將前端用戶提交過來的手機號和對應的金額塞到數據庫DB和緩存Redis中去”,其完整的源代碼如下所示:
//TODO:新增/手機話費充值 記錄 v2 @Transactional(rollbackFor = Exception.class) public Integer addRecordV2(PhoneFare fare) throws Exception{ log.info("----sorted set話費充值記錄新增V2:{} ",fare); int res=fareMapper.insertSelective(fare); if (res>0){ FareDto dto=new FareDto(fare.getPhone()); ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet(); Double oldFare=zSetOperations.score(Constant.RedisSortedSetKey2,dto); if (oldFare!=null){ //TODO:表示之前該手機號對應的用戶充過值了,需要進行疊加 zSetOperations.incrementScore(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue()); }else{ //TODO:表示只充過一次話費 zSetOperations.add(Constant.RedisSortedSetKey2,dto,fare.getFare().doubleValue()); } } return fare.getId(); }
在這裏,我們塞入到緩存SortedSet中的對象實體爲FareDto類,該類包含一個字段信息,即“手機號”,如下所示:
/**手機號唯一性 * @Author:debug (SteadyJack) weixin-> debug0868 qq-> 1948831260 **/ @Data @EqualsAndHashCode @NoArgsConstructor @AllArgsConstructor public class FareDto implements Serializable{ private String phone; }
(4)至此,我們已經完成了“用戶充值”業務模塊的功能,下面我們用Postman測試一波,貼幾張測試結果的圖吧:
二、用戶獲取充值排行榜模塊
既然我們的充值都成功插入到了數據庫DB和緩存Cache中,那麼接下來自然而然是需要將其從緩存中獲取出來,並將其處理成“排行榜”的形式展示給用戶觀看,其核心業務流程圖如下所示:
(1)同樣的道理, 我們仍然在SortedSetController中開發“獲取充值排行榜”的請求方法,其完整的源代碼如下所示:
@RequestMapping(value = "get/v2",method = RequestMethod.GET) public BaseResponse getV2(){ BaseResponse response=new BaseResponse(StatusCode.Success); try { response.setData(sortedSetService.getSortFaresV2()); }catch (Exception e) { response = new BaseResponse(StatusCode.Fail.getCode(), e.getMessage()); } return response; }
(2)而 sortedSetService.getSortFaresV2() 做的事情便是實現如何從緩存Redis的有序集合“SortedSet中獲取到充值排行榜”,其完整源碼如下所示:
//TODO:獲取充值排行榜V2 public List<PhoneFare> getSortFaresV2(){ List<PhoneFare> list= Lists.newLinkedList(); final String key=Constant.RedisSortedSetKey2; ZSetOperations<String,FareDto> zSetOperations=redisTemplate.opsForZSet(); final Long size=zSetOperations.size(key); Set<ZSetOperations.TypedTuple<FareDto>> set=zSetOperations.reverseRangeWithScores(key,0L,size); if (set!=null && !set.isEmpty()){ set.forEach(tuple -> { PhoneFare fare=new PhoneFare(); fare.setFare(BigDecimal.valueOf(tuple.getScore())); fare.setPhone(tuple.getValue().getPhone()); list.add(fare); }); } return list; }
(3)至此,我們已經將“獲取用戶充值排行榜”的功能模塊實戰完畢,下面我們也同樣基於Postman測試一波吧,貼幾張圖:
最終可以看到,展現在我們面前的確實一張排行榜(從大排到小)!而且這張排行榜是直接從緩存Redis的SortedSet中拿到的,而並非前往數據庫DB進行復雜的查詢、排序和計算(無疑減少了許多數據庫層面的查詢壓力)!
好了,本篇文章我們就介紹到這裏了,建議各位小夥伴一定要照着文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!對Redis相關技術棧以及實際應用場景實戰感興趣的小夥伴可以咱們51cto學院 debug親自錄製的課程進行學習:緩存中間件Redis技術入門與應用場景實戰(SpringBoot2.x + 搶紅包系統設計與實戰)
補充:
1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:https://gitee.com/steadyjack/SpringBootRedis
2、目前debug已將本文所涉及的內容整理錄製成視頻教程,感興趣的小夥伴可以前往觀看學習:https://edu.51cto.com/course/20384.html