Oracle Advance Queuing是否適合您?

banq發表於2019-01-08

最近在EmbedIT工作中,我需要評估Oracle AQ是否是一個替代舊的非同步任務管理系統的不錯選擇。所以,讓我分享一下我的經驗。首先,有關Oracle AQ的文件非常龐大,因此我將指出您想知道的最重要的內容。

何時考慮Oracle AQ?
如果出於某種原因你不想搞像HazelcastTerracotta這樣的大資料框架,或者 透過JMS或AMPQ當前的訊息傳遞解決方案對你來說還不夠,那麼一定要檢查:Oracle Advance Queuing
Oracle帶來了自己的訊息傳遞,它透過資料庫工作。什麼時候適合你?
  • 你每天都有數百萬封郵件。
  • 你收到了很多訊息。
  • 您希望以訊息方式與其他資料庫/應用程式通訊。
  • Oracle AQ對分散式事務來說非常棒。甲骨文明確指出:

Oracle AQ的工作方式是不會將訊息視為dequeued 已出列(並因此被刪除,假設您在預設的破壞模式下出列) ,直到保留到所有消費者都已將該訊息取出為止。

如果處理某些事件涉及多個系統,並且主應用程式需要某種機制來知道其中一個處理失敗,則Oracle AQ是一個完美的候選者。

Oracle AQ的技術觀點
Oracle AQ允許將訊息排入佇列並從資料庫管理的佇列中出列,這裡每個佇列與一個佇列表相關聯。

每個佇列都有一個有效載荷,可以是:

  • RAW
  • OBJECT:指定型別的訊息。
  • ANYDATA:具有任何物件型別的訊息。

Oracle AQ中的佇列可以是:
  • 單個消費者佇列(只有一個消費者能夠在一瞬間出隊)
  • 多個消費者佇列。可以透過以下方式實現:
    1)多個收件人 - 在排隊之前設定郵件的收件人。2)多個訂戶 - 佇列具有預設的訂戶集。

如何使用Oracle AQ佇列

重要說明:
Oracle AQ在訂閱者之間沒有任何型別的自動負載平衡,就像WebLogic JMS伺服器中存在迴圈一樣。但是你可以實現它。這並不難。

Oracle AQ演示
在演示的所有部分中,我將使用JDBC API和附加類AQTestObjectStruct和AQTestClient。

如何建立Oracle AQ表(多個消費者)
假設我們將在資料庫模式ho_kloucek_in中排隊“ Message_typ ” 型別的訊息。所以在資料庫中執行:

create or replace type ho_kloucek_in.Message_typ as object (
subject     VARCHAR2(30),
text        VARCHAR2(80))


好的,我們有一個物件型別。現在我們可以為它建立帶佇列的佇列表。

public void createQueue() throws SQLException, AQException, ClassNotFoundException
    {
        AQQueueTableProperty qtable_prop;
        AQQueueProperty queue_prop;
        AQQueueTable q_table;
        AQQueue queue;

        java.sql.Connection aqconn = getOracleDataSource().getConnection();
        aqconn.setAutoCommit(false);

        AQSession aqsession = null;

        // Register the Oracle AQ Driver
        Class.forName("oracle.AQ.AQOracleDriver");
        try {
            AQEnqueueOption enqueueOption = new AQEnqueueOption();

            aqsession = AQDriverManager.createAQSession(aqconn);

            qtable_prop = new AQQueueTableProperty("ho_kloucek_in.Message_typ");
            qtable_prop.setMultiConsumer(true);

            /* Creating a queue table called aq_table1 in aqjava schema: */
            q_table = aqsession.createQueueTable(queueOwner, queueTable, qtable_prop);
            System.out.println("Successfully created "+queueTable+" in "+queueOwner+" schema");

            /* Creating a new AQQueueProperty object */
            queue_prop = new AQQueueProperty();

            /* Creating a queue called aq_queue1 in aq_table1: */
            queue = aqsession.createQueue(q_table, queueName, queue_prop);

            queue.start(true, true);
            System.out.println("Successfully created "+queueName+" in "+queueOwner+"");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            aqsession.close();
            aqconn.close();
        }
    }

請注意我是如何呼叫qtable_prop.setMultiConsumer(true)的

透過設定收件人列表來定位多個消費者
現在,在佇列表和佇列設定之後,讓我們用subsName變數設定名稱,將消費者作為收件人:

