尚品汇总结九:RabbitMQ在项目的应用(面试专用)

news2024/11/24 11:22:24

项目中的问题

1.搜索与商品服务的问题

商品服务修改了 商品的上架状态,商品就可以被搜索到.采用消息通知,商品服务修改完商品上架状态,发送消息 给 搜索服务,搜索服务消费消息,进行商品数据ES保存.下架也是一样.

2.订单服务取消订单问题

延迟队里 

保存订单之后 开始计时,时间到了,取消未支付的订单.

rabbitMQ的 延时消息.

3.分布式事务问题

之前有一天 专门讲过分布式事务,都是概念.其中有一个  消息的最终数据一致性.

建议 每个人 回去把分布式事务的课件或者课程 看一遍.

场景:

支付----------订单----------库存  就是分布式事务场景.

4.秒杀的时候

使用rabbitMQ进行消息通知、进行用户的排队。

消息队列解决什么问题

消息队列都解决了什么问题?

 

1、异步

2、并行

 

3、解耦

4.排队   削峰  削去流量的峰值

  • 消息队列工具 RabbitMQ

1 、常见MQ产品

- ActiveMQ:基于JMS(java协议)、消息类型比较少,2种  一对一,一对多的

- RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好,消息类型多

- RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会,使用也不少。

- Kafka:分布式消息系统,高吞吐量,消息准确性不好

2 、RabbitMQ基础概念

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

Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列

Queue:消息队列载体,每个消息都会被投入到一个或多个队列

Binding:绑定,它的作用就是把 exchange和 queue按照路由规则绑定起来

Routing Key:路由关键字, exchange根据这个关键字进行消息投递

vhost:虚拟主机,一个 broker里可以开设多个 vhost,用作不同用户的权限分离

producer:消息生产者,就是投递消息的程序

consumer:消息消费者,就是接受消息的程序

channel:消息通道,在客户端的每个连接里,可建立多个 channel,每个 channel代表一个会话任务

3、消息模型

RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种。

但是其实3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。

基本消息模型:生产者–>队列–>消费者

work消息模型:生产者–>队列–>多个消费者共同消费

订阅模型-Fanout:广播模式,将消息交给所有绑定到交换机的队列,每个消费者都会收到同一条消息

订阅模型-Direct:定向,把消息交给符合指定 rotingKey 的队列

订阅模型-Topic 主题模式:通配符,把消息交给符合routing pattern(路由模式) 的队列

  • 消息不丢失

     消息的准确性保证!!!!

消息的不丢失,在MQ角度考虑,一般有三种途径

  1. 生产者不丢数据

 生产者 生产一个消息,准确的投递到交换机和队列中。

 解决:rabbitMQ提供了 消息的发送确认,交换机应答和队列应答。

消息生产出来 向交互机中投递,如投递成功,会给true回执,失败了,会给false的回执。

交换机向队列路由消息的时候,如果消息没到达队列中,会触发队列应答,会执行对应应答的方法。

rabbitmq:

    host: 192.168.200.128

    port: 5672

    username: guest

    password: guest

    publisher-confirm-type: correlated #开启交换机应答

    publisher-returns: true   #队列应答

    listener:

      simple:

        acknowledge-mode: manual #默认情况下消息消费者是自动确认消息的,如果要手动确认消息则需要修改确认模式为manual

        prefetch: 1 # 消费者每次从队列获取的消息数量。此属性当不设置时为:轮询分发,设置为1为:公平分发

prefetch: 1 消费者每次从队列中消费消息的数量。

 轮询分发:多个消费者的情况下,一人一次,不管消费方上次的消息有没有消费完,只要轮谁了就给谁。

 公平分发:多个消费者的情况下,一人一次,如果轮到这个消费方了,但是它上一个消息还没有消费完,这个消息就给别人。只有上一个消息消费完了,才能消费下一个消息。

2.MQ服务器不丢数据

消息队列数据存在内存中,MQ挂了重启,内存容易释放,消息就没了。

 提供了持久化,可以对 交换机、队列、消息 进行持久化。

3.消费者不丢数据

消费者在消费消息的过程中,可能会出现问题,消息还得存在。

