接口优化1

news2024/11/23 22:32:48

接口优化

文章目录

  • 接口优化
      • 1. 内容概述
      • 2. 集成RabbitMQ
        • 2.1 下载
        • 2.2 SpringBoot集成RabbitMQ 快速入门
          • 1.相关配置
          • 2.创建发送者者和接收者
        • 2.3 rabbitmq四种交换模式
        • 2.4 秒杀接口优化

1. 内容概述

核心思路:减少对数据库的访问,利用Redis的高并发特性来实现。

  1. 系统初始化,把商品库存数量加载到Redis。
    //这一步操作确保了在秒杀开始之前,商品库存数据已经预加载到Redis中,减少了在秒杀开始时对数据库的访问。
  2. 收到请求,Redis预减库存,库存不足,直接返回,否则进入3。
    //在收到用户的秒杀请求后,首先从Redis中减去相应的商品数量。如果减去后的数量小于0,说明商品库存不足,直接返回秒杀失败。否则,进入下一步。
  3. 请求入队,立即返回排队中。(1-3 异步下单)
    //如果商品库存充足,用户的秒杀请求会被放入一个队列中,然后立即返回给用户一个正在排队中的信息。这一步操作是异步的,即不需要等待队列中的请求全部处理完毕,就可以立即返回。
  4. 请求出队,生成订单,减少库存。
    //当队列中有请求需要处理时,就从队列中取出一个请求,然后生成相应的订单,并从Redis中减去相应的商品数量。
  5. 客户端轮询,是否秒杀成功(4-5 并发操作)
    //客户端在发送秒杀请求后,会定时向服务器发送轮询请求,询问秒杀是否成功。在这个过程中,步骤4和5是并发进行的,即服务器的订单生成和库存减少操作,以及客户端的轮询操作,可以同时进行。

内容模块

  • Redis预减库存减少数据库访问
  • 内存标记减少Redis访问
  • RabbitMQ队列缓冲,异步下单,增强用户体验
  • RabbitMQ安装与Spring Boot集成
  • 访问Nginx水平扩展
  • 压测

2. 集成RabbitMQ

2.1 下载

RabbitMQ介绍

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现

安装 erlang:安装erlang步骤

在这里插入图片描述

安装RabbitMQ

这里要注意:rabbitmq版本要和erlang版本对应

安装RabbitMQ

在这里插入图片描述

这里因为版本问题 rabbitmq一直无法运行
解决:https://blog.csdn.net/qq_43616898/article/details/105602839

运行成功
在这里插入图片描述
在这里插入图片描述

2.2 SpringBoot集成RabbitMQ 快速入门

步骤

  1. 添加依赖 spring-boot-starter-amqp
  2. 创建消息接受者
  3. 创建消息发送者
1.相关配置
<!--		集成rabbitmq-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
#rabbitmq
spring.rabbitmq.host=192.168.33.10
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
#\u6D88\u8D39\u8005\u6570\u91CF
spring.rabbitmq.listener.simple.concurrency= 10
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
#\u6D88\u8D39\u8005\u81EA\u52A8\u542F\u52A8
spring.rabbitmq.listener.simple.auto-startup=true
#\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
spring.rabbitmq.template.retry.multiplier=1.0
package com.example.rabbitmq;

import java.util.HashMap;
import java.util.Map;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MQConfig {
	public static final String QUEUE = "queue";
	@Bean
	public Queue queue() {
		return new Queue(QUEUE, true);
	}
}

2.创建发送者者和接收者

AmqpTemplate对象作用

  1. 发送消息:允许你发送各种类型的消息(如简单文本、JSON、POJOs等)到队列、主题或交换机。
  2. 接收消息:通过在方法上使用@RabbitListener注解,AmqpTemplate可以自动为你处理接收到的消息。
  3. 连接和断开连接:AmqpTemplate提供了连接和断开连接的方法,这样你就可以轻松地在你的应用中使用RabbitMQ。
  4. 错误处理:如果在发送或接收消息时发生异常,AmqpTemplate可以自动为你处理这些异常。
//创建发送者
@Service
public class MQSender {

	private static Logger log = LoggerFactory.getLogger(MQSender.class);
	
	@Autowired
	AmqpTemplate amqpTemplate ;
	
	
	public void send(Object message) {
		String msg = RedisService.beanToString(message);
		log.info("send message:"+msg);
		amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
	}
}
//创建接受者

