Redis實戰(zhàn)(8)-SortedSet典型應用場景實戰(zhàn)之游戲充值排行榜
作者:
修羅debug
版權聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 by-sa 版權協(xié)議,轉載請附上原文出處鏈接和本聲明。
摘要:緩存中間件Redis的數(shù)據(jù)結構~有序集合SortedSet在實際項目開發(fā)中還是比較常見的,特別是在一些諸如“排行榜”的業(yè)務場景更是經(jīng)??梢砸姷狡渖碛埃”疚奈覀儗⒁皂椖恐袑嶋H的業(yè)務場景“游戲充值排行榜”為案例,一起來踐行有序集合SortedSet的“有序 + 唯一”的特性,感受感受其在實際項目中是如何得到應用的!
內(nèi)容:“排行榜”,通俗地講,就是一份榜單,我們小時候每次考試之后學校貼出來的成績榜其實就是“排行榜”的一種。顧名思義就是將某些對象/實體,比如“某個人”、“某個手機號”按照某個值“從大排到小”、“從高排到低”或者“從小到排到大”、“從低排到高”而出來的一種結果。
站在程序的角度上看,“排行榜”亦可以說是某種“排序算法”運行出來的結果,典型、常見的業(yè)務場景包括:手機充值排行榜、商城積分排行榜、游戲充值排行榜等等…其最終的效果如下圖所示:
由于“排行榜”涉及到“排名”,故而在“放榜”的那一刻,會有很多小伙伴一擁而上前往觀看,這就類似于在某一瞬間,許許多多、并發(fā)產(chǎn)生的線程 請求 查看“排行榜”,而排行榜的數(shù)據(jù)一般是存儲在DB數(shù)據(jù)庫中的,如果每個請求過來時都走一遍數(shù)據(jù)庫查詢、排序,那無疑是需要付出很大的代價的,比如最為明顯的就是某一瞬間DB負載會變高、壓力變大,更夸張的可能會壓垮DB。
因此,我們將想辦法將那些跟排行榜相關的業(yè)務數(shù)據(jù)轉移到緩存Cache中,并在緩存中實現(xiàn)業(yè)務數(shù)據(jù)的排行,最終將得到的排行榜返回給到每個發(fā)起請求的用戶!
在這里我們使用的緩存Cache便是Redis,并使用其中的數(shù)據(jù)結構:有序集合SortedSet加以實現(xiàn)!SortedSet這種數(shù)據(jù)結構延伸了集合Set的“元素唯一/不重復”的特性,卻額外增添了不同于集合Set的另外一個特性:“有序性”,正是這個“有序性”,才使得我們的“排行榜”業(yè)務可以得到很好的實現(xiàn)!
值得一提的是,有序集合SortedSet “有序性”的實現(xiàn)是通過 “在添加成員時附帶一個double類型的參數(shù):分數(shù)”實現(xiàn)的,在接下來的代碼實戰(zhàn)中,各位小伙伴將會看到這個“分數(shù)”參數(shù)的無窮魅力!
接下來我們以“游戲充值排行榜”為案例,一起來踐行有序集合SortedSet在實際業(yè)務場景的應用。對于“游戲充值排行榜”這一業(yè)務而言,無非包含兩個核心模塊,一個用戶充值模塊,一個是用戶獲取排行榜模塊!下面我們將重點來介紹并實戰(zhàn)這兩大核心功能模塊
一、用戶游戲充值模塊
對于用戶充值模塊,玩過游戲的小伙伴估計都曉得其大概的業(yè)務流程,其實無非就是輸入手機號/游戲賬號以及金額,然后點擊支付即完成充值的整個過程,如下圖所示為該模塊的核心業(yè)務流程圖:
下面,我們進入代碼實戰(zhàn)環(huán)節(jié)!
(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逆向工程或者代碼生成器生成該數(shù)據(jù)庫表的實體類Entity、Mapper操作接口以及對應的用于寫動態(tài)SQL的Mapper.xml,在這里就不貼出來了,各位小伙伴可以前往文末提供的源碼地址進行下載觀看!
(2)緊接著我們需要開發(fā)一個SortedSetController,用于前端用戶發(fā)起“充值”的請求,其完整的源代碼如下所示:
/**@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) 要做的事情就是“如何將前端用戶提交過來的手機號和對應的金額塞到數(shù)據(jù)庫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();
}
在這里,我們?nèi)氲骄彺鍿ortedSet中的對象實體為FareDto類,該類包含一個字段信息,即“手機號”,如下所示:
/**手機號唯一性
* @Author:debug (SteadyJack) weixin-> debug0868 qq-> 1948831260 **/
@Data
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class FareDto implements Serializable{
private String phone;
}
(4)至此,我們已經(jīng)完成了“用戶充值”業(yè)務模塊的功能,下面我們用Postman測試一波,貼幾張測試結果的圖吧:
二、用戶獲取充值排行榜模塊
既然我們的充值都成功插入到了數(shù)據(jù)庫DB和緩存Cache中,那么接下來自然而然是需要將其從緩存中獲取出來,并將其處理成“排行榜”的形式展示給用戶觀看,其核心業(yè)務流程圖如下所示:
(1)同樣的道理, 我們?nèi)匀辉赟ortedSetController中開發(fā)“獲取充值排行榜”的請求方法,其完整的源代碼如下所示:
@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() 做的事情便是實現(xiàn)如何從緩存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)至此,我們已經(jīng)將“獲取用戶充值排行榜”的功能模塊實戰(zhàn)完畢,下面我們也同樣基于Postman測試一波吧,貼幾張圖:
最終可以看到,展現(xiàn)在我們面前的確實一張排行榜(從大排到?。?!而且這張排行榜是直接從緩存Redis的SortedSet中拿到的,而并非前往數(shù)據(jù)庫DB進行復雜的查詢、排序和計算(無疑減少了許多數(shù)據(jù)庫層面的查詢壓力)!
好了,本篇文章我們就介紹到這里了,建議各位小伙伴一定要照著文章提供的樣例代碼擼一擼,只有擼過才能知道這玩意是咋用的,否則就成了“空談者”!
對Redis相關技術棧以及實際應用場景實戰(zhàn)感興趣的小伙伴可以前往Debug搭建的技術社區(qū)的課程中心進行學習觀看:https://www.fightjava.com/web/index/course/detail/12 !
其他相關的技術,感興趣的小伙伴可以關注底部Debug的技術公眾號,或者加Debug的微信,拉你進“微信版”的真正技術交流群!一起學習、共同成長!
補充:
1、本文涉及到的相關的源代碼可以到此地址,check出來進行查看學習:
https://gitee.com/steadyjack/SpringBootRedis
2、目前Debug已將本文所涉及的內(nèi)容整理錄制成視頻教程,感興趣的小伙伴可以前往觀看學習:https://www.fightjava.com/web/index/course/detail/12
3、關注一下Debug的技術微信公眾號,最新的技術文章、課程以及技術專欄將會第一時間在公眾號發(fā)布哦!