手动签收消息,一旦消息消费过程中出问题了,可以拒绝签收,把消息转发到另外一个队列或者把消费异常的消息 做记录,等到问题解决了,再重新投递。

保证消息不丢失有两种实现方式:

  1. 开启事务模式
  2. 消息确认模式

说明:开启事务会大幅降低消息发送及接收效率,使用的相对较少。

在投递消息时开启事务支持,如果消息投递失败,则回滚事务,但是,很少有人这么干,因为这是同步操作,一条消息发送之后会使发送端阻塞,以等待RabbitMQ-Server的回应,之后才能继续发送下一条消息,生产者生产消息的吞吐量和性能都会大大降低。

因此我们生产环境一般都采取消息确认模式。

1、消息持久化

如果希望RabbitMQ重启之后消息不丢失,那么需要对以下3种实体均配置持久化

Exchange

    声明exchange时设置持久化(durable = true)并且不自动删除(autoDelete = false)

Queue

    声明queue时设置持久化(durable = true)并且不自动删除(autoDelete = false)

message

     发送消息时通过设置deliveryMode=2持久化消息

说明:

@Queue: 当所有消费客户端连接断开后,是否自动删除队列

true:删除 false:不删除

@Exchange:当所有绑定队列都不在使用时,是否自动删除交换器

true:删除 false:不删除

2、发送确认

有时,业务处理成功,消息也发了,但是我们并不知道消息是否成功到达了rabbitmq,如果由于网络等原因导致业务成功而消息发送失败,那么发送方将出现不一致的问题,此时可以使用rabbitmq的发送确认功能,即要求rabbitmq显式告知我们消息是否已成功发送。

3、手动消费确认

有时,消息被正确投递到消费方,但是消费方处理失败,那么便会出现消费方的不一致问题。比如订单已创建的消息发送到用户积分子系统中用于增加用户积分,但是积分消费方处理却失败了,用户就会问:我购买了东西为什么积分并没有增加呢?

要解决这个问题,需要引入消费方确认,即只有消息被成功处理之后才告知rabbitmq以ack,否则告知rabbitmq以nack

  • 商品搜索上下架

1、service-product发送消息

我在商品上架与商品添加时发送消息

商品上架

实现类

@Override

@Transactional

  public void onSale(Long skuId) {

    // 更改销售状态

    SkuInfo skuInfoUp = new SkuInfo();

    skuInfoUp.setId(skuId);

    skuInfoUp.setIsSale(1);

    skuInfoMapper.updateById(skuInfoUp);

    //商品上架

    rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_UPPER, skuId);

}
 

商品下架

实现类

@Override

@Transactional

  public void cancelSale(Long skuId) {

    // 更改销售状态

    SkuInfo skuInfoUp = new SkuInfo();

    skuInfoUp.setId(skuId);

    skuInfoUp.setIsSale(0);

    skuInfoMapper.updateById(skuInfoUp);

    //商品下架

    rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_GOODS, MqConst.ROUTING_GOODS_LOWER, skuId);

}
 

2、service-list消费消息

package com.atguigu.gmall.list.receiver;

  @Component

  public class ListReceiver {

    @Autowired

    private SearchService searchService;

    /**

     * 商品上架

     * @param skuId

     * @throws IOException

     */

    @RabbitListener(bindings = @QueueBinding(

            value = @Queue(value = MqConst.QUEUE_GOODS_UPPER, durable = "true"),

            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_GOODS, type = ExchangeTypes.DIRECT, durable = "true"),

            key = {MqConst.ROUTING_GOODS_UPPER}

    ))

    public void upperGoods(Long skuId, Message message, Channel channel) throws IOException {

        if (null != skuId) {

            searchService.upperGoods(skuId);

        }

        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

    }

    /**

     * 商品下架

        * @param skuId

     */

    @RabbitListener(bindings = @QueueBinding(

            value = @Queue(value = MqConst.QUEUE_GOODS_LOWER, durable = "true"),

            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_GOODS, type = ExchangeTypes.DIRECT, durable = "true"),

            key = {MqConst.ROUTING_GOODS_LOWER}

    ))

    public void lowerGoods(Long skuId, Message message, Channel channel) throws IOException {

        if (null != skuId) {

            searchService.lowerGoods(skuId);

        }

        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

    }

}

  • 延迟队列关闭过期订单

