開源直播課丨大數據集成框架ChunJun類載入器隔離方案探索及實踐

来源:https://www.cnblogs.com/DTinsight/archive/2022/10/09/16772429.html
-Advertisement-
Play Games

本期我們帶大家回顧一下無倦同學的直播分享《ChunJun類載入器隔離》,ChunJun類載入器隔離的方案是我們近期探索的一個新方案,這個方案目前還不是非常成熟,希望能藉由此次分享與大家一起探討下這方案,如果大家有一些新的想法歡迎大家在github上給我提issue或者pr。 一、Java 類載入器解 ...


本期我們帶大家回顧一下無倦同學的直播分享《ChunJun類載入器隔離》,ChunJun類載入器隔離的方案是我們近期探索的一個新方案,這個方案目前還不是非常成熟,希望能藉由此次分享與大家一起探討下這方案,如果大家有一些新的想法歡迎大家在github上給我提issue或者pr。

一、Java 類載入器解決類衝突基本思想

在學習方案之前,首先為大家介紹一下Java類載入器解決類衝突的基本思想。

01 什麼是 Classpath?

Classpath是JVM用到的一個環境變數,它用來指示JVM如何搜索Class。

因為Java是編譯型語言,源碼文件是.java,而編譯後的.class文件才是真正可以被JVM執行的位元組碼。因此,JVM需要知道,如果要載入一個com.dtstack.HelloWorld的類,應該去哪搜索對應的HelloWorld.class文件。

所以,Classpath就是一組目錄的集合,它設置的搜索路徑與操作系統相關,例如:

在Windows系統上,用;分隔,帶空格的目錄用""括起來,可能長這樣:

C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"

在MacOS & Linux系統上,用:分隔,可能長這樣:

/usr/shared:/usr/local/bin:/home/wujuan/bin

啟動JVM時設置Classpath變數, 實際上就是給java命令傳入-Classpath或-cp參數.

java -Classpath .;/Users/lzq/Java/a;/Users/lzq/Java/b com.dtstack.HelloWorld

沒有設置系統環境變數,也沒有傳入-cp參數,那麼JVM預設的Classpath為,即當前目錄:

java com.dtstack.HelloWorld

02 Jar 包中的類什麼時候被載入?

● Jar包

Jar 包就是 zip 包,只不過尾碼名字不同。用於管理分散的 .class 類。

生成 jar 包可以用 zip 命令 zip -r ChunJun.zip ChunJun

java -cp ./ChunJun.zip com.dtstack.HelloWorld

● 載入

“載入”(Loading) 階段是整個“類載入”(Class Loading) 過程中的一個階段,希望讀者沒有混淆這兩個看起來很相似的名詞。在載入階段,Java虛 擬機需要完成以下三件事情:

1.通過一個類的全限定名來獲取定義此類的二進位位元組流;

2.將這個位元組流所代表的靜態存儲結構轉化為方法區的運行時數據結構;

3.在記憶體中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。

● 解析

類或介面的解析

假設當前代碼所處的類為D,如果要把一個從未解析過的符號引用N解析為一個類或介面C的直接引用,那虛擬機完成整個解析的過程需要包括以下3個步驟:

1.如果C不是一個數組類型,那虛擬機將會把代表N的全限定名傳遞給D的類載入器去載入這個類C。

在載入過程中,由於元數據驗證、位元組碼驗證的需要,又可能觸發其他相關類的載入動作,例如載入這個類的父類或實現的介面。一旦這個載入過程出現了任何異常,解析過程就將宣告失敗。

2.如果C是一個數組類型,並且數組的元素類型為對象,也就是N的描述符會是類

