上文《秒杀抢红包实战系列(二)分配红包(发红包)》我们介绍了发红包的场景,已经实现了把红包提前分配到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个用户抢到了红包。完全没问题。最后按照惯例,附上本案例的远吗,登录后即可下载。



还没有评论,来说两句吧...