RoctetMQ使用(2):在項目中使用

一、導入相關依賴

????????在項目中引入MQ客戶端依賴,依賴版本最好和RocketMQ版本一致。

<!--            rocket客戶端--><dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-client</artifactId><version>${rocketmq.version}</version></dependency>

二、消息發送

????????首先介紹一下消息發送的大致流程,當我們調用消息發送方法時該方法會先對待發送消息進行前置驗證,如果消息主題和消息內容均沒有問題的話,就會根據消息主題(Topic)去獲取路由信息,即消息主題對應的隊列,broker,broker的ip和端口信息,然后選擇一條隊列發送消息,成功的話返回發送成功,失敗的話會根據我們設置的重試次數進行重新發送,單向消息發送不會進行失敗重試。

1、RocketMQ初始化

? ? ? ? RocketMQ配置類案例:

package com.example.framework.mq.config;import cn.hutool.core.thread.ThreadUtil;
import com.example.framework.mq.handler.MQHandler;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Map;
import java.util.concurrent.ExecutorService;/*** MQ配置*/
@Configuration
public class MQConfig {@Value("${spring.application.name:application}")private String groupName;//集群名稱,這邊以應用名稱作為集群名稱/***************************消息消費者***************************/@Autowiredprivate Map<String, MQHandler> mqHandlerMap;// 消費者nameservice地址@Value("${rocketmq.consumer.namesrvAddr:127.0.0.1:9876}")private String cNamesrvAddr;// 最小線程數@Value("${rocketmq.consumer.consumeThreadMin:20}")private int consumeThreadMin;// 最大線程數@Value("${rocketmq.consumer.consumeThreadMax:64}")private int consumeThreadMax;// 消費者監聽主題,多個主題以分號隔開(topic~tag;topic~tag)@Value("${rocketmq.consumer.topics:test~*}")private String topics;// 一次消費消息的條數,默認為1條@Value("${rocketmq.consumer.consumeMessageBatchMaxSize:1}")private int consumeMessageBatchMaxSize;@Beanpublic DefaultMQPushConsumer getRocketMQConsumer() throws Exception {DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(groupName);consumer.setNamesrvAddr(cNamesrvAddr);consumer.setConsumeThreadMin(consumeThreadMin);consumer.setConsumeThreadMax(consumeThreadMax);consumer.registerMessageListener(getMessageListenerConcurrently());// 設置Consumer第一次啟動是從隊列頭部開始消費還是隊列尾部開始消費,如果非第一次啟動,那么按照上次消費的位置繼續消費consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);// 設置消費模型,集群還是廣播,默認為集群//consumer.setMessageModel(MessageModel.CLUSTERING);// 設置一次消費消息的條數,默認為1條consumer.setConsumeMessageBatchMaxSize(consumeMessageBatchMaxSize);try {// 設置該消費者訂閱的主題和tag,如果是訂閱該主題下的所有tag,則tag使用*;如果需要指定訂閱該主題下的某些tag,則使用||分割,例如tag1||tag2||tag3String[] topicTagsArr = topics.split(";");for (String topicTags : topicTagsArr) {String[] topicTag = topicTags.split("~");consumer.subscribe(topicTag[0],topicTag[1]);}consumer.start();}catch (Exception e){throw new Exception(e);}return consumer;}// 并發消息偵聽器(如果對順序消費有需求則使用MessageListenerOrderly 有序消息偵聽器)@Beanpublic MessageListenerConcurrently getMessageListenerConcurrently() {return new MQListenerConcurrently(mqHandlerMap);}/***************************消息生產者***************************/// 事務消息監聽器@Autowiredprivate MQTransactionListener mqTransactionListener;// 生產者nameservice地址@Value("${rocketmq.producer.namesrvAddr:127.0.0.1:9876}")private String pNamesrvAddr;// 消息最大大小,默認4M@Value("${rocketmq.producer.maxMessageSize:4096}")private Integer maxMessageSize ;// 消息發送超時時間,默認3秒@Value("${rocketmq.producer.sendMsgTimeout:30000}")private Integer sendMsgTimeout;// 消息發送失敗重試次數,默認2次@Value("${rocketmq.producer.retryTimesWhenSendFailed:2}")private Integer retryTimesWhenSendFailed;// 執行任務的線程池private static ExecutorService executor = ThreadUtil.newExecutor(32);//普通消息生產者@Bean("default")public DefaultMQProducer getDefaultMQProducer() {DefaultMQProducer producer = new DefaultMQProducer(this.groupName);producer.setNamesrvAddr(this.pNamesrvAddr);producer.setMaxMessageSize(this.maxMessageSize);producer.setSendMsgTimeout(this.sendMsgTimeout);producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);try {producer.start();} catch (MQClientException e) {System.out.println(e.getErrorMessage());}return producer;}//事務消息生產者(rocketmq支持柔性事務)@Bean("transaction")public TransactionMQProducer getTransactionMQProducer() {//初始化事務消息基本與普通消息生產者一致TransactionMQProducer producer = new TransactionMQProducer("transaction_" + this.groupName);producer.setNamesrvAddr(this.pNamesrvAddr);producer.setMaxMessageSize(this.maxMessageSize);producer.setSendMsgTimeout(this.sendMsgTimeout);producer.setRetryTimesWhenSendFailed(this.retryTimesWhenSendFailed);//添加事務消息處理線程池producer.setExecutorService(executor);//添加事務消息監聽producer.setTransactionListener(mqTransactionListener);try {producer.start();} catch (MQClientException e) {System.out.println(e.getErrorMessage());}return producer;}
}