似“[Ljava/lang/Integer的形式,那將會按照第一點的規則載入數組元素類型。

如果N的描述符如前面所假設的形式,需要載入的元素類型就是“java.lang.Integer",接著由虛擬機生成一個代表該數組維度和元素的數組對象。

3.如果上面兩步沒有出現任何異常,那麼C在虛擬機中實際上已經成為一個有效的類或介面了,但在解析完成前還要進行符號引用驗證,確認D是否具備對C的訪問許可權。如果發現不具備訪問許可權,將拋出java.lang,llegalAccessEror異常。

03 哪些行為會觸發類的載入?

關於在什麼情況下需要開始類載入過程的第一個階段“載入”,《Java虛擬機規範》中並沒有進行 強制約束,這點可以交給虛擬機的具體實現來自由把握。但是對於初始化階段,《Java虛擬機規範》 則是嚴格規定了有且只有六種情況必須立即對類進行“初始化”(而載入、驗證、準備自然需要在此之 前開始):
file

● 場景一

遇到new、getstatic、putstatic或invokestatic這四條位元組碼指令時,如果類型沒有進行過初始 化,則需要先觸發其初始化階段。能夠生成這四條指令的典型Java代碼場景有:

1.使用new關鍵字實例化對象的時候。

2.讀取或設置一個類型的靜態欄位(被final修飾、已在編譯期把結果放入常量池的靜態欄位除外) 的時候。

3.調用一個類型的靜態方法的時候。

● 場景二

使用java.lang.reflect包的方法對類型進行反射調用的時候,如果類型沒有進行過初始化,則需 要先觸發其初始化。

● 場景三

當初始化類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。

● 場景四

當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先 初始化這個主類。

● 場景五

當使用JDK 7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。

●場景六

當一個介面中定義了JDK 8新加入的預設方法(被default關鍵字修飾的介面方法)時,如果有這個介面的實現類發生了初始化,那該介面要在其之前被初始化。

對於以上這六種會觸發類型進行初始化的場景,《Java虛擬機規範》中使用了一個非常強烈的限定語 ——“有且只有”,這六種場景中的行為稱為對一個類型進行主動引用。除此之外,所有引用類型的方 式都不會觸發初始化,稱為被動引用。

04 什麼是雙親委派機制?

雙親委派機制,是按照載入器的層級關係,逐層進行委派,例如下圖中的自定義類載入器想要載入類,它首先不會想要自己去載入,它會通過層級關係逐層進行委派,從自定義類載入器 -> App ClassLoader -> Ext ClassLoader -> BootStrap ClassLoader,如果在BootStrap ClassLoader中沒有找到想要載入的類,又會逆迴圈載入。

file

05 如何打破雙親委派機制?

那麼如何打破雙親委派機制呢?其實可以通過重寫 loadclass 方法來實現,具體過程大家可通過視頻瞭解,這裡就不過多贅述。

file
file

二、Flink 類載入隔離的方案

接下來我們來介紹下Flink 類載入隔離的方案,Flink有兩種類載入器Parent-First和Child-First,他們的區別是:

1.Parent-First

類似 Java 中的雙親委派的類載入機制。Parent First ClassLoader 實際的邏輯就是一個 URL ClassLoader。

2.Child-First

先用 classloader.parent-first-patterns.default 和 classloader.parent-first-patterns.additional 拼接的list做匹配,如果類名首碼匹配了,先走雙親委派。否則就用 ChildFirstClassLoader 先載入。

Child-First 存在的問題

每次新 new 一個 ChildFirstClassLoader,如果運行時間久的話,類似 Session 這種 TaskManager 一直不關閉的情況。任務運行多次以後,會出現元數據空間爆掉,導致任務失敗。

Child-First 載入原理

file

file

file
file

01 Flink是如何避免類泄露的?

大家可以參考Flink中的jira,這裡麵包含一些bug和處理方法:

https://issues.apache.org/jira/browse/FLINK-16245

https://issues.apache.org/jira/browse/FLINK-11205

Flink如何避免類泄露,主要是通過以下兩種方法:

  1. 增加一層委派類載入器,將真正的 UserClassloader 包裹起來。

  2. 增加一個回調鉤子,當任務結束的時候可以提供給用戶一個介面,去釋放未釋放的資源。

KinesisProducer 使用了這個鉤子

final RuntimeContext ctx = getRuntimeContext();

ctx.registerUserCodeClassLoaderReleaseHookIfAbsent(

KINESIS_PRODUCER_RELEASE_HOOK_NAME,

()-> this.runClassLoaderReleaseHook

(ctx.getUserCodeClassLoader()));

卸載用戶代碼中動態載入的類,所有涉及動態用戶代碼類載入(會話)的場景都依賴於再次卸載的類。

類卸載指垃圾回收器發現一個類的對象不再被引用,這時會對該類(相關代碼、靜態變數、元數據等)進行移除。

當TaskManager啟動或重啟任務時會載入指定任務的代碼,除非這些類可以卸載,否則就有可能引起記憶體泄露,因為更新新版本的類可能會隨著時間不斷的被載入積累。這種現象經常會引起OutOfMemoryError: Metaspace這種典型異常。

類泄漏的常見原因和建議的修複方式:

● Lingering Threads

確保應用代碼的函數/sources/sink關閉了所有線程。延遲關閉的線程不僅自身消耗資源,同時會因為占據對象引用,從而阻止垃圾回收和類的卸載。

● Interners

避免緩存超出function/sources/sinks生命周期的特殊結構中的對象。比如Guava的Interner,或是Avro的序列化器中的類或對象。

● JDBC

JDBC驅動會在用戶類載入器之外泄漏引用。為了確保這些類只被載入一次,可以將驅動JAR包放在Flink的 lib/ 目錄下,或者將驅動類通過 classloader-parent-first-patterns-additional 加到父級優先載入類的列表中。

釋放用戶代碼類載入器的鉤子(hook)可以幫助卸載動態載入的類,這種鉤子在類載入器卸載前執行,通常情況下最好把關閉和卸載資源作為正常函數生命周期操作的一部分(比如典型的close()方法)。有些情況下(比如靜態欄位)最好確定類載入器不再需要後就立即卸載。

釋放類載入器的鉤子可以通過

RuntimeContext.registerUserCodeClassLoaderReleaseHookIfAbsent()方法進行註冊。

BlobLibraryCacheManager$ResolvedClassLoader

private void runReleaseHooks() {

Set<map.entry> hooks = releaseHooks.entrySet();

if (!hooks.isEmpty()) {

    for (Map.EntryhookEntry : hooks) {

        try {

            LOG.debug("Running class loader shutdown hook: {}.", hookEntry.getKey());

            hookEntry.getValue().run();

        } catch (Throwable t) {

            LOG.warn(

                    "Failed to run release hook '{}' for user code class loader.",

                    hookEntry.getValue(),

                    t);

        }

    }

    releaseHooks.clear();

}

}

三、ChunJun 如何實現類載入隔離

接下來為大家介紹下ChunJun 如何實現類載入隔離。

首先我們需要上傳Jar包,整體流程如下圖所示:

file

● Yarn Perjob

提交任務的時候上傳 jar 包,會放到

hdfs://flink03:9000/user/root/.flink/application_1654762357754_0140。

● Yarn Session

啟動 Session 的時候,Yarn 的 App 上傳 Jar 包機制,往 Session 提交任務的時候,Flink 的 Blob Server 負責收。

02 Yarn 的分散式緩存

file

03 Yarn 的分散式緩存

分散式緩存機制是由各個NM實現的,主要功能是將應用程式所需的文件資源緩存到本地,以便後續任務的使用。資源緩存是用時觸發的,也就是第一個用到該資源的任務觸發,後續任務無需再進行緩存,直接使用即可。

根據資源類型和資源可見性,NM可將資源分成不同類型:

資源可見性分類

● Public

節點上所有的用戶都可以共用該資源,只要有一個用戶的應用程式將著這些資源緩存到本地,其他所有用戶的所有應用程式都可以使用。

● Private

節點上同一用戶的所有應用程式共用該資源,只要該用戶其中一個應用程式將資源緩存到本地,該用戶的所有應用程式都可以使用。

● Application

節點上同一應用程式的所有Container共用該資源

資源類型分類

● Archive

歸檔文件,支持.jar、.zip、.tar.gz、.tgz、.tar的5種歸檔文件。

● File

普通文件,NM只是將這類文件下載到本地目錄,不做任何處理

● Pattern

以上兩種文件的混合體

YARN是通過比較resource、type、timestamp和pattern四個欄位是否相同來判斷兩個資源請求是否相同的。如果一個已經被緩存到各個節點上的文件被用戶修改了,則下次使用時會自動觸發一次緩存更新,以重新從HDFS上下載文件。

分散式緩存完成的主要功能是文件下載,涉及大量的磁碟讀寫,因此整個過程採用了非同步併發模型加快文件下載速度,以避免同步模型帶來的性能開銷。

04 Yarn 的分散式緩存

NodeManager採用輪詢的分配策略將這三類資源存放在yarn.nodemanager.local-dirs指定的目錄列表中,在每個目錄中,資源按照以下方式存放:

● Public資源

存放在${yarn.nodemanager.local-dirs}/filecache/目錄下,每個資源將單獨存放在以一個隨機整數命名的目錄中,且目錄的訪問許可權均為0755。

● Private資源

存放在${yarn.nodemanager.local-dirs}/usercache/${user}/filecache/目錄下,(其中${user}是應用程式提交者,預設情況下均為NodeManager啟動者),每個資源將單獨存放在以一個隨機整數命名的目錄中,且目錄的訪問許可權均為0710。

● Application資源

存放在${yarn.nodemanager.local-dirs}/usercache/${user}/${appcache}/${appid}/filecache/目錄下(其中${appid}是應用程式ID),每個資源將單獨存放在以一個隨機整數命名的目錄中,且目錄的訪問許可權均為0710;

其中Container的工作目錄位於${yarn.nodemanager.local-dirs}/usercache/${user}/${appcache}/${appid}/${containerid}目錄下,其主要保存jar包文件、字典文件對應的軟鏈接。
file

file

06 如何快速提交,減少上傳 jar 包

Flink libs 下麵 jar包、Flink Plugins 下麵的 jar 包、Flink 任務的 jar 包(對於 ChunJun 來說就是所有 connector 和 core), Flink jar 用戶自定義 jar 包。

● Perjob

如果可以提前上傳到 HDFS:

  1. 提前把 Flink lib 、Flink plugins、ChunJun jar 上傳到 HDFS 上面。

  2. 提交任務的時候通過 yarn.provided.lib.dirs 指定 HDFS 上面的路徑即可。

如果不可以提前上傳到 HDFS:

  1. 任務提交上傳到 HDFS 固定位置,提交的時候檢查 HDFS 上如果有對應的 jar(有緩存策略),就把本地路徑替換成遠程路徑。

  2. 利用回調鉤子,清楚異常任務結束的垃圾文件。

● Seeion

如果可以提前上傳到 HDFS:

  1. 提前把 Flink lib 、Flink plugins、ChunJun jar 上傳到 HDFS 上面。

  2. 啟動 session 的時候通過 yarn.provided.lib.dirs 指定 HDFS 上面的路徑即可。

  3. 提交任務的時候不需要上傳 core 包。

如果不可以提前上傳到 HDFS:

  1. Session 啟動的時候就上傳所有 jar 到 HDFS 上面。通過 yarnship 指定。

  2. Flink 任務提交到 Session 的時候,不需要提交任何 jar 包。

file

07 類載入隔離遇到的問題分析

● 思路分析

  1. 首先要把不同插件(connector) 放到不同的 Classloader 裡面。

  2. 然後使用 child-first 的載入策略。

  3. 確保不會發生 x not cast x 錯誤。

  4. 元數據空間不會記憶體泄露,導致任務報錯。

  5. 要緩存 connector jar 包。

● 遇到的問題

  1. Flink 一個 job 可能有多個運算元,一個 connector 就是一個運算元。Flink 原生是為 job 級別新生成的 Classloader,無法把每個 connector 放在一個獨立的 Classloader 裡面。

  2. child-first 載入策略在 Session 模式下每次都新 new 一個 Classloader,導致元數據空間記憶體泄露。

  3. connecotor 之間用到公有的類會報錯。

  4. 和問題2類似,主要是因為有些線程池,守護線程會拿著一些類對象,或者類 class 對象的引用。

  5. 如果用原生 -yarnship 去上傳,會放到 App Classloader 裡面。那麼就會導致某些不期望用 App Classloader 載入的類被載入。

file

/** Set of JAR files required to run this job. */

private final ListuserJars = new ArrayList();

/** Set of custom files required to run this job. */

private final MapuserArtifacts = new HashMap<>();

/** List of Classpaths required to run this job. */

private ListClasspaths = Collections.emptyList();

  1. 客戶端處理,JobGraph 處理 userJars、userArtifacts、Classpaths 這三個屬性。

  2. Classpath 只留下 connector 的層級目錄。

  3. 啟動 Session 的時候上傳 jar,jar 緩存在 Yarn 的所有的 NodeManager 節點。

  4. jobmanager 和 taskmanager 構建 Classloader 的時候去修改 Classpath 的路徑,替換成當前節點 NodeManager 的緩存路徑。

  5. 根據不同 connecotr 去構建Flink Job 的 Classloader。

  6. 把構建出來的 classlaoder 進行緩存,下次任務還有相同的 Classloader。避免記憶體泄露。

  7. 重寫新的 ChildFirstCacheClassloader 裡面的 loadclass 方法,根據不同的 connector url 去生成 單獨的 Classloader。

四、遇到的問題和排查方案?

jar包衝突常見的異常為找不到類(java.lang.ClassNotFoundException)、找不到具體方法(java.lang.NoSuchMethodError)、欄位錯誤( java.lang.NoSuchFieldError)或者類錯誤(java.lang.LinkageError)。

● 常見的解決方法如下

1、首先做法是打出工程文件的依賴樹,將根據jar包依賴情況判定是不是同一個jar包依賴了多個版本,如果確認問題所在,直接exclusion其中錯誤的jar包即可。

2、如果通過看依賴樹不能確定具體衝突的jar包,可以使用添加jvm參數的方式啟動程式,將類載入的具體jar信息列印出來;-verbose:class 。

3、經過上述步驟基本就可以解決jar包衝突問題,具體的問題要具體分析。

● 常用工具推薦

1.Maven-helper

主要排查類衝突的 IDEA 插件。

2.Jstack

死鎖的一些問題可以通過這個工具查看 jstack 調用棧。

3.Arthas

排查一些性能問題和 Classloader 泄露問題。

4.VisualVM

排查一些對象記憶體泄露、dump 文件分析等。

袋鼠雲開源框架釘釘技術交流qun(30537511),歡迎對大數據開源項目有興趣的同學加入交流最新技術信息,開源項目庫地址:https://github.com/DTStack/Taier


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、概念 委托的本質也是一種類型,類似於Class這樣。作用是將一個方法作為參數傳遞給另一個方法,關鍵字是delegate 二、委托的定義使用步驟 第一步聲明委托: public delegate int myDelegate(int a, int b); 1、聲明一個委托類型,可以用訪問修飾符修飾 ...
  • keepalived高可用(haporxy) keepalived高可用的介紹可以參考keepalived高可用(nginx) 環境 |系統信息| 主機名 |IP |服務| | : | : | : | : | |centos8| master| 192.168.222.250| haproxy,ke ...
  • keepalived實現haporxy負載均衡機高可用 環境說明 | 系統信息 | 主機名 | IP | 服務 | | | | | | | centos8 | master | 192.168.111.141 | haproxykeepalived | | centos8 | backup | 19 ...
  • 1. 索引數組 一、什麼是索引數組? 所謂索引數組就是普通數組,以整數作為數組元素的索引下標。 二、實例。 備註: (a)使用-a選項定義索引數組,使用一對小括弧()定義數組中的元素列表。 (b)索引數組使用整數作為數組元素下標。 備註: (a)使用@和*作為數組下標,表示獲取所有元素。 三、實例。 ...
  • keepalived高可用(nginx) keepalived簡介 keepalived官網 Keepalived 軟體起初是專為LVS負載均衡軟體設計的,用來管理並監控LVS集群系統中各個服務節點的狀態,後來又加入了可以實現高可用的VRRP功能。因此,Keepalived除了能夠管理LVS軟體外, ...
  • 在應用開發的早期,數據量少,開發人員開發功能時更重視功能上的實現,隨著生產數據的增長,很多SQL語句開始暴露出性能問題,對生產的影響也越來越大,有時可能這些有問題的SQL就是整個系統性能的瓶頸。 ...
  • 1)對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。 2)應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。 3)應儘量避免在 where 子句中對欄位進行 null 值判斷,否則將導致引擎放棄使用索引 ...
  • 存儲引擎 1.基本介紹 基本介紹 MySQL的表類型由存儲引擎(Storage Engines)決定,主要包括MyISAM、innoDB、Memory等 MySQL數據表主要支持六種類型,分別是:CSV,Memory,ARCHIVE,MRG_MYISAM,MYISAM,InnoBDB。 這六種又分為 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...