MongoDB 分片集群實戰

来源:https://www.cnblogs.com/wefeng/archive/2019/10/16/11683328.html
-Advertisement-
Play Games

背景 在如今的互聯網環境下,海量數據已隨處可見並且還在不斷增長,對於如何存儲處理海量數據,比較常見的方法有兩種: 垂直擴展:通過增加單台伺服器的配置,例如使用更強悍的 CPU、更大的記憶體、更大容量的磁碟,此種方法雖然成本很高,但是實現比較簡單,維護起來也比較方便。 水平擴展:通過使用更多配置一般的服 ...


背景

在如今的互聯網環境下,海量數據已隨處可見並且還在不斷增長,對於如何存儲處理海量數據,比較常見的方法有兩種:

  • 垂直擴展:通過增加單台伺服器的配置,例如使用更強悍的 CPU、更大的記憶體、更大容量的磁碟,此種方法雖然成本很高,但是實現比較簡單,維護起來也比較方便。
  • 水平擴展:通過使用更多配置一般的伺服器來共同承擔工作負載,此種方法很靈活,可以根據工作負載的大小動態增減伺服器的數量,但是實現比較複雜,得有專門的人員來運維。

 

Databases for MongoDB

試用 IBM Cloud 上提供的 MongoDB 資料庫服務。

Hyper Protect DBaaS for MongoDB

試用 IBM Cloud 上提供的更加安全的 MongoDB 企業服務,您可以通過標準化的界面管理 MongoDB。

MongoDB 支持通過分片技術從而進行水平擴展,用以支撐海量數據集和高吞吐量的操作。如果數據集不夠大,還是建議您使用 MongoDB 副本集,因為分片需要處理更多的技術細節,所以在分片環境下其性能可能始終沒有副本集性能強。本文通過介紹如何搭建 MongoDB 分片集群以及及一些相關核心概念,可以幫您快速理解 MongoDB 是如何通過分片技術來處理海量數據的。

MongoDB 分片集群組件

在搭建 MongoDB 分片集群環境之前,我們先瞭解一下分片集群包含哪些組件。一個 MongoDB 分片集群(參考官方文檔 Sharded Cluster)由以下三個組件構成,缺一不可:

  • shard:每個分片是整體數據的一部分子集。每個分片都可以部署為副本集。強烈建議在生產環境下將分片部署為副本集且最少部署 2 個分片。
  • mongos:充當查詢路由器,提供客戶端應用程式和分片集群之間的介面。應用程式直接連接 mongos 即可,可以部署一個或多個。
  • config servers:配置伺服器存儲集群的元數據和配置(包括許可權認證相關)。從 MongoDB 3.4 開始,必須將配置伺服器部署為副本集(CSRS,全稱是 Config Servers Replica Set)。

MongoDB 分片集群的架構圖如下所示,該圖片引用自 MongoDB 官網文檔

分片集群環境搭建

接下來我們開始準備部署一個具有 2 個 shard(3 節點副本集)加 1 個 config server(3 節點副本集)加 2 個 mongos 的分片集群。

搭建環境

搭建環境如下:

虛擬機一

虛擬機二

虛擬機三

搭建步驟

MongoDB 分片集群搭建並不複雜,以下只描述關鍵步驟。

步驟 1. 配置文件

下載官方 MongoDB 4.0.12 版本的二進位包,按照如下步驟修改配置文件。

  1. 每台虛擬機的 27018 實例即分片 1 的配置文件需要配置以下關鍵參數:
    1 2 replSet = rep_shard1 # 副本集名稱 shardsvr = true # 3.4 版本之後必須明確指定該參數,3.4 版本之前該參數其實無實用性
  2. 每台虛擬機的 27019 實例即分片 2 的配置文件需要配置以下關鍵參數:
    1 2 replSet = rep_shard2 shardsvr = true
  3. 每台虛擬機的 20000 實例即配置伺服器的配置文件需要配置以下關鍵參數:
    1 2 replSet = rep_confsvr configsvr = true
  4. 虛擬機一和虛擬機二的 27017 實例即 mongos 路由的配置文件需要配置以下關鍵參數:
    1 configdb = rep_confsvr/10.0.4.6:20000,10.0.4.7:20000,10.0.4.8:20000 # 指定配置伺服器

步驟 2. 建立相關目錄並啟動副本集實例

關於如何配置副本集可以參考 MongoDB 副本集實戰,然後按照下列順序執行。

  1. 啟動每台虛擬機的 27018 實例即分片 1 並配置副本集。
  2. 啟動每台虛擬機的 27019 實例即分片 2 並配置副本集。
  3. 啟動每台虛擬機的 20000 實例即配置伺服器並配置副本集。
  4. 啟動虛擬機一和虛擬機二的 27017 實例即 mongos 路由,註意這裡是通過 mongos 啟動非 mongod。

