秒杀活动如何实现

一、分析

1.1 数据库准备

简单设计的话我们需要有一个产品,这个产品有相应的库存即可。

id产品库存
1火箭1000
2大炮800
3坦克600

访问量过高火或者MySQL配置文件中max_connections值过小,会产生“MySQL: ERROR 1040: Too many connections”的异常情况。考虑增加从服务器分散读压力,或者查看与设置max_connections:

show variables like'%max_connections%';
set global max_connections=1000 重新设置

1.2 redis准备

只有数据库实现秒杀功能会存在以下一些问题:

  1. 短时间内的大访问量对现有网站业务造成的冲击。
  2. 高并发下对服务器数据库造成的极大负载压力。
  3. 不能因为数据库宕机导致整个系统服务崩溃

我们需要将商品读到redis中,对于商品的消费,我们可以使用事务或者分布式锁watch来实现。

1.3 RocketMq准备

消息队列的作用:

  • 削去秒杀场景下的峰值写流量——流量削峰
  • 通过异步处理简化秒杀请求中的业务流程——异步处理
  • 解耦,实现秒杀系统模块之间松耦合——解耦

生产者流控,因为broker处理能力达到瓶颈;消费者流控,因为消费能力达到瓶颈。

  1. 生产者流控:
  • commitLog文件被锁时间超过osPageCacheBusyTimeOutMills时,参数默认为1000ms,发生流控。
  • 如果开启transientStorePoolEnable = true,且broker为异步刷盘的主机,且transientStorePool中资源不足,拒绝当前send请求,发生流控。
  • broker每隔10ms检查send请求队列头部请求的等待时间,如果超过 waitTimeMillsInSendQueue,默认200ms,拒绝当前send请求,发生流控。
  • broker通过拒绝send 请求方式实现流量控制。

注意,生产者流控,不会尝试消息重投。

  1. 消费者流控:
  • 消费者本地缓存消息数超过pullThresholdForQueue时,默认1000。
  • 消费者本地缓存消息大小超过pullThresholdSizeForQueue时,默认100MB。
  • 消费者本地缓存消息跨度超过consumeConcurrentlyMaxSpan时,默认2000。
  • 消费者流控的结果是降低拉取频率。

我们需要将用户的秒杀请求传入到消息队列中,我们的秒杀处理应用通过拉取或者订阅秒杀队列得到秒杀请求。我们去redis中查看商品的库存,存在则秒杀成功,不存在则返回商品已经秒杀完。redis中的数据我们还需要更新到数据库中,我们可以使用先更新数据库再删除缓存的方式,再增加一个双删机制保证数据一致性。

二、实战

2.1 需求

基于RocketMQ设计秒杀。

要求:

  1. 秒杀商品LagouPhone,数量100个。
    
  2. 秒杀商品不能超卖。
    
  3. 抢购链接隐藏
    
  4. Nginx+Redis+RocketMQ+Tomcat+MySQL
    

结构:
在这里插入图片描述

2.2 实现

2.2.1 搭建单机RocketMQ

1、安装jdk
jdk要求至少要1.8版本的,默认1.8版本,如果更高版本需要修改脚本

2、下载rocketmq

#下载
wget https://archive.apache.org/dist/rocketmq/4.5.1/rocketmq-all4.5.1-bin-release.zip

3、使用unzip命令将压缩包解压到/opt目录
4、执行命令mqnamesrv
我们需要修改runserver.sh中的部分内存配置

#修改内存:
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=64mm -XX:MaxMetaspaceSize=160mm"

运行结果
在这里插入图片描述
5、运行mqbroker -n localhost:9876
在这里插入图片描述
直接运行也会报错,我们需要修改runbroker.sh中的部分内存配置
在这里插入图片描述
修改类似如下
在这里插入图片描述
再次执行就不会报错了
在这里插入图片描述
6、发送消息测试,需要修改tools.sh文件

# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.使用安装包的Demo发送消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Producer

在这里插入图片描述
7、消费消息测试

# 1.设置环境变量
export NAMESRV_ADDR=localhost:9876
# 2.接收消息
sh bin/tools.sh org.apache.rocketmq.example.quickstart.Consumer

在这里插入图片描述

8、关闭nameserver broker

# 1.关闭NameServer
mqshutdown namesrv
# 2.关闭Broker
mqshutdown broker
2.2.2 搭建集群RocketMQ

NameServer集群

NameServer集群是无状态集群,所以我们直接在多台节点上启动即可,在打开broker得时候分隔符配置上即可。

单机搭建得时候NameServer是一台,IP地址直接写,如果是多台NameServer,则需要在 -n 后接多个NameServer的地址,使用分号分开。由于shell对分号敏感,可以使用单引号引起来多个NameServer的地址,禁止shell对分号的解释。

Broker集群
对于Broker集群我们需要使用rocketmq目录下得conf目录下得配置文件来实现集群搭建
在这里插入图片描述

2.2.3 RocketMQ 实现超时功能

定时消息(延迟队列)是指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic。 broker有配置项messageDelayLevel,默认值为“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”,18个level。可以配置自定义messageDelayLevel。注意,messageDelayLevel是broker的属性,不属于某个topic。发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。

level有以下三种情况:

  • level == 0,消息为非延迟消息
  • 1<=level<=maxLevel,消息延迟特定时间,例如level==1,延迟1s
  • level > maxLevel,则level== maxLevel,例如level==20,延迟2h

定时消息会暂存在名为SCHEDULE_TOPIC_XXXX的topic中,并根据delayTimeLevel存入特定的queue,queueId = delayTimeLevel – 1,即一个queue只存相同延迟的消息,保证具有相同发送延迟的消息能够顺序消费。broker会调度地消费SCHEDULE_TOPIC_XXXX,将消息写入真实的topic。需要注意的是,定时消息会在第一次写入和调度写入真实topic时都会计数,因此发送数量、tps都会变高。

我们使用这个功能实现超时机制,比如,等待三十分钟后发送消息到队列中,消费者消费消息检查是否有支付,未支付就恢复订单。

2.2.4 RocketMQ 实现限流功能

使用Sentinel限流


版权声明:本文为jiqiren1994原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。