public void dequeueMessage(final String subsName) throws AQException, SQLException, ClassNotFoundException  
{
    java.sql.Connection aqconn = getOracleDataSource().getConnection();
    aqconn.setAutoCommit(false);

    AQSession aq_sess = null;

    Class.forName("oracle.AQ.AQOracleDriver");

    try 
    {
        aq_sess = AQDriverManager.createAQSession(aqconn);

        AQQueue queue;
        AQMessage message;
        AQDequeueOption deq_option;

        queue = aq_sess.getQueue(queueOwner, queueName);

        AQDequeueOption opt = new AQDequeueOption();
        opt.setConsumerName(subsName);

        while (true) {
           System.out.println("Waiting on subscription:"+subsName);
           message = queue.dequeue(opt, oracle.sql.STRUCT.class);

           if (message == null) {
               System.out.println("no messages");
           } else {
               System.out.println("Successful dequeue");

               if (message.getObjectPayload().getPayloadData() instanceof STRUCT) {
                   STRUCT popedStruct = (STRUCT) message.getObjectPayload().getPayloadData();
                   System.out.println("subject: " + popedStruct.getAttributes()[0]);
                   System.out.println("text: " + popedStruct.getAttributes()[1]);
                }

                //Commit
                aqconn.commit();
            }
        }
    }
    finally {
       aq_sess.close();
       aqconn.close();
    }
}

在設定件收件人列表時,我們在排隊前設定收件人:

public void enqueueMessage(String xmlMessage) throws SQLException, AQException, ClassNotFoundException {
        java.sql.Connection aqconn = getOracleDataSource().getConnection();
        aqconn.setAutoCommit(false);

        AQSession aqsession = null;

        // Register the Oracle AQ Driver
        Class.forName("oracle.AQ.AQOracleDriver");
        try {
            AQEnqueueOption enqueueOption = new AQEnqueueOption();

            aqsession = AQDriverManager.createAQSession(aqconn);
            AQQueue queue = aqsession.getQueue(queueOwner, queueName);
            AQMessage msg = queue.createMessage();

            AQMessageProperty msgProps = new AQMessageProperty();
            msgProps.setPriority(1);
            Vector recipientList = new Vector();
            AQAgent subs1 = new AQAgent("Sub2", null, 0);
            recipientList.add(subs1);
            msgProps.setRecipientList(recipientList);
            msg.setMessageProperty(msgProps);

            AQObjectPayload payload = msg.getObjectPayload();

            Object [] test_attributes = new Object[2];
            test_attributes [0] = "AsyncTask";
            test_attributes [1] = "121212666";

            StructDescriptor personDesc =
                    StructDescriptor.createDescriptor("HO_KLOUCEK_IN.MESSAGE_TYP", aqconn);

            STRUCT new_async = new STRUCT(personDesc, aqconn, test_attributes);

            payload.setPayloadData(new_async);

            queue.enqueue(enqueueOption, msg);
            aqconn.commit();
            System.out.println("Message succesfully enqueued..");
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            aqsession.close();
            aqconn.close();
        }
    }

此方法將訊息傳送到我的佇列,並且僅為名為“Sub2”的消費者者使用!

我們來試試吧。
啟動兩個JVM,使用引數“Sub1”和“Sub2”啟動前面提到的dequeueMessage方法... 使用包含dequeueMessage方法的類AQTestObjectStruct。以下內容應出現在兩個JVM中:

JVM1:
Waiting on subscription: Sub1
JVM2:
Waiting on subscription: Sub2

如您所見,預設情況下AQQueue.dequeue方法是阻塞的。無論如何,您也可以指定阻塞一段時間,請參閱文件和AQDequeueOption

現在啟動類AQTestClient和先前修改過的方法enqueueMessage傳送訊息

Object [] test_attributes = new Object[2];
            test_attributes [0] = "AsyncTask";
            test_attributes [1] = "11111";


對於訂戶“Sub2”:

AQMessage msg = queue.createMessage();

            AQMessageProperty msgProps = new AQMessageProperty();
            msgProps.setPriority(1);
            Vector recipientList = new Vector();
            AQAgent subs2 = new AQAgent("Sub2", null, 0);
            recipientList.add(subs2);
            msgProps.setRecipientList(recipientList);
            msg.setMessageProperty(msgProps);



consumer由AQAgent的名稱設定。現在,在啟動AQTestClient之後,應該在JVM2中出現:

Successful dequeue
subject: AsyncTask
text: 11111
Waiting on subscription: Sub2


訂閱的多個消費者
使用AQMessage的收件人列表引數,您將在獲取之前設定收件人。Oracle AQ文件明確指出:
如果在入隊期間指定了收件人列表,則它將覆蓋訂閱列表。
那麼讓我們看看如何建立佇列訂閱 ...讓我們更改AQTestObjectStruct中的dequeue方法並將其作為訂閱者啟動:

