Nacos源碼—8.Nacos升級gRPC分析六

大綱

7.服務端對服務實例進行健康檢查

8.服務下線如何注銷注冊表和客戶端等信息

9.事件驅動架構源碼分析

一.處理ClientChangedEvent事件

也就是同步數據到集群節點:

public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {...@Overridepublic void onEvent(Event event) {...if (event instanceof ClientEvent.ClientVerifyFailedEvent) {syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);} else {syncToAllServer((ClientEvent) event);}}private void syncToAllServer(ClientEvent event) {Client client = event.getClient();//Only ephemeral data sync by Distro, persist client should sync by raft.//臨時實例使用Distro協議,持久化實例使用Raft協議//ClientManager.isResponsibleClient()方法,判斷只有該client的責任節點才能進行集群數據同步if (null == client || !client.isEphemeral() || !clientManager.isResponsibleClient(client)) {return;}if (event instanceof ClientEvent.ClientDisconnectEvent) {//如果event是客戶端注銷實例時需要進行集群節點同步的事件DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);distroProtocol.sync(distroKey, DataOperation.DELETE);} else if (event instanceof ClientEvent.ClientChangedEvent) {//如果event是客戶端注冊實例時需要進行集群節點同步的事件DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);distroProtocol.sync(distroKey, DataOperation.CHANGE);}}...
}@Component
public class DistroProtocol {private final ServerMemberManager memberManager;private final DistroTaskEngineHolder distroTaskEngineHolder;...//Start to sync by configured delay.public void sync(DistroKey distroKey, DataOperation action) {sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis());}//Start to sync data to all remote server.public void sync(DistroKey distroKey, DataOperation action, long delay) {//遍歷集群中除自身節點外的其他節點for (Member each : memberManager.allMembersWithoutSelf()) {syncToTarget(distroKey, action, each.getAddress(), delay);}}//Start to sync to target server.public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) {//先把要同步的集群節點targetServer包裝成DistroKey對象DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(), targetServer);//然后根據DistroKey對象創建DistroDelayTask任務DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);//接著調用NacosDelayTaskExecuteEngine.addTask()方法//往延遲任務執行引擎DistroDelayTaskExecuteEngine中添加延遲任務DistroDelayTaskdistroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);if (Loggers.DISTRO.isDebugEnabled()) {Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer);}}...
}

二.處理ClientDeregisterServiceEvent事件

也就是移除注冊表 + 訂閱表的服務實例:

@Component
public class ClientServiceIndexesManager extends SmartSubscriber {//注冊表(服務提供者),一個Service服務對象,對應多個服務實例的clientIdprivate final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();//訂閱者列表(服務消費者),一個Service服務對象,對應多個訂閱者的clientIdprivate final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();...@Overridepublic void onEvent(Event event) {if (event instanceof ClientEvent.ClientDisconnectEvent) {handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);} else if (event instanceof ClientOperationEvent) {handleClientOperation((ClientOperationEvent) event);}}private void handleClientOperation(ClientOperationEvent event) {Service service = event.getService();String clientId = event.getClientId();if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {//處理客戶端注冊事件ClientRegisterServiceEventaddPublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {//處理客戶端注銷事件ClientDeregisterServiceEventremovePublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {//處理客戶端訂閱服務事件ClientSubscribeServiceEventaddSubscriberIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {//處理客戶端取消訂閱事件ClientUnsubscribeServiceEventremoveSubscriberIndexes(service, clientId);}}private void removePublisherIndexes(Service service, String clientId) {if (!publisherIndexes.containsKey(service)) {return;}//移除注冊表中的服務實例publisherIndexes.get(service).remove(clientId);//發布服務改變事件ServiceChangedEventNotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));}...
}

三.處理ServiceChangeEvent事件

也就是通知訂閱了該服務的客戶端:

