上文《秒杀抢红包实战系列(二)分配红包(发红包)》我们介绍了发红包的场景,已经实现了把红包提前分配到redis中了。本文的话我们介绍下抢红包相关的概念。
对于抢红包,我们要考虑如下的场景:
1)时效要短
抢红包是一瞬间的过程,所以对于接口的响应时间要求就非常高,对于我们的业务来说,一般要求是保证TP99必须要保证在100ms以内。所以这里的话我们需要把不太重要的代码放在异步处理。
2)不能超额
比如红包总共的个数是10个,不能有第11个人抢到红包。那这个时候大家肯定都会想到的是分布式锁。
3)数据要准确记录
抢到的红包数据必须要准确的记录到对应的数据库。保证数据的最终一致性。
基于上诉信息,我们来实现一下抢红包的逻辑。
1、创建抢红包的接口
创建抢红包的接口,示例代码如下:
/** * 抢红包 * * @param redPacketId * @return */ @RequestMapping("/grab") public String grabRedPacket(@RequestParam String redPacketId) { return redPacketService.grabRedPacket(redPacketId); }
2、编写抢红包的逻辑
抢红包的逻辑代码示例如下;
public String grabRedPacket(String redPacketId) { // 随机生成一个用户,模拟100个用户,后面做高并发的时候做去重 Long userId = Long.valueOf(new Random().nextInt(100)); String userName = "User" + userId; return userGrab(redPacketId, userId, userName); } /** * 进入到用户抢红包环节,需要加分布式锁,所以粒度最细化 * @param redPacketId * @param userId * @param userName * @return */ @RedisDistributedLock(key = "'lock_redpks_'+#redPacketId", errorDesc = "当前请求频繁,请稍后重试") private String userGrab(String redPacketId, Long userId, String userName) { //判断用户是否抢购过了 TODO,如果抢过了,则直接进行展示这里代码省略,我们假设红包已经抢到了。 String amount = redisTemplate.opsForList().rightPop(redPacketId); if (amount != null) { // 如果不等于空,则代表抢到了红包 BigDecimal grabbedAmount = new BigDecimal(amount); RedPackageLogs logs = RedPackageLogs.builder().userId(userId).userName(userName) .redPackageAmount(grabbedAmount).cts(System.currentTimeMillis()).build(); // 这里应该是存库的,但是为了演示,所以把抢到的红包写入到redis中,方便一会查询抢红包记录 redisTemplate.opsForList().leftPush("grabbed:" + redPacketId, logs.toString()); return logs.toString(); } else { return "红包已抢完"; } }
这里的话我们把逻辑代码分成了两部分,其实应该是有3部分的,我这里介绍的三部分主要是:
1、查询用户的基本信息,对红包进行判断 2、抢红包的逻辑代码,带有分布式锁,也就是上文代码中的userGrab方法部分的代码。 3、数据异步罗盘到数据库(上文代码没有实现)
上文我们主要是实现了第一和第二部分,第三部分没有实现。大家可以看看。
备注:
1、这里的话,我们把任务的分布式锁进行最细粒度。保证其他用户可以同时进行抢红包。 2、上文我们原本应该把数据通过mq异步发送给消费者处理,然后落库,但是为了演示方便,我们直接把记录放在redis里面的。 3、这里redis使用的list数据结构,我们需要考虑比如从redis里面获取到了数据,但是数据没有异步发送给mq成功,这种场景应该如何处理。 4、在抢红包的系统中,一般都是一套单独的架构模块,几乎会和其他部分独立分开的,由于逻辑简单,所以几乎不存在分布式事务的情况(除了异步消费者的逻辑),所以前端抢红包是很快的。 5、抢红包这块虽然没有分布式事务,但是后端mysql还是会有分库分表的概念。所以在真实的情况下需要考虑到。 6、这里的分布式锁我们使用的是redisson,然后通过注解的方式来完善的。使用起来方便。
最后我们把项目运行起来,进行下并发测试,这里我们起50个线程同时抢一个红包,压测10轮,看看效果:
可以看到我这里抢红包非常的快,平均响应时间是26.044毫秒,真实生产环境还要加上mq相关的逻辑,判断的逻辑,外网http连接的耗时等情况。在这样的情况下我们是可以保障TP99在100ms以内的。
最后我们关心下有没有出现多抢购的情况:
可以看到500个请求下只有10个用户抢到了红包。完全没问题。最后按照惯例,附上本案例的远吗,登录后即可下载。
还没有评论,来说两句吧...