延迟消息有两种实现方案:

1.基于死信队列

   使用消息的存活时间,给队列设置参数,时间到了转发到另外一个队列,消费者真正消费的是 另一个队列。

2.集成延迟插件

   延迟插件 中有个小型的库,延迟交换机把消息消息先存在小型的库中,时间到了再转发到队列。

区别:

死信的延迟消息:要求消息的时间一致,如果不一致,后面的消息出不来。

延迟插件的:没有这个要求。消息的时间可以不一致。

1 、基于死信实现延迟消息

使用RabbitMQ来实现延迟消息必须先了解RabbitMQ的两个概念:消息的TTL和死信Exchange,通过这两者的组合来实现延迟队列

1.1、消息的TTL(Time To Live)

消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。

我们创建一个队列queue.temp,在Arguments 中添加x-message-ttl 为5000 (单位是毫秒),那所在压在这个队列的消息在5秒后会消失。

1.2、死信交换器  Dead Letter Exchanges

一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。

(1) 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。

(2)上面的消息的TTL到了,消息过期了。

(3)队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。

Dead Letter Exchange其实就是一种普通的exchange,和创建其他exchange没有两样。只是在某一个设置了Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。

2 、基于延迟插件实现延迟消息

2.1、插件安装

1. 首先我们将刚下载下来的rabbitmq_delayed_message_exchange-3.8.0.ez文件上传到RabbitMQ所在服务器,下载地址:https://www.rabbitmq.com/community-plugins.html

2. 切换到插件所在目录,执行 docker cp rabbitmq_delayed_message_exchange-3.8.0.ez rabbitmq:/plugins 命令,将刚插件拷贝到容器内plugins目录下

3. 执行 docker exec -it rabbitmq /bin/bash 命令进入到容器内部,并 cd plugins 进入plugins目录

4. 执行 ls -l|grep delay  命令查看插件是否copy成功

5. 在容器内plugins目录下,执行 rabbitmq-plugins enable rabbitmq_delayed_message_exchange  命令启用插件

6. exit命令退出RabbitMQ容器内部,然后执行 docker restart rabbitmq 命令重启RabbitMQ容器

2.2、代码实现

配置队列

package com.atguigu.gmall.mq.config;

  

  

  @Configuration

  public class DelayedMqConfig {

  

    public static final String exchange_delay = "exchange.delay";

    public static final String routing_delay = "routing.delay";

    public static final String queue_delay_1 = "queue.delay.1";

  

    /**

     * 队列不要在RabbitListener上面做绑定,否则不会成功,如队列2,必须在此绑定

     *

     * @return

     */

  

    @Bean

    public Queue delayQeue1() {

        // 第一个参数是创建的queue的名字,第二个参数是是否支持持久化

        return new Queue(queue_delay_1, true);

    }

  

    @Bean

    public CustomExchange delayExchange() {

        Map<String, Object> args = new HashMap<String, Object>();

        args.put("x-delayed-type", "direct");

        return new CustomExchange(exchange_delay, "x-delayed-message", true, false, args);

    }

  

    @Bean

    public Binding delayBbinding1() {

        return BindingBuilder.bind(delayQeue1()).to(delayExchange()).with(routing_delay).noargs();

    }

  

}

发送消息

@GetMapping("sendDelay")

  public Result sendDelay() {

   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

   this.rabbitTemplate.convertAndSend(DelayedMqConfig.exchange_delay, DelayedMqConfig.routing_delay, sdf.format(new Date()), new MessagePostProcessor() {

      @Override

      public Message postProcessMessage(Message message) throws AmqpException {

         message.getMessageProperties().setDelay(10 * 1000);

         System.out.println(sdf.format(new Date()) + " Delay sent.");

         return message;

      }

   });

   return Result.ok();

}

接收消息

package com.atguigu.gmall.mq.receiver;

  

  

  @Component

@Configuration

  public class DelayReceiver {

  

    @RabbitListener(queues = DelayedMqConfig.queue_delay_1)

    public void get(String msg) {

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println("Receive queue_delay_1: " + sdf.format(new Date()) + " Delay rece." + msg);

    }

  

}

