文章目录
前言
这是系列的第六篇文章接口优化设计首先来回顾一下之前的几篇文章:
章节名称 | 博客地址 |
---|---|
安装部署Redis | 集成Redis(已完结) |
页面登陆功能设计 | 登录功能设计(更新优化中) |
秒杀页面具体设计 | 秒杀详情页(已完结) |
JMeter初级压测学习 | Jmeter压测入门学习(已完结) |
页面优化设计 | 页面优化设计(已完结) |
接口优化 | RabbbitMq接口优化(已完结) |
图形验证码等 | 图形验证码及接口防刷(更新优化中) |
在前面的第五篇介绍到了页面优化技术,但是对于秒杀来说仅仅在前端做一些缓存的处理和页面静态化就想解决高并发访问的问题未免太过于天真了,并且对于一个前后端分离的项目来说,就更需要后端的一些技术支持。这一节就来具体介绍一些关于接口优化的方案。具体代码参见我的个人github
源码。
对于接口的优化处理主要有以下的几个部分:
- Redis预减库存来减少数据库访问。
- 内存标记来介绍Redis访问。
- 将请求先行放入到缓冲队列中,异步下单。
- nginx水平扩展。
思路
- 首先在系统初始化时候,就把商品的数量加载到Redis里面。进行提前的加载操作。
- 收到请求以后 Redis预减库存,就是reids先对库存进行一个减操作,库存不足的时候直接就回返回错误信息。
思考会有什么好处:例如我们就有100件商品,此时数据的加载时候就会预先加载到redis中,前端对数据进行请求,完成了100件订单以后,对于后续的订单就算是来再多也会显示没有了库存就不会请求到数据库,此时对数据库的访问几乎是零。 - 对于库存充足的情况下就会进入到第三步,将请求的信息入队(rabbitmq队列)。立即返回的是排队中 。不是返回成功或者失败,因为是入队但是还不能判断是否成功。
- 客户端会进行轮询操作,自己的这次请求是成功还是失败了 。对于服务端来说:将请求出队(就是放在队列中待执行的请求进行执行操作)生成订单,减少库存。
Rabbitmq安装与整合
- 首先在自己的电脑上安装Rabbitmq并进行启动,这里若是没有安装的小伙伴可以参看下面这篇文章,不仅有详细的安装方法,还附带百度云盘供大家下载对应的版本下载与安装。
- 完成安装以后启动起来,既然是和Spring Boot进行整合操作急需要添加依赖,添加如下依赖在自己的pom.xml文件中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- 添加配置信息,这里的配置文件是
properties
如下:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
# 因为我们的客户端在连接本地的rabbitmq时候并不是真正意义上连接到服务器上,对于服务器来说也会虚拟出很多的服务器,供不同的客户端建立连接,每个虚拟的服务器之间都是相互独立的,默认就是一个斜杠来访问。
#\u6D88\u8D39\u8005\u6570\u91CF
spring.rabbitmq.listener.simple.concurrency= 10
# 消费者数量,定义是1的时候 表示就一个消费者,此时就是串行化执行。
spring.rabbitmq.listener.simple.max-concurrency= 10
#\u6D88\u8D39\u8005\u6BCF\u6B21\u4ECE\u961F\u5217\u83B7\u53D6\u7684\u6D88\u606F\u6570\u91CF
spring.rabbitmq.listener.simple.prefetch= 1
# 每次取出来几个从队列中。也可以设置为多个,势必会增加处理的效率,但是若是长时间不消费(不进行处理)也是会产生问题,所以这里综合考量 选择处置数量为1,可以在秒杀业务中对数据进行快速的处理。
#\u6D88\u8D39\u8005\u81EA\u52A8\u542F\u52A8
spring.rabbitmq.listener.simple.auto-startup=true
# morn开启
#\u6D88\u8D39\u5931\u8D25\uFF0C\u81EA\u52A8\u91CD\u65B0\u5165\u961F
spring.rabbitmq.listener.simple.default-requeue-rejected= true
# 消费者消费失败以后,会重新将数据压入到队列中
#\u542F\u7528\u53D1\u9001\u91CD\u8BD5
spring.rabbitmq.template.retry.enabled=true
# 重试确定
spring.rabbitmq.template.retry.initial-interval=1000
# 一秒
spring.rabbitmq.template.retry.max-attempts=3
# 最多重试次数
spring.rabbitmq.template.retry.max-interval=10000
# 最大的间隔是10s
spring.rabbitmq.template.retry.multiplier=1.0
# 以上只是对当前项目所需进行一个简单配置文件配置有具体需求小伙伴可以查看官网配置
若是yml配置文件如下,具体的每一个字段的含义在上面的配置中有写到:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
listener:
simple:
concurrency: 10
max-concurrency: 10
prefetch: 1
auto-startup: true
default-requeue-rejected: true
template:
retry:
enabled: true
initial-interval: 1000
max-attempts: 3
max-interval: 10000
multiplier: 1.0
Rabbitmq交换机
对于Rabbitmq的交换机来说具体的几种模式这里我也是学习别人,这里贴出别人大佬的地址Rabbitmq交换机学习这里就不进行具体的讲解了,下面开始具体部分实例学习:
Direct 模式
对于direct模式也是最简单的模式,就是先行创建一个队列。生产者发送消息,消费者通过队列的名称与生产者建立关联。
首先我们来创建一个config配置文件:
@Configuration
public class MQConfig {
public static final String QUEUE="queue";
@Bean
public Queue queue(){
return new Queue(QUEUE,true);
}
}
然后创建生产者,我们已经成功的进行了pom依赖的引入和连接到本地的rabbitmq
服务器上,就可以使用到AmqpTemplate
使用注入的方式。
@Service
public class MQSend {
private static Logger logger= LoggerFactory.getLogger(MQConfig.class);
// 控制台进行打印
@Autowired
AmqpTemplate amqpTemplate;
public void send (Object message){
String msg= RedisService.beanToString(message);
// redis的将对象类型转为string类型
logger.info("send Message:"+msg);
amqpTemplate.convertAndSend(MQConfig.QUEUE,msg);
// 调用方法,提供队列名称,和想要发送的消息。
}
}
生成者创建完成创建消费者:
@Service
public class MQReceiver {
private static Logger logger= LoggerFactory.getLogger(MQConfig.class);
//控制台打印
@RabbitListener(queues = MQConfig.QUEUE)
// 添加监听的注解,表示监听这个队列
public void receiver(String message){
logger.info("receive Message:"+message);
}
}
以上完成以后我们在Controller中进行调用
//注入调用
@Autowired
MQSend mqSend;
@RequestMapping("/mq")
@ResponseBody
public Result<String> mq() {
mqSend.send("hello world");
return Result.success("Hello world");
}
测试
如下图所示,访问此地址,就可以调用我们的的send()方法:
因为我们在控制台打印了输出信息,进行查看:发现对于生成者发送的消息对于接收端也真的进行到了接受并正确打印出信息。
Topic 模式
对于Topic模式还是和Direct模式有部分的区别:
- 首先我们还是创建两个
queue
。 - 对于
Topic
模式需要创建一个交换机。 - 利用key值将交换机和Queue进行一个绑定。
@Bean
public Queue topicQueue1(){
return new Queue(TOPIC_QUEUE1,true);
}
@Bean
public Queue topicQueue2(){
return new Queue(TOPIC_QUEUE2,true);
}
// 创建交换机
@Bean
public TopicExchange topicExchange(){
return new TopicExchange(TOPIC_EXCHANGE);
}
// 创建一个绑定,将队列一与交换机进行一个绑定。key值是 topic.key1。
@Bean
public Binding topicBind1(){
return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("topic.key1");
}
// 创建第二个绑定,将队列二与交换机进行一个绑定 key值是topic.#
//# 表示通配符匹配,对于任意的topic.XX 都可以进行一个匹配。
@Bean
public Binding topicBind2(){
return BindingBuilder.bind(topicQueue2