步驟 3. 配置分片集群

登錄虛擬機一的 mongos 終端,和普通登錄 MongoDB 一樣,只需將埠改成 mongos 的埠 27017 即可,運行以下命令將 rep_shard1 和 rep_shard2 分片加入集群,到此一個分片環境已經搭建完成。

1 2 sh.addShard("rep_shard1/10.0.4.6:27018,10.0.4.7:27018,10.0.4.8:27018") sh.addShard("rep_shard2/10.0.4.6:27019,10.0.4.7:27019,10.0.4.8:27019")

步驟 4. 驗證分片集群是否搭建成功

通過運行 sh.status() 命令可以查看分片相關信息,如有以下輸出,說明分片集群搭建成功。

1 2 3 shards: {  "_id" : "rep_shard1",  "host" : "rep_shard1/10.0.4.6:27018,10.0.4.7:27018,10.0.4.8:27018",  "state" : 1 } {  "_id" : "rep_shard2",  "host" : "rep_shard2/10.0.4.6:27019,10.0.4.7:27019,10.0.4.8:27019",  "state" : 1 }

整個分片集群搭建成功的關鍵點為各個角色的配置文件需要配置正確,副本集的配置不能有誤,如果說需要配置許可權認證相關,最好在開始規劃集群的時候就定下來。在生產環境下,可以一臺伺服器掛載多個硬碟,每個硬碟對應一個分片實例,這樣可以將資源最大化利用,此種搭建方法需要註意 MongoDB 實例記憶體的限制。

分片集群操作的相關概念

為了更好地理解 MongoDB 分片集群的運行原理,需要對以下核心概念有所瞭解。

Shard Key(分片鍵)

MongoDB 通過定義 shared key(分片鍵)從而對整個集合進行分片,分片鍵的好壞直接影響到整個集群的性能。另外需要註意的是,一個集合只有且只能有一個分片鍵,一旦分片鍵確定好之後就不能更改。分片鍵分為以下兩種類型:

  • 基於 Hashed 的分片:MongoDB 會計算分片鍵欄位值的哈希值,用以確定該文檔存於哪個 chunk(參見下文 “Chunk(塊)“的介紹),從而達到將集合分攤到不同的 chunk。此種類型能夠使得數據整體分佈比較均勻,對於等值查詢效率很高,但是對於範圍查詢效率就比較低,因為可能要掃描所有的分片才能獲取到數據。
  • 基於 Ranged 的分片:MongoDB 會將相似的值放到一個 chunk 中,所以說如果在查詢的時候帶上分片鍵的範圍條件,查詢效率會非常高,因為不需要掃描所有的分片就可以定位到數據。註意,如果片鍵的值為單調遞增或單調遞減,那麼
  • 適合採用該分片策略,因為數據總會寫到一個分片,從而沒有很好地分散 IO。

分片鍵的類型需要根據實際的業務場景決定,例如有張非常大的用戶表,用戶表裡有用戶 ID 欄位,每次查詢的時候都會帶上用戶 ID,如果想對該用戶表進行分片,可以選擇將用戶 ID 欄位作為 shard key,並且分片鍵類型可以使用基於 Hashed 的分片。

Chunk(塊)

chunk(塊)是均衡器遷移數據的最小單元,預設大小為 64MB,取值範圍為 1-1024MB。一個塊只存在於一個分片,每個塊由片鍵特定範圍內的文檔組成,塊的範圍為左閉又開即 [start,end)。一個文檔屬於且只屬於一個塊,當一個塊增加到特定大小的時候,會通過拆分點(split point)被拆分成 2 個較小的塊。在有些情況下,chunk 會持續增長,超過 ChunkSize,官方稱為 jumbo chunk,該塊無法被 MongoDB 拆分,也不能被均衡器(參見下文 “blancer(均衡器)” 的介紹)遷移,故久而久之會導致 chunk 在分片伺服器上分佈不均勻,從而成為性能瓶頸,表現之一為 insert 數據變慢。

Chunk 的拆分

mongos 會記錄每個塊中有多少數據,一旦達到了閾值就會檢查是否需要對其進行拆分,如果確實需要拆分則可以在配置伺服器上更新這個塊的相關元信息。

chunk 的拆分過程如下:

  1. mongos 接收到客戶端發起的寫請求後會檢查當前塊的拆分閾值點。
  2. 如果需要拆分,mongos 則會像分片伺服器發起一個拆分請求。
  3. 分片伺服器會做拆分工作,然後將信息返回 mongos。

