方案设计

具体设计

缓存

选用Redis

缓存穿透解决方案:

当查询时缓存未命中,从数据库load数据到缓存,如果红包不存在,则load一条数据(key为红包id,value标记红包不存在)到缓存中。

缓存击穿解决方案:

加互斥锁,利用redisTemplate.opsForValue().setIfAbsent()方法

循环:

  1. 查缓存

  2. 缓存为空,则尝试加锁,否则跳出循环

  3. 尝试加锁失败,则跳到第1步继续循环,尝试加锁成功,则从DB load数据到缓存

// TODO

1.存储格式优化,改用hash格式

缓存雪崩解决方案:

消息队列

mq选型:RocketMq

具体方案:

先从缓存中查红包剩余数量,如果数量为0则直接返回,如果不为0,则使用mq发送一条红包金额扣减消息,然后返回秒杀中,客户端轮询秒杀结果。数据库层订阅扣减消息,进行红包金额扣减,扣减成功后刷新缓存。

前端/客户端

点击后,弹出加载动画,此时后端一般返回正在抢中的状态,然后前端轮询抢红包状态,直到查到抢红包结果确定,结束动画,展示结果。

扣减红包金额

金额全部使用int型,单位为分。

不可重复读:从查询红包余额时加排它锁,防止查询余额后,有其他线程更新了该余额,导致不可重复读。

解决办法:

  1. 排他锁(for update):mybatis实现排他锁不够优雅,抛弃

  2. 分布式锁

分布式锁:

使用stringRedisTemplate.opsForValue().setIfAbsent()方法实现

锁设置超时时间,如果线程卡住了,锁过期后其他线程可以抢占锁。

// TODO

当单次处理时间过长时,锁过期,存在锁泄漏情况。

解决方案:做好监控。

接口限流

使用guava的RateLimter配合aop实现

数据库设计

数据库:red-envelope

数据库表:

1.envelope_detail(红包详情)

CREATE TABLE `envelope_detail` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `user` varchar(25) DEFAULT NULL COMMENT '发红包者',
  `type` varchar(10) NOT NULL DEFAULT 'UNKONWN' COMMENT '红包类型',
  `amount` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '红包总额',
  `size` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '红包份数',
  `remain_money` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '红包余额',
  `remain_size` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '红包余下份数',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.envelope_grabber(抢到红包的人和金额等)

CREATE TABLE `envelope_grabber` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `envelope_id` int(11) unsigned NOT NULL COMMENT '红包id',
  `grabber` varchar(25) NOT NULL DEFAULT '' COMMENT '抢到用户',
  `money` int(11) unsigned NOT NULL COMMENT '抢到金额',
  `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_envelope_id_grabber` (`envelope_id`,`grabber`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4;

常见问题

1.如何解决抢超问题()

  • 在sql加上判断防止数据边为负数

  • 数据库加唯一索引防止用户重复抢红包

  • redis预减库存减少数据库访问 内存标记减少redis访问 请求先入队列缓冲,异步下单,增强用户体验

对账

压力测试

Jmeter/Siege

可优化点

  1. 数据库分库分表

  2. redis集群宕机解决方案

  3. 应用监控、基础设施监控

  4. 防范脚本抢红包:负载均衡设备中做一些配置,判断如果同一个用户在1分钟之内多次发送请求来进行抢红包,就认为是恶意脚本代抢

  5. 利用负载均衡层来过滤无效请求(红包已抢完)

  6. 红包过期,多余金额退换

  7. set化:红包系统根据红包 ID,按一定的规则(如按 ID 尾号取模等),垂直上下切分。切分后,一个垂直链条上的逻辑 Server 服务器、DB 统称为一个 SET。各个 SET 之间相互独立,互相解耦。并且同一个红包 ID 的所有请求,包括发红包、抢红包、拆红包、查详情详情等,垂直 stick 到同一个 SET 内处理,高度内聚。通过这样的方式,系统将所有红包请求这个巨大的洪流分散为多股小流,互不影响,分而治之

  8. 大key优化

参考资料

https://www.zhihu.com/question/22625187/answer/85530416 https://rocketmq.apache.org/docs/quick-start/ https://www.bilibili.com/video/av19582887/ https://www.infoq.cn/article/2017hongbao-weixin https://www.ibm.com/developerworks/cn/web/wa-design-small-and-good-kill-system/index.html https://zhuanlan.zhihu.com/p/25368538 https://www.zhihu.com/question/54895548 https://www.infoq.cn/article/2017hongbao-weixin

Last updated