3 、基于延迟插件实现取消订单

rabbit-util模块延迟接口封装
RabbitService
/**

 * 发送延迟消息

 * @param exchange 交换机

 * @param routingKey 路由键

 * @param message 消息

 * @param delayTime 单位:秒

 */

  public boolean sendDelayMessage(String exchange, String routingKey, Object message, int delayTime) {

    GmallCorrelationData correlationData = new GmallCorrelationData();

    String correlationId = UUID.randomUUID().toString();

    correlationData.setId(correlationId);

    correlationData.setMessage(message);

    correlationData.setExchange(exchange);

    correlationData.setRoutingKey(routingKey);

    correlationData.setDelay(true);

    correlationData.setDelayTime(delayTime);

  

    redisTemplate.opsForValue().set(correlationId, JSON.toJSONString(correlationData), OBJECT_TIMEOUT, TimeUnit.MINUTES);

    this.rabbitTemplate.convertAndSend(exchange, routingKey, message, new MessagePostProcessor() {

        @Override

        public Message postProcessMessage(Message message) throws AmqpException {

            message.getMessageProperties().setDelay(delayTime*1000);

            return message;

        }

    },correlationData);

    return true;

}
 

3.1、发送消息

创建订单时,发送延迟消息

修改保存订单方法

@Override

@Transactional

  public Long saveOrderInfo(OrderInfo orderInfo) {

    // orderInfo

    // 总金额,订单状态,用户Id,第三方交易编号,创建时间,过期时间,进程状态

    orderInfo.sumTotalAmount();

    orderInfo.setOrderStatus(OrderStatus.UNPAID.name());

    String outTradeNo = "ATGUIGU" + System.currentTimeMillis() + "" + new Random().nextInt(1000);

    orderInfo.setOutTradeNo(outTradeNo);

    orderInfo.setCreateTime(new Date());

    // 定义为1天

    Calendar calendar = Calendar.getInstance();

    calendar.add(Calendar.DATE, 1);

    orderInfo.setExpireTime(calendar.getTime());

  

    orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());

    orderInfoMapper.insert(orderInfo);

  

    StringBuffer tradeBody = new StringBuffer();

    // 保存订单明细

    List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();

    for (OrderDetail orderDetail : orderDetailList) {

        orderDetail.setId(null);

        orderDetail.setOrderId(orderInfo.getId());

        orderDetailMapper.insert(orderDetail);

  

        tradeBody.append(orderDetail.getSkuName()).append(" ");

    }

  

    //更新支付描述

    orderInfo.setTradeBody(tradeBody.toString());

    orderInfoMapper.updateById(orderInfo);

    //发送延迟队列,如果到过期时间了未支付,取消订单

 rabbitService.sendDelayMessage(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL, MqConst.ROUTING_ORDER_CANCEL, orderInfo.getId(), MqConst.DELAY_TIME);

    // 返回

    return orderInfo.getId();

}

3.2、接收消息

 
package com.atguigu.gmall.order.receiver;

  @Component

  public class OrderReceiver {

    @Autowired

    private OrderService orderService;

    /**

     * 取消订单消费者

       * 延迟队列,不能再这里做交换机与队列绑定
* 需要在配置类里去绑定死信交换机
     * @param orderId
     * @throws IOException
     */
    @RabbitListener(queues = MqConst.QUEUE_ORDER_CANCEL)
    public void orderCancel(Long orderId, Message message, Channel channel) throws IOException {
        if (null != orderId) {
            //防止重复消费
            OrderInfo orderInfo = orderService.getById(orderId);
            if (null != orderInfo && orderInfo.getOrderStatus().equals(ProcessStatus.UNPAID.getOrderStatus().name())) {
                orderService.execExpiredOrder(orderId);
            }
        }
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }

}

取消订单业务,取消订单要关闭支付交易

@Override

  public void execExpiredOrder(Long orderId) {

    // orderInfo

    updateOrderStatus(orderId, ProcessStatus.CLOSED);

    // paymentInfo

    //paymentFeignClient.closePayment(orderId);

    //发送取消交易的消息

    rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE, MqConst.ROUTING_PAYMENT_CLOSE, orderId);

}

