微服务项目【消息推送(RabbitMQ)】

news2024/10/2 1:25:05

创建消费者

第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: localhost
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 100
        max-wait: 10
        max-idle: 10
        min-idle: 10
    database: 0
  rabbitmq:
    host: 192.168.70.132
    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"})
@SpringBootApplication
public class ZmallRabbitmqApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZmallRabbitmqApplication.class, args);
    }

}

创建订单链路配置

定义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;

创建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重复投递机制

开启发送者消息确认模式

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

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

消息发送确认

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnR5uSTO-1676642010629)(images\rabbitmq01.jpg)]

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

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

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

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

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

创建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);
        }
    }
}

创建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);
    }
}

创建生产者

创建生产者,模拟发送消息

@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(0L);
        order.setState(0);

        //ConfirmCallback确认模式
        rabbitTemplate.setConfirmCallback(confirmCallbackService);

        //ReturnCallback退回模式
        rabbitTemplate.setReturnCallback(returnCallbackService);

        rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,
                RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);
    }
}

创建消费者(手动消费)

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

启动测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XmXGFFz-1676642010632)(images\20220822225322.jpg)]

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

采坑日记

异常点一:@RabbitListener

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ddzvKt7V-1676642010637)(images\20220822231205.jpg)]

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

异常点二:手动确认消息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gSXz2dKJ-1676642010643)(images\20220822231544.jpg)]

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

异常点三:消息格式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ucW7boZZ-1676642010646)(images\20220822232450.jpg)]

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

异常点四:消息不确认

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zaZ0BuJ-1676642010649)(images\rabbitmq02.jpg)]

异常点五:消息无限投递

最开始接触消息确认机制的时候,消费端代码就像下边这样写的,思路很简单:处理完业务逻辑后确认消息, 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%的情况是不会自动修复,一条消息会被无限投递进队列,消费端无限执行,导致了死循环。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nx4R6yby-1676642010650)(images\rabbitmq02.jpg)]

经过测试分析发现,当消息重新投递到消息队列时,这条消息不会回到队列尾部,仍是在队列头部。

消费者会立刻消费这条消息,业务处理再抛出异常,消息再重新入队,如此反复进行。导致消息队列处理出现阻塞,导致正常消息也无法运行。

而解决方案是,先将消息进行应答,此时消息队列会删除该条消息,同时我们再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。

//重新发送消息到队尾
//再次发送该消息到消息队列,异常消息就放在了消息队列尾部,这样既保证消息不会丢失,又保证了正常业务的进行。
channel.basicPublish(message.getMessageProperties().getReceivedExchange(),
                     message.getMessageProperties().getReceivedRoutingKey(), 								 MessageProperties.MINIMAL_PERSISTENT_BASIC,
                     JSON.toJSONBytes(order));

异常点六:重复消费

如何保证 MQ 的消费是幂等性,这个需要根据具体业务而定,可以借助MySQL、或者redis 将消息持久化。

秒杀业务优化

修改秒杀订单生成方式

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

spring:
  rabbitmq:
    host: 192.168.70.132
    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) {
    //判断用户是否登录
    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);

    //将生成的秒杀订单保存到Redis中
    redisService.setKillOrderToRedis(pid,order);
    //将生成的秒杀订单推送到RabbitMQ中的订单队列中
    rabbitTemplate.convertAndSend(RabbitmqOrderConfig.ORDER_EXCHANGE,
    	RabbitmqOrderConfig.ORDER_ROUTING_KEY,order);

    return new JsonResponseBody<>();
}

消费者监听器完成秒杀订单生成

第1步:将zmall-order订单模块中的service业务处理接口及实现类移至消息者监听器模块。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzDmWFbI-1676642010651)(images\20220823000031.jpg)]