@Service
public class MQReceiver {

		private static Logger log =  LoggerFactory.getLogger(MQReceiver.class);
		
		@Autowired
		RedisService redisService;
	
		@RabbitListener(queues=MQConfig.QUEUE)
		public void receive(String message) {
			log.info("receive message:"+message);
		}

测试

   @RequestMapping("/mq")
    @ResponseBody
    public Result<String> mq(){
        mqSender.send("nihao,我是mq");
        return Result.success("hello,mq");
    }

在这里插入图片描述

2.3 rabbitmq四种交换模式

以下的图表展示了RabbitMQ四种交换机模式:

名称描述
Direct Exchange(直连交换机)根据Routing Key将消息投递到不同的队列。每个队列都会绑定一个交换机和特定的Routing Key。当消息被发送到交换机时,它会被送到指定的队列。
Fanout Exchange(扇出交换机)采用广播模式,根据绑定的交换机,路由到与之对应的所有队列。也就是说,如果一个交换机绑定了多个队列,消息将会被广播到这些队列。
Topic Exchange(主题交换机)对路由键进行模式匹配后进行投递。这种类型的交换机将根据Routing Key的模式匹配来决定将消息投递到哪个队列。
Headers Exchange(头交换机)不处理Routing Key,而是根据发送的消息内容中的headers属性进行匹配。这种类型的交换机将根据消息的headers属性来决定将消息投递到哪个队列。

rabbitmq的四种交换机模式



@Configuration
public class MQConfig {
	
	public static final String MIAOSHA_QUEUE = "miaosha.queue";
	public static final String QUEUE = "queue";
	public static final String TOPIC_QUEUE1 = "topic.queue1";
	public static final String TOPIC_QUEUE2 = "topic.queue2";
	public static final String HEADER_QUEUE = "header.queue";
	public static final String TOPIC_EXCHANGE = "topicExchage";
	public static final String FANOUT_EXCHANGE = "fanoutxchage";
	public static final String HEADERS_EXCHANGE = "headersExchage";
	
	/**
	 * Direct模式 交换机Exchange
	 * */
	@Bean
	public Queue queue() {
		return new Queue(QUEUE, true);
	}
	
	/**
	 * Topic模式 交换机Exchange
	 * */
	@Bean
	public Queue topicQueue1() {
		return new Queue(TOPIC_QUEUE1, true);
	}
	@Bean
	public Queue topicQueue2() {
		return new Queue(TOPIC_QUEUE2, true);
	}
	@Bean
	public TopicExchange topicExchage(){
		return new TopicExchange(TOPIC_EXCHANGE);
	}
	@Bean
	public Binding topicBinding1() {
		return BindingBuilder.bind(topicQueue1()).to(topicExchage()).with("topic.key1");
	}
	@Bean
	public Binding topicBinding2() {
		return BindingBuilder.bind(topicQueue2()).to(topicExchage()).with("topic.#");
	}
	/**
	 * Fanout模式 交换机Exchange
	 * */
	@Bean
	public FanoutExchange fanoutExchage(){
		return new FanoutExchange(FANOUT_EXCHANGE);
	}
	@Bean
	public Binding FanoutBinding1() {
		return BindingBuilder.bind(topicQueue1()).to(fanoutExchage());
	}
	@Bean
	public Binding FanoutBinding2() {
		return BindingBuilder.bind(topicQueue2()).to(fanoutExchage());
	}
	/**
	 * Header模式 交换机Exchange
	 * */
	@Bean
	public HeadersExchange headersExchage(){
		return new HeadersExchange(HEADERS_EXCHANGE);
	}
	@Bean
	public Queue headerQueue1() {
		return new Queue(HEADER_QUEUE, true);
	}
	@Bean
	public Binding headerBinding() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("header1", "value1");
		map.put("header2", "value2");
		return BindingBuilder.bind(headerQueue1()).to(headersExchage()).whereAll(map).match();
	}
	
	
}

package com.example.rabbitmq;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.redis.RedisService;
//创建发送者
@Service
public class MQSender {

	private static Logger log = LoggerFactory.getLogger(MQSender.class);
	
	@Autowired
	AmqpTemplate amqpTemplate ;
	
