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

项目中的问题

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.128port: 5672username: guestpassword: guestpublisher-confirm-type: correlated #开启交换机应答publisher-returns: true   #队列应答listener:simple:acknowledge-mode: manual #默认情况下消息消费者是自动确认消息的,如果要手动确认消息则需要修改确认模式为manualprefetch: 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@Transactionalpublic 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@Transactionalpublic 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;@Componentpublic class ListReceiver {@Autowiredprivate 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;@Configurationpublic 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*/@Beanpublic Queue delayQeue1() {// 第一个参数是创建的queue的名字,第二个参数是是否支持持久化return new Queue(queue_delay_1, true);}@Beanpublic 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);}@Beanpublic 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() {@Overridepublic 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@Configurationpublic 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() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {message.getMessageProperties().setDelay(delayTime*1000);return message;}},correlationData);return true;}
 

3.1、发送消息

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

修改保存订单方法

@Override@Transactionalpublic 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;@Componentpublic class OrderReceiver {@Autowiredprivate 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);}}

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

@Overridepublic void execExpiredOrder(Long orderId) {// orderInfoupdateOrderStatus(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;@Componentpublic class PaymentReceiver {@Autowiredprivate 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);}}}

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

@Overridepublic 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*/@Overridepublic 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.rhkb.cn/news/80934.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

参考RabbitMQ实现一个消息队列

文章目录 前言小小消息管家1.项目介绍2. 需求分析2.1 API2.2 消息应答2.3 网络通信协议设计 3. 开发环境4. 项目结构介绍4.1 配置信息 5. 项目演示 前言 消息队列的本质就是阻塞队列&#xff0c;它的最大用途就是用来实现生产者消费者模型&#xff0c;从而实现解耦合以及削峰填…

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

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

重发布选路问题

一、思路 &#xff1b; 1.增加不优选路开销解决选路不佳问题 2.用增加开销的方式使R1 不将ASBR传的R7传给另一台ASBR解决R1、R2、R3、R4pingR7环回环路 二、操作 ------IP地址配置如图 1.解决环路 [r2] ip ip-prefix a permit 7.7.7.0 24 [r2]route-policy huawei per…

c++ boost circular_buffer

boost库中的 circular_buffer顾名思义是一个循环缓冲器&#xff0c;其 capcity是固定的当容量满了以后&#xff0c;插入一个元素时&#xff0c;会在容器的开头或结尾处删除一个元素。 circular_buffer为了效率考虑&#xff0c;使用了连续内存块保存元素 使用固定内存&#x…

The Sandbox 与 D.OASIS 联手打造 D.OASIS 城市

我们非常高兴地宣布与 D.OASIS 建立合作伙伴关系&#xff0c;共同打造无与伦比的娱乐体验&#xff1a;The Sandbox 中的 D.OASIS 城市&#xff01; 作为合作的一部分&#xff0c;The Sandbox 和D.OASIS将共同打造 D.OASIS 城市&#xff0c;一座充满无限可能的大都市&#xff0…

CSS基础介绍笔记1

官方文档 CSS指的是层叠样式&#xff08;Cascading Style Sheets&#xff09;地址&#xff1a;CSS 教程离线文档&#xff1a;放大放小&#xff1a;ctrl鼠标滚动为什么需要css&#xff1a;简化修改HTML元素的样式&#xff1b;将html页面的内容与样式分离提高web开发的工作效率&…

Grafana 曲线图报错“parse_exception: Encountered...”

问题现象 配置的Grafana图报错如下&#xff1a; 原因分析 点开报错&#xff0c;可以看到报错详细信息&#xff0c;是查询语句的语法出现了异常。 变量pool的取值为None 解决方案 需要修改变量pool的查询SQL&#xff0c;修改效果如下&#xff1a; 修改后&#x…

华为OD机试(含B卷)真题2023 算法分类版,58道20个算法分类,如果距离机考时间不多了,就看这个吧,稳稳的

目录 一、数据结构1、线性表2、优先队列3、滑动窗口4、二叉树5、并查集6、栈 二、算法1、基础算法2、字符串3、图4、动态规划5、数学 三、漫画算法2&#xff1a;小灰的算法进阶参与方式 很多小伙伴问我&#xff0c;华为OD机试算法题太多了&#xff0c;知识点繁杂&#xff0c;如…

机器学习中的工作流机制

机器学习中的工作流机制 在项目开发的时候&#xff0c;经常需要我们选择使用哪一种模型。同样的数据&#xff0c;可能决策树效果不错&#xff0c;朴素贝叶斯也不错&#xff0c;SVM也挺好。有没有一种方法能够让我们用一份数据&#xff0c;同时训练多个模型&#xff0c;并用某种…

Java源码-Context源码解析

您好&#xff0c;我们来一起了解一下Java源码中的Context源码解析。 Context是Android中的一个重要的概念&#xff0c;在Android开发中可以用来获取应用程序的各种信息&#xff0c;如Activity、Service、Application等等。在Android中&#xff0c;Context是一个抽象类&#xf…

Apache+Tomcat 整合

目录 方式一&#xff1a;JK 1、下载安装包 2、添加依赖 3、启动服务&#xff0c;检查端口是否监听 4、提供apxs命令 5、检查是否确实依赖 6、编译安装 7、重要配置文件 方式二&#xff1a;http_proxy 方式三&#xff1a;ajp_proxy 方式一&#xff1a;JK 1、下载安装…

【大数据】Flink 详解(二):核心篇 Ⅰ

Flink 详解&#xff08;二&#xff09;&#xff1a;核心篇 Ⅰ 14、Flink 的四大基石是什么&#xff1f; ​ Flink 的四大基石分别是&#xff1a; Checkpoint&#xff08;检查点&#xff09;State&#xff08;状态&#xff09;Time&#xff08;时间&#xff09;Window&#xff…

Java、Android 之 TCP / IP

TCP、IP是一系列协议组成的网络分层模型 客户端向服务端发送请求可能会走N条链路&#xff0c;这个过程叫路由 TCP传输 一般在1--1024端口 必须连接以后才能传输数据 UDP协议通常只是发送数据 TCP连接 TCP需要建立连接才能通信&#xff0c;建立连接需要端口&#xff0c;Sock…

20.4 HTML 表单

1. form表单 <form>标签: 用于创建一个表单, 通过表单, 用户可以向网站提交数据. 表单可以包含文本输入字段, 复选框, 单选按钮, 下拉列表, 提交按钮等等. 当用户提交表单时, 表单数据会发送到服务器进行处理.action属性: 应指向一个能够处理表单数据的服务器端脚本或UR…

Zabbix监控系统

目录 一、zabbix简介 1.1 zabbix 是什么&#xff1f; 1.2 zabbix 监控原理 二、安装zabbix 5.0 2.1 部署 zabbix 服务端 2.2 部署 zabbix 客户端 2.3 自定义监控内容 作为一个云计算行业从业人员&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff…

一篇文章看懂Apipost Mock功能怎么用

在接口开发过程中&#xff0c;Mock功能可以帮助开发者快速测试和验证接口的正确性和稳定性&#xff0c;以便快速迭代和修复问题。Apipost推出智能Mock功能&#xff0c;可以在智能期望中填写一些触发条件&#xff0c;开启后&#xff0c;Apipost会根据已设置的触发条件&#xff0…

口-肠-脑轴与精神健康的关系

谷禾健康 在个体中&#xff0c;每个微生物栖息地都表现出独特的微生物种群模式。迄今为止&#xff0c;关于微生物组相关疾病的研究主要集中在器官特异性微生物组上。然而&#xff0c;器官间的微生物网络正逐渐成为生理功能和病理过程中的重要调节因子和治疗机会。 在正常情况下…

mybatis打印sql语句出现多余的limit关键字

1、事情起因 在项目中使用了PageHelper分页插件&#xff0c;由于需求特殊&#xff0c;需要自定义分页&#xff0c;代码编写完成后&#xff0c;事故出现了。 前端传参: {pageNum: 1,pageSize: 10, }已知表中数据10条&#xff0c;但是每次分页查询只有10条数据&#xff0c;排查…

Mac 创建和删除 Automator 工作流程,设置 Terminal 快捷键

1. 创建 Automator 流程 本文以创建一个快捷键启动 Terminal 的自动操作为示例。 点击打开 自动操作&#xff1b; 点击 新建文稿 点击 快速操作 选择 运行 AppleScript 填入以下内容 保存名为 “Open Terminal” 打开 设置 > 键盘&#xff0c;选择 键盘快捷键 以此选择 服…

二叉树的性质、前中后序遍历【详细】

1. 树概念2.二叉树的概念1.2二叉树的性质 3.二叉树遍历3.2前序遍历3.2 中序遍历3.3 后序遍历 1. 树概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合&#xff0c;有二叉树&#xff0c;N叉树等等。 子树…