@org.springframework.stereotype.Service
public class NamingSubscriberServiceV2Impl extends SmartSubscriber implements NamingSubscriberService {...@Overridepublic void onEvent(Event event) {if (!upgradeJudgement.isUseGrpcFeatures()) {return;}if (event instanceof ServiceEvent.ServiceChangedEvent) {//If service changed, push to all subscribers.//如果服務變動,會向Service服務的所有訂閱者推送Service服務的實例信息,讓訂閱者(客戶端)更新本地緩存ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event;Service service = serviceChangedEvent.getService();//調用NacosDelayTaskExecuteEngine.addTask()方法,往延遲任務執行引擎添加任務delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));} else if (event instanceof ServiceEvent.ServiceSubscribedEvent) {//If service is subscribed by one client, only push this client.//如果Service服務被一個客戶端訂閱,則只推送Service服務的實例信息給該客戶端ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;Service service = subscribedEvent.getService();//調用NacosDelayTaskExecuteEngine.addTask()方法,往延遲任務執行引擎添加任務delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay(), subscribedEvent.getClientId()));}}...
}

(3)總結

9.事件驅動架構源碼分析

(1)如何使用Nacos的事件發布

(2)Nacos通知中心的事件發布源碼

(3)Nacos通知中心注冊訂閱者的源碼

Nacos 2.x大量使用了事件發布的動作,比如客戶端注冊服務實例、客戶端下線服務實例、服務改變、服務訂閱等。

(1)如何使用Nacos的事件發布

一.首先自定義一個事件

下面定義了一個名為TestEvent的事件,繼承自Nacos的Event類。

import com.alibaba.nacos.common.notify.Event;public class TestEvent extends Event {}

二.然后定義一個訂閱者

有了事件之后,還需要一個訂閱者,這樣發布的事件才能被這個訂閱者進行處理。

自定義的訂閱者需要繼承Nacos的SmartSubscriber抽象類,自定義的訂閱者需要實現三個方法。

方法一:構造方法

需要將自定義的訂閱者注冊到Nacos的通知中心NotifyCenter里,這樣NotifyCenter在發布自定義事件時,才能讓自定義的訂閱者進行響應。

方法二:subscribeTypes()方法

實現該方法時,需要把自定義的事件添加到方法的返回結果中,所以可以通過該方法獲取自定義訂閱者監聽了哪些事件。

方法三:onEvent()方法

Nacos的通知中心NotifyCenter在發布自定義事件時,便會調用該方法,所以該方法中需要實現自定義訂閱者對自定義事件的處理。

import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.SmartSubscriber;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;//自定義的訂閱者需要繼承Nacos的SmartSubscriber抽象類
@Component
public class TestSubscriber extends SmartSubscriber {//構造方法中需要將自定義的訂閱者TestSubscriber注冊到Nacos的通知中心NotifyCenterpublic TestSubscriber() {NotifyCenter.registerSubscriber(this);}//實現subscribeTypes()方法時,把自定義的事件TestEvent添加進去返回@Overridepublic List<Class<? extends Event>> subscribeTypes() {List<Class<? extends Event>> result = new LinkedList<>();result.add(TestEvent.class);return result;}//實現onEvent()方法//當Nacos的通知中心NotifyCenter發布一個TestEvent事件時,就會響應該方法處理訂閱者的邏輯@Overridepublic void onEvent(Event event) {System.out.println("TestSubscriber onEvent");}
}

三.最后通過Nacos的通知中心NotifyCenter發布自定義事件

這樣便完成了自定義事件、自定義訂閱者通過Nacos實現發布訂閱功能。

@RestController
@RequestMapping("/sub/")
public class SubscriberController {@GetMapping("/test")public void test() {NotifyCenter.publishEvent(new TestEvent());    }
}

(2)Nacos通知中心的事件發布源碼

通知中心NotifyCenter執行publishEvent()方法發布事件時,比如會調用DefaultPublisher的publish()方法來發布事件。

DefaultPublisher的publish()方法會先把事件放入到一個阻塞隊列queue中,而在DefaultPublisher創建時會啟動一個線程從阻塞隊列取出事件來處理。處理時就會調用到DefaultPublisher的receiveEvent()方法通知事件訂閱者,也就是執行DefaultPublisher的notifySubscriber()方法通知事件訂閱者。

在DefaultPublisher的notifySubscriber()方法中,首先會創建一個調用訂閱者的onEvent()方法的任務,然后如果訂閱者有線程池,則將任務提交給訂閱者的線程池去執行。如果訂閱者沒有線程池,則直接執行該任務。

可見事件的發布也使用了阻塞隊列 + 異步任務,來實現對訂閱者的通知。

public class NotifyCenter {private static final NotifyCenter INSTANCE = new NotifyCenter();//key是事件Class的canonicalName,value是EventPublisher對象,一個事件對應一個EventPublisher對象//在EventPublisher對象中就會包含訂閱了該事件的所有訂閱者//EventPublisher的實現類有DefaultPublisher、NamingEventPublisherprivate final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);...//Request publisher publish event Publishers load lazily, calling publisher. Start () only when the event is actually published.public static boolean publishEvent(final Event event) {try {return publishEvent(event.getClass(), event);} catch (Throwable ex) {LOGGER.error("There was an exception to the message publishing : ", ex);return false;}}//Request publisher publish event Publishers load lazily, calling publisher.private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {return INSTANCE.sharePublisher.publish(event);}//獲取發布的事件的Class的canonicalNamefinal String topic = ClassUtils.getCanonicalName(eventType);//根據發布事件類型獲取EventPublisher對象,該對象中會包含所發布事件的所有訂閱者信息EventPublisher publisher = INSTANCE.publisherMap.get(topic);if (publisher != null) {//比如調用DefaultPublisher.publish()方法發布事件return publisher.publish(event);}LOGGER.warn("There are no [{}] publishers for this event, please register", topic);return false;}...
}//The default event publisher implementation.
//一個事件只會對應一個DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {private Class<? extends Event> eventType;//阻塞隊列存放待發布的事件private BlockingQueue<Event> queue;//Class為eventType的事件的所有訂閱者protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();@Overridepublic void init(Class<? extends Event> type, int bufferSize) {...start();}@Overridepublic synchronized void start() {if (!initialized) {super.start();...}}@Overridepublic void run() {openEventHandler();}void openEventHandler() {try {...for (; ;) {...//從阻塞隊列取數據final Event event = queue.take();//處理事件receiveEvent(event);...}} catch (Throwable ex) {LOGGER.error("Event listener exception : ", ex);}}...@Overridepublic boolean publish(Event event) {checkIsStart();//把事件放入到了一個阻塞隊列queue中,由DefaultPublisher創建時啟動的線程來處理boolean success = this.queue.offer(event);if (!success) {//如果事件放入阻塞隊列失敗,則直接處理LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);//通知事件的訂閱者去進行事件處理receiveEvent(event);return true;}return true;}//通知事件的訂閱者去進行事件處理void receiveEvent(Event event) {...//遍歷當前事件的訂閱者,對訂閱者執行notifySubscriber()方法,實際上就是執行訂閱者的onEvent()方法for (Subscriber subscriber : subscribers) {...//觸發執行訂閱者的onEvent()方法,實現對訂閱者的通知notifySubscriber(subscriber, event);}}@Overridepublic void notifySubscriber(final Subscriber subscriber, final Event event) {//創建一個任務,該任務會調用訂閱者的onEvent方法final Runnable job = () -> subscriber.onEvent(event);final Executor executor = subscriber.executor();if (executor != null) {//將任務提交給訂閱者的線程池去執行executor.execute(job);} else {try {//如果訂閱者沒有線程池,則直接執行該任務job.run();} catch (Throwable e) {LOGGER.error("Event callback exception: ", e);}}}...
}

(3)Nacos通知中心注冊訂閱者的源碼

在執行NotifyCenter的registerSubscriber()方法注冊訂閱者時,會調用訂閱者實現的subscribeTypes()方法獲取訂閱者要監聽的所有事件,然后遍歷這些事件并調用NotifyCenter的addSubscriber()方法。

執行NotifyCenter的addSubscriber()方法時會為這些事件添加訂閱者。由于每個事件都會對應一個EventPublisher對象,所以會先從NotifyCenter.publisherMap中獲取EventPublisher對象,然后調用EventPublisher的addSubscriber()方法向EventPublisher添加訂閱者,從而完成向通知中心注冊訂閱者。

public class NotifyCenter {private static final NotifyCenter INSTANCE = new NotifyCenter();//key是事件Class的canonicalName,value是EventPublisher對象,一個事件對應一個EventPublisher對象//在EventPublisher對象中就會包含訂閱了該事件的所有訂閱者//EventPublisher的實現類有DefaultPublisher、NamingEventPublisherprivate final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);...public static void registerSubscriber(final Subscriber consumer) {//注冊訂閱者registerSubscriber(consumer, DEFAULT_PUBLISHER_FACTORY);}public static void registerSubscriber(final Subscriber consumer, final EventPublisherFactory factory) {if (consumer instanceof SmartSubscriber) {//調用subscribeTypes()方法獲取訂閱者consumer需要監聽的事件,然后對這些事件進行遍歷for (Class<? extends Event> subscribeType : ((SmartSubscriber) consumer).subscribeTypes()) {//For case, producer: defaultSharePublisher -> consumer: smartSubscriber.if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {//添加訂閱者INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);} else {//For case, producer: defaultPublisher -> consumer: subscriber.//添加訂閱者addSubscriber(consumer, subscribeType, factory);}}return;}final Class<? extends Event> subscribeType = consumer.subscribeType();if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);return;}addSubscriber(consumer, subscribeType, factory);}//Add a subscriber to publisher.private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType, EventPublisherFactory factory) {//獲取訂閱的事件的Class的canonicalNamefinal String topic = ClassUtils.getCanonicalName(subscribeType);synchronized (NotifyCenter.class) {//MapUtils.computeIfAbsent is a unsafe method.//創建EventPublisher對象,一個事件會對應一個EventPublisher對象MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);}//獲取事件對應的EventPublisher對象,比如DefaultPublisher對象EventPublisher publisher = INSTANCE.publisherMap.get(topic);if (publisher instanceof ShardedEventPublisher) {((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);} else {//往EventPublisher對象添加訂閱者信息,比如調用DefaultPublisher.addSubscriber()方法publisher.addSubscriber(consumer);}}...
}//一個事件只會對應一個DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {private Class<? extends Event> eventType;//Class為eventType的事件的所有訂閱者protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();...@Overridepublic void addSubscriber(Subscriber subscriber) {//添加訂閱者subscribers.add(subscriber);}...
}

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

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