	public void sendMiaoshaMessage(MiaoshaMessage mm) {
		String msg = RedisService.beanToString(mm);
		log.info("send message:"+msg);
		amqpTemplate.convertAndSend(MQConfig.MIAOSHA_QUEUE, msg);
	}
	
//	public void send(Object message) {
//		String msg = RedisService.beanToString(message);
//		log.info("send message:"+msg);
//		amqpTemplate.convertAndSend(MQConfig.QUEUE, msg);
//	}
//	
	public void sendTopic(Object message) {
		String msg = RedisService.beanToString(message);
		log.info("send topic message:"+msg);
		amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key1", msg+"1");
		amqpTemplate.convertAndSend(MQConfig.TOPIC_EXCHANGE, "topic.key2", msg+"2");
	}

	public void sendFanout(Object message) {
		String msg = RedisService.beanToString(message);
		log.info("send fanout message:"+msg);
		amqpTemplate.convertAndSend(MQConfig.FANOUT_EXCHANGE, "", msg);
	}

	public void sendHeader(Object message) {
		String msg = RedisService.beanToString(message);
		log.info("send fanout message:"+msg);
		MessageProperties properties = new MessageProperties();
		properties.setHeader("header1", "value1");
		properties.setHeader("header2", "value2");
		Message obj = new Message(msg.getBytes(), properties);
		amqpTemplate.convertAndSend(MQConfig.HEADERS_EXCHANGE, "", obj);
	}

	
	
}

package com.example.rabbitmq;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.domain.MiaoshaOrder;
import com.example.domain.MiaoShaUser;
import com.example.redis.RedisService;
import com.example.service.GoodsService;
import com.example.service.MiaoshaService;
import com.example.service.OrderService;
import com.example.vo.GoodsVo;

@Service
public class MQReceiver {

		private static Logger log = LoggerFactory.getLogger(MQReceiver.class);
		
		@Autowired
		RedisService redisService;
		
		@Autowired
		GoodsService goodsService;
		
		@Autowired
		OrderService orderService;
		
		@Autowired
		MiaoshaService miaoshaService;
		
//		@RabbitListener(queues=MQConfig.MIAOSHA_QUEUE)
//		public void receive(String message) {
//			log.info("receive message:"+message);
//			MiaoshaMessage mm  = RedisService.stringToBean(message, MiaoshaMessage.class);
//			MiaoShaUser user = mm.getUser();
//			long goodsId = mm.getGoodsId();
//
//			GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);
//	    	int stock = goods.getStockCount();
//	    	if(stock <= 0) {
//	    		return;
//	    	}
//	    	//判断是否已经秒杀到了
//	    	MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
//	    	if(order != null) {
//	    		return;
//	    	}
//	    	//减库存 下订单 写入秒杀订单
//	    	miaoshaService.miaosha(user, goods);
//		}
	
//		@RabbitListener(queues=MQConfig.QUEUE)
//		public void receive(String message) {
//			log.info("receive message:"+message);
//		}

		@RabbitListener(queues=MQConfig.TOPIC_QUEUE1)
		public void receiveTopic1(String message) {
			log.info(" topic  queue1 message:"+message);
		}

		@RabbitListener(queues=MQConfig.TOPIC_QUEUE2)
		public void receiveTopic2(String message) {
			log.info(" topic  queue2 message:"+message);
		}

		@RabbitListener(queues=MQConfig.HEADER_QUEUE)
		public void receiveHeaderQueue(byte[] message) {
			log.info(" header  queue message:"+new String(message));
		}

		
}

2.4 秒杀接口优化

初始实现秒杀功能


    @RequestMapping(value="/do_miaosha", method= RequestMethod.POST)
    @ResponseBody
    public Result<OrderInfo> miaosha(Model model, MiaoShaUser user,
                                     @RequestParam("goodsId")long goodsId) {
        model.addAttribute("user", user);
        if(user == null) {
            return Result.error(CodeMsg.SESSION_ERROR);
        }
        //判断库存
        GoodsVo goods = goodsService.getGoodsVoByGoodsId(goodsId);//10个商品,req1 req2
        int stock = goods.getStockCount();
        if(stock <= 0) {
            return Result.error(CodeMsg.MIAO_SHA_OVER);
        }
        //判断是否已经秒杀到了
        MiaoshaOrder order = orderService.getMiaoshaOrderByUserIdGoodsId(user.getId(), goodsId);
        if(order != null) {
            return Result.error(CodeMsg.REPEATE_MIAOSHA);
        }
        //减库存 下订单 写入秒杀订单
        OrderInfo orderInfo = miaoshaService.miaosha(user, goods);
        return Result.success(orderInfo);
    }

