JMS 개발
본 장에서는 ProObject의 JMS 사용 방법에 대하여 설명한다.
1. 개요
이전에는 대부분의 요청을 소수의 서버가 각자 알아서 요청에 대한 처리를 하는 단순한 형태의 서버-클라이언트(Server-Client) 모델을 사용하거나 아예 통신 모델 없이 자신이 받은 요청을 처리만 하면 되는 단순한 형태의 애플리케이션이 주류를 이루었다. 하지만 요즘에는 하나의 요청을 처리하기 위해 수 많은 서버들의 협업이 필요하기에 때문에 여러 서버들이 협업을 할 수 있도록 각 서버간의 메시지를 잘 주고받을 수는 코드를 작성하는 것이 애플리케이션에서 매우 중요하게 되었다.
한 대의 서버가 다른 서버에 메시지를 보내는 것은 쉬우며 사용자가 신경써야 할 내용이 별로 없다. 하지만 그때그때 서비스 도중에 생성한 데이터가 필요한 서버들을 식별하여 메시지를 보낼 수 있도록 관리하는 것은 어려운 일이고, 메시지를 수신하는 서버가 메시지를 받아 정말로 자신이 원하는 작업을 제대로 끝마쳤는지 신뢰성(Reliablity)이 높도록 애플리케이션을 작성하는 것 또한 매우 어렵다.
서버와 서버가 직접 연결하여 통신하는 모델에서는 각 서버가 다른 서버에 어떤 메시지를 주고받아야 하는지 관리해야 하며, 각 메시지의 송수신 상태를 관리해주어야 한다. 프로그래머는 이 모든 작업을 모든 서버마다 보장할 수 있도록 코드를 작성해주어야 한다.
ProObject에서는 이러한 작업들을 엔진쪽에서 보장함으로써 보다 쉽게 여러 서버와 협업하여 서비스를 처리할 수 있는 기능을 JEUS의 JMS(Java Message Service)를 통해 제공하고 있다. 위와 같이 다수의 서버에서 메시지를 주고받는 모델은 크게 Pub/Sub(Publisher/Subscriber) 모델과 Reliable Queue(RQ) 모델로 나뉘며 목적에 따라 선택한다.
본 장에서는 이 두 모델에 대한 간략한 설명과 설정 방법에 대하여 설명한다.
2. 기본 설정
ProObject에서는 메시지 모델을 JMS를 이용하여 제공하고 있기 때문에 JEUS의 JMS 설정이 필수적으로 필요하다. JEUS의 JMS 설정을 마친 후에는 ProObject에서도 설정한 JMS 정보를 알려주어야 ProObject에서 성공적으로 메시지 모델을 사용할 수 있다.
2.1. JMS 설정
ProObject에 JMS를 설정하기에 전에 메시지를 처리할 수 있는 다음의 기본적인 설정을 해야한다. 자세한 설명은 "JEUS MQ 안내서"를 참고한다.
매개변수 | 설명 |
---|---|
connection-factory |
JMS 연결을 생성하는데 필요한 객체로 각 서버간의 연결의 속성을 정의하는 역할을 한다. 채널 생성 정책, 생성하는 연결의 종류에 따라 다음을 설정한다.
|
Queue |
ProObject Reliable Queue를 사용하는 경우에 설정하는 JMS 리소스이다. |
Topic |
ProObject Pub/Sub을 사용하는 경우에 설정하는 JMS 리소스이다. |
2.2. ProObject JNDI 설정
설정한 정보들을 ProObject에 전달해야 정상적으로 Pub/Sub 모델이나 Reliable Queue 모델을 사용할 수 있다. 본 절에서는 커넥션 정보들을 수정하는 방법에 대해 설명한다.
ProObject에서는 각 모델에 따라서 설정하는 파일과 최상위 항목이 다르므로 주의한다.
-
Pub/Sub 모델
-
설정 파일
${APP_HOME}/config/topic.xml
-
최상위 항목 : <topic-config>
-
-
Reliable Queue 모델
-
설정 파일
${APP_HOME}/config/reliablequeue.xml
-
최상위 항목 : <queue-config>
-
다음과 같이 기본적인 항목을 설정한다.
최상위 항목은 topic인 경우에는 <topic-config>, reliablequeue인 경우에는 <queue-config>로 설정하고, 하위 <jndi-config> 항목을 사용해서 여러 JMS를 등록할 수 있다. 각 모델별 설정하는 하위 항목은 동일하다.
<topic-config(queue-config)>
<jndi-config>
<name>factory</name>
<connection-factory>factory</connection-factory>
<remote-url>192.168.0.2:9736</remote-url>
</jndi-config>
<!-- 각종 설정 -->
</topic-config(queue-config)>
태그 | 설명 |
---|---|
<name> |
ProObject에서 사용할 JNDI의 이름을 설정한다. 설정한 이름은 ProObject 내에서 서로를 구분하기 위한 용도로 이용하는 것으로 실제 JNDI와는 같지 않으니 주의하여 설정한다. |
<connection-factory> |
JMS에서 설정한 Connection-Factory 중 어떤 것을 사용할지 설정한다. JNDI 이름(Exprot-Name)과 동일하게 설정한다. |
<remote-url> |
현재 서버가 JMS 서버가 아닌 경우 접속한 JMS 서버의 주소를 설정한다. 설정하지 않은 경우 로컬 서버로 지정되며, proobject.xml의 <provide-url>의 설정을 따른다. |
3. Pub/Sub 모델 설정
Pub/Sub 모델은 특정한 Topic에 대해 Publisher가 발행한 메시지를 Subscriber들에게 전달하는 방식이다. 때문에 Pub/Sub 모델은 브로드캐스팅(Broadcasting) 방식에 적합하다.
Publisher와 Subscriber는 서로 같은 Topic를 알고 있으며, 각자 어떤 Topic로 메시지를 발행하고 구독할지를 설정한다. Pub/Sub 모델에서 Subscriber는 자신이 구독하겠다는 의사를 밝히기 전까지 이미 발행된 메시지들에 대해서는 그 어떠한 메시지도 받을 수 없으며 반드시 구독을 신청한 이후부터 발행된 메시지만 수신할 수 있다.
이러한 구조는 메시지를 구독하던 서버가 사고로 잠시 기동을 멈추는 경우에 일부 프로그래밍 모델에 부합하지 않을 수 있다. 서버가 오류에서 복구된 이후 서버는 다시 Subscriber로 참여하여 메시지를 받게 되겠지만 이미 지나간 메시지들을 받아올 수 있는 방법은 없다. 하지만 서버가 특이한 경우에는 반드시 지나간 메시지들에 대한 구독을 완료해야 하는 경우가 있는데, 이러한 경우를 위해 JMS에서는 Durable Subscriber 모델을 지원한다.
Durable Subscriber 모델은 특정 Topic에 대해 '반드시' 메시지를 수신해야 하며, JMS 서버는 Durable Subscriber가 모두 메시지를 수신받기 전까지는 메시지를 서버에서 지우지 않는다. JMS 서버는 해당 Topic의 모든 Durable Subscriber가 메시지를 수신 받았을 경우에는 메시지를 서버에서 제거하며, Durable Subscriber는 수신에 참여하지 않고 있더라도 언젠가 메시지를 전달받을 수 있음을 보장받는다.
ProObject에서는 이러한 모델을 고정 수신자라고 지칭하며, JMS의 Durable Subscriber에 대응시켜 사용한다.
3.1. JMS 설정
ProObject의 Pub/Sub 모델을 사용하기 위해서는 JMS Resource로 Topic를 미리 등록해주어야 한다.
애플리케이션 개발에서 기본적인 설정 후 JEUS WebAdmin의 [Engine] > [Resource] > [Jms Resource]에서 Topic / Durable Subscriber 정보를 등록한다. 자세한 설명은 "JEUS MQ 안내서"를 참고한다.
3.2. ProObject 설정
ProObject에서 Pub/Sub 모델을 사용하기 위해서 다음의 파일에서 JMS 리소스를 등록한다.
${APP_HOME}/config/topic.xml
JMS 리소스를 등록할 때에는 <topic-config> 아래 항목을 설정해야 한다. <jndi-config>는 ProObject JNDI 설정을 참고한다.
<topic-config>
<jndi-config>
<!-- Global JNDI Setting -->
</jndi-config>
<topic transacted="true" selector="memory_usage" acknowledge-mode="TRANSACTED" local="true">
<jndi-config>
<!-- Topic JNDI Setting -->
</jndi-config>
<name>topic1</name>
<jndi-name>topic1</jndi-name>
<subscriber>
<!-- Auto Subscriber 설정 -->
</subscriber>
</topic>
</topic-config>
-
<topic>
<topic> 항목에 속성을 지정해서 여러 JMS Topic을 등록할 수 있다.
다음은 세부 속성에 대한 설명이다.
속성 설명 selector
선택한 Topic 중에서 하위 Topic를 설정한다.
Subscriber가 하위 Topic를 선택하지 않았다면 모든 메시지를 수신받을 수 있으나, 하위 Topic이 설정된 Subscriber가 있다면 해당 하위 Topic를 포함한 Topic의 메시지 만을 구독할 수 있다.
Publisher가 발행하려는 Topic에 대해 하위 Topic이 선택되어 있다면 해당 하위 Topic로만 메시지를 발행할 수 있다.
transacted
연결을 트랜잭션으로 묶을지 여부를 설정한다.
-
true : XA 트랜잭션으로 묶는다.
-
false : XA 트랜잭션으로 묶지 않는다.
local
자기 자신의 커넥션으로 메시지를 줄지 여부를 설정한다.
-
true : 자기 자신에게도 메시지를 전달한다.
-
false : 자기 자신에게도 메시지를 전달하지 않는다.
acknowledge-mode
메시지의 수신을 확인하는 방법을 설정한다.
-
CLIENT : 클라이언트가 메시지를 수신 받았음을 ACK로 받으면 메시지를 받았다고 기록한다.
-
DUPS : 클라이언트가 메시지를 수신 받았음을 ACK로 받은 후 다시 ACK_REQUEST를 보내 다시 한 번 더 ACK를 받는 방식으로 메시지를 받았음을 기록한다.
-
TRANSACTED : 트랜잭션을 이용해 메시지를 받았음을 기록한다(사용자가 명시적으로 세션에 commit하거나, 서비스가 정상종료되지 않으면 ACK로 기록되지 않는다).
-
AUTO : 서버가 자동으로 선택하도록 한다.
다음은 하위 항목에 연결할 JMS 리소스의 정보를 설정한다.
태그 설명 <jndi-config>
Topic에서 사용할 JNDI 설정한다.
Global JNDI 설정과 다른 JNDI 설정을 하는 경우 설정한다.
<name>
ProObjectTopic의 ID를 설정한다.
ProObject Pub/Sub 모델의 경우 사용할 Topic의 아이디는 <name>에 설정한 정보를 이용한다. 따라서 어떤 Topic인지 명확히 알 수 있는 이름으로 설정한다.
<jndi-name>
JMS 리소스로 등록한 Topic의 JNDI 이름(export-name)을 설정한다.
JMS 리소스의 JNDI 이름과 다른 경우 리소스를 얻어올 수 없으므로 주의하도록 한다.
<subscriber>
Auto Subscriber를 설정한다. 자세한 설명은 Auto Subscriber/Auto Consumer 설정을 참고한다.
-
3.3. 서비스 내에서의 활용
Pub/Sub 에 대한 설정이 완료된 후에는 서비스내에서 ProObjectTopic 클래스의 정적 메소드를 이용하여 특정 Topic에 대한 Publisher / Subscriber를 만들 수 있다.
이 때 각 Publisher와 Subscriber를 만드는 API는 다음과 같다.
-
Publisher 생성
// * Publisher를 생성한다. ProObjectTopicPublisher publisher = ProObjectTopic.getTopicPublisher(TOPIC_NAME);
-
Subscriber 생성
// * Subscriber를 생성한다. ProObjectTopicSubscriber subscriber = ProObjectTopic.getTopicSubscriber(TOPIC_NAME);
3.3.1. 메시지 발행
메시지를 발행할 때에는 ProObjectTopicPublisher를 통해 메시지를 발행하되, byte[]와 Map<String, Object>, String을 인자로서 넘겨주어 자동으로 JMS Message를 만들게 하거나, JMS Message 객체를 직접 만들어 발행하는 것이 가능하다.
ProObjectTopicPublisher에서는 다음과 같이 메시지를 발행시킬 수 있다.
ProObjectTopicPublisher publisher = ProObjectTopic.getTopicPublisher(TOPIC_NAME);
Message jmsMessageObject = null;
publisher.publish("Message");
publisher.publish("Message".getBytes());
publisher.publish(new HashMap<String, Object>());
publisher.publish(jmsMessageObject);
3.3.2. 메시지 구독
메시지를 구독할 때에는 JMS Message 객체를 Synchronous하게 받을 수 있는 receive() API와 Asyncrhnous하게 응답을 받을 수 있는 subscriber() API가 제공된다.
receive()의 경우에는 다음과 같이 사용하며, 타임아웃 시간을 지정하여 최대 일정 시간 동안 기다리는 것이 가능하다.
ProObjectTopicSubscriber subscriber = ProObjectTopic.getTopicSubscriber(TOPIC_NAME);
/* 응답을 무한히 대기한다 */
Message message = subscriber.receive();
/* 60 초동안 응답이 오지 않으면 null 을 반환한다 */
Message message2 = subscriber.receive(/*timeout*/60);
응답을 비동기적으로 이용하고 싶은 경우에는 다음과 같이 코드를 작성하도록 한다.
ProObjectTopicSubscriber subscriber = ProObjectTopic.getTopicSubscriber(TOPIC_NAME);
WaitObject wo = subscriber.subscribe();
이 때 반환된 WaitObject는 ServiceManager의 rcall과 getReply 등의 메소드를 이용하여 스레드의 점유없이 메시지의 전달을 기다리거나, 동기적으로 응답을 기다리는 것이 가능하다.
4. Reliable Queue 모델 설정
Reliable Queue(이하 RQ)는 큐(Queue)를 여러 서버가 공유해서 사용하는 개념이다.
일반적으로 메모리에만 상주하여 서버가 다운되면 저장되어 있던 모든 내용들이 사라지는 큐와는 달리 데이터베이스나 파일 등에 현재 저장된 큐의 내용을 기록해둠으로써 서버가 다운되었을 경우 큐의 내용이 사라지지 않도록 보장할 수 있다. 또한 Consumer가 큐의 내용을 제대로 얻지 못했을 때 정상적으로 큐의 내용을 읽을 수 있도록 보장함으로써 높은 신뢰성을 보장한다.
Reliable Queue는 일반적인 큐와 동일하게 사용하고, JMS 서버가 메시지를 받아 큐에 내용을 기록하고 Consumer 중 하나에게 완벽하게 '전달’되었음을 보장해 준다.
4.1. JMS 설정
ProObject의 Reliable Queue 모델을 사용하기 위해서는 JMS Resource로 큐(Queue)를 미리 등록해주어야 한다.
애플리케이션 개발에서 기본적인 설정 후 JEUS JWebAdmin의 [Engine] > [Resource] > [Jms Resource]에서 Queue 정보를 등록한다. 자세한 설명은 "JEUS MQ 안내서"를 참고한다.
4.2. ProObject 설정
ProObject에서 Reliable Queue 모델을 사용하기 위해서 다음의 파일에서 JMS 리소스를 등록한다.
${APP_HOME}/config/reliablequeue.xml
JMS 리소스를 등록할 때에는 <queue-config> 아래 항목을 설정해야 한다. <jndi-config>는 ProObject JNDI 설정을 참고한다.
<queue-config>
<jndi-config>
<!-- Global JNDI Setting -->
</jndi-config>
<queue transacted="true" selector="memory_usage" acknowledge-mode="TRANSACTED">
<jndi-config>
<!-- Topic JNDI Setting -->
</jndi-config>
<name>queue1</name>
<jndi-name>queue1</jndi-name>
<subscriber>
<!-- Auto Consumer 설정 -->
</subscriber>
</queue>
</queue-config>
-
<queue>
<queue> 항목에 속성을 지정해서 여러 큐를 등록할 수 있다.
다음은 세부 속성에 대한 설명이다.
속성 설명 selector
선택한 큐(Queue) 중에서 받을 필터를 적용한다.
Consumer가 하위 Topic를 선택하지 않았다면 모든 메시지를 수신받을 수 있으나, 하위 Topic이 설정된 Consumer가 있다면 해당 하위 Topic를 포함한 Topic의 메시지 만을 수신할 수 있다.
Producer가 발행하려는 Topic에 대해 하위 Topic이 선택되어 있다면 해당 하위 Topic로만 메시지를 송신할 수 있다.
transacted
연결을 트랜잭션으로 묶을지 여부를 설정한다.
-
true : XA 트랜잭션으로 묶는다.
-
false : XA 트랜잭션으로 묶지 않는다.
acknowledge-mode
메시지의 수신을 어떻게 확인할지에 대한 타입을 설정한다.
-
CLIENT : 클라이언트가 메시지를 수신 받았음을 ACK로 받으면 메시지를 받았다고 기록한다.
-
DUPS : 클라이언트가 메시지를 수신 받았음을 ACK로 받은 후 다시 ACK_REQUEST를 보내 다시 한 번 더 ACK를 받는 방식으로 메시지를 받았음을 기록한다.
-
TRANSACTED : 트랜잭션을 이용해 메시지를 받았음을 기록한다(사용자가 명시적으로 Session에 commit하거나, 서비스가 정상종료되지 않으면 ACK로 기록되지 않는다).
-
AUTO : 서버가 자동으로 선택하도록 한다.
하위 항목에 연결할 JMS 리소스의 설정 정보는 topic과 동일하므로 자세한 설명은 Pub/Sub 모델 설정를 참고한다.
-
4.3. 서비스 내에서의 활용
Reliable Queue에 대한 설정이 완료된 후에는 서비스내에서 MessageQueue 클래스의 정적 메소드를 이용하여 특정 Topic에 대한 Producer/Consumer를 만들 수 있다.
이 때 각 Producer와 Consumer를 만드는 API 는 다음과 같다.
-
Producer 생성
// * Producer를 생성한다. MessageQueueProducer producer = MessageQueue.getQueueProducer(QUEUE_NAME);
-
Subscriber 생성
// * Subscriber를 생성한다. MessageQueueConsumer consumer = MessageQueue.getQueueConsumer(QUEUE_NAME);
4.3.1. 메시지 생성
메시지를 생성할 때에는 MessageQueueProducer를 통해 메시지를 생성하되, byte[]와 Map<String, Object>, String을 인자로서 넘겨주어 자동으로 JMS Message를 만들게 하거나, JMS Message 객체를 직접 만들어 발행하는 것이 가능하다.
MessageQueueProducer에서는 다음과 같이 메시지를 생성시킬 수 있다.
MessageQueueProducer producer = MessageQueue.getQueueProducer(QUEUE_NAME);
Message jmsMessageObject = null;
producer.push("Message");
producer.push("Message".getBytes());
producer.push(new HashMap<String, Object>());
producer.push(jmsMessageObject);
4.3.2. 메시지 수신
메시지를 수신할 때에는 JMS Message 객체를 Synchronous하게 받을 수 있는 wait() API와 Asyncrhnous하게 응답을 받을 수 있는 popNoWait() API가 제공된다.
receive()의 경우에는 다음과 같이 사용하며, 타임아웃 시간을 지정하여 최대 일정 시간 동안 기다리는 것이 가능하다.
MessageQueueConsumer consumer = MessageQueue.getQueueConsumer(QUEUE_NAME);
/* 응답을 무한히 대기한다 */
Message message = consumer.pop();
/* 60 초동안 응답이 오지 않으면 null 을 반환한다 */
Message message2 = consumer.pop(/*timeout*/60);
응답을 비동기적으로 이용하는 경우 다음과 같이 코드를 작성하도록 한다.
MessageQueueConsumer consumer = MessageQueue.getQueueConsumer(QUEUE_NAME);
WaitObject wo = consumer.popNoWait();
이때 반환된 WaitObject는 ServiceManager의 rcall과 getReply 등의 메소드를 이용하여 스레드의 점유없이 메시지의 전달을 기다리거나, 동기적으로 응답을 기다리는 것이 가능하다.
5. Auto Subscriber/Auto Consumer 설정
JMS 연동을 마친 후에는 Auto Subscriber/Auto Consumer를 설정할 수 있다.
ProObject에서는 JMS를 통해 받은 메시지를 하나의 요청으로 바라보며, 설정에 따라 단순 이벤트로 처리할지 일반적인 서비스로 처리할지를 지정하도록 할 수 있다. 호출된 이벤트와 서비스는 일반적인 웹이나 TCP로 호출한 서비스와 크게 다르지 않으므로, 일반적인 연동 API를 비롯한 여러 ProObject의 API를 사용할 수 있다.
Topic인 경우에는 Auto Subscriber라고 하며, Queue인 경우에는 Auto Consumer라고 한다. 각각의 의미는 동일하고 옵션에만 차이가 있다.
5.1. Auto Subscriber 설정
특정 Topic에 대해 구독하는 경우 다음과 같이 설정한다.
<subscriber>
<!-- 설정한 Topic에 대한 메시지가 발행되면 test 애플리케이션의 example 서비스 그룹에 속한
TopicAutoSubscriber 서비스를 호출한다. -->
<name>service</name>
<service>test.example.TopicAutoSubscriber</service>
</subscriber>
<subscriber>
<!-- 설정한 Topic에 대한 메시지가 발행되면 20번 ID를 지니는 이벤트를 발생시킨다. -->
<name>event</name>
<event-type>20</event-type>
</subscriber>
<subscriber>
<!-- 설정한 Topic에 대한 메시지가 발행되면 test 애플리케이션의 example 서비스 그룹에 속한
TopicReliableAutoSubscriber 서비스를 호출한다. 이때 durableTopic1이라
JNDI 이름을 지니는 JMS DurableSubscriber 를 이용한다. -->
<name>reliable-service</name>
<service>test.example.TopicReliableAutoSubscriber</service>
<reliable-subscriber>durableTopic1</reliable-subscriber>
</subscriber>
다음은 각 항목에 대한 설명이다.
항목 | 설명 |
---|---|
<name> |
Auto Subscriber를 ProObject가 구분하기 위해 붙이는 아이디이다. 설정한 아이디는 해당 파일 내에서 중복되지 않는 유일한 값이어야 한다. |
<service> |
설정한 Topic에 대한 메시지가 발행되면 호출할 서비스의 이름을 설정한다. 서비스를 연동할 때와 같이 다음의 형식으로 설정한다.
입력과 출력이 고정되어 있음을 유의한다.
|
<event-type> |
설정한 Topic에 대한 메시지가 발행되면 발생시킬 이벤트의 타입을 설정한다. 다만 이벤트의 클래스는 com.tmax.proobject.engine.messagequeue.MessageEvent 클래스로 고정된다. |
<reliable-subscriber> |
고정 Subscriber를 지정하는 경우 지정하며, 필요하지 않은 경우 생략해도 무방하다. 설정한 JMS Durable Subscriber의 JNDI 이름을 설정한다. |
5.2. Auto Consumer 설정
Reliable Queue에서 특정 큐에서 메시지를 받는 경우 다음과 같이 설정한다.
<consumer>
<!-- 설정한 큐에서 메시지를 받으면 test 애플리케이션의
example 서비스 그룹에 속한 QueueAutoConsumer 서비스를 호출한다.-->
<name>service</name>
<service>test.example.QueueAutoConsumer</service>
</consumer>
<consumer>
<!-- 설정한 큐에서 메시지를 받으면 21번 ID를 지니는 이벤트를 발생시킨다. -->
<name>event</name>
<event-type>21</event-type>
</consumer>
다음은 각 항목에 대한 설명이다.
항목 | 설명 |
---|---|
<name> |
Auto Consumer를 ProObject가 구분하기 위해 붙이는 아이디이다. 설정한 아이디는 해당 파일 내에서 중복되지 않는 유일한 값이어야 한다. |
<service> |
설정한 큐에 대한 메시지가 발행되면 호출할 서비스의 이름을 설정한다. 서비스를 연동할 때와 같이 다음의 형식으로 설정한다.
입력과 출력이 고정되어 있음을 유의한다.
|
<event-type> |
설정한 큐에 대한 메시지가 발행되면 발생시킬 이벤트의 타입을 설정한다. 다만 이벤트의 클래스는 com.tmax.proobject.engine.messagequeue.MessageEvent 클래스로 고정된다. |