相關文章

設計雜談-工廠模式

“工廠”模式在各種框架中非常常見&#xff0c;包括 MyBatis&#xff0c;它是一種創建對象的設計模式。使用工廠模式有很多好處&#xff0c;尤其是在復雜的框架中&#xff0c;它可以帶來更好的靈活性、可維護性和可配置性。 讓我們以 MyBatis 為例&#xff0c;來理解工廠模式及…

AI與IoT攜手,精準農業未來已來

AIoT&#xff1a;農業領域的變革先鋒 在科技飛速發展的當下&#xff0c;人工智能&#xff08;AI&#xff09;與物聯網&#xff08;IoT&#xff09;的融合 ——AIoT&#xff0c;正逐漸成為推動各行業變革的關鍵力量&#xff0c;農業領域也不例外。AIoT 技術通過將 AI 的智能分析…

排錯-harbor-db容器異常重啟

排錯-harbor-db容器異常重啟 環境&#xff1a; docker 19.03 , harbor-db(postgresql) goharbor/harbor-db:v2.5.6 現象&#xff1a; harbor-db 容器一直restart&#xff0c;查看日志發現 報錯 initdb: error: directory "/var/lib/postgresql/data/pg13" exists…

Docker容器啟動失敗?無法啟動?

Docker容器無法啟動的疑難雜癥解析與解決方案 一、問題現象 Docker容器無法啟動是開發者在容器化部署中最常見的故障之一。盡管Docker提供了豐富的調試工具&#xff0c;但問題的根源往往隱藏在復雜的配置、環境依賴或資源限制中。本文將從環境變量配置錯誤這一細節問題入手&am…