关闭交易消息消费者

package com.atguigu.gmall.payment.receiver;

@Component

public class PaymentReceiver {

    @Autowired

    private PaymentService paymentService;

    /**

     * 取消交易

     * @param orderId

     * @throws IOException

     */

    @RabbitListener(bindings = @QueueBinding(

            value = @Queue(value = MqConst.QUEUE_PAYMENT_CLOSE, durable = "true"),

            exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_PAYMENT_CLOSE),

            key = {MqConst.ROUTING_PAYMENT_CLOSE}

    ))

    public void closePayment(Long orderId) throws IOException {

        if (null != orderId) {

            paymentService.closePayment(orderId);

        }

    }

}

更改支付日志表,状态为关闭交易

@Override

public void closePayment(Long orderId) {

    QueryWrapper<PaymentInfo> queryWrapper = new QueryWrapper<>();

    queryWrapper.eq("order_id", orderId);

    PaymentInfo paymentInfoUp = new PaymentInfo();

    paymentInfoUp.setPaymentStatus(PaymentStatus.ClOSED.name());

    paymentInfoMapper.update(paymentInfoUp, queryWrapper);

    //关闭交易

     alipayService.closePay(orderId);
}
支付宝支付AlipayServiceImpl实现类
/***

 * 关闭交易

 * @param orderId

 * @return

 */

@Override

public Boolean closePay(Long orderId) {

    OrderInfo orderInfo = orderFeignClient.getOrderInfo(orderId);



    //AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","GBK","alipay_public_key","RSA2");

    AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();

    HashMap<String, Object> map = new HashMap<>();

    map.put("trade_no", "");

    map.put("out_trade_no", orderInfo.getOutTradeNo());

    map.put("operator_id", "YX01");



    request.setBizContent(JSON.toJSONString(map));

    AlipayTradeCloseResponse response = null;

    try {

        response = alipayClient.execute(request);

    } catch (AlipayApiException e) {

        e.printStackTrace();

    }

    if(response.isSuccess()){

        log.info("调用成功");

        return true;

    }

    return false;

}

 

  • 项目中分布式事务的业务场景

  • RabbitMQ常见问题

1、使用RabbitMQ有什么好处?

1.解耦:系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入,系统A还需要修改代码,过于麻烦!

2.异步:将消息写入消息队列,非必要的业务逻辑以异步的方式运行,加快响应速度

3.削峰:并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常

2、消息顺序问题

场景:比如支付操作,支付成功之后,会发送修改订单状态和扣减库存的消息,如果这两个消息同时发送,就不能保证完全按照顺序消费,有可能是先减库存了,后更改订单状态。

解决方案:同步执行,当一个消息执行完之后,再发布下一个消息。

3、如何保证RabbitMQ消息的可靠传输?

消息不可靠的原因是因为消息丢失

生产者丢失消息:

RabbitMQ提供transaction事务和confirm模式来确保生产者不丢消息;

Transaction事务机制就是说:发送消息前,开启事务(channel.txSelect(),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback(),如果发送成功则提交事务(channel.txCommit()),然而,这种方式有个缺点:吞吐量下降。

confirm模式用的居多:一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;

rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;

如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,可以进行重试操作。

消息列表丢失消息:

可以消息持久化, 即使rabbitMQ挂了,重启后也能恢复数据

消费者丢失消息:

消费者丢数据一般是因为采用了自动确认消息模式,消费者在收到消息之后,处理消息之前,会自动回复RabbitMQ已收到消息;如果这时处理消息失败,就会丢失该消息;改为手动确认消息即可!

4、消息重复消费问题

为什么会重复消费:

正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除;

但是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道已经消费过该消息了,再次将消息发送。

解决方案:保证消息的唯一性,就算是多次传输,不要让消息的多次消费带来影响,保证消息消费的幂等性;

5、幂等性操作

幂等性就是一个数据或者一个请求,给你重复来了多次,你得确保对应的数据是不会改变的,不能出错。

要保证消息的幂等性,这个要结合业务的类型来进行处理。

1)、可在内存中维护一个map集合,只要从消息队列里面消费一个消息,先查询这个消息在不在map里面,如果在表示已消费过,直接丢弃;如果不在,则在消费后将其加入map当中。
2)、如果要写入数据库,可以拿唯一键先去数据库查询一下,如果不存在在写,如果存在直接更新或者丢弃消息。

