全部產品
Search
文件中心

DataHub:DataHub成本節省攻略

更新時間:Sep 22, 2026

本文介紹了DataHub新版本帶來的改動,介紹batch的原理和實現,以及使用batch後所帶來的效能的提升和費用的減少。切換為batch後,對於DataHub而言,服務端的資源消耗會明顯降低,同時,效能會明顯提升,使用費用也會大幅降低。

升級內容

支援zstd壓縮

DataHub在新版本中對zstd壓縮演算法做了支援,相較於DataHub支援的lz4和deflate壓縮演算法,效果卓越。

說明

zstd是一種高效能壓縮演算法,由Facebook開發,於2016年開源,zstd在壓縮速度和壓縮比兩方面都有不俗的表現,非常契合DataHub的使用情境。

序列化改造

DataHub引入了batch序列化,batch序列化本質上就是DataHub對資料轉送中資料的定義的一種組織方式,batch並不是特指某種序列化的方式,而是對序列化的資料做了二次封裝。例如:一次發送100條資料,將100條資料序列化後得到一個buffer,給這個buffer選擇一個壓縮演算法得到壓縮後的buffer,這個時候給這個壓縮後的buffer添加一個header記錄這個buffer大小、資料條數、壓縮演算法、crc等資訊,從而獲得一條完整batch buffer。

解決的問題:

  • 可以有效避免業務層面的髒資料。

  • 減少服務端CPU開銷,提高資料處理效能。

  • 較少同時讀寫延遲。

說明

batch的buffer發送到服務端後,因用戶端已經做了充分的資料有效性的校正,所以服務端只需檢驗資料中的crc確認為有效buffer後,便可以直接落盤,省去了序列化、還原序列化、加解壓以及校正的操作,服務端效能提升超過80%,因為是多條資料一起壓縮的,所以壓縮率也提高了,儲存成本也降低了。

費用對比

驗證batch所帶來的實際效果,進行以下測試,假設情境如下:

  • 測試資料為廣告投放相關的資料,大約200列,資料中null比例大約20%~30%。

  • 1000條資料一個batch。

  • batch內部的序列化使用的是avro。

  • lz4是之前版本預設的壓縮演算法,壓縮使用zstd來替代lz4。

測試結果如下:

資料來源大小(Byte)

lz4壓縮(Byte)

zstd壓縮(Byte)

protobuf序列化

11,506,677

3,050,640

1,158,868

batch序列化

11,154,596

2,931,729

1,112,693

Datahub的收費項主要是這兩個維度為儲存與流量,其他主要是為了防止濫用而設定的懲罰性質的收費項。因此,以上測試結果從儲存與流量兩個角度進行分析。

  • 從儲存成本上來看,DataHub的protobuf序列化是沒有儲存壓縮的(只是HTTP傳輸環節壓縮),如果替換為batch+zstd,那麼儲存會由11506KB降為1112KB,也就是說,儲存成本下降幅度達到約90%。

  • 從流量成本上來看,DataHub的protobuf+lz4後的大小為3050KB,batch+zstd的大小為1112KB,也就是說,流量成本會降低約60%。

重要

以上為樣本資料測試結果,不同資料測試效果有差異,請您根據您業務實際情況進行測試。

使用batch

說明

注意事項

  • batch寫入最大的優勢需要充分攢批,如果用戶端無法攢批,或者攢批的資料較少,可能帶來的效果提升並不顯著。

  • 為了讓使用者移轉更加方便,DataHub在各種讀寫方式之間做了相容,保證使用者中間狀態可以更平滑地過渡,即batch寫入依舊可以使用原方式讀取,原方式寫入依舊可以使用batch讀取。因此,在寫入端更新為batch寫入之後,最好消費端也更新為batch,寫入和消費不對應反而會降低效能。

重要

DataHub新版本已經取消只有開啟多version的Topic 才可以使用batch協議,無需開啟Topic 多version開關就可以使用batch協議,但需要使用者更新到最新版的用戶端,目前支援的用戶端以及所需版本如下所示:

語言

支援狀態

版本要求

Java SDK

支援

1.5.1 及以上

Go SDk

支援

1.1.0 及以上

Python

暫不支援

-

C++

暫不支援

-

Java 樣本:

在 1.5.1 及以後的版本中,預設序列化協議已經設定為 Batch,使用者無需做任何特殊配置,只需要正常的寫入即可,同步和非同步寫入均可以。

Maven依賴
<dependency>
  <groupId>com.aliyun.datahub</groupId>
  <artifactId>datahub-client-library</artifactId>
  <version>1.5.1</version>