public void dequeueMessage(final String subsName) throws AQException, SQLException, ClassNotFoundException  
{
    java.sql.Connection aqconn = getOracleDataSource().getConnection();
        aqconn.setAutoCommit(false);

        AQSession aq_sess = null;

        Class.forName("oracle.AQ.AQOracleDriver");

        try {
            aq_sess = AQDriverManager.createAQSession(aqconn);

            AQQueue queue;
            AQMessage message;
            AQDequeueOption deq_option;

            queue = aq_sess.getQueue(queueOwner, queueName);

            // add subscription
            AQAgent subs = new AQAgent(subsName, null, 0);
            queue.removeSubscriber(subs);
            queue.addSubscriber(subs,null);


            AQDequeueOption opt = new AQDequeueOption();
            opt.setConsumerName(subsName);

            while (true) {
                System.out.println("Waiting on subscription:"+subsName);
                message = queue.dequeue(opt, oracle.sql.STRUCT.class);

                if (message == null) {
                    System.out.println("no messages");
                } else {
                    System.out.println("Successful dequeue");

                    if (message.getObjectPayload().getPayloadData() instanceof STRUCT) {
                        STRUCT popedStruct = (STRUCT) message.getObjectPayload().getPayloadData();
                        System.out.println("subject: " + popedStruct.getAttributes()[0]);
                        System.out.println("text: " + popedStruct.getAttributes()[1]);
                    }

                    //Commit
                    aqconn.commit();
                }
            }
        }
        finally {
            aq_sess.close();
            aqconn.close();
        }
    }

(注意:如果要新增新訂閱者,請註釋掉“queue.removeSubscriber(subs)”行)
現在,您可以在排隊之前省略訊息中的收件人列表,因為訂閱會設定一組訊息目標。

讓我再引用Oracle AQ DOC:
如果enqueue的訊息生成者提供消費者的收件人列表,則無需為多消費者佇列指定訂閱。 在某些情況下,可能需要將針對特定消費者集的訊息排隊,而不是預設的訂戶列表。
這就是它!如果未在訊息中指定收件人列表,系統將向所有訂戶傳送訊息。如果指定收件人列表,則系統會將郵件傳遞給指定的收件人。如果你不指定收件人列表,佇列將沒有任何訂閱者(AQQueue.addSubscriber方法),那麼你最終會得到錯誤:

oracle.AQ.AQOracleSQLException: ORA-24033: no recipients for message
ORA-06512: at "SYS.DBMS_AQIN", line 345
ORA-06512: at line 1


    at oracle.AQ.AQOracleQueue.enqueue(AQOracleQueue.java:1267)
    at com.sachinhandiekar.oracle.aq.AQTestClient.enqueueMessage(AQTestClient.java:55)
    at com.sachinhandiekar.oracle.aq.AQTestClient.main(AQTestClient.java:84)


測試多個訂閱者
首先改變的方法入隊中AQTestClient,就像我說的,我們可以努力忽略任何消費者的設定:

public void enqueueMessage(String xmlMessage) throws SQLException, AQException, ClassNotFoundException {
    java.sql.Connection aqconn = getOracleDataSource().getConnection();
        aqconn.setAutoCommit(false);

        AQSession aqsession = null;

        // Register the Oracle AQ Driver
        Class.forName("oracle.AQ.AQOracleDriver");
        try {
            AQEnqueueOption enqueueOption = new AQEnqueueOption();

            aqsession = AQDriverManager.createAQSession(aqconn);
            AQQueue queue = aqsession.getQueue(queueOwner, queueName);
            AQMessage msg = queue.createMessage();

            AQMessageProperty msgProps = new AQMessageProperty();
            msgProps.setPriority(1);

            AQObjectPayload payload = msg.getObjectPayload();

            Object [] test_attributes = new Object[2];
            test_attributes [0] = "AsyncTask";
            test_attributes [1] = "5555";

            StructDescriptor personDesc =
                    StructDescriptor.createDescriptor("HO_KLOUCEK_IN.MESSAGE_TYP", aqconn);

            STRUCT new_async = new STRUCT(personDesc, aqconn, test_attributes);

            payload.setPayloadData(new_async);

            queue.enqueue(enqueueOption, msg);
            aqconn.commit();
            System.out.println("Message succesfully enqueued..");
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
        finally {
            aqsession.close();
            aqconn.close();
        }
    }

現在透過兩個JVM中的AQTestObjectStruct類啟動前面提到的dequeueMessage方法和訂閱:

JVM1:Waiting on subscription: Sub1
JVM2:Waiting on subscription: Sub2

現在執行修改後的沒有收件人的AQTestClient.enqueueMessage方法和兩個JVM中的輸出將是:

**JVM1**:
Waiting on subscription: Sub1
Successful dequeue
subject: AsyncTask
text: 5555
Waiting on subscription: Sub1

**JVM2**:
Waiting on subscription: Sub2
Successful dequeue
subject: AsyncTask
text: 5555
Waiting on subscription: Sub2


如您所見,訊息已傳送給所有訂閱者,因為我們未在訊息中指定收件人。

總結
我希望我能很好地解釋所有細節。我真的很想念Oracle AQ的一些訊息預設負載均衡。這也許是我不會將它用於我們的應用程式節點之間的非同步任務分配的原因,因為我需要迴圈,WebLogic JMS免費提供給我。但是如果你想在某種分散式事務中與多個應用程式通訊,那麼肯定會使用Oracle AQ。

點選標題見原文,原始碼
 

相關文章