查看購物車

一.查看購物車 查看購物車使用get請求。我們要查看當前用戶的購物車&#xff0c;就要獲取當前用戶的userId字段進行條件查詢。因為在用戶登錄時就已經將userId封裝在token中了&#xff0c;因此我們只需要解析token獲取userId即可&#xff0c;不需要前端再傳入參數了。 Control…

C/C++ 內存管理深度解析:從內存分布到實踐應用(malloc和new,free和delete的對比與使用,定位 new )

一、引言&#xff1a;理解內存管理的核心價值 在系統級編程領域&#xff0c;內存管理是決定程序性能、穩定性和安全性的關鍵因素。C/C 作為底層開發的主流語言&#xff0c;賦予開發者直接操作內存的能力&#xff0c;卻也要求開發者深入理解內存布局與生命周期管理。本文將從內…

使用Stable Diffusion(SD)中CFG參數指的是什么?該怎么用!

1.定義&#xff1a; CFG參數控制模型在生成圖像時&#xff0c;對提示詞&#xff08;Prompt&#xff09;的“服從程度”。 它衡量模型在“完全根據提示詞生成圖像”和“自由生成圖像”&#xff08;不參考提示詞&#xff09;之間的權衡程度。 數值范圍&#xff1a;常見范圍是 1 …

【GESP】C++三級練習 luogu-B2156 最長單詞 2

GESP三級練習&#xff0c;字符串練習&#xff08;C三級大綱中6號知識點&#xff0c;字符串&#xff09;&#xff0c;難度★★☆☆☆。 題目題解詳見&#xff1a;https://www.coderli.com/gesp-3-luogu-b2156/ 【GESP】C三級練習 luogu-B2156 最長單詞 2 | OneCoderGESP三級練…

Linux網絡基礎 -- 局域網,廣域網,網絡協議,網絡傳輸的基本流程,端口號,網絡字節序