核心思路:减少对数据库的访问,利用Redis的高并发特性来实现。

  1. 系统初始化,把商品库存数量加载到Redis。
    //这一步操作确保了在秒杀开始之前,商品库存数据已经预加载到Redis中,减少了在秒杀开始时对数据库的访问。
  2. 收到请求,Redis预减库存,库存不足,直接返回,否则进入3。
    //在收到用户的秒杀请求后,首先从Redis中减去相应的商品数量。如果减去后的数量小于0,说明商品库存不足,直接返回秒杀失败。否则,进入下一步。
  3. 请求入队,立即返回排队中。(1-3 异步下单)
    //如果商品库存充足,用户的秒杀请求会被放入一个队列中,然后立即返回给用户一个正在排队中的信息。这一步操作是异步的,即不需要等待队列中的请求全部处理完毕,就可以立即返回。
  4. 请求出队,生成订单,减少库存。
    //当队列中有请求需要处理时,就从队列中取出一个请求,然后生成相应的订单,并从Redis中减去相应的商品数量。
  5. 客户端轮询,是否秒杀成功(4-5 并发操作)
    //客户端在发送秒杀请求后,会定时向服务器发送轮询请求,询问秒杀是否成功。在这个过程中,步骤4和5是并发进行的,即服务器的订单生成和库存减少操作,以及客户端的轮询操作,可以同时进行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1004579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20.Xaml GroupBox控件 ---->带标题的内容控件

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

【excel加密】excel工作表保护和工作簿保护,有什么区别?

Excel不能编辑大家都知道时设置了工作表保护&#xff0c;在我们平时设置或取消工作表保护的时候也能够看到旁边的工作簿保护&#xff0c;那它的作用是什么呢&#xff1f;今天我们来看一下&#xff0c;工作表保护和工作簿保护的区别是什么吧&#xff01; 先统一讲一下如何设置保…

蓝色光标发布营销行业模型“Blue AI” 人机协同重构产业新格局

“Blue AI 行业模型的发布&#xff0c;标志着蓝色光标Al战略迈入新阶段”。蓝色光标集团CEO潘飞在发布会现场表示&#xff0c;把握好AI机会&#xff0c;会让蓝色光标更加接近于一家全球化的科技公司。今后&#xff0c;我们将继续沿着三个方面迭代与迈进&#xff1a;一是生产效率…

如何在多版本C#工程上添加程序集

给出工程配置文件添加程序集示例&#xff1a; <ItemGroup Condition"$(TargetFramework) net451"> <PackageReference Include"MySql.Data" Version"6.7.9" /> <Reference Include"System.Configuration" /&…

台式电脑组装爬坑之路

台式电脑十大部件 CPU 主板 内存 硬盘 机箱 电源 显卡 CPU散热器 显示器 鼠标 键盘 基本知识

Java面试笔试acm版输入

首先区分scanner.nextInt()//输入一个整数&#xff0c;只能读取一个数&#xff0c;空格就停止。 scanner.next()//输入字符串&#xff0c;只能读取一个字符串&#xff0c;空格就停止&#xff0c;但是逗号不停止。 scanner.nextLine() 读取一行&#xff0c;换行停止&#xff0c…

视频可以裁剪画面大小吗?教你几种简单裁切方法

视频可以裁剪画面大小吗&#xff1f;现在人们拍摄的视频越来越多&#xff0c;因此&#xff0c;视频编辑已成为一项必要的技能。视频裁剪是视频编辑中最基本的操作之一&#xff0c;它可以帮助你删除不需要的内容、调整画面大小和焦点&#xff0c;以及改善视频的视觉效果。那么视…

【uniapp】使用canvas组件编译到微信小程序兼容出错问题

使用uniapp编译跨平台项目会遇到不少兼容问题&#xff0c;这里主要讲canvas组件的&#xff0c;编译到微信小程序会有兼容出错问题&#xff0c;这里给讲一下解决方案&#xff0c;希望有帮助。 常见问题 draw无法绘制图形 如果使用CanvasContext绘制&#xff0c;以下代码&…

profinet是什么?

profinet是什么&#xff1f; 参考&#xff1a;一文读懂Profibus、Profinet、Ethernet的区别 PROFINETPROFIbusetherNET&#xff0c;把Profibus的主从结构移植到以太网上&#xff0c;所以profinet会有Controller和Device&#xff0c;他们的关系可以简单的对应于profibus的Maste…

