Redis實戰(8)-有序集合SortedSet典型應用場景實戰之遊戲充值排行榜

概述:本系列博文所涉及的相關內容來源於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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章