註意,相同的片鍵只能保存在相同的塊中,如果一個相同的片鍵過多,則會導致一個塊過大,成為 jumbo chunk,所以具有不同值的片鍵很重要。

Chunk 的遷移

chunk 在以下情況會發生遷移:

  • chunk 數位於 [1,20),閾值為 2。
  • chunk 數位於 [20,80),閾值為 4。
  • chunk 數位於 [80,max),閾值為 8。

chunk 的遷移過程如下,可以參考官方文檔

  1. 均衡器進程發送 moveChunk 命令到源分片。
  2. 源分片使用內部 moveChunk 命令,在遷移過程,對該塊的操作還是會路由到源分片。
  3. 目標分片構建索引。
  4. 目標分片開始進行數據複製。
  5. 複製完成後會同步在遷移過程中該塊的更改。
  6. 同步完成後源分片會連接到配置伺服器,使用塊的新位置更新集群元數據。
  7. 源分片完成元數據更新後,一旦塊上沒有打開的游標,源分片將刪除其文檔副本。

遷移過程可確保一致性,併在平衡期間最大化塊的可用性。

修改 chunk 大小的註意事項

修改 chunk 大小需要註意以下幾點:

  1. chunk 的自動拆分操作僅發生在插入或更新的時候。
  2. 如果減少 chunk size,將會耗費一些時間將原有的 chunk 拆分到新 chunk,並且此操作不可逆。
  3. 如果新增 chunk size,已存在的 chunk 只會等到新的插入或更新操作將其擴充至新的大小。
  4. chunk size 的可調整範圍為 1-1024MB。

Balancer(均衡器)

MongoDB 的 balancer(均衡器)是監視每個分片的 chunk 數的一個後臺進程。當分片上的 chunk 數達到特定遷移閾值時,均衡器會嘗試在分片之間自動遷移塊,使得每個分片的塊的數量達到平衡。分片群集的平衡過程對用戶和應用程式層完全透明,但在執行過程時可能會對性能產生一些影響。

從 MongoDB 3.4 開始,balancer 在配置伺服器副本集(CSRS)的主伺服器上運行,

在 3.4 版本中,當平衡器進程處於活動狀態時,主配置伺服器的的 locks 集合通過修改 _id: "balancer" 文檔會獲取一個 balancer lock,該 balancer lock 不會被釋放,是為了保證只有一個 mongos 實例能夠在分片集群中執行管理任務。從 3.6 版本開始,均衡器不再需要 balancer lock。

均衡器可以動態的開啟和關閉,也可以針對指定的集合開啟和關閉,還可以手動控制均衡器遷移 chunk 的時間,避免在業務高峰期的時候遷移 chunk 從而影響集群性能。以下命令將均衡器的遷移 chunk 時間控制在凌晨 02 點至凌晨 06 點:

1 2 3 4 5 6 use config db.settings.update(    { _id: "balancer" },    { $set: { activeWindow : { start : "02:00", stop : "06:00" } } },    { upsert: true } )

分片集群操作實戰

瞭解了一些基本概念後,我們就可以來做一些實戰操作,假設在 test 庫下有個空集合為 test_shard,註意這裡是個空集合,該集合有年齡欄位,我們將選擇年齡欄位作為分片鍵分別進行範圍分片和哈希分片。

為了觀察效果,提前將 chunk 的大小調整為 1MB,並且所有的操作都在 mongos 節點執行,隨便哪個 mongos 都可以執行。以下為命令和輸出示例:

1 2 3 4 5 6 7 8 9 10 11 use config db.settings.save({_id:"chunksize",value:1}) db.serverStatus().sharding {     "configsvrConnectionString" : "confsvr/10.0.4.6:20000,10.0.4.7:20000,10.0.4.8:20000",     "lastSeenConfigServerOpTime" : {         "ts" : Timestamp(1566895485, 2),         "t" : NumberLong(16)     },     "maxChunkSizeInBytes" : NumberLong(1048576) }

基於 Ranged 的分片操作

基於範圍分片特別適合範圍查找,因為可以直接定位到分片,所以效率很高。