3)、消息执行完会更改某个数据状态,判断数据状态是否更新,如果更新,则不进行重复消费。
4)、如果是写redis那没有问题,每次都是set,天然的幂等性。

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

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

相关文章

每日一题——最小花费爬楼梯

题目 给定一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第i 个台阶向上爬需要支付的费用&#xff0c;下标从0开始。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返回达到楼梯顶部的…

【Matlab】RBF神经网络遗传算法(RBF-GA)函数极值寻优——非线性函数求极值

上一篇博客介绍了GRNN-GA&#xff1a;GRNN神经网络遗传算法(GRNN-GA)函数极值寻优——非线性函数求极值&#xff0c;神经网络用的是RBF神经网络&#xff0c;RBF神经网络和GRNN神经网络有相似之处。本篇博客将GRNN神经网络替换成RBF神经网络&#xff0c;希望能帮助大家快速入门R…

推荐5本软件测试人员必读经典书籍

学会选择对的学习方法 俗话说&#xff1a;“选择大于努力”。 初学软件测试也如此。很多刚入行测试的同学最容易陷入一个误区&#xff0c;那就是优先买一堆视频来学习。结果时间过去了&#xff0c;视频仅以形式主义存在电脑的硬盘里&#xff0c;从此走上了入门到放弃之路。 …

第1章:绪论

科学、技术、工程、应用 科学&#xff1a;是什么、为什么技术&#xff1a;怎么做工程&#xff1a;怎样做的多快好省应用&#xff1a;怎么使用 定义 机器学习&#xff1a;利用经验改善系统自身的性能。 研究 智能数据分析&#xff08;数据分析算法&#xff09; 典型的机器…

Q-Tester 3.8:适用于开发、生产和售后的诊断测试软件

Q-Tester是一款简易使用的诊断测试软件&#xff0c;同时也是一款基于ODX&#xff08;ASAM MCD-2D/ISO 22901-1&#xff09;国际标准的工程诊断仪&#xff0c;通过该诊断仪可实现与ECU控制之间的数据交互。这一方案的优势是&#xff0c;在功能方面确定并完成相关开发工作后&…

【MATLAB第65期】基于LSTM长短期记忆网络的多输入单输出数据预测未来思路介绍(短期预测)

【MATLAB第65期】基于LSTM长短期记忆网络的多输入单输出数据预测未来思路介绍&#xff08;短期预测&#xff09; 往期第13期已实现多输入单输出滑动窗口回归预测 多输入单输出滑动窗口回归预测 往期第54期已实现多输入多输出滑动窗口回归预测 多输入多输出滑动窗口回归预测 一…

基于gpt4all的企业内部知识问答服务应用搭建

文章目录 痛点项目缘起技术选型fine-tuningfew shot prompt engineering选定方案的特征描述 模型赛马gpt4all调优部署时踩坑python3.9 header缺失 -- 安装下缺失的就行运行时参数调优 代码分析项目代码库代码 效果展示例子1例子2 附录&#xff1a;所用的公司内部API文档例子&am…

【SpringBoot】日志是什么+基于lombok的日志输出

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 在我们日常的程序开发中&#xff0c;日志是程序的重要组成部分&#xff0c;想象⼀下&#xff0c;如果程序报错了&#xff0c;不让你打开控制台看⽇志&#xff0c;那么你能找到报错的原因吗…

datax-web登陆时出现账号密码错误

在查找问题时&#xff0c;在admin里面查看日志时&#xff1a; 目录的位置&#xff1a;datax-web-2.1.2/modules/datax-admin/bin/console.out 发现了java程序没有跑起来&#xff0c;解决对应的bug问题即可&#xff0c;一般都是数据库连接的问题&#xff0c;可能和使用的数据库版…

华为OD机试之报文重排序(Java源码)

