【Java微服務組件】分布式協調P1-數據共享中心簡單設計與實現

歡迎來到啾啾的博客🐱。
記錄學習點滴。分享工作思考和實用技巧,偶爾也分享一些雜談💬。
歡迎評論交流,感謝您的閱讀😄。

目錄

  • 引言
  • 設計一個共享數據中心
    • 選擇數據模型
      • 鍵值對設計
    • 數據可靠性設計
      • 持久化
        • 快照 (Snapshotting/RDB-like)
        • 操作日志(Write-Ahead Log -WAL /Append-Only File AOF-like)
        • 快照與日志融合使用(Snapshot + WAL)
      • 一致性
    • 監控數據狀態
      • 狀態通知機制
      • 集群間數據同步
  • 實現一個共享數據中心

引言

分布式微服務架構中,有一些常見的分布式協調問題,如配置管理、命名服務(共享服務實例的地址信息)、分布式鎖、集群管理、Master選舉等。
這些協調問題可以簡單歸納為數據的共享與狀態監控,我們需要解決這些問題來保障架構的可用、性能,同時降低耦合、開放拓展等。

為此,我們的框架需要一個可靠的、一致的、可觀察的共享數據存儲。
ZooKeeper就是這樣的一個存在。

不過在深入了解ZooKeeper之前,我們先就這些特性來簡單實現一下共享數據存儲。
在上篇注冊中心中,我們設計了主從讀寫分離、數據安全結構與讀寫鎖,實現了簡單的注冊中心。其數據共享主要是集群內數據共享。
本篇共享數據存儲是面對整個架構的所有服務的。

設計一個共享數據中心

首先,我們需要考慮數據以什么樣的形式、什么樣的結構存在。
是僅存在于內存還是需要持久化,持久化是否需要進行事務特性實現以確保一致性……等等。

需要考慮的問題很多,首先,讓我們來進行數據結構的選型、決定數據以什么樣的形式存在。

選擇數據模型

數據模型可以簡單分為以下幾種:

數據模型類型說明
鍵值對(Key-Value)這是最簡單和最常見的數據模型。
比如Redis。
key一般為String類型、value的值靈活多變。

這樣的結構易于實現,但查詢能力優先,只能按鍵查找。
文檔型(Document-Oriented)比如MongoDB。
數據以類似JSON或BSON的文檔形式存儲。

適合存儲半結構化數據(不完全符合嚴格的表結構,但包含一些內部組織標記或元數據的數據),結構靈活、具備層次結構、可解析、字描述。

這樣的結構易于解析和傳輸大量數據,但查詢更難。
列式(Columnar)比如HBase或Cassandra。

適合大規模數據分析。

關系型(Relational)比如MySQL。

適合數據間有復雜關系,有事務需求時的選擇。

實現一個完整的關系型數據庫非常困難。
圖(Graph)比如 Neo4j。

適合數據之前關系非常重要復雜的情況。

顯然實現起來更復雜。

對于一個共享數據存儲中心,它需要能存儲各式各樣的值,且用戶服務可以很快速地獲取共享的值。
那么選擇“鍵值對”作為數據存儲的結構是個很合理的選擇。

鍵值對設計

鍵值對應該如何設計呢?
了解基本數據結構的我們很容易想到使用Hash計算的方式映射,用數組進行存儲。
在Java中簡單來說就是使用HashMap。共享數據存儲中心需要考慮多個用戶服務同時調用的情況,因此,我們的結構應當是線程安全的,即使用ConcurrentHashMap,或者對普通的HashMap使用鎖,如讀寫鎖。

數據可靠性設計

數據是僅內存(In-Memory Only)還是需要持久化(Persistence)呢?

  • 僅內存
    僅內存性能高,但共享數據存儲中心一旦重啟或崩潰,數據容易丟失。

  • 持久化
    持久化雖然更可靠,但是復雜度顯著提升。
    需要以什么形式持久化?
    持久化是否需要事務來確保一致性(內存和持久化存儲的一致性、并發訪問的一致性)?
    以什么樣的方式持久化?
    等,有很多需要考慮的地方,越可靠越復雜。
    因此必須在復雜度和可靠中做取舍,這往往也取決于對數據丟失的容忍度和數據的重要性。

持久化

持久化設計需要考慮持久化方式(Durability)與崩潰恢復(Crash Recovery)。從內存到磁盤的持久化有3種方式。

