秒杀项目的消息推送
目录
一、创建消费者
二、创建订单链路配置
1.定义RabbitMQ配置类
2.创建RabbitmqOrderConfig配置类
三、如何实现RabbitMQ重复投递机制
1.开启发送者消息确认模式
2.消息发送确认
① 创建ConfirmCallBacker确认模式
② 创建ReturnCallBack退回模式
3.创建生产者
4.创建消费者(手动消费)
5.启动测试
6.采坑日记
① 异常点一:@RabbitListener
② 异常点二:手动确认消息
③ 异常点三:消息格式
④ 异常点四:消息不确认
⑤ 异常点五:消息无限投递
⑥ 异常点六:重复消费
四、RabbitMQ在项目中的秒杀业务优化
1.修改秒杀订单生产方式
2.消费者监听器完成秒杀订单生成
一、创建消费者
第1步:基于Spring Initialzr方式创建zmall-rabbitmq消费者模块
第2步:在公共模块中添加rabbitmq相关依赖
<!--rabbitmq-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
第3步:配置子模块zmall-rabbitmq的pom.xml,引入公共模块zmall-common
<dependencies><dependency><groupId>com.zking.zmall</groupId><artifactId>zmall-common</artifactId><version>1.0-SNAPSHOT</version></dependency>
</dependencies>
第4步:配置父模块的pom.xml,添加子模块zmall-rabbitmq
<modules><module>zmall-common</module>...<module>zmall-rabbitmq</module>
</modules>
第5步:配置application.yml
server:port: 8050
spring:application:name: zmall-rabbitmqdatasource:#type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikaritype: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/zmall?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=trueusername: rootpassword: 123456cloud:nacos:config:server-addr: localhost:8848redis:host: localhostport: 6379password: 123456jedis:pool:max-active: 100max-wait: 10max-idle: 10min-idle: 10database: 0rabbitmq:host: 192.168.29.128port: 5672username: adminpassword: adminvirtual-host: my_vhost# 发送者开启 confirm 确认机制#publisher-confirm-type: correlated# 发送者开启 return 确认机制#publisher-returns: true# 设置消费端手动 acklistener:simple:#手动应答acknowledge-mode: manual#消费端最小并发数concurrency: 5#消费端最大并发数max-concurrency: 10#一次请求中预处理的消息数量prefetch: 5# 是否支持重试retry:#启用消费重试enabled: true#重试次数max-attempts: 3#重试间隔时间initial-interval: 3000cache:channel:#缓存的channel数量size: 50
#mybatis-plus配置
mybatis-plus:#所对应的 XML 文件位置mapper-locations: classpath*:/mapper/*Mapper.xml#别名包扫描路径type-aliases-package: com.zking.zmall.modelconfiguration:#驼峰命名规则map-underscore-to-camel-case: true
#日志配置
logging:level:com.zking.zmall.mapper: debug
注意:这里面的配置,记得要改换为你的虚拟机ID
消费者采用的是手动消费模式,请注意设置
spring.rabbitmq.listener.simple.acknowledge-mode=manual
第6步:配置启动类
package com.zking.zmallrabbitmq;import org.checkerframework.checker.units.qual.Mass;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@EnableFeignClients
@EnableDiscoveryClient
@MapperScan({"com.zking.zmall.mapper","com.zking.zmallrabbitmq.mapper"})
@SpringBootApplication
public class ZmallRabbitmqApplication {public static void main(String[] args) {SpringApplication.run(ZmallRabbitmqApplication.class, args);}}
启动项目的时候,记得打开我们的nacos注册中心 ,测试一下看看有没有问题。能运行起来说明就没有问题。
二、创建订单链路配置
1.定义RabbitMQ配置类
定义RabbitMQ配置类,设置生成者发送数据时自动转换成JSON,设置消费者获取消息自动转换成JSON。
package com.zking.zmallrabbitmq.config;import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqConfig {@Beanpublic RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {RabbitTemplate template = new RabbitTemplate(connectionFactory);template.setMessageConverter(new Jackson2JsonMessageConverter());return template;}@Beanpublic SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();factory.setConnectionFactory(connectionFactory);factory.setMessageConverter(new Jackson2JsonMessageConverter());return factory;}
}
设置RabbitTemplate消息转换模式为Jackson2JsonMessageConverter;
设置RabbitMQ消费者监听器的的消息转换模式为Jackson2JsonMessageConverter;
2.创建RabbitmqOrderConfig配置类
创建RabbitmqOrderConfig配置类,增加订单队列、交换机及绑定关系。
package com.zking.zmallrabbitmq.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RabbitmqOrderConfig {public static final String ORDER_QUEUE="order-queue";public static final String ORDER_EXCHANGE="order-exchange";public static final String ORDER_ROUTING_KEY="order-routing-key";@Beanpublic Queue orderQueue(){return new Queue(ORDER_QUEUE,true);}@Beanpublic DirectExchange orderExchange(){return new DirectExchange(ORDER_EXCHANGE,true,false);}@Beanpublic Binding orderBinding(){return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(ORDER_ROUTING_KEY);}
}
三、如何实现RabbitMQ重复投递机制
1.开启发送者消息确认模式
配置application.yml,开启发送者confirm确认机制和return确认机制
spring:rabbitmq:# 发送者开启 confirm 确认机制publisher-confirm-type: correlated# 发送者开启 return 确认机制publisher-returns: true
2.消息发送确认
rabbitmq
的消息确认分为两部分:发送消息确认 和 消息接收确认
发送消息确认:用来确认生产者 producer
将消息发送到 broker
,broker
上的交换机 exchange
再投递给队列 queue
的过程中,消息是否成功投递。
消息从 producer
到 rabbitmq broker
有一个 confirmCallback
确认模式。
消息从 exchange
到 queue
投递失败有一个 returnCallback
退回模式。
我们可以利用这两个Callback
来确保消息100%送达。
Broker:简单来说,就是一个消息队列服务器实体。
① 创建ConfirmCallBacker确认模式
package com.zking.zmallrabbitmq.component;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {/**** @param correlationData 对象内部只有一个 id 属性,用来表示当前消息的唯一性* @param ack 消息投递到broker 的状态,true表示成功* @param cause 表示投递失败的原因*/@Overridepublic void confirm(CorrelationData correlationData, boolean ack, String cause) {if (!ack) {log.error("消息发送异常!");} else {log.info("发送者已经收到确认,ack={}, cause={}",ack, cause);}}
}
② 创建ReturnCallBack退回模式
@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {/**** @param message 消息体* @param replyCode 响应code* @param replyText 响应内容* @param exchange 交换机* @param routingKey 路由键*/@Overridepublic void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);}
}
3.创建生产者
创建生产者,模拟发送消息
package com.zking.zmallrabbitmq.controller;import com.zking.zmall.model.Order;
import com.zking.zmallrabbitmq.component.ConfirmCallbackService;
import com.zking.zmallrabbitmq.component.ReturnCallbackService;
import com.zking.zmallrabbitmq.config.RabbitmqOrderConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;@RestController
public class ProducerController {@Autowiredprivate RabbitTemplate rabbitTemplate;@Autowiredprivate ReturnCallbackService returnCallbackService;@Autowiredprivate ConfirmCallbackService confirmCallbackService;@RequestMapping("/sendMessage")public void sendMessage(){Order order=new Order();order.setId(1);order.setUserId(2);order.setLoginName("zhangsan");order.setUserAddress("长沙");order.setCreateTime(new Date());order.setCost(120.0F);order.setSerialNumber("123");order.setState(0);//ConfirmCallback确认模式rabbitTemplate.setConfirmCallback(confirmCallbackService);//ReturnCallback退回模式rabbitTemplate.setReturnCallback(returnCallbackService);rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);}
}
4.创建消费者(手动消费)
package com.zking.zmallrabbitmq.component;import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import com.zking.zmall.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class OrderConsumerListener {//最大重试次数private static final Integer MAX_RECONSUME_COUNT=3;//用于记录消息重试次数的集合,可以采用Redis方式实现private static Map<String,Integer> retryMap=new HashMap<>();@RabbitHandler@RabbitListener(queues = {"order-queue"},ackMode = "MANUAL")public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));//模拟异常,开始消息重试int i= 1/0;} catch (Exception e) {e.printStackTrace();String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");Integer retryCount = retryMap.get(msgId)==null?1:retryMap.get(msgId);log.info("即将开始第{}次消息重试....",retryCount);if(retryCount>=MAX_RECONSUME_COUNT){log.info("重试次数达到3次,消息被拒绝,retryCount="+retryCount);//此处要注意:当重试次数到达3次后,将拒绝消息且不在重新入队列channel.basicReject(deliverTag,false);}else{//重新发送消息到队尾//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));}retryMap.put(msgId,retryCount+1);}//成功确认消息,非批量模式channel.basicAck(deliverTag, false);}
}
5.启动测试
访问:http://localhost:8050/sendMessage
6.采坑日记
① 异常点一:@RabbitListener
异常原因:@RabbitListener作用于类上引发异常; 解决方案:@RabbitListener移至消费者监听器的方法上,而@RabbitListener只适用于方法级别。
② 异常点二:手动确认消息
虽然在消费者端的application.yml中配置手动消费模式,但是在服务消费时引发了这个异常错误,导致重复消费的问题。原因是使用@RabbitListener注解会自动ACK,如果方法中再手动ACK会造成重复ACK,所以报错;解决方式就是在@RabbitListener中配置手动消费模式:ackMode = "MANUAL"。
③ 异常点三:消息格式
在消费者消费消息时引发异常,触发消息重新投递,但是由于重新投递时导致消息格式问题引发了消息转换异常。 具体原因通过查看日志发现,重新投递的消息格式为text/plain,而我们在处理消息时采用的是json方式,导致消息转换异常。解决方案:将重新发送消息的状态由
MessageProperties.PERSISTENT_TEXT_PLAIN
更改为MessageProperties.MINIMAL_PERSISTENT_BASIC
④ 异常点四:消息不确认
这是一个非常没技术含量的坑,但却是非常容易犯错的地方。开启消息确认机制,消费消息别忘了channel.basicAck
,否则消息会一直存在,导致重复消费。
⑤ 异常点五:消息无限投递
最开始接触消息确认机制的时候,消费端代码就像下边这样写的,思路很简单:处理完业务逻辑后确认消息, int a = 1 / 0
发生异常后将消息重新投入队列
@RabbitHandler
public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));int i = 1 / 0;channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);} catch (Exception e) {channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);}
}
但是有个问题是,业务代码一旦出现 bug
99.9%的情况是不会自动修复,一条消息会被无限投递进队列,消费端无限执行,导致了死循环。
经过测试分析发现,当消息重新投递到消息队列时,这条消息不会回到队列尾部,仍是在队列头部。
消费者会立刻消费这条消息,业务处理再抛出异常,消息再重新入队,如此反复进行。导致消息队列处理出现阻塞,导致正常消息也无法运行。
而解决方案是,先将消息进行应答,此时消息队列会删除该条消息,同时我们再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。
//重新发送消息到队尾
//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。
channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));
⑥ 异常点六:重复消费
如何保证 MQ 的消费是幂等性,这个需要根据具体业务而定,可以借助MySQL
、或者redis
将消息持久化。
上述代码结构图
四、RabbitMQ在项目中的秒杀业务优化
1.修改秒杀订单生产方式
第1步:修改zmall-order订单模块的application.yml,加入rabbitmq相关配置
spring:rabbitmq:host: 192.168.195.143port: 5672username: adminpassword: adminvirtual-host: my_vhost# 设置消费端手动 acklistener:simple:acknowledge-mode: manual# 是否支持重试retry:enabled: truemax-attempts: 3
第2步:修改秒杀订单生成方式,针对抢购成功的秒杀订单直接推送到RabbitMQ中
OrderServiceImpl
@Autowiredprivate RabbitTemplate rabbitTemplate;@Transactional@Overridepublic JsonResponseBody<?> createKillOrder(User user, Integer pid, Float price) {//判断用户是否登录if(null==user)throw new BusinessException(JsonResponseStatus.TOKEN_ERROR);//根据秒杀商品ID和用户ID判断是否重复抢购Order order = redisService.getKillOrderByUidAndPid(user.getId(), pid);if(null!=order)return new JsonResponseBody<>(JsonResponseStatus.ORDER_REPART);//Redis库存预减long stock = redisService.decrement(pid);if(stock<0){redisService.increment(pid);return new JsonResponseBody<>(JsonResponseStatus.STOCK_EMPTY);}//创建订单order=new Order();order.setUserId(user.getId());order.setLoginName(user.getLoginName());order.setPid(pid);order.setCost(price);//将生成的秒杀订单保存到Redis中redisService.setKillOrderToRedis(pid,order);//将生成的秒杀订单推送到RabbitMQ中的订单队列中rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);return new JsonResponseBody<>();}
Order
// 数据库不存在的字段
@TableField(exist = false)private Integer pid;
2.消费者监听器完成秒杀订单生成
第1步:将zmall-order订单模块中的service业务处理接口及实现类移至消息者监听器模块。
第2步:zmall-rabbitmq模块中在IOrderService及OrderServiceImpl中重新定义生成秒杀订单方法
public interface IOrderService extends IService<Order> {void saveOrder(Order order);
}
package com.zking.zmallrabbitmq.service.impl;import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zking.zmall.config.RabbitmqOrderConfig;
import com.zking.zmall.mapper.OrderMapper;
import com.zking.zmall.model.Kill;
import com.zking.zmall.model.Order;
import com.zking.zmall.model.OrderDetail;
import com.zking.zmall.model.User;
import com.zking.zmall.service.impl.RedisServiceImpl;
import com.zking.zmall.util.JsonResponseBody;
import com.zking.zmall.util.JsonResponseStatus;
import com.zking.zmall.util.SnowFlake;
import com.zking.zmallrabbitmq.service.IOrderService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;//import io.seata.spring.annotation.GlobalTransactional;/*** <p>* 服务实现类* </p>** @author zking* @since 2023-02-08*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {@Autowiredprivate KillServiceImpl killService;@Autowiredprivate OrderDetailServiceImpl orderDetailService;@Autowiredprivate RabbitTemplate rabbitTemplate;@Transactional@Overridepublic void saveOrder(Order order) {//2.秒杀商品库存减一boolean flag=killService.update(new UpdateWrapper<Kill>().setSql("total=total-1").eq("item_id",order.getPid()).gt("total",0));if(!flag)return;//3.生成秒杀订单及订单项SnowFlake snowFlake=new SnowFlake(2,3);Long orderId=snowFlake.nextId();int orderIdInt = new Long(orderId).intValue();//订单order.setSerialNumber(orderIdInt+"");this.save(order);//订单项OrderDetail orderDetail=new OrderDetail();orderDetail.setOrderId(orderIdInt);orderDetail.setProductId(order.getPid());orderDetail.setQuantity(1);orderDetail.setCost(order.getCost());orderDetailService.save(orderDetail);}}
第3步:修改秒杀订单消费者监听器
OrderDetailServiceImpl
package com.zking.zmallrabbitmq.component;import com.alibaba.fastjson.JSON;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;
import com.zking.zmall.model.Order;
import com.zking.zmallrabbitmq.service.IOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class OrderConsumerListener {@Autowiredprivate IOrderService orderService;//最大重试次数private static final Integer MAX_RECONSUME_COUNT=3;//用于记录消息重试次数的集合,可以采用Redis方式实现private static Map<String,Integer> retryMap=new HashMap<>();@RabbitHandler@RabbitListener(queues = {"order-queue"},ackMode = "MANUAL")public void recieveMessage(Message message,Order order,Channel channel) throws IOException {//channel内按顺序自增的消息IDlong deliverTag = message.getMessageProperties().getDeliveryTag();try {System.out.println("接收到消息:"+message+",消息内容:"+ JSON.toJSONString(order));//模拟异常,开始消息重试
// int i= 1/0;
// 修改库存、生成订单表数据,生成订单项表数据
// 保存秒杀订单及订单项orderService.saveOrder(order);} catch (Exception e) {e.printStackTrace();String msgId = (String) message.getMessageProperties().getHeaders().get("spring_returned_message_correlation");Integer retryCount = retryMap.get(msgId)==null?1:retryMap.get(msgId);log.info("即将开始第{}次消息重试....",retryCount);if(retryCount>=MAX_RECONSUME_COUNT){log.info("重试次数达到3次,消息被拒绝,retryCount="+retryCount);//此处要注意:当重试次数到达3次后,将拒绝消息且不在重新入队列channel.basicReject(deliverTag,false);}else{//重新发送消息到队尾//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。channel.basicPublish(message.getMessageProperties().getReceivedExchange(),message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.MINIMAL_PERSISTENT_BASIC,JSON.toJSONBytes(order));}retryMap.put(msgId,retryCount+1);}//成功确认消息,非批量模式channel.basicAck(deliverTag, false);}
}
重启Jmeter压测,并查看测试结果。
注意1:目前微服务架构明显拆分,存在重复类的现象,所以在做微服务架构设计的时候,要尽可能避免此问题;
如service层中尽量避免调用service;service中尽可能只调用Mapper;
注意2:实际项目开发中,对于消息未能正常消费,应该设置人工补偿机制;
测试如下:
启动七个服务,nacos、nginx,项目
相关文章:
秒杀项目的消息推送
目录 一、创建消费者 二、创建订单链路配置 1.定义RabbitMQ配置类 2.创建RabbitmqOrderConfig配置类 三、如何实现RabbitMQ重复投递机制 1.开启发送者消息确认模式 2.消息发送确认 ① 创建ConfirmCallBacker确认模式 ② 创建ReturnCallBack退回模式 3.创建生产者 …...
最近开发及 vue3 几个小总结
只是单纯记录一下最近维护了几个项目之后的感触,也只是在自己现在水平上面的一些感觉。发发牢骚,水水文章 是否过度封装 可能水平不够吧,在 axios 封装的时候,只是把响应和拦截还有一些全局的配置封装了一下,并没有加…...
代谢组学分享-花青素通过调节氨基酸代谢改善糖尿病肾病的肾功能
代谢组学文献分享,文章标题:Anthocyanin improves kidney function in diabetic kidney disease by regulating amino acid metabolism 发表期刊:Journal of Translational Medicine 影响因子:8.44 作者单位:中山大…...
超简单!pytorch入门教程:Tensor
超简单!pytorch入门教程:Tensor 一、pytorch安装 安装pytorch之前,需要安装好python(废话),还没安装过python的宝宝请先移步到廖雪峰的python教程,待安装熟悉完之后,再过来这边。 …...
如何使用COCO数据集,注意事项
COCO数据集可用来训练目标检测,分类,实例分割等。 下面简单说下如何使用这个数据集, 数据集下载可用如下的代码进行,以2017为例。 # Download the image data. cd ./images echo "Downloading MSCOCO train images ...&quo…...
金三银四跳槽季,JAVA面试撸题就来【笑小枫】微信小程序吧~
JAVA面试撸题就来【笑小枫】微信小程序啦~ 疫情已过,金三银四即将到来,小伙伴们是否有跳槽的打算呢?不管有没有,技术不能丢,让我们一起来撸题吧。 博主最近整理了一批面试题,包括JAVA基础、多线程与锁、Red…...
分享115个HTML电子商务模板,总有一款适合您
分享115个HTML电子商务模板,总有一款适合您 115个HTML电子商务模板下载链接:https://pan.baidu.com/s/158y3jP0tv7ZikxNOBMKsSg?pwdt970 提取码:t970 Python采集代码下载链接:采集代码.zip - 蓝奏云 import os import shuti…...
Python 字符串
字符串是 Python 中最常用的数据类型。我们可以使用引号 ( 或 " ) 来创建字符串。创建字符串很简单,只要为变量分配一个值即可。例如:var1 Hello World!var2 "Python Runoob"Python 访问字符串中的值Python 不支持单字符类型ÿ…...
总线定义,车载总线:车载etherNet or CAN
总线的定义总线是连接多个设备或者接入点的数据传输通路。这里面的关键词是多个设备或者接入点,所以不要过于局限的看待总线。根据互联的设备/接入点不同,传输的数据带宽,速率,距离不同和应用场景的不同都可能有不同的总线。不同的…...
Python(for和while)循环嵌套及用法
Python 不仅支持 if 语句相互嵌套,while 和 for 循环结构也支持嵌套。 所谓嵌套(Nest),就是一条语句里面还有另一条语句,例如 for 里面还有 for,while 里面还有 while,甚至 while 中有 for 或者…...
6万字电力行业系统解决方案光伏电站综合安防系统解决方案
【版权声明】本资料来源网络,知识分享,仅供个人学习,请勿商用。【侵删致歉】如有侵权请联系小编,将在收到信息后第一时间删除!完整资料领取见文末,部分资料内容: 目录 第 一 章背景与需求 1.1行…...
[Android Studio]Android 数据存储--SQLite数据库存储
🟧🟨🟩🟦🟪 Android Debug🟧🟨🟩🟦🟪 Topic 发布安卓学习过程中遇到问题解决过程,希望我的解决方案可以对小伙伴们有帮助。 📋笔记目…...
学校节能降耗减排方案——能耗监管平台的建设及效果剖析
摘要:作为崭新的校园能耗管理手段,能耗监测平台以传统管理方式无法企及的优势有力地提升了高校能源管理工作的水平.从而受到了相关管理者的青睐。本文梳理总结了高校能耗监测平台的基本组成和优势特点,同时对能耗平台建设和使用中…...
探索IP地址的应用
无论是互联网行业还是传统行业都会用到网络,作为企业如何维护网络安全,保障网站不被攻击,数据不被泄露等。这个时候我们就会通查询IP归属地,辅助企业解决安全问题。下面介绍一下ip归属地在各行业的具体应用。1.网安行业应用一&…...
点赞破万!阿里面试官总结的2022最新1685页Java面试宝典太全了
程序员入职企业的难度也在持续加大,如何顺利通过面试成为了大家所关心的话题。针对这些人群的需求,小编从阿里找来一份让大家在求职过程中旗开得胜!是从什么时候开始准备的?大概的我已经记不清了,可能是 4 月份左右开始…...
项目搭建规范
一. 代码规范 1.1. 集成editorconfig配置 EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。 # http://editorconfig.org root true [*] # 表示所有文件适用 charset utf-8 # 设置文件字符集为 utf-8 indent_style space # 缩进…...
8.Docker Machine
Docker Machine Docker Machine是Docker官方编排(Orchestration)项目之一,负责在多种平台上快速安装 Docker 环境。 Docker Machine项目基于Go语言实现,目前在Github上进行维护。 Docker Machine是 Docker 官方提供的一个工具&…...
如何配合使用ESLINT 和 PRETTIER
各自的作用 eslint:静态分析语法错误prettier:代码格式化工具(需要在IDE中安装prettier的插件) 使用方法: 安装 npm install eslint --save-dev // 创建 .eslintrc.json 文件 npx eslint --init npm install eslint-…...
学英语的优势已来,抓住这个机会
文 / 冰雪(微信公众号:王不留) ChatGPT大火,国外的商业价值还没找到,咱们这边已经开始变现了。谷雨小姐姐昨天在”一起学英语”微信群发了一张“收割韭菜”的文案截图。 299入社群,服务内容为:免…...
基于微信小程序云开发实现考研题库小程序项目(完整版)
今天手把手的带大家实现一款答题类的题库小程序,如果着急的话,可以直接去看文末源码.下载与项目部署。考研题库小程序云开发实战,完整版提供给大家学习。题库小程序,基于云开发的微信答题小程序,软件架构是微信原生小程…...
AI一点通:使用 ColumnTransformer 转换 Pandas DataFrame 的一个或多个列
在处理表格数据时,常常需要对一个或多个列进行转换以使它们更适合于分析或建模。在许多情况下,可以使用 Pandas 库轻松完成这些转换。然而,在处理大型数据集或构建机器学习管道时,使用 scikit-learn 的 ColumnTransformer 类来将转…...
【C语言】全局变量、局部变量和静态变量的区别
目录一、变量(一)全局变量(二)局部变量(三)静态变量(1)静态全局变量(2)静态局部变量二、常量一、变量 1、变量定义 变量的名称可以由字母、数字和下划线字符…...
血氧仪「上潜」,智能穿戴「下沉」
文|智能相对论作者|沈浪缺货、涨价、一“仪”难求......过去短短的几周,血氧仪市场持续走热,受到前所未有的关注,像鱼跃医疗这样的业内巨头更是赚得盆满钵满,但同时也深陷“发国难财”的舆论泥潭,说来也是唏嘘。尽管目…...
CPP2022-计算机类-期末考试
6-1 判断素数 分数 5 全屏浏览题目 切换布局 作者 李国瑞 单位 东北大学秦皇岛分校 设计一个函数,判断输入数据是否为素数,返回bool类型结果。 函数接口定义: bool prime(int num); 说明:num为正整数。 裁判测试程序样例&…...
【蓝桥集训】第二天——差分
作者:指针不指南吗 专栏:Acwing 蓝桥集训每日一题 🐾做题过程中首先应该注意时间复杂度问题🐾 文章目录1.改变数组元素2.差分3.差分矩阵1.改变数组元素 给定一个空数组 V 和一个整数数组 a1,a2,…,an。 现在要对数组 V 进行 n 次操…...
Spring Boot最核心的27个注解,你了解多少?
https://blog.csdn.net/ManuMAX/article/details/129017443 导读 Spring Boot方式的项目开发已经逐步成为Java应用开发领域的主流框架,它不仅可以方便地创建生产级的Spring应用程序,还能轻松地通过一些注解配置与目前比较火热的微服务框架SpringCloud集成…...
css3弹性盒子
弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。 弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。 弹性容器内包含了一个或多个弹性子元素。 display:flex; 修改排列方式: 0. direction: rtl; //(right-to-left),弹性子元素的…...
数据分析与SAS学习笔记2
SAS在企业使用的情况: SAS是一个很昂贵的商业软件。在企业中使用SAS比较多,在企业界中是比较流行,在学术界使用R比较多。 SAS简介:统计分析系统 处理生物分析数据。 SAS成为统计领域的国际标准软件,服务全球50000多家…...
零信任-Akamai零信任介绍(6)
Akamai零信任介绍 Akamai是一家专注于分布式网络服务的公司,它提供了一系列的互联网内容和应用加速服务。关于Akamai的零信任,它指的是Akamai的安全架构中不存在任何一个环节是可以被单独的控制或影响的,因此可以提供更高的安全性。通过使…...
表现良好的最长时段[前缀和思想子数组]
前缀和与最长子数组前言一、表现良好的最长时间段二、前缀和思想&子数组1、前缀和&map2、前缀和&单调栈总结参考文献前言 对于子数组/子串问题,紧密连续前缀和/滑动窗口/单调栈;挖掘内在规律,可以简化代码,降低时空复…...
男科医院网站建设/2345网址导航应用
HTML 图像在 HTML 中,图像由 标签定义, 标签并不会在网页中插入图像,而是从网页上链接图像。实例Paoo教程 - 水乡景色尝试一下 在线实例插入图像本例演示如何在网页中显示图像。从不同的位置插入图片本例演示如何将其他文件夹或服务器的图片显示到网页中…...
百度地图嵌入公司网站/写软文能赚钱吗
1、启动 使用 service 启动:service mysqld start 2、停止 使用 service 启动:service mysqld stop 3、重启 使用 service 启动:service mysqld restart...
做网站的工作/靠谱的免费建站
继 2014 年 3 月 Java 8 发布之后,时隔 4 年,2018 年 9 月,Java 11 如期发布,其间间隔了 Java 9 和 Java 10 两个非LTS(Long Term Support)版本。作为最新的LTS版本,相比 Java 8,Java 11 包含了模块系统、改…...
网页视频下载软件免费版/资源网站排名优化seo
给定两个整数 nnn 和 kkk,请你找到并输出能够整除 nnn 的第 kkk 小的正整数。 输入格式 一行,两个整数 nnn 和 kkk。 输出格式 输出能够整除 nnn 的第 kkk 小的整数。 如果不存在,则输出 −1−1−1。 数据范围 1≤n≤10151≤n≤10^{15}1≤…...
wordpress评论采集插件/新手seo要学多久
接口与类的调用在java并发编程开发项目中是非常常见的一种开发需求,而今天我们就通过案例分析来了解一下,java并发编程常见的接口与类都有哪些类型。1、接口:ConditionCondition为接口类型,它将Object监视器方法(wait、notify和notifyAll)分解…...
外国做家具的网站/it行业培训机构哪个好
三.下面分析一下高通的android2.3的代码中SD卡驱动的流程。 在kernel中,SD卡是作为平台设备加入到内核中去的,在/kernel/arch/arm/mach-msm/devices-msm7627a.c中: [cpp] view plaincopy static void __init msm7x2x_init(void) …...