动态 import

文章目录 动态 import1. 动态导入2. 语法3. 描述4. 模块命名空间对象5. 使用示例6. 动态导入的原理7. 兼容 动态 import import() 语法&#xff0c;通常称为动态导入&#xff0c;是一种类似函数的表达式&#xff0c;允许将 ECMAScript 模块异步和动态地加载到可能的非模块环境…

【python自动化应用】借助ChatGPT与Python轻松实现办公自动化 —— AIC松鼠活动第九期

背景&#xff1a;当今的工作环境中&#xff0c;高效和快速地完成日常任务对于个人和机构都至关重要。许多人正在利用Python自动化来提高他们的工作效率。Python自动化可以帮助您自动完成繁琐的、重复的、容易出错的任务&#xff0c;从而节省时间和精力。 Python自动化有很多应用…

《徐亦达机器学习:Kalman Filter 卡尔曼滤波笔记 (一)》

P ( x t P(x_t P(xt​| x t − 1 ) x_{t-1}) xt−1​) P ( y t P(y_t P(yt​| x t ) x_t) xt​) P ( x 1 ) P(x_1) P(x1​)Discrete State DM A X t − 1 , X t A_{X_{t-1},X_t} AXt−1​,Xt​​Any π \pi πLinear Gassian Kalman DM N ( A X t − 1 B , Q ) N(AX_{t-1}B,Q)…

Golang gin Cookie的使用

Cookie介绍 HTTP是无状态协议&#xff0c;服务器不能记录浏览器的访问状态&#xff0c;也就是说服务器不能区分两次请求是否由同一个客户端发出Cookie就是解决HTTP协议无状态的方案之一&#xff0c;中文是小甜饼的意思Cookie实际上就是服务器保存在浏览器上的一段信息。浏览器有…

AI绘画:StableDiffusion实操教程-斗破苍穹-云韵-婚服(附高清图下载)

大家好&#xff0c;我是小梦&#xff0c;最近一直研究AI绘画。 不久前&#xff0c;我与大家分享了StableDiffusion的全面教程&#xff1a;“AI绘画&#xff1a;Stable Diffusion 终极宝典&#xff1a;从入门到精通 ” 然而&#xff0c;仍有些读者提出&#xff0c;虽然他们已经…

nVisual光纤资源管理软件,亮相第24届光博会

第24届中国国际光电博览会&#xff08;CIOE&#xff09;于9月6日至8日在深圳国际会展中心盛大开幕。作为行业领先的网络基础设施管理软件供应商&#xff0c;耐威迪携nVisual光纤资源管理软件亮相9号馆C001、C002展位&#xff0c;全方位展示室外网络基础设施管理、光纤资源管理、…

【数据结构】堆的创建

文章目录 一、堆的概念及结构1、什么是堆2、堆的性质3、堆的结构及分类 二、堆的创建1、堆向下调整算法2、堆向上调整算法3、堆的创建 一、堆的概念及结构 1、什么是堆 堆就是以二叉树的顺序存储方式来存储元素&#xff0c;同时又要满足父亲结点存储数据都要大于儿子结点存储数…

Win10系统电脑没有键盘怎么启用软键盘

如果使用电脑的过程中出现键盘故障的话&#xff0c;我们是可以通过系统自带的软键盘&#xff0c;也就是屏幕键盘来应急的。那么如何打开软键盘来使用呢&#xff1f;下面就和大家讲讲电脑Win10如何打开软键盘吧&#xff0c;操作也是很简单的。 1、找到桌面上的控制面板&#xf…

距离度量方法

距离度量方法 一、欧式距离(Euclidean Distance)二、余弦相似度(Cosine Similarity)三、汉明距离(Hamming Distance)四、曼哈顿距离(Manhattan Distance)五、切比雪夫距离(Chebyshev Distance)六、闵可夫斯基距离(Minkowski Distance)七、Jaccard Index八、Haversine Distance九…

【C++--类和对象】开篇

内联 在频繁调用一个函数时&#xff0c;会建立栈帧&#xff0c;如何减少内存的消耗 int add(int x,int y) { return (xy)*10; } c语言中&#xff0c;用宏函数 #define add(x,y) (((x)(y))*10) 若不加括号会出现一些问题 宏优势 不需要建立栈帧&#xff0c;提高调用效率&a…

【文末送书】2023年以就业为目的学习Java还有必要吗?

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…