以下為具體操作步驟:

  1. 開啟 test 庫的分片功能。
    1 sh.enableSharding("test")
  2. 選擇集合的分片鍵,此時 MongoDB 會自動為 age 欄位創建索引。
    1 sh.shardCollection("test.test_shard",{"age": 1})
  3. 批量造測試數據。
    1 2 3 use test for (i = 1; i < = 20000; i++) db.test_shard.insert({age:(i%100), name:"user"+i, create_at:new Date()})
  4. 觀察分片效果。以下為命令和部分輸出示例:
    1 2 3 4 5 6 7 8 9 10 11 12 13 sh.status() test.test_shard shard key: { "age" : 1 } unique: false balancing: true chunks:         rep_shard1  2         rep_shard2  3 { "age" : { "$minKey" : 1 } } --<< { "age" : 0 } on : rep_shard1 Timestamp(2, 0) { "age" : 0 } --<< { "age" : 36 } on : rep_shard1 Timestamp(3, 0) { "age" : 36 } --<< { "age" : 73 } on : rep_shard2 Timestamp(2, 3) { "age" : 73 } --<< { "age" : 92 } on : rep_shard2 Timestamp(2, 4) { "age" : 92 } --<< { "age" : { "$maxKey" : 1 } } on : rep_shard2 Timestamp(3, 1)

從輸出結果可以看到 test.test_shard 集合總共有 2 個分片,分片 rep_shard1 上有 2 個 chunk,分片 rep_shard2 上有 3 個 chunk,年齡大於或等於 0 並且小於 36 的文檔數據放到了第一個分片 rep_shard1,年齡大於或等於 36 並且小於 73 的文檔數據放到了第二個分片 rep_shard2,此時已經達到了分片的效果。我們可以使用 find 命令來確認是否對應的數據存在相應的分片,以下為命令和部分輸出示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 db.test_shard.find({ age: { $gte : 36 ,$lt : 73 } }).explain() {     "queryPlanner" : {         "winningPlan" : {             "stage" : "SINGLE_SHARD",             "shards" : [                 {                     "shardName" : "rep_shard2",                     "connectionString" : "rep_shard2/10.0.4.6:27019,10.0.4.7:27019,10.0.4.8:27019",                     "namespace" : "test.test_shard",                     "winningPlan" : {                         "stage" : "FETCH",                         "inputStage" : {                             "stage" : "SHARDING_FILTER",                             "inputStage" : {                                 "stage" : "IXSCAN",                                 "keyPattern" : {                                     "age" : 1                                 },                                 "indexName" : "age_1",                                 "direction" : "forward",                                 "indexBounds" : {                                     "age" : [                                         "[36.0, 73.0)"                                     ]                                 }                             }                         }                     },                 }             ]         }     } }

從以上輸出結果可以看到,當查找年齡範圍為大於等於 36 並且小於 73 的文檔數據,MongoDB 會直接定位到分片 rep_shard2,從而避免全分片掃描以提高查找效率。如果將 $gte : 36 改為 $gte : 35,結果會是怎麼樣的呢?答案是 MongoDB 會掃描全部分片,執行計劃的結果將由 SINGLE_SHARD 變為 SHARD_MERGE,如果感興趣,您可以自行驗證。

基於 Hashed 的分片操作

為了和基於範圍分片形成對比,這一步操作使用相同的測試數據。操作步驟如下所示。

  1. 開啟 test 庫的分片功能。
    1 sh.enableSharding("test")
  2. 選擇集合的分片鍵,註意這裡創建的是 hash 索引。
    1 sh.shardCollection("test.test_shard",{"age": "hashed"})
  3. 批量造測試數據。
    1 2 use test for (i = 1; i <= 20000; i++) db.test_shard.insert({age:(i%100), name:"user"+i, create_at:new Date()})
  4. 觀察分片效果。以下為命令和部分輸出示例:
    1 2 3 4 5 6 7 8 sh.status() chunks:         rep_shard1  2         rep_shard2  2 { "age" : { "$minKey" : 1 } } --<< { "age" : NumberLong("-4611686018427387902") } on : rep_shard1 Timestamp(1, 0) { "age" : NumberLong("-4611686018427387902") } --<< { "age" : NumberLong(0) } on : rep_shard1 Timestamp(1, 1) { "age" : NumberLong(0) } --<< { "age" : NumberLong("4611686018427387902") } on : rep_shard2 Timestamp(1, 2) { "age" : NumberLong("4611686018427387902") } --<< { "age" : { "$maxKey" : 1 } } on : rep_shard2 Timestamp(1, 3)

