秒杀项目之消息推送

news2024/7/6 19:42:51

目录

  • 一、创建消费者
  • 二、创建订单链路配置
    • 2.1 定义RabbitMQ配置类
    • 2.2 创建RabbitmqOrderConfig配置类
  • 三、如何实现RabbitMQ重复投递机制
    • 3.1 开启发送者消息确认模式
    • 3.2 消费发送确认
      • 3.2.1 创建ConfirmCallBack确认模式
      • 3.2.2 创建ReturnCallBack退回模式
    • 3.3 创建生产者
    • 3.4 创建消费者(手动消费)
    • 3.5 启动测试
    • 3.6 踩坑日记
      • 3.6.1 异常点一:@RabbitListener
      • 3.6.2 手动确认消息
      • 3.6.3 消息格式
      • 3.6.4 消息不确认
      • 3.6.5 消息无限投递
      • 3.6.6 重复消费
  • 四、秒杀业务优化
    • 4.1 修改秒杀订单生成方式
    • 4.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-rabbitmq
  datasource:
    #type连接池类型 DBCP,C3P0,Hikari,Druid,默认为Hikari
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/zmall?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
    username: root
    password: 1234
  cloud:
    nacos:
      config:
        server-addr: localhost:8848
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 100
        max-wait: 10
        max-idle: 10
        min-idle: 10
    database: 0
  rabbitmq:
    host: 43.143.169.245
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 发送者开启 confirm 确认机制
    publisher-confirm-type: correlated
    # 发送者开启 return 确认机制
    publisher-returns: true
    # 设置消费端手动 ack
    listener:
      simple:
        #手动应答
        acknowledge-mode: manual
        #消费端最小并发数
        concurrency: 5
        #消费端最大并发数
        max-concurrency: 10
        #一次请求中预处理的消息数量
        prefetch: 5
        # 是否支持重试
        retry:
          #启用消费重试
          enabled: true
          #重试次数
          max-attempts: 3
          #重试间隔时间
          initial-interval: 3000
    cache:
      channel:
        #缓存的channel数量
        size: 50
#mybatis-plus配置
mybatis-plus:
  #所对应的 XML 文件位置
  mapper-locations: classpath*:/mapper/*Mapper.xml
  #别名包扫描路径
  type-aliases-package: com.zking.zmall.model
  configuration:
    #驼峰命名规则
    map-underscore-to-camel-case: true
#日志配置
logging:
  level:
    com.zking.zmall.mapper: debug

消费者采用的是手动消费模式,请注意设置spring.rabbitmq.listener.simple.acknowledge-mode=manual

第6步:配置启动类

@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);
    }

}

二、创建订单链路配置

2.1 定义RabbitMQ配置类

定义RabbitMQ配置类,设置生产者发送数据时自动转换成JSON,设置消费者获取消息自动转换成JSON。

@Configuration
public class RabbitmqConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        return factory;
    }
}

设置RabbitTemplate消息转换模式为Jackson2JsonMessageConverter;
设置RabbitMQ消费者监听器的的消息转换模式为Jackson2JsonMessageConverter;

2.2 创建RabbitmqOrderConfig配置类

创建RabbitmqOrderConfig配置类,增加订单队列、交换机及绑定关系。

@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";

    @Bean
    public Queue orderQueue(){
        return new Queue(ORDER_QUEUE,true);
    }

    @Bean
    public DirectExchange orderExchange(){
        return new DirectExchange(ORDER_EXCHANGE,true,false);
    }

    @Bean
    public Binding orderBinding(){
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with(ORDER_ROUTING_KEY);
    }
}

三、如何实现RabbitMQ重复投递机制

3.1 开启发送者消息确认模式

配置application.yml,开启发送者confirm确认机制和return确认机制

spring:
	rabbitmq:
        # 发送者开启 confirm 确认机制
        publisher-confirm-type: correlated
        # 发送者开启 return 确认机制
        publisher-returns: true

3.2 消费发送确认

rabbitmq 的消息确认分为两部分:发送消息确认 和 消息接收确认

在这里插入图片描述
发送消息确认:用来确认生产者 producer 将消息发送到 brokerbroker 上的交换机 exchange 再投递给队列 queue的过程中,消息是否成功投递。

消息从 producerrabbitmq broker有一个 confirmCallback 确认模式。

消息从 exchangequeue 投递失败有一个 returnCallback 退回模式。

我们可以利用这两个Callback来确保消息100%送达。

Broker:简单来说,就是一个消息队列服务器实体。

3.2.1 创建ConfirmCallBack确认模式

@Slf4j
@Component
public class ConfirmCallbackService implements RabbitTemplate.ConfirmCallback {

    /**
     *
     * @param correlationData 对象内部只有一个 id 属性,用来表示当前消息的唯一性
     * @param ack 消息投递到broker 的状态,true表示成功
     * @param cause 表示投递失败的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {

        if (!ack) {
            log.error("消息发送异常!");
        } else {
            log.info("发送者已经收到确认,ack={}, cause={}",ack, cause);
        }
    }
}

3.2.2 创建ReturnCallBack退回模式

@Slf4j
@Component
public class ReturnCallbackService implements RabbitTemplate.ReturnCallback {

    /**
     *
     * @param message 消息体
     * @param replyCode 响应code
     * @param replyText 响应内容
     * @param exchange 交换机
     * @param routingKey 路由键
     */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.info("returnedMessage ===> replyCode={} ,replyText={} ,exchange={} ,routingKey={}", replyCode, replyText, exchange, routingKey);
    }
}

