摘要:缓存中间件Redis的数据结构~有序集合SortedSet在实际项目开发中还是比较常见的,特别是在一些诸如“排行榜”的业务场景更是经常可以见到其身影!本文我们将以项目中实际的业务场景“游戏充值排行榜”为案例,一起来践行有序集合SortedSet的“有序 + 唯一”的特性,感受感受其在实际项目中是如何得到应用的!

内容:“排行榜”,通俗地讲,就是一份榜单,我们小时候每次考试之后学校贴出来的成绩榜其实就是“排行榜”的一种。顾名思义就是将某些对象/实体,比如“某个人”、“某个手机号”按照某个值“从大排到小”、“从高排到低”或者“从小到排到大”、“从低排到高”而出来的一种结果。

站在程序的角度上看,“排行榜”亦可以说是某种“排序算法”运行出来的结果,典型、常见的业务场景包括:手机充值排行榜、商城积分排行榜、游戏充值排行榜等等…其最终的效果如下图所示:

msys2 按照redis_redis

由于“排行榜”涉及到“排名”,故而在“放榜”的那一刻,会有很多小伙伴一拥而上前往观看,这就类似于在某一瞬间,许许多多、并发产生的线程 请求 查看“排行榜”,而排行榜的数据一般是存储在DB数据库中的,如果每个请求过来时都走一遍数据库查询、排序,那无疑是需要付出很大的代价的,比如最为明显的就是某一瞬间DB负载会变高、压力变大,更夸张的可能会压垮DB。

因此,我们将想办法将那些跟排行榜相关的业务数据转移到缓存Cache中,并在缓存中实现业务数据的排行,最终将得到的排行榜返回给到每个发起请求的用户!

在这里我们使用的缓存Cache便是Redis,并使用其中的数据结构:有序集合SortedSet加以实现!SortedSet这种数据结构延伸了集合Set的“元素唯一/不重复”的特性,却额外增添了不同于集合Set的另外一个特性:“有序性”,正是这个“有序性”,才使得我们的“排行榜”业务可以得到很好的实现!

值得一提的是,有序集合SortedSet “有序性”的实现是通过 “在添加成员时附带一个double类型的参数:分数”实现的,在接下来的代码实战中,各位小伙伴将会看到这个“分数”参数的无穷魅力!

接下来我们以“游戏充值排行榜”为案例,一起来践行有序集合SortedSet在实际业务场景的应用。对于“游戏充值排行榜”这一业务而言,无非包含两个核心模块,一个用户充值模块,一个是用户获取排行榜模块!下面我们将重点来介绍并实战这两大核心功能模块

一、用户游戏充值模块

对于用户充值模块,玩过游戏的小伙伴估计都晓得其大概的业务流程,其实无非就是输入手机号/游戏账号以及金额,然后点击支付即完成充值的整个过程,如下图所示为该模块的核心业务流程图:

msys2 按照redis_java_02

下面,我们进入代码实战环节!

(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;
    }
}