Nacos源碼解讀10——配置中心的客戶端怎么處理服務端推送的配置信息變更

自動裝配

SpringBoot 自動裝配機制 加載 WEB/INF spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration```java
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {......@Bean@ConditionalOnMissingBeanpublic NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);}......}

創建 ConfigService

構建NacosConfigManagerBean的時候會在實例化的時候調用構造方法 他的構造方法中會創建ConfigService

	public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {this.nacosConfigProperties = nacosConfigProperties;// Compatible with older code in NacosConfigProperties,It will be deleted in the// future.createConfigService(nacosConfigProperties);}
	static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {if (Objects.isNull(service)) {synchronized (NacosConfigManager.class) {try {if (Objects.isNull(service)) {service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());}}catch (NacosException e) {log.error(e.getMessage());throw new NacosConnectionFailureException(nacosConfigProperties.getServerAddr(), e.getMessage(), e);}}}return service;}
  public static ConfigService createConfigService(Properties properties) throws NacosException {return ConfigFactory.createConfigService(properties);}
  public static ConfigService createConfigService(Properties properties) throws NacosException {try {//反射拿到classClass<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");// 獲取帶Properties參數的構造函數Constructor constructor = driverImplClass.getConstructor(Properties.class);//通過反射構建實例ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);return vendorImpl;} catch (Throwable e) {throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);}}

NacosConfigService會在構造方法中 注入Listener接受server配置變更通知。

public NacosConfigService(Properties properties) throws NacosException {ValidatorUtils.checkInitParam(properties);// 設置namespace可以通過properties.setProperty(PropertyKeyConst.NAMESPACE)initNamespace(properties);// 初始化namespace、server地址等信息ServerListManager serverListManager = new ServerListManager(properties);// 啟動主要用于endpoint方式定時獲取server地址,當本地傳入isFixed=trueserverListManager.start();//  clientWorker初始化this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, properties);// 將被廢棄HttpAgent,先忽略// will be deleted in 2.0 later versionsagent = new ServerHttpAgent(serverListManager);
}

ClientWorker初始化

public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager,final Properties properties) throws NacosException {this.configFilterChainManager = configFilterChainManager;// 初始化超時時間、重試時間等init(properties);// gRPC config agent初始化agent = new ConfigRpcTransportClient(properties, serverListManager);// 調度線程池,「處理器核數」ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.Worker");t.setDaemon(true);return t;}});agent.setExecutor(executorService);// 啟動grpc agentagent.start();}

初始化超時時間等

private void init(Properties properties) {// 超時時間,默認30秒timeout = Math.max(ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT),Constants.CONFIG_LONG_POLL_TIMEOUT), Constants.MIN_CONFIG_LONG_POLL_TIMEOUT);// 重試時間,默認2秒taskPenaltyTime = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_RETRY_TIME), Constants.CONFIG_RETRY_TIME);// 開啟配置刪除同步,默認falsethis.enableRemoteSyncConfig = Boolean.parseBoolean(properties.getProperty(PropertyKeyConst.ENABLE_REMOTE_SYNC_CONFIG));
}

GRPCConfigAgent初始化

public ConfigTransportClient(Properties properties, ServerListManager serverListManager) {// 默認編碼UTF-8String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);if (StringUtils.isBlank(encodeTmp)) {this.encode = Constants.ENCODE;} else {this.encode = encodeTmp.trim();}// namespace租戶,默認空this.tenant = properties.getProperty(PropertyKeyConst.NAMESPACE);this.serverListManager = serverListManager;// 用戶名和密碼驗證this.securityProxy = new SecurityProxy(properties,ConfigHttpClientManager.getInstance().getNacosRestTemplate());}

啟動GRPC Config Agent

public void start() throws NacosException {// 簡單用戶名和密碼驗證if (securityProxy.isEnabled()) {securityProxy.login(serverListManager.getServerUrls());this.executor.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {securityProxy.login(serverListManager.getServerUrls());}}, 0, this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);}startInternal();
}

這里線程會一直運行從listenExecutebell這個阻塞隊列中獲取元素
listenExecutebell這里阻塞隊列會在服務變更之后發布變更事件最后會往這個阻塞隊列中塞元素 如果隊列為空等待5秒后執行,如果隊列不為空立即執行

        @Overridepublic void startInternal() {executor.schedule(() -> {//線程池沒有管理并且所有線程沒有運行完while (!executor.isShutdown() && !executor.isTerminated()) {try {// 最長等待5秒listenExecutebell.poll(5L, TimeUnit.SECONDS);//如果線程池已經關閉 或者所有線程運行完直接if (executor.isShutdown() || executor.isTerminated()) {continue;}executeConfigListen();} catch (Exception e) {LOGGER.error("[ rpc listen execute ] [rpc listen] exception", e);}}}, 0L, TimeUnit.MILLISECONDS);}

注冊Listener

在Spring啟動的時候會在run方法中執行
SpringApplicationRunListeners的running(context)這里面會發送一個ApplicationReadyEvent事件
NacosContextRefresher會監聽到ApplicationReadyEvent事件進行nacos監聽器的注冊

	@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// many Spring contextif (this.ready.compareAndSet(false, true)) {this.registerNacosListenersForApplications();}}
	private void registerNacosListenersForApplications() {......registerNacosListener(propertySource.getGroup(), dataId);......}
	private void registerNacosListener(final String groupKey, final String dataKey) {.....//添加監聽器configService.addListener(dataKey, groupKey, listener);......
}

添加監聽器

構建CacheData,并緩存在cacheMap中,key是由「dataId+group+tenant」組成;每個CacheData會綁定了Listener列表,也綁定了taskId,3000個不同的CacheData對應一個taskId,對應一個gRPC通道實例

    @Overridepublic void addListener(String dataId, String group, Listener listener) throws NacosException {worker.addTenantListeners(dataId, group, Arrays.asList(listener));}
    public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)throws NacosException {// 默認DEFAULT_GROUPgroup = blank2defaultGroup(group);//獲取租戶默認是空String tenant = agent.getTenant();//構建緩存數據CacheData并放入cacheMap中CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);synchronized (cache) {for (Listener listener : listeners) {cache.addListener(listener);}// cache md5 data是否來自server同步cache.setSyncWithServer(false);//往阻隊列中添加數據  listenExecutebell.offer(bellItem);agent.notifyListenConfig();}}

往緩存中添加內容

構建緩存數據CacheData并放入cacheMap中,緩存的key為 「dataId+group+tenant」例如:test+DEFAULT_GROUP。每個CacheData會綁定對應的taskId,每3000個CacheData對應一個taskId。其實從后面的代碼中可以看出,每個taskId會對應一個gRPC

    public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {// 從緩存中獲取 如果不是空的直接返回CacheData cache = getCache(dataId, group, tenant);if (null != cache) {return cache;}// 構造緩存key以+連接,test+DEFAULT_GROUPString key = GroupKey.getKeyTenant(dataId, group, tenant);synchronized (cacheMap) {CacheData cacheFromMap = getCache(dataId, group, tenant);// multiple listeners on the same dataid+group and race condition,so// double check again// other listener thread beat me to set to cacheMapif (null != cacheFromMap) { // 再檢查一遍cache = cacheFromMap;// reset so that server not hang this checkcache.setInitializing(true); // 緩存正在初始化} else {// 構造緩存數據對象cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);// 初始值taskId=0,注意此處每3000個CacheData共用一個taskIdint taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize();cache.setTaskId(taskId);// fix issue # 1317 // 默認falseif (enableRemoteSyncConfig) {ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L, false);cache.setContent(response.getContent());}}Map<String, CacheData> copy = new HashMap<>(this.cacheMap.get());// key = test+DEFAULT_GROUPcopy.put(key, cache);// cacheMap = {test+DEFAULT_GROUP=CacheData [test, DEFAULT_GROUP]}cacheMap.set(copy);}LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size());return cache;}

緩存的內容

public class CacheData {//ConfigTransportClient名稱,config_rpc_clientprivate final String name;//filter攔截鏈條,可以執行一些列攔截器private final ConfigFilterChainManager configFilterChainManager;//dataIdpublic final String dataId;//group名稱,默認為DEFAULT_GROUPpublic final String group;//租戶名稱public final String tenant;//添加的Listener列表,線程安全CopyOnWriteArrayListprivate final CopyOnWriteArrayList<ManagerListenerWrap> listeners;//MD5private volatile String md5;//配置內容private volatile String content; }

配置變更

public void executeConfigListen() {Map<String/*taskId*/, List<CacheData>> listenCachesMap = new HashMap<String, List<CacheData>>(16);Map<String, List<CacheData>> removeListenCachesMap = new HashMap<String, List<CacheData>>(16);long now = System.currentTimeMillis();// 超過5分鐘boolean needAllSync = now - lastAllSyncTime >= ALL_SYNC_INTERNAL;for (CacheData cache : cacheMap.get().values()) {synchronized (cache) {//  isSyncWithServer初始為false,在下文代碼中校驗結束后會設置為true,表示md5 cache data同步來自server。如果為true會校驗Md5.if (cache.isSyncWithServer()) { cache.checkListenerMd5(); // 內容有變更通知Listener執行if (!needAllSync) { // 不超過5分鐘則不再全局校驗continue;}}if (!CollectionUtils.isEmpty(cache.getListeners())) { // 有添加Listeners// get listen config 默認 falseif (!cache.isUseLocalConfigInfo()) {List<CacheData> cacheDatas = listenCachesMap.get(String.valueOf(cache.getTaskId()));if (cacheDatas == null) {cacheDatas = new LinkedList<CacheData>();listenCachesMap.put(String.valueOf(cache.getTaskId()), cacheDatas);}// CacheData [test, DEFAULT_GROUP]cacheDatas.add(cache);}} else if (CollectionUtils.isEmpty(cache.getListeners())) { // 沒有添加Listenersif (!cache.isUseLocalConfigInfo()) {List<CacheData> cacheDatas = removeListenCachesMap.get(String.valueOf(cache.getTaskId()));if (cacheDatas == null) {cacheDatas = new LinkedList<CacheData>();removeListenCachesMap.put(String.valueOf(cache.getTaskId()), cacheDatas);}cacheDatas.add(cache);}}}}boolean hasChangedKeys = false;if (!listenCachesMap.isEmpty()) { // 有Listenersfor (Map.Entry<String, List<CacheData>> entry : listenCachesMap.entrySet()) {String taskId = entry.getKey();List<CacheData> listenCaches = entry.getValue();ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(listenCaches);configChangeListenRequest.setListen(true);try {// 每個taskId構建rpcClient,例如:taskId= config-0-c70e0314-4770-43f5-add4-f258a4083fd7;結合上下文每3000個CacheData對應一個rpcClientRpcClient rpcClient = ensureRpcClient(taskId);// 向server發起configChangeListenRequest,server端由ConfigChangeBatchListenRequestHandler處理,還是比較md5是否變更了,變更后server端返回變更的key列表。ConfigChangeBatchListenResponse configChangeBatchListenResponse = (ConfigChangeBatchListenResponse) requestProxy(rpcClient, configChangeListenRequest);if (configChangeBatchListenResponse != null && configChangeBatchListenResponse.isSuccess()) {Set<String> changeKeys = new HashSet<String>();// handle changed keys,notify listener// 有變化的configContextif (!CollectionUtils.isEmpty(configChangeBatchListenResponse.getChangedConfigs())) {hasChangedKeys = true;for (ConfigChangeBatchListenResponse.ConfigContext changeConfig : configChangeBatchListenResponse.getChangedConfigs()) {String changeKey = GroupKey.getKeyTenant(changeConfig.getDataId(), changeConfig.getGroup(),changeConfig.getTenant());changeKeys.add(changeKey);boolean isInitializing = cacheMap.get().get(changeKey).isInitializing();//當server返回變更key列表時執行refreshContentAndCheck方法。然后回調ListenerrefreshContentAndCheck(changeKey, !isInitializing);}}//handler content configsfor (CacheData cacheData : listenCaches) {String groupKey = GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.getTenant());if (!changeKeys.contains(groupKey)) {// key沒有變化的,內容由server同步,設置SyncWithServer=truesynchronized (cacheData) {if (!cacheData.getListeners().isEmpty()) {cacheData.setSyncWithServer(true);continue;}}}cacheData.setInitializing(false);}}} catch (Exception e) {LOGGER.error("Async listen config change error ", e);try {Thread.sleep(50L);} catch (InterruptedException interruptedException) {//ignore}}}}if (!removeListenCachesMap.isEmpty()) {for (Map.Entry<String, List<CacheData>> entry : removeListenCachesMap.entrySet()) {String taskId = entry.getKey();List<CacheData> removeListenCaches = entry.getValue();ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(removeListenCaches);configChangeListenRequest.setListen(false);try {// 向server發送Listener取消訂閱請求ConfigBatchListenRequest#listen為falseRpcClient rpcClient = ensureRpcClient(taskId);boolean removeSuccess = unListenConfigChange(rpcClient, configChangeListenRequest);if (removeSuccess) {for (CacheData cacheData : removeListenCaches) {synchronized (cacheData) {if (cacheData.getListeners().isEmpty()) {// 移除本地緩存ClientWorker.this.removeCache(cacheData.dataId, cacheData.group, cacheData.tenant);}}}}} catch (Exception e) {LOGGER.error("async remove listen config change error ", e);}try {Thread.sleep(50L);} catch (InterruptedException interruptedException) {//ignore}}}if (needAllSync) {lastAllSyncTime = now;}//If has changed keys,notify re sync md5.if (hasChangedKeys) { // key有變化觸發下一輪notifyListenConfig();}
}

校驗MD5

當CacheData從server同步后,會校驗md5是否變更了,當變更時會回調到我們注冊的Listener完成通知。通知任務被封裝成Runnable任務,執行線程池可以自定義,默認為5個線程。

void checkListenerMd5() {for (ManagerListenerWrap wrap : listeners) {if (!md5.equals(wrap.lastCallMd5)) { // 配置內容有變更時,回調到]Listener中。safeNotifyListener(dataId, group, content, type, md5, wrap);}}
}
private void safeNotifyListener(final String dataId, final String group, final String content, final String type,final String md5, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;if (listenerWrap.inNotifying) {// ...return;}Runnable job = new Runnable() {@Overridepublic void run() {long start = System.currentTimeMillis();ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener) listener;adapter.fillContext(dataId, group);// ...}Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);// filter攔截繼續過濾configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listenerWrap.inNotifying = true;// 回調注冊Listener的receiveConfigInfo方法或者receiveConfigChange邏輯listener.receiveConfigInfo(contentTmp);// compare lastContent and contentif (listener instanceof AbstractConfigChangeListener) {Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);ConfigChangeEvent event = new ConfigChangeEvent(data);// 回調變更事件方法((AbstractConfigChangeListener) listener).receiveConfigChange(event);listenerWrap.lastContent = content;}listenerWrap.lastCallMd5 = md5;// ..} catch (NacosException ex) {// ...} catch (Throwable t) {// ...} finally {listenerWrap.inNotifying = false;Thread.currentThread().setContextClassLoader(myClassLoader);}}};final long startNotify = System.currentTimeMillis();try {// 優先使用我們示例中注冊提供的線程池執行job,如果沒有設置使用默認線程池「INTERNAL_NOTIFIER」,默認5個線程if (null != listener.getExecutor()) {listener.getExecutor().execute(job);} else {try {INTERNAL_NOTIFIER.submit(job); // 默認線程池執行,為5個線程} catch (RejectedExecutionException rejectedExecutionException) {// ...job.run();} catch (Throwable throwable) {// ...job.run();}}} catch (Throwable t) {// ...}final long finishNotify = System.currentTimeMillis();// ...
}

key有變更

注冊Listener后,會構建與server的RPC通道rpcClient;向server發起變更查詢請求configChangeListenRequest,server端通過比較緩存的md5值,返回client變更的key列表;client通過變更的key列表向server發起配置查詢請求ConfigQueryRequest,獲取變更內容,并回調我們注冊的Listener。

private void refreshContentAndCheck(CacheData cacheData, boolean notify) {try {// 向server發起ConfigQueryRequest,查詢配置內容String[] ct = getServerConfig(cacheData.dataId, cacheData.group, cacheData.tenant, 3000L, notify);//設置最新的內容信息cacheData.setContent(ct[0]);if (null != ct[1]) {cacheData.setType(ct[1]);}if (notify) { // 記錄日志// ...}// 回調注冊的Listener邏輯cacheData.checkListenerMd5();} catch (Exception e) {//...}
}

總結

客戶端在啟動的時候會構建一個ConfigService的處理類,然后再ConfigService的構造,方法中會創建一個ClientWorker 用來處理對服務端的網絡通信及后續變更處理,
當服務端有配置變更的時候會發送配置變更事件最終會往一個阻塞隊列中取offer數據,然后ClientWorker啟動的時候會構建一個定時線程去從這個阻塞隊列中阻塞拿數據 如果隊列為空等待5秒后執行,如果隊列不為空立即執行 然后會將3000個CacheDate其實就是配置數據組成一個taskId 然后往服務端發送grpc請求,服務端會檢查 md5比較哪些配置發生了變更 然后會返回發生變更的key列表,然后客戶端根據服務端返回的key列表 去服務端拉取最新的配置信息 然后緩存到本地

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

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

相關文章

MongoDB的連接數據庫,創建、刪除數據庫,創建、刪除集合命令

本文主要介紹MongoDB的連接數據庫&#xff0c;創建、刪除數據庫&#xff0c;創建、刪除集合命令。 目錄 MongoDB連接數據庫連接到本地 MongoDB 實例連接到遠程 MongoDB 實例 MongoDB創建和刪除數據庫MongoDB創建和刪除集合創建集合刪除集合 MongoDB連接數據庫 連接 MongoDB 數…

P1317 低洼地題解

題目 一組數&#xff0c;分別表示地平線的高度變化。高度值為整數&#xff0c;相鄰高度用直線連接。找出并統計有多少個可能積水的低洼地&#xff1f; 如圖&#xff1a;地高變化為 [0,1,0,2,1,2,0,0,2,0]。 輸入輸出格式 輸入格式 兩行&#xff0c;第一行n, 表示有n個數。第…

Spark DataFrame和Dataset使用例子

文章目錄 1、基本操作1.1、創建SparkSession1.2、創建DataFrames1.3、創建Dataset操作1.4、運行sql查詢1.5、創建全局臨時視圖1.6、創建Datasets1.7、與rdd進行互操作1.7.1、使用反射推斷模式1.7.2、以編程方式指定模式 2、完整的測試例子 1、基本操作 1.1、創建SparkSession …

openGauss學習筆記-151 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_basebackup

文章目錄 openGauss學習筆記-151 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_basebackup151.1 背景信息151.2 前提條件151.3 語法151.4 示例151.5 從備份文件恢復數據 openGauss學習筆記-151 openGauss 數據庫運維-備份與恢復-物理備份與恢復之gs_basebackup 151.1 …

NeuralKG運行備忘

環境配置&#xff1a; conda create -n neuralkg python3.8 conda activate neuralkg pip install torch1.9.1cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install dgl-cu111 dglgo -f https://data.dgl.ai/wheels/repo.html pip install neuralkg! co…

基于java swing 藥品銷售管理系統

大家好&#xff0c;我是DeBug&#xff0c;很高興你能來閱讀&#xff01;作為一名熱愛編程的程序員&#xff0c;我希望通過這些教學筆記與大家分享我的編程經驗和知識。在這里&#xff0c;我將會結合實際項目經驗&#xff0c;分享編程技巧、最佳實踐以及解決問題的方法。無論你是…

短視頻賬號剪輯矩陣+無人直播系統源頭開發

抖去推爆款視頻生成器&#xff0c;通過短視頻矩陣、無人直播&#xff0c;文案引流等&#xff0c;打造實體商家員工矩陣、用戶矩陣、直播矩陣&#xff0c;輔助商家品牌曝光&#xff0c;團購轉化等多功能賦能商家拓客引流。 短視頻矩陣通俗來講就是批量剪輯視頻和批量發布視頻&am…

Multisim電路仿真軟件使用教程

安裝直接參考這篇文章&#xff1a;Multisim 14.0安裝教程 軟件管家公眾號里有很多軟件&#xff0c;需要的可以去找下然后安裝&#xff0c;這里用的是14.0版本。 這里有個大神的詳細教程&#xff0c;可以參考&#xff1a; Multisim軟件使用詳細入門教程&#xff08;圖文全解&…

Java Docker 生產環境部署

1. 引言 隨著容器化技術的廣泛應用&#xff0c;Docker成為了一種非常流行的容器化解決方案。Java作為一種跨平臺的編程語言&#xff0c;在生產環境中也廣泛使用。本文將介紹如何使用Docker來部署Java應用程序&#xff0c;并探討一些最佳實踐和注意事項。 2. Docker簡介 Dock…

Python房價分析(二)隨機森林分類模型

目錄 1 數據預處理 1.1 房價數據介紹 1.2 數據預處理 1.2.1 缺失值處理 1.2.2異常值處理 1.2.3 數據歸一化 1.2.4 分類特征編碼 2 隨機森林模型 2.1 模型概述 2.2 建模步驟 2.3 參數搜索過程 3模型評估 3.1 模型評估結果 3.2 混淆矩陣 3.3 繪制房價類別三分類的…

面試官:性能測試瓶頸調優你是真的會嗎?

引言&#xff1a;性能瓶頸調優 在實際的性能測試中&#xff0c;會遇到各種各樣的問題&#xff0c;比如 TPS 壓不上去等&#xff0c;導致這種現象的原因有很多&#xff0c;測試人員應配合開發人員進行分析&#xff0c;盡快找出瓶頸所在。 理想的性能測試指標結果可能不是很高&…

Linux內核--內存管理(六)補充--進程頁表

目錄 一、引言 二、頁表 ------>2.1、頁表的大小 ------>2.2、頁表起始地址 ------>2.3、CPU調度 ------>2.4、用戶態訪問虛擬地址 ------>2.5、頁表組成部分 ------------>2.5.1、進程用戶態頁表 ------------>2.5.2、內核態頁表 ------>2.…

c++學習之異常

前言 早在c語言的時候&#xff0c;就已經有處理錯誤的方式了&#xff0c;第一種方式太過暴力&#xff0c;就是斷言&#xff0c;程序發生錯誤&#xff0c;直接終止退出&#xff0c;這樣的報錯對于真正開發應用等太過暴力。第二種方式&#xff0c;就是返回errno&#xff0c;其實&…

Latex公式中矩陣的方括號和圓括號表示方法

一、背景 在使用Latex寫論文時&#xff0c;不可避免的涉及到矩陣公式。有的期刊要求矩陣用方括號&#xff0c;有的期刊要求矩陣用圓括號。因此&#xff0c;特記錄一下Latex源碼在兩種表示方法上的區別&#xff0c;以及數組和方程組的擴展。 二、矩陣的方括號表示 首先所有的…

OpenGLES:glReadPixels()獲取相機GLSurfaceView預覽數據并保存

Android現行的Camera API2機制可以通過onImageAvailable(ImageReader reader)回調從底層獲取到Jpeg、Yuv和Raw三種格式的Image&#xff0c;然后通過保存Image實現拍照功能&#xff0c;但是卻并沒有Api能直接在上層直接拿到實時預覽的數據。 Android Camera預覽的實現是上層下發…

Java學習筆記——instanceof關鍵字

instanceof關鍵字&#xff1a; 作用&#xff1a;保證對象向下轉型的安全性在對象向下轉型前判斷某一對象實例是否屬于某個類 判斷時&#xff0c;如果對象是null&#xff0c;則 instanceof 判斷結果為 false

Spring Boot 整合kafka:生產者ack機制和消費者AckMode消費模式、手動提交ACK

目錄 生產者ack機制消費者ack模式手動提交ACK 生產者ack機制 Kafka 生產者的 ACK 機制指的是生產者在發送消息后&#xff0c;對消息副本的確認機制。ACK 機制可以幫助生產者確保消息被成功寫入 Kafka 集群中的多個副本&#xff0c;并在需要時獲取確認信息。 Kafka 提供了三種…

ei源刊和ei會議的幾個區別

1、含義不同 公開發表論文&#xff0c;可以在期刊上刊登&#xff0c;也可以在會議上宣讀。ei源刊對應的是期刊&#xff0c;是指被ei檢索收錄的工程類的期刊。ei會議對應的是會議&#xff0c;是指被ei檢索收錄的會議。 2、檢索類型不同 期刊和會議都能被ei檢索&#xff0c;但…

Tr0ll

信息收集 探測主機存活信息&#xff1a; nmap -sn --min-rate 10000 192.168.182.0/24Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-14 15:45 CST Nmap scan report for 192.168.182.1 Host is up (0.00026s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap…

qt 雙緩沖機制

在圖形編程中&#xff0c;雙緩沖機制是一種常用的技術&#xff0c;用于減少圖形繪制時的閃爍和抖動。它的基本思想是將圖形繪制到一個后臺緩沖中&#xff0c;然后一次性將后臺緩沖的內容顯示到屏幕上。 在 Qt 中&#xff0c;雙緩沖機制可以通過QPainter的begin()和end()方法來實…