摘要:缓存中间件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;
}
}