第2步:在IOrderService及OrderServiceImpl中重新定义生成秒杀订单方法

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private IKillService killService;

    @Autowired
    private ApiProductService productService;

    @Autowired
    private IOrderDetailService orderDetailService;

    @Transactional
    @Override
    public void saveOrder(Order order) {
        //1.根据商品ID获取商品
        Product product = productService.getProductById(order.getPid());
        //2.秒杀商品库存减一
        boolean flag=killService.updateKillStockById(order.getPid());
        if(!flag)
            return;
        //3.生成秒杀订单及订单项
        SnowFlake snowFlake=new SnowFlake(2,3);
        Long orderId=snowFlake.nextId();
        //订单
        order.setSerialNumber(orderId);
        order.setCost(product.getPrice());
        this.save(order);
        //订单项
        OrderDetail orderDetail=new OrderDetail();
        orderDetail.setOrderId(orderId);
        orderDetail.setProductId(product.getId());
        orderDetail.setQuantity(1);
        orderDetail.setCost(product.getPrice());
        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压测,并查看测试结果。

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

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

相关文章

有趣的HTML实例(十三) 咖啡选择(css+js)

一个人追求目标的路途是孤单的&#xff0c;一个人独品辛酸的时候是寂寥的&#xff0c;一个人马不停蹄的追赶着&#xff0c;狂奔着&#xff0c;相信前方是一片光明&#xff0c;我从不放弃希望&#xff0c;就像我对生活的信念&#xff0c;没有人可以动摇。 ——《北京青年》 目录…

Qt扫盲- QLocalSocket类

QLocalSocket类总结一、概述二、使用一、概述 QLocalSocket类是一个比较特殊的传输数据的的一个工具类&#xff0c;它和 QTcpSocket 的区别就是&#xff0c;这个QLocalServer 只是在connectToServer 的时候连接主机是用的一个字符串或者标识符来表示主机&#xff0c;而QTcpSoc…

基于模型预测控制(MPC)的悬架系统仿真分析

目录 前言 1.悬架系统 2.基于MPC的悬架系统仿真分析 2.1 simulink模型 2.2仿真结果 2.3 结论 3 总结 前言 模型预测控制是无人驾驶中较为热门的控制算法&#xff0c;但是对于悬架等这类系统的控制同样适用。 我们知道模型预测控制主要可以划分为三个部分&#xff1a; …

ForkJoinPool原理

1、概述 Fork/Join框架是Java7提供了的一个用于并行执行任务的框架。ForkJoinPool是Java中提供了一个线程池&#xff0c;特点是用来执行分治任务。主题思想是将大任务分解为小任务&#xff0c;然后继续将小任务分解&#xff0c;直至能够直接解决为止&#xff0c;然后再依次将任…

再谈Java的String字符串

我们先看下面几个常见的面试题&#xff1a; String s1 "abc";String s2 new String("abc");String s3 "a" "b" "c";String s4 s2.intern();System.out.printf("s1s2:%s\n", s1 s2);System.out.printf(&quo…

IOS安全区域适配

对于 iPhone 8 和以往的 iPhone&#xff0c;由于屏幕规规整整的矩形&#xff0c;安全区就是整块屏幕。但自从苹果手机 iphoneX 发布之后&#xff0c;前端人员在开发移动端Web页面时&#xff0c;得多注意一个对 IOS 所谓安全区域范围的适配。这其实说白了就是 iphoneX 之后的苹果…

Django图书商场购物系统python毕业设计项目推荐

mysql数据库进行开发&#xff0c;实现了首页、个人中心、用户管理、卖家管理、图书类型管理、图书信息管理、订单管理、系统管理等内容进行管理&#xff0c;本系统具有良好的兼容性和适应性&#xff0c;为用户提供更多的网上图书商城信息&#xff0c;也提供了良好的平台&#x…

【APP渗透测试】 Android APP渗透测试技术实施以及工具使用(客户端服务端)

文章目录前言一、安全威胁分析二、主要风险项三、Android测试思维导图四、反编译工具五、Android客户端漏洞一、Jnaus漏洞漏洞二、数据备份配置风险漏洞漏洞三、Activity组件泄露漏洞漏洞四、BroadcastReceiver组件泄露漏洞漏洞五、允许模拟器Root环境登录漏洞漏洞六、未识别代…

LeetCode 刷题之 BFS 广度优先搜索【Python实现】

1. BFS 算法框架 BFS&#xff1a;用来搜索 最短路径 比较合适&#xff0c;如&#xff1a;求二叉树最小深度、最少步数、最少交换次数&#xff0c;一般与 队列 搭配使用&#xff0c;空间复杂度比 DFS 大很多DFS&#xff1a;适合搜索全部的解&#xff0c;如&#xff1a;寻找最短…

C++类和对象补充

目录 前言&#xff1a; 1. 构造函数->初始化列表 1.1 初始化列表出现原因 1.2 初始化列表写法 2. explicit关键字 2.1 explict的出现 2.2 explict的写法 3. static成员 4. 友元 4.1 友元函数 4.2 友元类 5. 内部类和匿名对象 5.1 内部类 5.2 匿名对象 前言&a…

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

本篇文章是对北京理工大学嵩天老师的《Python语言程序设计》第五章考试题及练习题的学习记录。 一、考试题 1、随机密码生成 问题描述&#xff1a; 描述 补充编程模板中代码&#xff0c;完成如下功能&#xff1a;‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪…

Altium Designer 18中原理图DRC编译和PCB DRC检查-AD DRC

一、原理图编译 原理图检查的主要内容有&#xff1a; 1、元件位号冲突。也即多个元件编号相同&#xff0c;例如两个电容在原理图中都被命名为C2&#xff0c;显然肯定是无法生成PCB的。 2、网络悬浮。也即网络标号没有附着在电气走线上&#xff0c;一般这种是人操作失误&…

【Azure 架构师学习笔记】-Azure Logic Apps(7)- 自定义Logic Apps 调度

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Logic Apps】系列。 接上文【Azure 架构师学习笔记】-Azure Logic Apps&#xff08;6&#xff09;- Logic Apps调用ADF 前言 在稍微了解Logic Apps的使用之后&#xff0c;需要开始考虑如何调度起来。在Logic Apps里面…

ChatGPT会让6个低端岗位失业

​我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 其实最近我们也是研究探索ChatGPT的商业应用方向&#xff1a;比如账号买卖、卖文章、论文、脚本文案、卖使用次数和时长、OEM系统等等。 目前来看&#xff0c;ChatGPT会让一部分低端的岗位失业&…

GEE学习笔记 七十八:干涸的洪泽湖

今天看了一篇报道直击60年一遇气象干旱&#xff1a;洪泽湖缩小近一半&#xff0c;鱼蟹受灾严重&#xff01;_新华报业网&#xff08;直击60年一遇气象干旱&#xff1a;洪泽湖缩小近一半&#xff0c;鱼蟹受灾严重&#xff01;&#xff09;&#xff0c;既然玩GEE那就要玩出点花样…

图学习——03预备知识

本章我们介绍关于图的基础知识&#xff0c;包括图的定义、类型和性质、图谱理论、图的傅里叶分析等。在之后介绍图神经网络会基于这些基础知识展开&#xff0c; 想要简单运用图神经网络&#xff0c;这部分知识可以不用学。想要系统的理解图神经网络的来源和本质&#xff0c;这…

2019蓝桥杯真题年号字串(填空题) C语言/C++

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小明用字母 A 对应数字 11&#xff0c;B 对应 2&#xff0c;以此类推&#xff0c;用 Z 对应 26。对于 27 以上的数字&#xff0c;小明用两位或更长位的字符串来对应&…

在Windows10上安装虚拟机---VMware 17 Pro下载与安装

在Windows10上安装虚拟机---VMware下载与安装0 前言1 下载VMware 17 pro2 安装VMware 17 Pro3. 打开Vmware0 前言 电脑原生系统&#xff1a;Windows10虚拟机软件&#xff1a;VMware 17 pro准备好安装虚拟机的文件夹路径 1 下载VMware 17 pro 下载网址&#xff1a;VMware 官网…

采用工时表软件能为企业员工带来哪些好处

如今社会人力成本一直在攀升&#xff0c;企业对提升人员工作饱和度和有效分配和利用人力资源非常在意&#xff0c;而工时表软件是现代被广泛应用于各行业领域&#xff0c;用来解决项目及非项目上的人员时效管理问题的手段之一。 企业员工工时管理不一定是一件麻烦事。采用像8M…

12.centos7部署sonarqube9.6

12.centos7部署sonarqube9.6环境&#xff1a;sonarqube9.6Postgresql13JDK11sonarqube9.6下载地址&#xff1a;Postgresql13 rpm下载地址&#xff1a;JDK11下载地址&#xff1a;准备工作&#xff1a;修改文件句柄数&#xff08;最大文件数&#xff09;和用户最大进程数限制修改…