2、發送消息

????????消息發送根據消息功能主要分為普通消息、事務消息、順序消息、延時消息等!特別說明一下事務消息多用于保證多服務模塊間的事務一致性,事務消息發送后并不會直接通知消費者消費消息,而是會先生成一個半消息,會先進入事務消息監聽器中,確保該消息事務提交成功后才會向broker發送消息,從而被消費者獲取并進行消費。

????????根據發送方式可以分為同步消息,異步消息和單向消息等:

  • 同步消息常用于比較重要的消息發送,需要等待broker響應告知消息發送狀態。
  • 異步消息的話常用于對響應時間敏感,需要快速返回的模塊,我們會設置一個回調代碼塊去異步監聽Borker的響應。
  • 單向消息的話主要用于對發送結果不敏感,不會影響業務的模塊,無需監聽broker響應,常用于日志發送等模塊。

? ? ? ? 下面代碼演示四種消息的使用方式:

package com.example.order.service.impl;import com.alibaba.fastjson.JSON;
import com.example.framework.utils.SonwflakeUtils;
import com.example.order.entity.Order;
import com.example.order.mapper.OrderMapper;
import com.example.order.service.OrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;/*** 服務實現類*/
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {@Qualifier("default")@Autowiredprivate DefaultMQProducer producer;@Autowiredprivate TransactionMQProducer transactionMQProducer;/*** 添加訂單(發送消息積分模塊同步添加積分)* @param order 訂單信息* @return org.apache.rocketmq.client.producer.TransactionSendResult**/@Overridepublic Order addOder(Order order) {order.setOrderId(SonwflakeUtils.get().id());if (order.getMessageType() == 1) {//普通消息this.save(order);Message message = new Message("points", "default", JSON.toJSONString(order).getBytes());try {//同步消息SendResult sendResult = producer.send(message);System.out.println("發送狀態:" + sendResult.getSendStatus() +",消息ID:" + sendResult.getMsgId() +",隊列:" + sendResult.getMessageQueue().getQueueId());
//                producer.sendOneway(message);//單向消息
//----------------------------異步消息-----------------------------------
//                producer.send(message, new SendCallback() {
//                    @Override
//                    public void onSuccess(SendResult sendResult) {
//
//                    }
//
//                    @Override
//                    public void onException(Throwable throwable) {
//
//                    }
//                });} catch (RemotingException | MQBrokerException | InterruptedException | MQClientException e) {e.printStackTrace();}} else {//事務消息Message message = new Message("points", "transaction", JSON.toJSONString(order).getBytes());try {transactionMQProducer.sendMessageInTransaction(message, null);} catch (MQClientException e) {e.printStackTrace();}}return order;}
}

3、消息消費

????????邊對MessageListenerConcurrently有進行一定封裝,主要是為了在消息處理時通過注解定位消息Topic和tag而自動選擇對應的消息處理類進行業務處理;封裝代碼如下:

package com.example.framework.mq.config;import cn.hutool.core.util.StrUtil;
import com.example.framework.mq.annotation.MQHandlerActualizer;
import com.example.framework.mq.handler.MQHandler;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;import java.util.Arrays;
import java.util.List;
import java.util.Map;/*** 并發消息監聽器*/
public class MQListenerConcurrently implements MessageListenerConcurrently {@Autowiredprivate Map<String, MQHandler> mqHandlerMap;public MQListenerConcurrently(Map<String, MQHandler> mqHandlerMap) {this.mqHandlerMap = mqHandlerMap;}@Overridepublic ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {if(CollectionUtils.isEmpty(list)){System.out.println("接受到的消息為空,不處理,直接返回成功");return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}MessageExt messageExt = list.get(0);// 判斷該消息是否重復消費(RocketMQ不保證消息不重復,如果你的業務需要保證嚴格的不重復消息,需要你自己在業務端去重)// 獲取該消息重試次數int reconsume = messageExt.getReconsumeTimes();if(reconsume ==3){//消息已經重試了3次,需做告警處理,已經相關日志return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}// 處理對應的業務邏輯String topic = messageExt.getTopic();String tags  = messageExt.getTags();System.out.println("接受到的消息主題為:" + topic + "; tag為:" + tags);MQHandler mqMsgHandler = null;//獲取消息處理類中的topic和tag注解,根據topic和tag進行策略分發出來具體業務for (Map.Entry<String, MQHandler> entry : mqHandlerMap.entrySet()) {MQHandlerActualizer msgHandlerActualizer = entry.getValue().getClass().getAnnotation(MQHandlerActualizer.class);if (msgHandlerActualizer == null) {//非消息處理類continue;}String annotationTopic = msgHandlerActualizer.topic();if (!StrUtil.equals(topic,annotationTopic)) {//非該主題處理類continue;}String[] annotationTags = msgHandlerActualizer.tags();if(StrUtil.equals(annotationTags[0],"*")){//獲取該實例mqMsgHandler = entry.getValue();break;}boolean isContains = Arrays.asList(annotationTags).contains(tags);if(isContains){//注解類中包含tag則獲取該實例mqMsgHandler = entry.getValue();break;}}if (mqMsgHandler == null) {return ConsumeConcurrentlyStatus.RECONSUME_LATER;}ConsumeConcurrentlyStatus status = mqMsgHandler.handle(tags,messageExt);// 如果沒有return success,consumer會重新消費該消息,直到return successreturn status;}
}

????????事務消息監聽器封裝:

package com.example.framework.mq.config;import cn.hutool.core.util.StrUtil;
import com.example.framework.mq.annotation.MQHandlerActualizer;
import com.example.framework.mq.handler.MQTransactionHandler;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;import java.util.Arrays;
import java.util.Map;public class MQTransactionListener implements TransactionListener {@Autowiredprivate Map<String, MQTransactionHandler> mqTransactionHandlerMap;@Overridepublic LocalTransactionState executeLocalTransaction(Message message, Object o) {MQTransactionHandler mqTransactionHandler = getListenner(message.getTopic(),message.getTags());return mqTransactionHandler.executeLocalTransaction(message,o);}@Overridepublic LocalTransactionState checkLocalTransaction(MessageExt messageExt) {MQTransactionHandler mqTransactionHandler = getListenner(messageExt.getTopic(),messageExt.getTags());return mqTransactionHandler.checkLocalTransaction(messageExt);}private MQTransactionHandler getListenner(String topic,String tags) {MQTransactionHandler mqTransactionHandler = null;for (Map.Entry<String, MQTransactionHandler> entry : mqTransactionHandlerMap.entrySet()) {MQHandlerActualizer msgHandlerActualizer = entry.getValue().getClass().getAnnotation(MQHandlerActualizer.class);if (msgHandlerActualizer != null) {String annotationTopic  = msgHandlerActualizer.topic();String[] annotationTags = msgHandlerActualizer.tags();if (!StrUtil.equals(topic,annotationTopic)) {//非該主題處理類continue;}if(StrUtil.equals(annotationTags[0],"*")){//獲取該實例mqTransactionHandler = entry.getValue();break;}boolean isContains = Arrays.asList(annotationTags).contains(tags);if(isContains){//注解類中包含tag則獲取該實例mqTransactionHandler = entry.getValue();break;}}}return mqTransactionHandler;}
}

????????使用注解@MQHandlerActualizer標明該消息處理類的主題,默認監聽所有tag,如果需要對tag監聽進行分類,后面加上tag即可。消息監聽器在收到消息后會自動調用主題對應的處理類進行業務處理,示例如下:

package com.example.points.mqHandler;import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.framework.mq.annotation.MQHandlerActualizer;
import com.example.framework.mq.handler.MQHandler;
import com.example.framework.utils.SonwflakeUtils;
import com.example.points.entity.Points;
import com.example.points.service.PointsService;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.factory.annotation.Autowired;import java.util.Map;/***/
@MQHandlerActualizer(topic = "points")
public class PointsMQHandler implements MQHandler {@Autowiredprivate PointsService pointsService;@Overridepublic ConsumeConcurrentlyStatus handle(String tag, MessageExt messageExt) {//消息監聽String messageStr = new String(messageExt.getBody());Map orderMap = (Map) JSON.parse(messageStr);Points points = new Points();Long orderId = (Long) orderMap.get("orderId");System.out.println("消息tag為:" + tag);System.out.println("消息監聽:"  + "為訂單" + orderId + "添加積分");//查詢該訂單是否已經生成對應積分(rocketMQ可能會重復發送消息,需實現冪等)QueryWrapper<Points> pointsQueryWrapper = new QueryWrapper<>();pointsQueryWrapper.lambda().eq(Points::getOrderId,orderId);Points tempPoints = pointsService.getOne(pointsQueryWrapper);if (tempPoints != null) {//該訂單已經生成積分System.out.println(orderId + "已經生成積分");return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}points.setPointsId(SonwflakeUtils.get().id());points.setOrderId(orderId);Integer orderAmout = (Integer) orderMap.get("orderAmout");points.setPoints(orderAmout * 10);pointsService.save(points);return  ConsumeConcurrentlyStatus.CONSUME_SUCCESS;}
}

使用消息隊列注意事項

  1. RocketMQ確保所有消息至少傳遞一次。雖然大多數情況下,消息不會重復,但還是需要對重復消息做。
  2. 盡量減小消息的體積,例如選擇輕量的協議,超過一定體積做壓縮處理,就消息協議而言, 二進制協議 < 文本協議。而文本協議中 json < xml 等等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/13757.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/13757.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/13757.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

npm常用指令

基礎 命令&#xff1a;run 解釋&#xff1a;運行腳本 示例&#xff1a;npm run dev 命令&#xff1a;list || ls 解釋&#xff1a;查看依賴列表 示例&#xff1a;npm list || npm ls 命令&#xff1a;install || i 解釋&#xff1a;安裝依賴 示例&#xff1a;npm install ||…

一文掌握python隨機數random模塊

目錄 一、常用函數 1、 random.random() 2、random.uniform(a, b) 3、random.randint(a, b) 4、random.randrange(start, stop[, step]) 5、random.choice(sequence) 6、random.shuffle(x[, random]) 7、random.sample(population, k) 8、random.choices(population, w…

Qml:錨點

import QtQuick import QtQuick.WindowWindow {width: 800height: 600visible: truetitle: qsTr("Test Anchors")///錨點 上下左右Rectangle{id: anchor1width:200height: 150color:"#EEEEEE"Rectangle{id:rect1width:50height:50color: "red"Te…

Ubuntu 20/22 安裝 Jenkins

1. 使用 apt 命令安裝 Java Jenkins 作為一個 Java 應用程序&#xff0c;要求 Java 8 及更高版本&#xff0c;檢查系統上是否安裝了 Java。 sudo apt install -y openjdk-17-jre-headless安裝完成后&#xff0c;再次驗證 Java 是否已安裝 java --version2. 通過官方存儲庫安…

動態地控制kafka的消費速度,從而滿足業務要求

kafka是一個分布式流媒體平臺&#xff0c;它可以處理大規模的數據流&#xff0c;并允許實時消費該數據流。在實際應用中&#xff0c;我們需要動態控制kafka消費速度&#xff0c;以便處理數據流的速率能夠滿足系統和業務的需求。本文將介紹如何在kafka中實現動態控制消費速度的方…

APH-Archives of Public Health

文章目錄 一、期刊簡介二、征稿信息三、期刊表現四、投稿須知五、投稿咨詢 一、期刊簡介 Archives of Public Health是一份范圍廣泛的公共衛生雜志&#xff0c;致力于出版公共衛生領域所有可靠的科學。該雜志旨在更好地了解人群的健康。該雜志有助于公共衛生知識&#xff0c;加…

【考研數學】李林《880》是什么難度水平強化夠用嗎

880是公認的質量高&#xff0c;但要是刷的方法不對&#xff0c;心態直接炸裂&#xff01;&#x1f649; 我24年二戰就是用的 880660 的黃金搭檔&#xff0c;143分逆襲上岸211&#xff01;&#xff08;為什么說逆襲呢&#xff0c;因為我23年一戰數學83&#xff0c;妥妥的菜雞&am…

2024.5.20 學習記錄

1、react 原理&#xff08;jsx的本質、事件機制原理、setState和batch Update、組件渲染更新和diff算法、fiber&#xff09; 2、代碼隨想錄貪心刷題

ArcGIS10.X入門實戰視頻教程(arcgis入門到精通)

點擊學習&#xff1a; ArcGIS10.X入門實戰視頻教程&#xff08;GIS思維&#xff09;https://edu.csdn.net/course/detail/4046?utm_sourceblog2edu 點擊學習&#xff1a; ArcGIS10.X入門實戰視頻教程&#xff08;GIS思維&#xff09;https://edu.csdn.net/course/detail/404…

銀河麒麟操作系統下使用QT連接TiDB數據庫開發步驟

目標:實現項目軟件+硬件都運行在國產化操作系統平臺上。 方法:在虛擬機中安裝麒麟系統V10Sp1+Qt5.14.2+MySql8.0+TiDB軟件,編譯MySql驅動,測試連接TiDB數據庫項目。 步驟: 1、使用虛擬機軟件VMWare安裝銀河麒麟操作系統。 2、在銀河麒麟系統上安裝QT5.14.2軟件。 3、…

Web Server項目實戰3-Web服務器簡介及HTTP協議

Web Server&#xff08;網頁服務器&#xff09; 一個 Web Server 就是一個服務器軟件&#xff08;程序&#xff09;&#xff0c;或者是運行這個服務器軟件的硬件&#xff08;計算機&#xff09;。其主要功能是通過 HTTP 協議與客戶端&#xff08;通常是瀏覽器&#xff08;Brow…

【精品】使用 v-md-editor 上傳圖片

簡介 v-md-editor 是基于 Vue 開發的 markdown 編輯器組件&#xff0c;即支持vue2也支持vue3。 gitee&#xff1a;https://gitee.com/ckang1229/vue-markdown-editor文檔&#xff1a;https://code-farmer-i.github.io/vue-markdown-editor/zh/ 服務器端代碼 RestController…

[Cocos Creator 3.5賽車游戲]第5節 為汽車節點掛載自定義腳本

在前面的章節中您已經學會了如何創建一個汽車節點&#xff0c;這一章我們將會學習如何通過掛載自定義節點的方式讓小車變得可控制&#xff0c;所以通過這一章的學習后&#xff0c;您將實現一個效果&#xff1a;開始運行后&#xff0c;小車每隔一幀就延y軸向上移動一段距離。在這…

cx_Oracle Python 庫連接 Oracle 數據庫時遇到報錯

這個錯誤 DPI-1047: Cannot locate a 64-bit Oracle Client library: "The specified module could not be found" 是在嘗試使用 cx_Oracle Python 庫連接 Oracle 數據庫時遇到的。這個錯誤表明 cx_Oracle 無法找到 Oracle 客戶端庫&#xff08;通常稱為 Instant Cli…

頂頂通呼叫中心中間件-自動外呼輸入分機號(比如隱私號)(mod_cti基于FreeSWITCH)

頂頂通呼叫中心中間件-自動外呼輸入分機號(比如隱私號)(mod_cti基于FreeSWITCH) 比如有些人的號碼是這樣的就需要用上自動外呼輸入分機號了 號碼1&#xff1a;182XXXX8111-1234 號碼2&#xff1a;182XXXX8222 如果號碼是這樣的就根據以下步驟配置 注意使用這個需要&#xff1a;…

Redis學習篇2:Redis在IEDA中的應用

本文繼上文開始講述了Redis在IDEA中如何應用以及集成進入spring開發環境&#xff0c;以及如何使用Redis客戶端。上一個文章&#xff1a;Redis學習篇1&#xff1a;初識Redishttps://blog.csdn.net/jialuosi/article/details/139057088 一、Redis在java中的客戶端 二、SpringDat…

MySQL存儲過程_觸發器_游標——Baidu Comate

# 問題1&#xff1a; 幫我創建2個表student與score表&#xff0c;要求student表有id,createDate,userName,phone,age,sex,introduce, 要求score表有id,scoreName,result,studentId(student表的id外鍵)。 要求student表中插入5條學生信息&#xff0c;都要是中文的。 要求score表…

onload和onunload有什么區別(代碼舉例說明)

onload 和 onunload 是兩種常用于網頁中的事件處理器&#xff08;event handlers&#xff09;&#xff0c;但它們處理的是完全不同的頁面生命周期事件。 onload onload 事件會在頁面或指定的元素&#xff08;如圖片、框架等&#xff09;完成加載后觸發。對于頁面整體來說&…

樹莓派 Raspberry Pi M.2 HAT+ 現已發售!原理圖流出!

?Raspberry Pi M.2 HAT 使您能夠將 M.2 M-key 外設&#xff08;如 NVMe 驅動器和人工智能加速器&#xff09;連接到 Raspberry Pi 5。它能夠提供與這些外設之間的快數據傳輸&#xff08;高達 500 MB/s&#xff09;&#xff0c;現在就可以從樹莓派的授權經銷商網絡購買&#xf…

c語言:strcmp

strcmp函數是用于比較兩個字符串的庫函數&#xff0c;其功能是根據ASCII值逐一對兩個字符串進行比較。 語法&#xff1a;strcmp(str1, str2) 返回值&#xff1a; 如果str1等于str2&#xff0c;則返回0。 如果str1小于str2&#xff0c;則返回負數&#xff08;具體值取決于C…