题目描述 对报文进行重传和重排序是常用的可靠性机制&#xff0c;重传缓中区内有一定数量的子报文&#xff0c;每个子报文在原始报文中的顺序已知&#xff0c;现在需要恢复出原始报文。 输入描述 输入第一行为N&#xff0c;表示子报文的个数&#xff0c;0 &#xff1c;N ≤ 100…

文章采集伪原创发布工具-147采集

在当今信息爆炸的时代&#xff0c;企业和个人都意识到了获取高质量、原创的内容的重要性。然而&#xff0c;手动撰写大量的原创内容是一项耗时费力的任务。为了解决这个问题&#xff0c;我向您介绍一款颠覆性的数据采集工具——147采集。 147采集是一款专业且高效的数据采集软件…

3D二次元背景的引导页,带六个按钮可作导航源码下载

二、下载&#xff1a;引导页.zip - 蓝奏云文件大小&#xff1a;615.5 K|https://wwwf.lanzout.com/iruSu14t6e6b 三、用途&#xff1a; 可作为画师和企业介绍页&#xff0c;记得把背景换成自己的作品和企业产品展示 可修改打赏页面&#xff0c;每个按钮设一个二维码 可替换…

WebRTC | 音视频实时通信的本质

目录 一、音视频实时通信的两种指标 1. 实时通信延迟指标 2. 视频相关的基本概念 3. 音视频服务质量指标 二、解决实时通信的主要矛盾 1. 增加带宽 A. 提供更优质的接入服务 B. 保证云端网络的带宽和质量 C. 更合理的路由调度策略 2. 减少数据量 A. 采用更好的压缩算…

HBase API

我们之后的实际开发中不可能在服务器那边直接使用shell命令一直敲的&#xff0c;一般都是通过API进行操作的。 环境准备 新建Maven项目&#xff0c;导入Maven依赖 <dependencies><dependency><groupId>org.apache.hbase</groupId><artifactId>…

计算机网络性能指标

比特&#xff1a;数据量的单位 KB 2^10B 2^13 bit 比特率&#xff1a;连接在计算机网络上的主机在数字通道上传送比特的速率 kb/s 10^3b/s 带宽&#xff1a;信号所包含的各种频率不同的成分所占据的频率范围 Hz 表示在网络中的通信线路所能传送数据的能力&#xff08…

CentOS虚拟机更改屏幕锁屏时间

&#xff08;1&#xff09;点击“应用程序”&#xff0c;再点击“系统工具”&#xff0c;再点击“设置” &#xff08;2&#xff09; &#xff08;3&#xff09;在“设置”中点击“Privacy”&#xff0c;点击“锁屏”

【博客691】VictoriaMetrics如何支持Multi Retention

VictoriaMetrics如何支持Multi Retention 场景&#xff1a; 实现Multi Retention Setup within VictoriaMetrics Cluster&#xff0c;使得为不同的监控数据采用不同的保存时间 Multi Retention实现方式 方式&#xff1a; VictoriaMetrics 的社区版本通过 -retentionPeriod 命…

系统学习Linux-Redis基础

一、redis概述 NoSQL&#xff08;非关系型数据库、内存存储&#xff09; 类型 文档型数据库&#xff08;Document-oriented database&#xff09;如MongoDB&#xff1b; 列族数据库&#xff08;Column-family database&#xff09;如HBase、Cassandra等&#xff1b; 图形数…

如何将视频转成gif图?视频怎么转gif高清图片?

在看电视或是短视频的时候&#xff0c;总能发现一些有趣的片段&#xff0c;当想把这些视频转gif图片发送给朋友的时候该怎么处理呢&#xff1f;其实可以试试专业的视频转gif工具&#xff0c;本文介绍一个视频在线转gif的方法&#xff0c;一起来了解一下吧。 打开首页&#xff…

Profibus DP主站转Modbus TCP网关profibus主站模拟软件

捷米JM-DPM-TCP网关。这款产品在Profibus总线侧实现了主站功能&#xff0c;在以太网侧实现了ModbusTcp服务器功能&#xff0c;为我们的工业自动化网络带来了全新的可能。 捷米JM-DPM-TCP网关是如何实现这些功能的呢&#xff1f;首先&#xff0c;让我们来看看它的Profibus总线侧…