目錄 1. 計算機網絡背景 1.1 局域網 1.1.2 局域網的組成 1.2 廣域網 1.1.2 廣域網的組成 2. 初始網絡協議 2.1 網絡協議的定義和作用 2.2 網絡協議的分層結構 2.2.1 OSI七層模型 2.2.2 TCP/IP 五層&#xff08;四層&#xff09;模型 3. 再識網絡協議 3.1 為什么要有…

【PostgreSQL】超簡單的主從節點部署

1. 啟動數據庫 啟動主節點 docker run --name postgres-master -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres啟動從節點 docker run --name postgres-slave -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres需要配置掛載的存儲卷 2. 數據…

c#修改ComboBox當前選中項的文本

對于一個C#的Combobox列表&#xff0c;類型設置為下拉樣式&#xff0c;不允許輸入&#xff0c;只能選擇&#xff0c;樣子如下&#xff1a; 該控件的名字為 cbb1&#xff0c;如果要修改當前這個“A1區”的文件&#xff0c;則用如下方式&#xff1a; cbb1.Items[cbb1.SelectedInd…

Java設計模式之適配器模式:從入門到精通

適配器模式(Adapter Pattern)是Java中最常用的結構型設計模式之一,它像一座橋梁連接兩個不兼容的接口,使得原本由于接口不兼容而不能一起工作的類可以協同工作。本文將全面深入地解析適配器模式,從基礎概念到高級應用,包含豐富的代碼示例、詳細注釋、使用場景分析以及多維對…

中國黃土高原中部XF剖面磁化率和粒度數據

時間分辨率&#xff1a;1000年 < x空間分辨率為&#xff1a;空共享方式&#xff1a;申請獲取數據大小&#xff1b;35.75 KB數據時間范圍&#xff1a;743-0 ka元數據更新時間&#xff1a;2023-08-15 數據集摘要 該數據集包括中國黃土高原中部XF剖面磁化率和粒度數據。將所有…

【Python訓練營打卡】day23 @浙大疏錦行

test pipeline管道 知識回顧: 1. 轉化器和估計器的概念 2. 管道工程 3. ColumnTransformer和Pipeline類 作業&#xff1a; 整理下全部邏輯的先后順序&#xff0c;看看能不能制作出適合所有機器學習的通用pipeline 偽代碼 # 適合所有機器學習的通用pipeline #偽代碼 import p…

【android bluetooth 框架分析 02】【Module詳解 13】【CounterMetrics 模塊介紹】

1. CounterMetrics 介紹 CounterMetrics 模塊代碼很少&#xff0c; 我簡單介紹一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…

QMK鍵盤固件配置詳解

QMK鍵盤固件配置詳解 前言 大家好&#xff01;今天給大家帶來QMK鍵盤固件配置的詳細指南。如果你正在DIY機械鍵盤或者想要給自己的鍵盤刷固件&#xff0c;這篇文章絕對不容錯過。QMK是目前最流行的開源鍵盤固件框架之一&#xff0c;它允許我們對鍵盤進行高度自定義。接下來&a…

基于STM32、HAL庫的DPS368XTSA1氣壓傳感器 驅動程序設計

一、簡介: DPS368XTSA1 是 InvenSense(TDK 集團旗下公司)生產的一款高精度數字氣壓傳感器,專為需要精確測量氣壓和溫度的應用場景設計。它具有超低功耗、高精度、快速響應等特點,非常適合物聯網、可穿戴設備和無人機等應用。 二、硬件接口: DPS368XTSA1 引腳STM32L4XX 引…

因子分析——數學原理及R語言代碼

正交因子分析 目的數學原理參數估計方法主成分法主因子法極大似然法 因子旋轉模型檢驗因子得分加權最小二乘法回歸法 代碼實現注意事項例子 Reference 目的 FactorAnalysis的目的是從多個高度相關的觀測變量中提取出少數幾個LatentFactor&#xff0c;這些因子代表了變量背后的…

ACL訪問控制列表:access-list 10 permit 192.168.10.1

ACL訪問控制列表 標準ACL語法 1. 創建ACL access-list <編號> <動作> <源IP> <通配符掩碼> // 編號范圍 1-99 // 動作&#xff1a;permit 允許 、 deny 拒絕2. 示例 //允許192.168.1.0/24g整個網絡,0.0.0.255 反掩碼 access-list 10 permit 192.1…

解決社區錄音應用橫屏狀態下,錄音后無法播放的bug

最近看到社區有小伙伴反映&#xff0c;社區錄音應用橫屏時&#xff0c;錄音后無法播放的問題。現分享解決辦法。 社區錄音應用的來源&#xff1a;https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.2-Release/code/SystemFeature/Media/Recorder …