Oracle Golden Gate(簡稱OGG)是Oralce出品的資料即時抄寫工具,它讀取redo.log來進行抄寫避免影響正在運作的Oracle的效能,目前可同步抄寫到Oracle、MySQL和SQL Server。OGG本身是一支standalone程式,也提供一個Java Adapter方便開發者客製抄寫作業。怎麼安裝自有Consultant處理,但實作Java Adapter還真的有些難度。這主題目前是我寫過最難寫的。

  OGG啟動後,可以切換到OGG Home目錄執行ggsci執行檔,會出帶出ggsci>的提示。下達info all命令後,可以work的狀況下,會顯示三類Extract(不是三個),Extract之於OGG類似於Process之於OS。其實extract就是process,可以用ps -ef | grep ogg觀察。這三類Extract分別是:

  • Manager:OGG的主控程式extract,是必須的。
  • ext1:不一定叫ext1。屬OGG for Oracle,是由User建立,負責讀取redo.log。若只單純同步其它DB,應只需這類Extract,通常一個Extract對映一個DB(Oracle的SID),可以指定抄寫等定的table。
  • javaue:則是OGG的Java Adapter特定的extract,也是本篇Blog主題。

  info all會帶出每個extract的status,分別是:RUNNING、STOP和ABENDED。ABENDED查好久,是異常中止的意思。也就在javaue底下寫Java adpater若寫得不好,狀態就是ABENDED。

  未談OGG for Java之前,先略述OGG for Oracle的運行架構,在OGG Home目錄下有個重要的子目錄:dirprm,裡面放的.prm檔即是上述的extract的參數檔,需手動編輯,除了mgr.prm很簡單的只設定PORT number之外,而ext1.prm內容長得像這樣:

extract ext1                # 宣告extract name
userid ogg, password oracle # 連Oracle的ID/PWD
EXTTRAIL ./dirdat/je        # OGG放置redo.log路徑,檔名je開頭
table jemmy.FOO;            # 允許Schema Jemmy的FOO table
table orcl.BAR;             # 允許Schema ocrl的BAR table

  設定完後,在ggsci>下start ext1,它就會讀取jemmy.FOO和table.orcl有異動到redo.log寫到./dirdat/je。至於怎麼直接抄寫到不同的DB,要另外找找設定,這裡是要給OGG for Java讀取使用。

  OGG for Java安裝後,在OGG Home目錄下會多出一個ggjava的子目錄,即是javaue extract運行的地方。一樣在/dirprm子目錄有個javaue.prm,其內容後面再討論。另外還需利用defgen.prm做code gen產出javaue.def,作為javaue與ext1之間的mapping,在Home目錄執行:./defgen PARAMFILE dirprm/defgen.prm。所參考defgen.prm的內容,和ext1.prm內容頗為類似

userid ogg, password oracle
defsfile ./dirprm/javaue.def # 產出javaue.def檔
table jemmy.*;
table ocrl.*;

  而javaue.prm則是設定OGG for Java Adapter運作的環境及參數:

Extract javaue

SetEnv (JAVA_HOME = "/usr/java5_64")
SetEnv (LIBPATH = "/usr/java5_64/jre/bin/j9vm:/usr/java5_64/jre/bin:/oracle10/lib:/ogg/ogg_sync")
SetEnv (GGS_USEREXIT_CONF = "dirprm/cuserexit.properties" ) # OGG for Oracle的設定
SetEnv (GGS_JAVAUSEREXIT_CONF = "dirprm/javaue.properties") # OGG for Java的核心設定

CUserExit Java5_UserExit.so CUSEREXIT PassThru IncludeUpdateBefores # CUserExit是OGG for Oracle核心程式

GetUpdateBefores
NoCompressDeletes
NoCompressUpdates

Table jemmy.*;
Table orcl.*;

  講了上述不甚完全的配置後,現在才要進入如何開發的階段。OGG for Java的客製化限制還真的很OOXX的多。

  • 第一個,不是做成Jar檔放進去,而是在/dirprm子目錄下放classes的內容,如OGG-Home/dirprm/com/mycompany/…。所以先前有個困惑是不應該放在ggjava目錄下嗎?目前無解。
  • 第二個更OX了,log4j.properties以ggjava下的設定為主,而ggjava也import Spring的jar,版本是2.5.6,所以也有它的config檔,因不想複雜化,不用Spring。
  • 寫好的OGG for Java程式放到dirprm目錄下後,如果有引額外的jar怎麼處理,這需要從dirprm下的javaue.properties做配置了。ggjava本身就有提供一個透過Velocity產出redo.log資料的sample,開發過程式可以和這sample並行對照驗證。以下是節錄的部份:

#gg.handlerlist=sample_test # sample_test是ggjava提供的用Velocity讀取redo.log的處理程式
#gg.handlerlist=test_jms,sample_test
gg.handlerlist=sample_test,ogg_adapter

# OGG Adapter
gg.handler.ogg_adapter.type=com.mycompany.OggHandler
gg.classpath=/ogg/ogg_tprs33a/ggjava/resources/lib/NCSO.jar,/ogg/ogg_tprs33a/ggjava/resources/lib/Notes.jar

  javaue.properties的配置觀念與log4j類似。

    • gg.handlerlist:後面接的是處理程式代名,可以多個,用逗號分隔。和log4j指定appender方法一樣。
    • gg.handlerlist.程式代名.type:是指這程式代名實際對應的類別,需要在/dirprm/com/mycompany下找到OggHandler這個類別。該類別實作容後再敍。
    • gg.classpath:import額外的jar檔,用逗號分隔。
    • gg.handlerlist.程式代名.自訂屬性:ggjava好像進步到可以該程式代名需要的屬性。
  • 如何開發OGG for Java:由上述的javaue.properties得知,啟動的入口在com.mycompany.OggHandler這支,它需要繼承OGG for Java提供抽象類別AbstractHandler,原程式碼如下:

package com.mycompany;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.goldengate.atg.datasource.AbstractHandler;
import com.goldengate.atg.datasource.DsConfiguration;
import com.goldengate.atg.datasource.DsEvent;
import com.goldengate.atg.datasource.DsOperation;
import com.goldengate.atg.datasource.DsTransaction;
import com.goldengate.atg.datasource.GGDataSource.Status;
import com.goldengate.atg.datasource.meta.DsMetaData;

public class OggHandler extends AbstractHandler { // 繼承它
    private static Logger logger = LoggerFactory.getLogger(OggHandler.class);
    private List<DsOperation> rows = new ArrayList<DsOperation>(); // DsOperation表示一筆增改刪動作

    @Override
    public void init(DsConfiguration arg0, DsMetaData arg1) {
        super.init(arg0, arg1);
        logger.debug("[1]init");
    }

    @Override
    public Status operationAdded(DsEvent e, DsTransaction tx, DsOperation op) {
        logger.debug("[4]operationAdded");
        this.rows.add(op); // 增加一筆record至list
        return Status.OK;
    }

    @Override
    public Status transactionBegin(DsEvent e, DsTransaction tx) {
        logger.debug("[3]transactionBegin");
        return Status.OK;
    }

    @Override
    public Status transactionCommit(DsEvent e, DsTransaction tx) {
        logger.info("[5]transactionCommit size={}", this.rows.size());
        OggThread poster = new OggThread();
        poster.setOps(this.rows); // 傳該transaction所有record過去(即一個List)
        poster.setDsMetaData(this.getMetaData()); // 傳該DataSource的中介資訊
        Thread t = new Thread(poster);
        t.start();
        this.rows = null;
        this.rows = new ArrayList<DsOperation>();
        return Status.OK;
    }

    @Override
    public void destroy() {
        logger.debug("[7]destory");
    }

    @Override
    public Status metaDataChanged(DsEvent e, DsMetaData meta) {
        logger.debug("[2]metaDataChanged");
        return Status.OK;
    }

    @Override
    public String reportStatus() {
        logger.debug("destory[6]");
        return "status report...";
    }

}

  我在Log裡標注每個method的號碼,是OGG for Java執行順序:init—>metaDataChanged—>transactionBegin—>operationAdded—>transactionCommit—>reportStatus—>destroy。

  1. init:在ggsci>start javaue後,init只會被執行第一次。
  2. reportStatus:在ggsci>stop javaue後,reportStatus只被執行一次,回傳字串記錄於log4j.properties所寫的Log。
  3. destroy:在ggsci>stop javaue後,只被執行一次。
  4. 在每一筆交易被commit後,metaDataChanged—>transactionBegin—>operationAdded—>transactionCommit均被執行一輪,最重要的method是operationAdded和transactionCommit,由operationAdded是取得每一筆record(增改刪資訊),即DsOperation物件,加到一個List;到transactionCommit就取得transaction裡所有record,在OGG for Java預設作法就分出thread去處理。所以了解DsOperation構造就很重要。
  5. 解析DsOperation還需由AbstractHandler取得DsMetaData,以下是OggThread片段:

for (DsOperation op : this.ops) { // 取得每筆record
    TableName tableName = op.getTableName(); // 該record所屬table
    TableMetaData tableMetaData = this.dsMetaData.getTableMetaData(tableName); // 取得該table中介資訊
    List<ColumnMetaData> keyList = tableMetaData.getKeyColumns(); // 取得該table的primary key欄位
    DsOperation.OpType opType = op.getOperationType();  // 取得該record的操作(增|刪|改)
    for(DsColumn dsCol: op.getColumns()) {              // 取得該record每個欄位
        ColumnMetaData cMeta = tableMetaData.getColumnMetaData(colNum); // 取得該欄位中介資訊
        String colName = cMeta.getColumnName();         // 取得欄位名稱
        String afterValue = dsCol.getAfterValue();      // 取得欄位修改後值
        String beforeValue = dsCol.getBeforeValue();    // 取得欄位修改前值
        // ...
    }
}

  DsOperation的Type一共有八種,目前會常用的是四種:

OpType  \ Value getBeforeValue getAfterValue Description
DO_INSERT null Yes All fields values
DO_DELETE Yes null 只會帶PK的before值
DO_UPDATE_FIELDCOMP null Yes 不含PK值欄位的update
DO_UPDATE_FIELDCOMP_PK Yes Yes 含PK值欄位的update

  要注意的是,Update都不會帶所有欄位,只有被影響的欄位值,而DO_UPDATE_FIELDCOMP_PK只有PK欄位有After和Before值。

  另外四種Type會在什麼情況出現還不知,分別是:DO_UPDATE_AC、DO_UPDATE_AC、DO_LOB和DO_TRUNCATE。要使truncate table可以作用到OGG的DO_TRUNCATE還需另外加裝script。而不解的是DO_UPDATE和DO_UPDATE_AC各代表什麼東東?

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 Jemmy 的頭像
    Jemmy

    Jemmy Walker

    Jemmy 發表在 痞客邦 留言(0) 人氣()