使用Kettle動態生成頁碼並實現分頁數據同步

需求

將 DB2 數據庫中的表數據導入另一個 DB2 數據庫的表裏面。

源表(DB2):table1

目標表(DB2):table2

數據量:千萬級別

思路

當時直接使用 Kettle 將數據從源表導入到目標表中,但是考慮到數據量過於龐大,實際執行過程花費了很長時間,因此考慮採用分頁導入的方式來進行數據傳輸,即:

根據實際情況設置一個每次處理的數據量,比如:5,000條,然後根據總的數據條數和每次處理的數據量計算出一共分幾頁。

假設總數據量有:10,000,000,所以頁數爲:10,000,000/5,000=2000頁

注: 若存在小數,小數部分算一頁,比如:20.3算21頁

解決方案

根據上述思路,我們首先需要考慮如何計算得到總頁數,以及頁碼。可以考慮增加一個輔助配置表來存放頁碼,這也是我在網上看到的處理方法,但是不符合工作需求,所以我考慮必須自動計算一個表的總頁數,並生成頁碼。最後根據頁碼來循環導入數據。

主流程如下:onetable_A.kjb

流程說明:

  1. 首先我們建立一個作業流程,然後配置一個 START 節點;
  2. 第一個轉換流程用於查詢表數據的數目,並計算得到總頁數,以及得到一個頁碼集合;
  3. 再建一個作業流程,來循環處理頁碼,主要是將第幾頁的數據從源表同步到目標表中。

根據表數據生成頁碼

首先我們來重點關注第一個轉換流程,這也是本次實現過程中最重要的一點。

流程結構如下:

getTotal 節點用於查詢表數據的數目,其實就是在系統表中做查詢操作,具體實現如下:

點擊預覽按鈕,結果如下:

Java 代碼腳本部分主要是根據上一步得到的數據數目來計算頁碼,並生成一個頁碼集合。關於 Java 代碼腳本的使用,這裏就不做介紹了。構建過程如下:

Java 代碼如下:

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
  if (first) {
    first = false;

    /* TODO: Your code here. (Using info fields)

    FieldHelper infoField = get(Fields.Info, "info_field_name");

    RowSet infoStream = findInfoRowSet("info_stream_tag");

    Object[] infoRow = null;

    int infoRowCount = 0;

    // Read all rows from info step before calling getRow() method, which returns first row from any
    // input rowset. As rowMeta for info and input steps varies getRow() can lead to errors.
    while((infoRow = getRowFrom(infoStream)) != null){

      // do something with info data
      infoRowCount++;
    }
    */
  }

  Object[] r = getRow();

  if (r == null) {
    setOutputDone();
    return false;
  }

  // It is always safest to call createOutputRow() to ensure that your output row's Object[] is large
  // enough to handle any new fields you are creating in this step.

  /* TODO: Your code here. (See Sample)

  // Get the value from an input field
  String foobar = get(Fields.In, "a_fieldname").getString(r);

  foobar += "bar";
    
  // Set a value in a new output field
  get(Fields.Out, "output_fieldname").setValue(r, foobar);

  */
//此處創建 r,是爲了獲取輸入參數TOTAL_SRC的值
r = createOutputRow(r, data.outputRowMeta.size());

double num = get(Fields.In, "TOTAL_SRC").getNumber(r);
int pageNum = 5000;
int pages = (int)num/pageNum +1;	//計算總頁數
//生成頁碼,並輸出
for(int i=0;i<pages;i++){
    //個人覺得r類似於輸出器,如果想將每個頁碼都輸出去,則必須獨立進行聲明,此步驟爲本人測試所得
  	r = createOutputRow(r, data.outputRowMeta.size());
	get(Fields.Out, "PAGE").setValue(r, i);		//將頁碼賦值給PAGE
	//get(Fields.Out, "TJOBSEQ").setValue(r, get(Fields.In, "JOB_SEQ").getString(r));
	//get(Fields.Out, "id").setValue(r, i);
  putRow(data.outputRowMeta, r);
} 
  // Send the row on to the next step.

  return true;
}

雖然我們想要得到的頁碼爲整數類型,但是在設置 PAGE 類型時需要設置爲 String 類型,否則會報錯,如下圖所示:

並不影響後續的使用。

對於下述流程進行測試驗證。

執行結果爲:

從結果可以看出,每個頁碼都被正確輸出。那麼接下來我們需要將頁碼複製到結果中,傳遞到接下來的作業流程中。

根據頁碼循環同步數據

頁碼生成完畢後,接下來就是根據頁碼從源表查詢數據,然後同步到目標表中。流程設計如下:

傳進來的頁碼必須先從結果中獲取到,然後再定義爲變量,才能被後續所使用。

第一個轉換流程的內部實現如下:

爲了區分,我們將變量名叫做 EPAGE。

接下來就是數據同步,查看 getData_Epage。

首先需要根據頁碼從源表中獲取到數據,注意這裏爲了使用頁碼條件,查詢得到的結果不得不多了一列結果。

表輸出時,注意不要勾選裁剪表,需要指定數據庫字段,將上面查到的結果中多的一列給剔除掉。如果不想在此處做數據庫字段指定操作,可以修改表輸入中的查詢語句。

select
REC_CREATOR,
REC_CREATE_TIME,
....
from (select ROW_NUMBER() over() as a, g001.* from
ZGROD112.D112_L2_FV_QD_CPCSEG002 g001) as temp 
where a>=(5*${EPAGE}+1) and a<=(5*(${EPAGE}+1))

至此,關於大規模數據表之間的同步操作結束。

總結

本例重點講述瞭如何根據表數據的數目動態生成頁碼,從而減少了頁碼配置表的構建步驟,最終減輕數據同步對於內存資源的佔用。

拓展

實際工作中,我們不可能只對一張表做數據同步,往往會針對多張表做同步操作,所以對每張表還會有一個循環處理操作。關於這種情況的處理,需要一個額外的配置表,用來存放源系統和目標系統基本信息,以及源表和目標表信息,如果需要做增量同步操作,還可以加幾個字段。後續有時間可以簡單寫一下。

參考文獻

採用Kettle分頁處理大數據量抽取任務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章