3.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 {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ReturnCallbackService returnCallbackService;

    @Autowired
    private 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);
    }
}

3.4 创建消费者(手动消费)

@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内按顺序自增的消息ID
        long 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);
    }
}

3.5 启动测试

在这里插入图片描述

从测试结果上来看,当消费者监听器出现异常后;进入消息重试模式,并且设置消息重试次数为3次,重试次数达到3次,消息被拒绝,不再重新投递到队列中。这里只是为了演示消息重试机制,并未考虑到后续的消息拒绝之后的处理。

3.6 踩坑日记

3.6.1 异常点一:@RabbitListener

在这里插入图片描述

异常原因:@RabbitListener作用于类上引发异常;
解决方案:@RabbitListener移至消费者监听器的方法上,而@RabbitListener只适用于方法级别。

3.6.2 手动确认消息

在这里插入图片描述

虽然在消费者端的application.yml中配置手动消费模式,但是在服务消费时引发了这个异常错误,导致重复消费的问题。原因是使用@RabbitListener注解会自动ACK,如果方法中再手动ACK会造成重复ACK,所以报错;解决方式就是在@RabbitListener中配置手动消费模式:ackMode = “MANUAL”。

3.6.3 消息格式

在这里插入图片描述

在消费者消费消息时引发异常,触发消息重新投递,但是由于重新投递时导致消息格式问题引发了消息转换异常。
具体原因通过查看日志发现,重新投递的消息格式为text/plain,而我们在处理消息时采用的是json方式,导致消息转换异常。解决方案:将重新发送消息的状态由MessageProperties.PERSISTENT_TEXT_PLAIN更改为MessageProperties.MINIMAL_PERSISTENT_BASIC

3.6.4 消息不确认

这是一个非常没技术含量的坑,但却是非常容易犯错的地方。开启消息确认机制,消费消息别忘了channel.basicAck,否则消息会一直存在,导致重复消费。

在这里插入图片描述

3.6.5 消息无限投递

最开始接触消息确认机制的时候,消费端代码就像下边这样写的,思路很简单:处理完业务逻辑后确认消息, int a = 1 / 0 发生异常后将消息重新投入队列

@RabbitHandler
public void recieveMessage(Message message,
                               Order order,
                               Channel channel) throws IOException {
    //channel内按顺序自增的消息ID
    long 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));

3.6.6 重复消费

如何保证 MQ 的消费是幂等性,这个需要根据具体业务而定,可以借助MySQL、或者redis 将消息持久化。
上述代码结构图
在这里插入图片描述

四、秒杀业务优化

4.1 修改秒杀订单生成方式

第1步:修改zmall-order订单模块的application.yml,加入rabbitmq相关配置

spring:
  rabbitmq:
    host: 192.168.195.143
    port: 5672
    username: admin
    password: admin
    virtual-host: my_vhost
    # 设置消费端手动 ack
    listener:
      simple:
        acknowledge-mode: manual
        # 是否支持重试
        retry:
          enabled: true
          max-attempts: 3

第2步:修改秒杀订单生成方式,针对抢购成功的秒杀订单直接推送到RabbitMQ中

@Transactional
    @Override
    public 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<>();
    }

