「高并发秒杀」微信抢红包实战案例,最新阿里P7技术体系

107 阅读4分钟

基础面试题

主要内容包括:HTML,CSS,JavaScript,浏览器,性能优化等等

开源分享:docs.qq.com/doc/DSmRnRG…

架构设计

  • 老板发红包,此时缓存初始化红包个数,红包金额(单位分),并异步入库。

  • 抢红包,判断缓存剩余红包金额,剩余金额大于零则抢到红包,否则手慢了,红包派完了

  • 拆红包,根据 redPacketId 获取分布式锁,如果获取到锁,红包个数减一,如果剩余红包个数大于零抢红包成功、否则失败。成功则计算红包金额,缓存总红包金额减去抢到的红包金额,异步入库、异步到账。

  • 若获取分布式锁失败,使用 Redis的 decr命令对红包个数加一。

数据库设计

  • 红包信息表

CREATE TABLE red_racket (

id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',

red_packet_id bigint(20) NOT NULL COMMENT '红包唯一ID',

total_amount int(11) NOT NULL COMMENT '红包金额单位分',

total_packet int(11) NOT NULL COMMENT '红包个数',

type int(11) NOT NULL COMMENT '红包类型',

create_time datetime DEFAULT NULL COMMENT '创建时间',

version int(11) NOT NULL COMMENT '版本号',

PRIMARY KEY (id)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='红包信息表'

  • 抢红包记录表

CREATE TABLE red_packet_record (

id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',

amount int(11) NOT NULL COMMENT '抢到红包的金额',

red_packet_id bigint(20) NOT NULL COMMENT '红包ID',

uid int(11) NOT NULL COMMENT '抢到红包用户的用户标识',

create_time datetime DEFAULT NULL COMMENT '创建时间',

PRIMARY KEY (id)

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 CHECKSUM=1 DELAY_KEY_WRITE=1 ROW_FORMAT=DYNAMIC COMMENT='抢红包记录表'

代码案例

老板发了10个红包一共200人民币,100个人同时抢红包,伪代码分别为拆红包和抢红包相关业务逻辑。模拟抢红包伪代码:

/**

  • 抢红包 拆红包 抢到不一定能拆到

  • @param redPacketId

  • @return

*/

@ApiOperation(value="抢红包二",nickname="爪哇笔记")

@PostMapping("/startTwo")

public Result startTwo(long redPacketId){

int skillNum = 100;

final CountDownLatch latch = new CountDownLatch(skillNum);//N个抢红包

/**

  • 初始化红包数据,抢红包拦截

*/

redisUtil.cacheValue(redPacketId+"-num",10);

/**

  • 初始化红包金额,单位为分

*/

redisUtil.cacheValue(redPacketId+"-money",20000);

/**

  • 模拟100个用户抢10个红包

*/

for(int i=1;i<=skillNum;i++){

int userId = i;

Runnable task = () -> {

/**

  • 抢红包 判断剩余金额

*/

Integer money = (Integer) redisUtil.getValue(redPacketId+"-money");

if(money>0){

/**

  • 虽然能抢到 但是不一定能拆到

  • 类似于微信的 点击红包显示抢的按钮

*/

Result result = redPacketService.startTwoSeckil(redPacketId,userId);

if(result.get("code").toString().equals("500")){

LOGGER.info("用户{}手慢了,红包派完了",userId);

}else{

Double amount = DoubleUtil.divide(Double.parseDouble(result.get("msg").toString()), (double) 100);

LOGGER.info("用户{}抢红包成功,金额:{}", userId,amount);

}

}else{

/**

  • 直接显示手慢了,红包派完了

*/

//LOGGER.info("用户{}手慢了,红包派完了",userId);

}

latch.countDown();

};

executor.execute(task);

}

try {

latch.await();

Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());

LOGGER.info("剩余金额:{}",restMoney);

} catch (InterruptedException e) {

e.printStackTrace();

}

return Result.ok();

}

业务层拆红包:

@Override

@Transactional

public Result startTwoSeckil(long redPacketId, int userId) {

Integer money = 0;

boolean res=false;

try {

/**

  • 获取锁 保证红包数量和计算红白金额的原子性操作

*/

res = RedissLockUtil.tryLock(redPacketId+"", TimeUnit.SECONDS, 3, 10);

if(res){

long restPeople = redisUtil.decr(redPacketId+"-num",1);

if(restPeople>0){

/**

  • 如果是最后一人

*/

if(restPeople==1){

money = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());

}else{

Integer restMoney = Integer.parseInt(redisUtil.getValue(redPacketId+"-money").toString());

Random random = new Random();

总结

=============================================================

从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

个人将这段时间所学的知识,分为三个阶段:

第一阶段:HTML&CSS&JavaScript基础

第二阶段:移动端开发技术

第三阶段:前端常用框架

  • 推荐学习方式:针对某个知识点,可以先简单过一下我的笔记,如果理解,那是最好,可以帮助快速解决问题;如果因为我的笔记太过简陋不理解,可以关注我以后我还会继续分享。

  • 大厂的面试难在,针对一个基础知识点,比如JS的事件循环机制,不会上来就问概念,而是换个角度,从题目入手,看你是否真正掌握。所以对于概念的理解真的很重要。