快照 (Snapshotting/RDB-like)

定期將內存中的整個數據結構完整地序列化到磁盤(分布式的“狀態轉移”)。
快照時,系統會創建一個當前內存數據結構的副本,對副本進行序列化操作。快照文件通常是二進制格式,考慮存儲效率和加載速度。例如Redis的RDB文件。
快照操作后臺線程執行,對正在運行的操作影響小。
但如果兩次快照之間發生故障,快照之間的部分數據會丟失。且快照過程比較耗時且消耗I/O,尤其數據量大時。

快照可以設置的觸發策略如下:

  • 基于時間的策略 (Time-based)
  • 基于操作數量\數據變化的策略(Change-based)
  • 基于日志文件大小(Log-size-based - 通常與WAL/AOF結合
  • 手動觸發 (Manual Trigger)
  • 系統關閉 (On Shutdown)
操作日志(Write-Ahead Log -WAL /Append-Only File AOF-like)

數據操作日志 是一種按時間順序記錄所有對數據產生修改的“操作”的日志文件。 它記錄的是如何達到當前數據狀態的過程,而不是數據狀態本身(分布式的“操作轉移”)。
數據需要先記錄到日志,然后再更新到內存中。

  • 追加寫入(Append-Only)
    新的操作日志條目總是被添加到日志文件的末尾。順序寫入的方式通常比隨機寫入磁盤效率高。
  • 預寫(Write-Ahead)
    在數據真正被修改到內存中的持久化結構(在數據被刷到數據文件)之前,描述該修改的日志條目必須首先被安全地寫入到持久化的操作日志中并刷盤 (fsync)。

日志條目內容一般包含:操作類型、操作參數、事務信息、時間戳或其他序列號(LSN)。

操作日志恢復數據時,會從日志頭開始讀取文件并按順序重新執行,從而在內存中重建數據狀態。

這種方式數據持久性更好,崩潰時只丟失最后未刷盤的少量操作。缺點是恢復時需要重放所有日志,可能較慢,日志文件會不斷增長,需要定期進行壓縮或與快照結合。

快照與日志融合使用(Snapshot + WAL)

快照只能恢復數據的最終狀態,且兩次快照之間數據會丟失,雖然效率高,但是數據量大時I/O消耗大。
而日志模式雖然不會丟失太多數據,但是重放日志效率更低,日志數量多時恢復效率會顯著下降。
因此,生產級方案往往是快照與日志融合。

  • 融合方案
    定期做快照,同時記錄快照之后的WAL。恢復時先加載最近的快照,再重放后續的WAL。

比如Redis和MySQL(InnoDB)就是用的融合方案。

  • MySQL
    MySQL的InnoDB設計有重做日志Redo Log,在數據被刷入磁盤之前,數據修改的記錄(Redo Log)必須先被寫入到Redo Log Buffer,并從Buffer刷到磁盤的Redo Log文件中。
    在InnoDB中,Redo Log有一個概念為檢查點(Checkpoint),它記錄一個LSN(Log Sequence Number)。
    數據恢復時不需要重放所有的Redo Log,只需要從最近的Checkpoint開始重放。
    Checkpoint 確保了其記錄點之前的所有臟頁都已刷盤,因此Checkpoint之前的Redo Log文件都可以被覆蓋。。這個機制解決了日志文件不斷增長的問題。

  • Redis
    Redis有兩種持久化方案:RDB與AOF,且可以同時開啟。
    RDB對應快照,AOF對應操作日志。
    Redis應對操作日志不斷增大的機制是 AOF重寫(AOF Rewrite)。AOF重寫在不中斷服務的情況下,創建一個新的更小的AOF文件,新文件包含達到當前數據集狀態所需的最小命令集。即去掉數據狀態變更過程,只保留最新數據狀態。
    Redis從4.0開始RDB-AOF混合持久化。AOF重寫時,新的AOF文件可以配置為以RDB格式開頭,后跟增量的AOF命令(aof-use-rdb-preamble yes)。
    新的 AOF 文件首先包含一個 RDB 快照部分(記錄重寫開始時的數據狀態),然后是重寫期間發生的增量寫命令。
    這使得恢復時可以先加載 RDB 部分,然后只重放少量的 AOF 命令,大大加快了恢復速度,同時保留了 AOF 的高持久性。

比如我們計劃使用ConcurrentHashMap,那么快照時就需要將這個ConcurrentHashMap序列化。
常見的方式是快照方式是非阻塞式的后臺復制——寫時復制(Copy-on-Write COW)。快照策略選擇按數據量進行觸發。
快照與日志融合方案使用MySQL的更為簡單。

一致性

在通過持久化的方式來保證數據的可靠后,我們的共享數據存儲中心有了一定的可用性保障。這時我們需要開始考慮數據的一致性。
但一個完整的ACID事務系統是極其困難的,設計到并發控制、恢復管理、日志管理等多個復雜的子系統。
因此,簡單實現可以暫不追求完整的ACID事務。
僅先考慮基本的一致性,如簡單原子性,批量操作提交視為一個整體。集群數據同步的一致性。

監控數據狀態

狀態通知機制

當數據發生變更時,我們需要一個機制能可靠地通知所有相關節點,并保證它們獲取到的最新的、一致性的數據。
我們很容易想到觀察者模式。當數據被修改時,告訴共享數據的訂閱者數據已更改。
因此通知機制的前提是需要有一張注冊表。
在上篇的注冊中心我們已經知道,注冊表可以通過心跳來維持其有效性。
但還有另一種做法,就是ZooKeeper的的Watcher機制。
每次修改數據通知完訂閱者后,刪除其在注冊表中的信息,每次getData()時再重新注冊。即一次性觸發 (One-time Trigger)。
這樣可以精簡設計,省去心跳機制。

集群間數據同步

簡單追求集群數據同步的強一致性,共享數據做讀寫分離處理提升性能。

實現一個共享數據中心

簡單實現兩個model類,用于存儲在內存的共享數據對象ShareData

  
import lombok.Data;/*** 內存中的共享數據** @author crayon* @version 1.0* @date 2025/5/14*/
@Data
public class ShareData {private String id;/*** 數據更新時間戳*/private Long lsn;/*** 數據*/private Object data;/*** 數據版本*/private int version;public ShareData(String id1, String initialValueForKey1, int version) {this.id = id1;this.data = initialValueForKey1;this.version = version;this.lsn = System.currentTimeMillis();}public void incrementVersion() {this.version++;}
}

與用于序列化的PersistenceData

package com.crayon.datashare.model;import lombok.Data;import java.io.Serializable;/*** 內存共享數據的序列化對象** @author crayon* @version 1.0* @date 2025/5/15*/
@Data
public class PersistenceData implements Serializable {/*** 數據序列化時間*/private Long serialDateTime;/*** 操作類型*/private String operaType;/*** 數據key*/private String key;/*** 共享數據*/private ShareData shareData;public PersistenceData(Builder builder) {this.key = builder.key;this.shareData = builder.shareData;this.operaType = builder.operaType;this.serialDateTime = System.currentTimeMillis();}@Overridepublic String toString() {return "PersistenceData{" +"serialDateTime=" + serialDateTime +", operaType='" + operaType + '\'' +", key='" + key + '\'' +", shareData=" + shareData +'}';}// 建造者模式public static class Builder {private String key;private ShareData shareData;private String operaType;public Builder key(String key) {this.key = key;return this;}public Builder shareData(ShareData shareData) {this.shareData = shareData;return this;}public Builder operaType(String operaType) {this.operaType = operaType;return this;}public PersistenceData build() {return new PersistenceData(this);}}}

實現最重要的數據共享中心服務端


import com.crayon.datashare.model.ShareData;import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 簡單的共享數據存儲中心** <p>* 功能如下:* <p>* API-獲取數據* API-存儲數據* API-注冊信息* <p>* 數據量到了應規模時進行序列化快照,存儲數據時日志追加** @author crayon* @version 1.0* @date 2025/5/14*/
public class ShareDataServer {/*** 使用ConcurrentHashMap存儲共享數據* <p>* keyName -> ShareData* </p>* <p>* 集群做讀寫分離設計,Leader-Follower 模型 。* master寫同步到slave,slave節點讀,負載均衡采用隨機策略。* </p>* <p>* 數據容量暫不設置上限與對應清理機制。* </p>* <p>* 沒有選舉機制,也沒有邏輯時鐘* </p>*/private static ConcurrentHashMap<String, ShareData> shareDataMaster = new ConcurrentHashMap<>();private static ConcurrentHashMap<String, ShareData> shareDataSlave1 = new ConcurrentHashMap<>();private static ConcurrentHashMap<String, ShareData> shareDataSlave2 = new ConcurrentHashMap<>();/*** 用于隨機獲取從節點*/private static Random random = new Random();/*** <p>* keyName -> ReentrantReadWriteLock* </p>* 線程安全方案一:* <p>* 使用讀寫鎖控制共享數據安全。* ConcurrentHashMap 操作數據是安全的,但是共享數據內容是可變的(Mutable)。* 當需要組合多個ConcurrentHashMap操作時,其是不安全的。* 其他線程可能在ConcurrentHashMap多個操作之間,對可變對象進行更改。* <p>* 因此需要讀寫鎖來保證寫入時候數據安全。* 在共享數據中,因為原子操作為:寫數據+日志追加,所以更需要使用鎖來控制。* <p>* 在分布式系統中,共享數據中心本身常被作為分布式鎖使用。* <p>* 如果不是需要WAL,其實可以通過不可變對象(Immutable Objects)來消除數據共享來簡化并發問題* </p>*/private static ConcurrentHashMap<String, ReentrantReadWriteLock> readWriteLocks = new ConcurrentHashMap<>();/*** 訂閱者集合* <p>* 采取一次性觸發機制(One-time Trigger),省去心跳檢測的麻煩* 每次通知訂閱者時,會從集合中移除訂閱者,訂閱者每次需要重新注冊* 比如在調用get時重新注冊* <p>* 訂閱者也可以封裝成一個對象,這里簡單一點=ip:port* <p>* keyName -> Set<ip:port>* 使用線程安全的Set,如 ConcurrentHashMap.newKeySet()* </p>*/private static ConcurrentHashMap<String, Set<String>> subscribers = new ConcurrentHashMap<>();/*** Watcher*/private static Notifier notifier = new Notifier();/*** 序列化服務* 日志操作,數據恢復(暫無)等*/private static SerializableService serializableService = new SerializableService();/*** 獲取共享數據* 采取一次性觸發機制(One-time Trigger)由Server完成** @param key* @param ipPort (可選) 客戶端標識,用于重新注冊Watcher* @param watch  (可選) 是否要設置Watcher* @return*/public ShareData get(String key, String ipPort, boolean watch) {ReentrantReadWriteLock readWriteLock = readWriteLocks.computeIfAbsent(key, k -> new ReentrantReadWriteLock());readWriteLock.readLock().lock();try {ConcurrentHashMap<String, ShareData> readNode = getReadNode();ShareData shareData = readNode.get(key);if (watch && null != ipPort && !"".equals(ipPort) && null != shareData) {register(key, ipPort);}return shareData;} finally {readWriteLock.readLock().unlock();}}/*** 注冊訂閱者** @param key* @param ipPort*/public void register(String key, String ipPort) {// 使用ConcurrentHashMap.newKeySet() 創建一個線程安全的Setsubscribers.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()).add(ipPort);}/*** 添加共享數據* 組合 日志追加 + 添加 + 集群同步* <p>* 原子操作設計:* 一般這種帶集群同步的標準方案是共識算法(Consensus Algorithm)。太復雜了,搞不來。** </p>** @param key* @param value*/public boolean set(String key, ShareData value) {ReentrantReadWriteLock readWriteLock = readWriteLocks.computeIfAbsent(key, k -> new ReentrantReadWriteLock());readWriteLock.writeLock().lock();try {// 1、寫入日志 WALboolean logSuccess = serializableService.appendLog(OperaTypeEnum.SET.getType(), key, value);if (!logSuccess) {return false;}// 2、寫入內存MastershareDataMaster.put(key, value);/*** 3、集群同步* 簡單模擬,沒有處理網絡失敗、異步、其他復雜ack機制等*/syncToSlave(key, value);// 4、通知訂閱者,從注冊表移除// 獲取并移除,實現一次性觸發Set<String> currentSubscribers = subscribers.remove(key);if (currentSubscribers != null && !currentSubscribers.isEmpty()) {for (String subscriberIpPort : currentSubscribers) {// 實際應用中,這里會通過網絡連接向客戶端發送通知notifier.notify(subscriberIpPort, key, OperaTypeEnum.CHANGE.getType());}}} catch (Exception e) {// 實際生產需要回滾等事務操作、日志記錄等return false;} finally {readWriteLock.writeLock().unlock();}return true;}/*** 集群同步* <p>* 只在set操作中調用** @param key* @param value*/private void syncToSlave(String key, ShareData value) {shareDataSlave1.put(key, value); // 模擬同步到slave1shareDataSlave2.put(key, value); // 模擬同步到slave2}/*** 50%概率隨機取節點** @return*/private ConcurrentHashMap<String, ShareData> getReadNode() {return random.nextBoolean() ? shareDataSlave1 : shareDataSlave2;}}

封裝操作類型枚舉


/*** 操作類型枚舉*/
public enum OperaTypeEnum {GET("GET"),SET("SET"),CHANGE("CHANGE");private String type;OperaTypeEnum(String type) {this.type = type;}public String getType() {return type;}}

實現序列化方法


import com.crayon.datashare.model.PersistenceData;
import com.crayon.datashare.model.ShareData;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;/*** 序列化服務** <p>* 序列化:采用快照+日志方式* 按數據量策略進行快照* <p>* 文件內容:類Redis融合方案* 快照內容+日志內容。文件前面是RDB格式,后面是AOF格式* <p>* RDB格式:* AOF格式* <p>* 文件大小處理:采用保留數據最終狀態的壓縮方案** <p>* 恢復機制:** @author crayon* @version 1.0* @date 2025/5/15*/
public class SerializableService {/*** 假設的日志文件名*/private static final String MASTER_LOG_FILE = System.getProperty("user.dir") + "/wal/master_wal.log";/*** 日志追加* <p>* 簡化的日志格式,實際應該至少有操作類型、時間戳、序列號、狀態碼,* 數據庫的話會有數據庫的一些信息,如數據庫名字、server id等* </p>* 生產日志會有壓縮、刷盤等操作,這里簡化了*/public boolean appendLog(String operaType, String key, ShareData value) {PersistenceData persistenceData = new PersistenceData.Builder().operaType(operaType).key(key).shareData(value).build();try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(MASTER_LOG_FILE, true)))) {// out.flush(); // 可以考慮更頻繁的flush或根據策略fsyncout.println(persistenceData.toString());return true;} catch (IOException e) {System.err.println("Error writing to WAL: " + e.getMessage());return false;}}
}

封裝一個用于通知的監視者Watcher


/*** @author crayon* @version 1.0* @date 2025/5/16*/
public class Notifier {/*** 通知訂閱者** @param subscriberIpPort* @param key* @param operaType*/public void notify(String subscriberIpPort, String key, String operaType) {// 調用訂閱者的接口,讓訂閱者進行相應的處理}
}

封裝一個客戶端

/*** 簡單模擬客戶端* <p>*     訂閱和獲取、更新數據等操作* </p>* * @author crayon* @version 1.0* @date 2025/5/16*/
public class SubscriberClient {private String ipPort;private ShareDataServer shareDataServer = new ShareDataServer();public SubscriberClient(String ipPort) {this.ipPort = ipPort;}public void subscribe(String key) {shareDataServer.register(key, ipPort);}public ShareData get(String key, String ipPort) {return shareDataServer.get(key, ipPort, true);}}

test


import com.crayon.datashare.client.SubscriberClient;
import com.crayon.datashare.model.ShareData;
import com.crayon.datashare.server.ShareDataServer;/*** 簡單數據共享中心演示** @author crayon* @version 1.0* @date 2025/5/14*/
public class Demo {public static void main(String[] args) {ShareDataServer shareDataServer = new ShareDataServer();// 模擬客戶端1注冊對 key1 的訂閱SubscriberClient subscriberClient8080 = new SubscriberClient("127.0.0.1:8080");subscriberClient8080.subscribe("key1");SubscriberClient subscriberClient8081 = new SubscriberClient("127.0.0.1:8081");subscriberClient8081.subscribe("key1");// 模擬客戶端2注冊對 key2 的訂閱subscriberClient8081.subscribe("key2");System.out.println("\n Setting data for key1...");shareDataServer.set("key1", new ShareData("id1", "Initial Value for key1", 1));System.out.println("\n Getting data for key1 by client1 (will re-register watcher)...");ShareData data1 = shareDataServer.get("key1", "client1_ip:port", true);System.out.println("Client1 got: " + data1);System.out.println("\n Setting data for key1 again (client1 should be notified)...");shareDataServer.set("key1", new ShareData("id1", "Updated Value for key1", 2));System.out.println("\n Setting data for key2...");shareDataServer.set("key2", new ShareData("id2", "Value for key2", 1));System.out.println("\n Client2 getting data for key1 (not subscribed initially, but sets a watch now)...");ShareData data1_by_client2 = shareDataServer.get("key1", "client2_ip:port", true);System.out.println("Client2 got (for key1): " + data1_by_client2);System.out.println("\n Simulating a read from a random slave for key1:");ShareData slaveData = shareDataServer.get("key1", null, false); // No re-registerSystem.out.println("Read from slave for key1: " + slaveData);}}

結果展示如下
在這里插入圖片描述

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

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

相關文章

在SpringBoot項目中,使用單元測試@Test

1.引入依賴 <!--單元測試Test的依賴--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>3.2.1</version> </dependency> 2.在src/test/java目錄…

在Java中,將Object對象轉換為具體實體類對象

在Java中&#xff0c;將Object對象轉換為具體實體類對象可以通過以下幾種方法實現&#xff1a; 1?.使用instanceof關鍵字進行類型檢查和轉換?&#xff1a; 首先&#xff0c;使用instanceof關鍵字檢查Object對象是否為目標實體類的類型。 如果是&#xff0c;則進行強制類型…

JAVA學習-練習試用Java實現“音頻文件的讀取與寫入 :使用Java音頻庫處理音頻數據”

問題&#xff1a; java語言編輯&#xff0c;實現音頻文件的讀取與寫入 &#xff1a;使用Java音頻庫處理音頻數據。 解答思路&#xff1a; 在Java中處理音頻文件通常需要使用第三方庫&#xff0c;例如javax.sound.sampled包&#xff0c;它提供了處理音頻文件的基本功能。以下是一…

Flink架構概覽,Flink DataStream API 的使用,FlinkCDC的使用

一、Flink與其他組件的協同 Flink 是一個分布式、高性能、始終可用、準確一次&#xff08;Exactly-Once&#xff09;語義的流處理引擎&#xff0c;廣泛應用于大數據實時處理場景中。它與 Hadoop 生態系統中的組件可以深度集成&#xff0c;形成完整的大數據處理鏈路。下面我們從…

linux 查看java的安裝路徑

一、驗證Java安裝狀態 java -version正常安裝會顯示版本信息&#xff1a; openjdk version "1.8.0_65" OpenJDK Runtime Environment (build 1.8.0_65-b17) OpenJDK 64-Bit Server VM (build 25.65-b01, mixed mode)二、檢查環境變量配置 若已配置JAVA_HOME&#…

2025-5-21 個人筆記篇matlab小筆記和clang基礎使用(簡單記錄)

個人筆記篇 再不記錄就找不到了&#xff0c;之前學的一點基礎&#xff0c;看看就行,請不要提問,因為很久了>_<(至少我看來是這樣的) matlab小筆記 % 開繪制(新建) figure % 設置繪制標題 title(標題); % 設置繪制的X軸Lable xlabel(x); % 設置繪制的y軸Lable ylabel(cos…

前端JavaScript-嵌套事件

點擊 如果在多層嵌套中&#xff0c;對每層都設置事件監視器&#xff0c;試試看 <!DOCTYPE html> <html lang"cn"> <body><div id"container"><button>點我&#xff01;</button></div><pre id"output…

網感驅動下開源AI大模型AI智能名片S2B2C商城小程序源碼的實踐路徑研究

摘要&#xff1a;在數字化浪潮中&#xff0c;網感已成為內容創作者與商業運營者必備的核心能力。本文以開源AI大模型、AI智能名片及S2B2C商城小程序源碼為技術載體&#xff0c;通過解析網感培養與用戶需求洞察的內在關聯&#xff0c;提出"數據驅動-場景適配-價值重構"…

AG-UI:重構AI代理與前端交互的下一代協議標準

目錄 技術演進背景與核心價值協議架構與技術原理深度解析核心功能與標準化事件體系典型應用場景與實戰案例開發者生態與集成指南行業影響與未來展望1. 技術演進背景與核心價值 1.1 AI交互的三大痛點 當前AI應用生態面臨三大核心挑戰: 交互碎片化:LangGraph、CrewAI等框架各…

游戲引擎學習第301天:使用精靈邊界進行排序

回顧并為今天的內容做準備 昨天&#xff0c;我們解決了一些關于排序的問題&#xff0c;這對我們清理長期存在的Z軸排序問題很有幫助。這個問題我們一直想在開始常規游戲代碼之前解決。雖然不確定是否完全解決了問題&#xff0c;但我們提出了一個看起來合理的排序標準。 有兩點…

Ajax快速入門教程

輸入java時&#xff0c;頁面并沒有刷新但是下面自動聯想出了跟java有關的東西&#xff0c;像這種就叫異步交互 它不會妨礙你的輸入&#xff0c;同時還能夠同步進行對于java相關聯想詞的推送 發送異步請求需要借助工具axios 引入axios&#xff0c;可以直接在scripts中引入 get和…

Anti Spy安卓版:智能防護,守護手機安全

Anti Spy安卓版是一款專為安卓設備設計的智能防護應用&#xff0c;旨在幫助用戶實時防護手機安全&#xff0c;抵御間諜軟件、惡意軟件和其他潛在威脅。它基于人工智能和啟發式搜索方法的引擎&#xff0c;能夠檢測并阻止已知和未知的間諜軟件、后門程序、賬單欺詐、短信欺詐、電…

超低延遲音視頻直播技術的未來發展與創新

引言 音視頻直播技術正在深刻改變著我們的生活和工作方式&#xff0c;尤其是在教育、醫療、安防、娛樂等行業。無論是全球性的體育賽事、遠程醫療、在線教育&#xff0c;還是智慧安防、智能家居等應用場景&#xff0c;都離不開音視頻技術的支持。為了應對越來越高的需求&#x…

系統架構設計(十二):統一過程模型(RUP)

簡介 RUP 是由 IBM Rational 公司提出的一種 面向對象的軟件工程過程模型&#xff0c;以 UML 為建模語言&#xff0c;是一種 以用例為驅動、以架構為中心、迭代式、增量開發的過程模型。 三大特征 特征說明以用例為驅動&#xff08;Use Case Driven&#xff09;需求分析和測…

海康相機連接測試-極簡版

文章目錄 1、下載客戶端 1、下載客戶端 海康機器人官網下載軟件 軟件下載地址 先下載客戶端測試連接 按照你的相機的類型選擇客戶端 安裝完畢后&#xff0c;確保USB線插的是3.0的端口 軟件會自動識別相機型號 在上方有播放按鈕&#xff0c;可以采集圖像信息顯示

Linux 磁盤擴容實戰案例:從問題發現到完美解決

Linux 磁盤擴容實戰案例&#xff1a;從問題發現到完美解決 案例背景 某企業服務器根目錄 (/) 空間不足&#xff0c;運維人員通過 df -h 發現 /dev/vda1 分區已 100% 占滿&#xff08;99G 已用&#xff09;。檢查發現物理磁盤 /dev/vda 已擴展至 200G&#xff0c;但分區和文件…

深入解析FramePack:高效視頻幀打包技術原理與實踐

摘要 本文深入探討FramePack技術在視頻處理領域的核心原理&#xff0c;解析其在不同場景下的應用優勢&#xff0c;并通過OpenCV代碼示例演示具體實現方法&#xff0c;為開發者提供可落地的技術解決方案。 目錄 1. FramePack技術背景 2. 核心工作原理剖析 3. 典型應用場景 …

RVTools 官網遭入侵,被用于分發攜帶 Bumblebee 惡意軟件的篡改安裝包

VMware 環境報告工具 RVTools 的官方網站遭黑客入侵&#xff0c;其安裝程序被植入惡意代碼。安全研究人員 Aidan Leon 發現&#xff0c;從該網站下載的受感染安裝程序會側加載一個惡意 DLL 文件&#xff0c;經確認是已知的 Bumblebee 惡意軟件加載器。 官方回應與風險提示 RV…

mysql故障排查與環境優化

一、mysql運行原理 mysql的運行分為三層 客戶端和連接服務 核心服務功能&#xff08;sql接口、緩存的查詢、sql的分析和優化以及部分內置函數的執行等。&#xff09; 存儲引擎層&#xff08;負責mysql中數據的存儲和提取。&#xff09; 二、示例 1、實驗環…

Codex與LangChain結合的智能代理架構:重塑軟件開發的未來

??「炎碼工坊」技術彈藥已裝填! 點擊關注 → 解鎖工業級干貨【工具實測|項目避坑|源碼燃燒指南】 引言:當代碼生成遇見智能決策 想象以下場景: 凌晨三點:你需要緊急修復一個遺留系統的內存泄漏漏洞,但代碼注釋缺失且邏輯復雜; 產品經理需求變更:要求在24小時內將現有…