4.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.mapper.OrderMapper;
import com.zking.zmall.model.Kill;
import com.zking.zmall.model.Order;
import com.zking.zmall.model.OrderDetail;
import com.zking.zmall.service.IRedisService;
import com.zking.zmall.service.impl.RedisServiceImpl;
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;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zking
 * @since 2023-02-05
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {
    @Autowired
    private KillServiceImpl killService;
    @Autowired
    private OrderDetailServiceImpl orderDetailService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Transactional
    @Override
    public 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步:修改秒杀订单消费者监听器

@Slf4j
@Component
public class OrderConsumerListener {

    //最大重试次数
    private static final Integer MAX_RECONSUME_COUNT=3;

    //用于记录消息重试次数的集合,可以采用Redis方式实现
    private static Map<String,Integer> retryMap=new HashMap<>();

    @Autowired
    private IOrderService orderService;
    
    @RabbitHandler
    @RabbitListener(queues = {"order-queue"},ackMode = "MANUAL")
    public void recieveMessage(Message message,
                               Order order,
                               Channel channel) throws IOException {
        //channel内按顺序自增的消息ID
        long 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:实际项目开发中,对于消息未能正常消费,应该设置人工补偿机制;

测试

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

金三银四必看软件测试面试题,上百家公司面试都是这些套路

自我介绍说一下测试用例嗯&#xff0c;做测试&#xff0c;好多时间是在琢磨分析测试用例怎么去写&#xff0c;这个每个公司规范可能不太一样&#xff0c;但是大致思想是一致的。都是想要通过测试用例&#xff0c;把每一个分析到位&#xff0c;进行测试。就拿我上家公司来说吧&a…

数据库(2)--加深对统计查询的理解,熟练使用聚合函数

一、内容要求 利用sql建立学生信息数据库&#xff0c;并定义以下基本表&#xff1a; 学生&#xff08;学号&#xff0c;年龄&#xff0c;性别&#xff0c;系号&#xff09; 课程&#xff08;课号&#xff0c;课名&#xff0c;学分&#xff0c;学时&#xff09; 选课&#xff0…

融云入围「2022 云办公平台 TOP50」,进入「中国协同办公产业图谱」

2 月 10 日&#xff0c;中国科学院旗下《互联网周刊》颁布“2022 云办公平台 TOP50”&#xff0c;融云荣登榜单。 2 月 13 日&#xff0c;艾瑞咨询发布《2023 年中国协同办公行业研究报告》&#xff08;下简称《报告》&#xff09;&#xff0c;对协同办公行业的供需动态和迭代方…

黑马】后台管理-项目优化和上线

一。项目优化优化1&#xff0c;加载进度条显示安装一个运行依赖&#xff0c;nprogress然后导包&#xff0c;调用对象展示和隐藏在main中基于拦截器实现展示进度条和隐藏进度条的效果如果触发请求拦截器&#xff0c;证明发起请求&#xff0c;希望展示进度条&#xff0c;如果触发…

消防应急照明和疏散指示系统——集中控制型系统的设计与应用

安科瑞 李亚俊 V:Acrel8757 摘要&#xff1a;伴随着建筑领域的良好发展&#xff0c;建筑工程建设越来越复杂&#xff0c;相应的消防配套设施也越来越先进&#xff0c;火灾发生时&#xff0c;人在燃烧产生的噪音和烟气中会产生恐惧、不安等不良的心理状态&#xff0c;进而影响他…

NLP篇章2:理解Transformer

Transformer编码&#xff0c;解码大的结构的理解 编码部分&#xff0c;每一个的小编码器的输入是前一个小编码器的输出&#xff0c; 而每一个小解码器的输入不光是它的前一个解码器的输出&#xff0c;还包括了整个编码部分的输出。 self-attention 自注意力机制 顾名思义就是…

【数据库】 MySQL备份恢复

目录 MySQL日志管理 一&#xff0c; MySQL日志类型 二&#xff0c;错误日志 三&#xff0c; 通用查询日志 四&#xff0c; 慢查询日志 五&#xff0c;二进制日志 1&#xff0c;开启日志 2&#xff0c;二进制日志的管理 3&#xff0c;日志查看 5&#xff0c;二进制日志还原数据…

MAC OSX安装Python环境 + Visual Studio Code

MAC上开发python怎么能少得了python3环境呢&#xff0c;而安装python3环境的方式也有多种&#xff0c;这里仅选用并记录本人认为比较方便的方式 安装Homebrew Homebrew是macOS 缺失的软件包管理器&#xff0c; 使用它可以在MAC上安装很多没有预装的东西&#xff0c;详细说明可…

上海霄腾自动化装备盛装亮相2023生物发酵展

上海霄腾自动化携液体膏体粉剂颗粒等灌装生产线解决方案亮相2023生物发酵展BIO CHINA2023生物发酵展&#xff0c;作为生物发酵产业一年一度行业盛会&#xff0c;由中国生物发酵产业协会主办&#xff0c;上海信世展览服务有限公司承办&#xff0c;2023第10届国际生物发酵产品与技…

SAS应用入门学习笔记5

input 操作符&#xff1a; 代码说明&#xff1a; 1&#xff09;1 表示第1列字符&#xff1b;7表示第7列字符&#xff1b; 2&#xff09;col1 表示第一列数据&#xff1b;col2 表示第二列数据&#xff1b; 3&#xff09;4.2 表示的是4个字符&#xff0c;2表示小数点后两位&a…

Hazel游戏引擎(005)

本人菜鸟&#xff0c;文中若有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/GameEngineLightWeight&#xff08;中文的注释适合中国人的你&#xff09; 文章目录前言关键操作代码文件关键代码代码流程代码文件关键代码exter…

5、MyBatis框架——Mapper接口映射、给类起别名、MyBatis开启驼峰映射、MyBatis开启日志打印

目录 一、Mapper接口映射 1、创建数据库 2、搭建MVC分层结构 3、配置MyBatis全局参数 4、编辑DAO层接口 5、配置Mapper文件与DAO层绑定 &#xff08;1&#xff09;配置Mapper文件 &#xff08;2&#xff09;DAO层与Mapper文件标签的快速生成 6、代码测试 二、给类起别…

python|第四章考试题及练习题

本篇文章是对北京理工大学嵩天老师的《Python语言程序设计》第四章考试题及练习题的学习记录。 一、考试题 1、四位玫瑰数 问题描述&#xff1a; 四位玫瑰数是4位数的自幂数。自幂数是指一个 n 位数&#xff0c;它的每个位上的数字的 n 次幂之和等于它本身。‪‬‪‬‪‬‪‬…

【云原生-Docker】docker容器自定义DNS解析

描述 在特定的情况下&#xff0c;或者在网络策略特殊定义下&#xff0c;需要自定义dns进行域名访问&#xff0c;在宿主机上配置了域名解析&#xff0c;对于docker容器无效。对于局域网内的域名解析&#xff0c;Docker 需要到 Docker 容器中配置 hosts 文件。 实现方式 docke…

使用Pandas也可以进行数据可视化

数据可视化是数据分析的一个重要方面&#xff0c;它提供了一种理解数据并从中得出有意义见解的方法。Pandas 是最常见的于数据分析的 Python 库&#xff0c;它基于Matplotlib扩展了一些常用的可视化图表&#xff0c;可以方便的调用&#xff0c;本篇文章就让我们看看有哪些图表可…

使用JavaScript+Selenium玩转Web应用自动化测试

自动化测试 在软件开发过程中, 测试是功能验收的必要过程, 这个过程往往有测试人员参与, 提前编写测试用例, 然后再手动对测试用例进行测试, 测试用例都通过之后则可以认为该功能通过验收. 但是软件中多个功能之间往往存在关联或依赖关系, 某一个功能的新增或修改可能或影响到…

牛客网Python篇数据分析习题(四)

1.现有一个Nowcoder.csv文件&#xff0c;它记录了牛客网的部分用户数据&#xff0c;包含如下字段&#xff08;字段与字段之间以逗号间隔&#xff09;&#xff1a; Nowcoder_ID&#xff1a;用户ID Level&#xff1a;等级 Achievement_value&#xff1a;成就值 Num_of_exercise&a…

每日学术速递2.14

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CL 1.Type-Aware Decomposed Framework for Few-Shot Named Entity Recognition 标题&#xff1a;识别少数命名实体的类型感知分解框架 作者&#xff1a;Yongqi Li, Tieyun Qian 文章链…

Flink 连接流详解

连接流 1 Union 最简单的合流操作&#xff0c;就是直接将多条流合在一起&#xff0c;叫作流的“联合”&#xff08;union&#xff09;。联合操作要求必须流中的数据类型必须相同&#xff0c;合并之后的新流会包括所有流中的元素&#xff0c;数据类型不变。这种合流方式非常简…

tensorflow.js 对视频 / 直播人脸检测和特征点收集

前言&#xff1a;这里要介绍的是 Tensorflow.js 官方提供的两个人脸检测模型&#xff0c;分别是 face-detection 和 face-landmarks-detection。他们不但可以对视频中的人间进行精确定位&#xff0c;而且还能对当前设备 (手机 / 电脑摄像头) 采集的直播流实时监测人脸。所以这些…