</dependency>
程式碼範例:
public static void main(String[] args) throws InterruptedException {
	// 通過環境變數擷取AK資訊
	EnvironmentVariableCredentialProvider provider = EnvironmentVariableCredentialProvider.create();

	String endpoint ="https://dh-cn-hangzhou.aliyuncs.com";
	String projectName = "test_project";
	String topicName = "test_topic";

	// 初始化Producer,這裡直接使用預設配置
	ProducerConfig config = new ProducerConfig(endpoint, provider);
	DatahubProducer producer = new DatahubProducer(projectName, topicName, config);

	RecordSchema schema = producer.getTopicSchema();
	// 如果開啟了多version schema,這裡也可以擷取指定version的schema
	// RecordSchema schema = producer.getTopicSchema(3);

	// 對於非同步寫入,可以根據需要來選擇是否註冊回呼函數
	WriteCallback callback = new WriteCallback() {
		@Override
		public void onSuccess(String shardId, List<RecordEntry> records, long elapsedTimeMs, long sendTimeMs) {
			System.out.println("write success");
		}

		@Override
		public void onFailure(String shardId, List<RecordEntry> records, long elapsedTimeMs, DatahubClientException e) {
			System.out.println("write failed");
		}
	};

	for (int i = 0; i < 10000; ++i) {
		try {
            // generate data by schema
            TupleRecordData data = new TupleRecordData(schema);
            data.setField("field1", "hello");
            data.setField("field2", 1234);
            RecordEntry recordEntry = new RecordEntry();
            recordEntry.setRecordData(data);

            producer.sendAsync(recordEntry, callback);
            // 如果不需要關心資料是否發送成功,那麼就不需要註冊回調,直接發送
            // producer.sendAsync(recordEntry, null);
        } catch (DatahubClientException e) {
            // TODO 處理異常,一般是不可重試錯誤或者超過重試次數;
            Thread.sleep(1000);
        }
	}

	// 保證退出前,資料全部被發送完
	producer.flush(true);
	producer.close();
}

Go 樣本:

go.mod依賴
require (
	github.com/aliyun/aliyun-datahub-sdk-go v1.1.0
)
程式碼範例:

在 1.1.0 及以後的版本中,預設序列化協議已經設定為 Batch,無需使用者做任何特殊配置,只需要正常的寫入即可,同步和非同步寫入均可以。

func handleSuccessRun(producer datahub.AsyncProducer) {
	for suc := range producer.Successes() {
		// handle request success
		fmt.Printf("shard:%s, rid:%s, records:%d, latency:%v\n",
			suc.ShardId, suc.RequestId, len(suc.Records), suc.Latency)
	}
}

func handleFailedRun(producer datahub.AsyncProducer) {
	// handle request failed
	for err := range producer.Errors() {
		fmt.Printf("shard:%s, records:%d, latency:%v, error:%v\n",
			err.ShardId, len(err.Records), err.Latency, err.Err)
	}
}

func main() {
	cfg := datahub.NewProducerConfig()

    // 通過環境變數擷取AK資訊
	credential, err := credentials.NewCredential(nil)
	if err != nil {
		fmt.Println(err)
		// TODO: handle error
	}
	cfg.Account = datahub.NewCredentialAccount(credential)
	cfg.Endpoint = "https://dh-cn-hangzhou.aliyuncs.com"
	cfg.Project = "test_project"
	cfg.Topic = "test_topic"

	producer := datahub.NewAsyncProducer(cfg)
	err = producer.Init()

	if err != nil {
		// TODO: handle error
		fmt.Println(err)
	}

	schema, err := producer.GetSchema()
	if err != nil {
		// TODO: handle error
		fmt.Println(err)
	}

	// 處理success channle
	go handleSuccessRun(producer)

	// 處理error channle
	go handleFailedRun(producer)

	// 迴圈產生資料
	for i := 0; i < 1000; i++ {
		record := datahub.NewTupleRecord(schema)
        // 每次設定值都需要檢查設定是否成功
		err = record.SetValueByName("f1", "val1")
		if err != nil {
			fmt.Println(err)
			return
		}

		err = record.SetValueByName("f2", 1234)
		if err != nil {
			fmt.Println(err)
			return
		}

		producer.Input() <- record
	}

	err = producer.Close()
	if err != nil {
		fmt.Println(err)
	}
}

後續支援

如果您在使用中有任何問題或者疑問,歡迎提工單諮詢或加入使用者群諮詢,群號:33517130。