從輸出結果可以看到總共有 4 個 chunk,分片 rep_shard1 有 2 個 chunk,分片 rep_shard2 有 2 個 chunk,分片後按照分片值 hash 後,存放到對應不同的分片。現在我們來將使用同一查詢,看看在基於 Hashed 分片和基於 Ranged 分片的效果,以下為命令和部分輸出示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 db.test_shard.find({ age: { $gte : 36 ,$lt : 73 } }).explain() {     "queryPlanner" : {         "winningPlan" : {             "stage" : "SHARD_MERGE",             "shards" : [                 {                     "shardName" : "rep_shard1",                     "connectionString" : "rep_shard1/10.0.4.6:27018,10.0.4.7:27018,10.0.4.8:27018",                     "winningPlan" : {                         "stage" : "SHARDING_FILTER",                         "inputStage" : {                             "stage" : "COLLSCAN",                       }                     }                 {                     "shardName" : "rep_shard2",                     "connectionString" : "rep_shard2/10.0.4.6:27019,10.0.4.7:27019,10.0.4.8:27019",                     "winningPlan" : {                         "stage" : "SHARDING_FILTER",                         "inputStage" : {                             "stage" : "COLLSCAN",                         }                     }         }     }

從以上結果可以看到,對於範圍查找,基於 Hashed 的分片很可能需要全部分片都掃描一遍才能找到對應的數據,效率比較低下,如果等值查找,效率會高些,接下來我們來驗證。

同樣還是數據不變,我們將查詢改為只查找年齡為 36 歲的文檔數據。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38     db.test_shard.find({ age: 36 }).explain()     {     "queryPlanner" : {         "winningPlan" : {             "stage" : "SINGLE_SHARD",             "shards" : [                 {                     "shardName" : "rep_shard2",                     "connectionString" : "rep_shard2/10.0.4.6:27019,10.0.4.7:27019,10.0.4.8:27019",                     "namespace" : "test.test_shard",                     "parsedQuery" : {                         "age" : {                             "$eq" : 36                         }                     },                     "winningPlan" : {                         "stage" : "SHARDING_FILTER",                         "inputStage" : {                             "stage" : "FETCH",                             "inputStage" : {                                 "stage" : "IXSCAN",                                 "keyPattern" : {                                     "age" : "hashed"                                 },                                 "indexName" : "age_hashed",                                 "indexBounds" : {                                     "age" : [                                         "[7618808261848727468, 7618808261848727468]"                                     ]
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 前言 電腦的基本工作就是處理數據,包括磁碟文件中的數據,通過網路傳輸的數據流或數據包,資料庫中的結構化數據等。隨著互聯網、物聯網等技術得到越來越廣泛的應用,數據規模不斷增加,TB、PB量級成為常態,對數據的處理已無法由單台電腦完成,而只能由多台機器共同承擔計算任務。而在分散式環境中進行大數 ...
  • Redis 是一種記憶體資料庫,將數據保存在記憶體中,讀寫效率要比傳統的將數據保存在磁碟上的資料庫要快很多。所以,監控 Redis 的記憶體消耗並瞭解 Redis 記憶體模型對高效並長期穩定使用 Redis 至關重要。 ...
  • 轉載自:https://www.cnblogs.com/vice/p/9163241.html 操作系統是Windows2008R2 ,資料庫是SQL2014 64位。 近階段伺服器出現過幾次死機,管理員反饋機器記憶體使用率100%導致機器卡死。於是做了個監測伺服器的軟體實時記錄CPU數據,幾日觀察得 ...
  • 前兩篇講了solr安裝和導入數據,這篇講如何整合到SSM中。 一、整合SSM 1.1 引入依賴 1.2 初始化solr 1.3 寫service 1.4 寫控制層 1.5 查詢 二、IK分詞器 2.1.添加jar包 下載地址:https://search.maven.org/search?q=com ...
  • 1 前言 在使用Kylin的時候,最重要的一步就是創建cube的模型定義,即指定度量和維度以及一些附加信息,然後對cube進行build,當然我們也可以根據原始表中的某一個string欄位(這個欄位的格式必須是日期格式,表示日期的含義)設定分區欄位,這樣一個cube就可以進行多次build,每一次的 ...
  • Flink 的 API 大體上可以劃分為三個層次:處於最底層的 ProcessFunction、中間一層的 DataStream API 和最上層的 SQL/Table API,這三層中的每一層都非常依賴於時間屬性。時間屬性是流處理中最重要的一個方面,是流處理系統的基石之一,貫穿這三層 API。在 ...
  • --創建測試表 if object_id(N'T_Test',N'U') is null CREATE TABLE [dbo].[T_Test] ( [ID] int IDENTITY(1, 1) PRIMARY key NOT NULL, [Grouping] varchar(50) NOT NU... ...
  • 1.如果要用子查詢,那就用EXISTS替代IN、用NOT EXISTS替代NOT IN。因為EXISTS引入的子查詢只是測試是否存在符合子查詢中指定條件的行,效率較高。無論在哪種情況下,NOT IN都是最低效的。因為它對子查詢中的表執行了一個全表遍歷。 2.對查詢進行優化,應儘量避免全表掃描,首先應 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...