はじめに
IBM MQの公式チュートリアルを引き続き進める。今回は以下の記事、javaアプリからMQI Client経由でIBM MQにエンキュー/デキューする手順をやってみる。
環境は前回作ったコンテナ環境を引き続き利用する。
完成イメージは以下。
補足 : MQIチャネルでの接続について
MQのキューのやり取りの方式は、
- メッセージチャネル
- 2つのMQキューマネージャー同士が接続。
- 単方向チャネル。
- 送信元キューマネージャーからチャネル開始・終了する
- MQ虎の巻でいうところの「キューマネージャー接続」に相当
- MQIチャネル
- アプリケーションがMQIclientを介して、1つのMQキューマネージャーと接続。
- 双方向チャネル。
- アプリケーション側からコネクションを確立・切断する
- MQ虎の巻でいうところの「MQクライアント接続」に相当
の主に2つが存在するが、今回は後者に相当する。双方向チャネルということで、Javaアプリ始動でキューマネージャーとの間にMQIチャネルを作り、その接続の中でエンキューとデキューを続けざまに行うことを目指す。コネクション確立・切断がどちらもjavaアプリ側始動であるさまも同時に確認する。
↓公式のMQIチャネルの図。(左右逆だが)これが今回想定する接続形式。
↓MQ虎の巻より「MQクライアント接続」の図。本来はこういう(クライアントAが入れて、クライアントBが取る)使い方と思うが、今回は検証用途でエンキューもデキューも同一アプリから行う。
MQの構築
この手順は、公式チュートリアルに従ってMQコンテナを立てていれば不要。
公式手順をそのまま実施すると、コンテナ起動時に、デフォルトでキューマネージャ・リスナ・サーバ接続チャネル・ローカルキューが作成されているため。
ここでは、勉強目的として一部手動で環境構築する手順を記載する。
MQコンテナの起動
前回の手順とほぼ同様だが、
--env MQ_DEV=false
とすることで、デフォルト作成されるオブジェクトが作成されないようにする。
docker pull icr.io/ibm-messaging/mq:9.2.4.0-r1
docker volume create qm1data
docker run \
--name QM1 \
--detach \
--rm \
--volume qm1data:/mnt/mqm \
--publish 1414:1414 \
--publish 9443:9443 \
--env LICENSE=accept \
--env MQ_QMGR_NAME=QM1 \
--env MQ_DEV=false \ #★
--env MQ_APP_USER=app \
--env MQ_APP_PASSWORD=passw0rd \
--env MQ_ADMIN_USER=admin \
--env MQ_ADMIN_PASSWORD=passw0rd \
icr.io/ibm-messaging/mq:9.2.4.0-r1
# コンテナにログイン
docker exec -it QM1 bash
キューマネージャ・リスナの存在確認
デフォルト作成抑止しても、それでもキューマネージャ・リスナは作成される。
リスナを新規作成するとしてポート転送等がまた面倒なため今回は作成確認のみとする。
# キューマネージャー存在、起動中を確認
bash-4.4$ dspmq
QMNAME(QM1) STATUS(Running)
# リスナ存在、起動中を確認
bash-4.4$ echo "DISPLAY LSSTATUS(*) ALL" | runmqsc QM1
5724-H72 (C) Copyright IBM Corp. 1994, 2021.
Starting MQSC for queue manager QM1.
1 : DISPLAY LSSTATUS(*) ALL
AMQ8631I: Display listener status details.
LISTENER(SYSTEM.LISTENER.TCP.1) STATUS(RUNNING)
PID(316) STARTDA(2024-12-14)
STARTTI(12.24.11) DESCR( )
TRPTYPE(TCP) CONTROL(QMGR)
IPADDR(*) PORT(1414)
BACKLOG(100)
One MQSC command read.
No commands have a syntax error.
All valid MQSC commands were processed.
キュー・チャネルの作成
bash-4.4$ runmqsc QM1
5724-H72 (C) Copyright IBM Corp. 1994, 2021.
Starting MQSC for queue manager QM1.
# ローカルキュー作成
define qlocal(DEV.QUEUE.1)
1 : define qlocal(DEV.QUEUE.1)
AMQ8006I: IBM MQ queue created.
# ローカルキュー作成確認
display qlocal(DEV*) ALL
2 : display qlocal(DEV*) ALL
AMQ8409I: Display Queue details.
QUEUE(DEV.QUEUE.1) TYPE(QLOCAL)
ACCTQ(QMGR) ALTDATE(2024-12-14)
ALTTIME(12.25.43) BOQNAME( )
BOTHRESH(0) CLUSNL( )
CLUSTER( ) CLCHNAME( )
CLWLPRTY(0) CLWLRANK(0)
CLWLUSEQ(QMGR) CRDATE(2024-12-14)
CRTIME(12.25.43) CURDEPTH(0)
CUSTOM( ) DEFBIND(OPEN)
DEFPRTY(0) DEFPSIST(NO)
DEFPRESP(SYNC) DEFREADA(NO)
DEFSOPT(SHARED) DEFTYPE(PREDEFINED)
DESCR( ) DISTL(NO)
GET(ENABLED) HARDENBO
IMGRCOVQ(QMGR) INITQ( )
IPPROCS(0) MAXDEPTH(5000)
MAXMSGL(4194304) MAXFSIZE(DEFAULT)
MONQ(QMGR) MSGDLVSQ(PRIORITY)
NOTRIGGER NPMCLASS(NORMAL)
OPPROCS(0) PROCESS( )
PUT(ENABLED) PROPCTL(COMPAT)
QDEPTHHI(80) QDEPTHLO(20)
QDPHIEV(DISABLED) QDPLOEV(DISABLED)
QDPMAXEV(ENABLED) QSVCIEV(NONE)
QSVCINT(999999999) RETINTVL(999999999)
SCOPE(QMGR) SHARE
STATQ(QMGR) STREAMQ( )
STRMQOS(BESTEF) TRIGDATA( )
TRIGDPTH(1) TRIGMPRI(0)
TRIGTYPE(FIRST) USAGE(NORMAL)
# サーバ接続チャネル作成
define channel(DEV.APP.SVRCONN) chltype(svrconn) MCAUSER(app) replace
3 : define channel(DEV.APP.SVRCONN) chltype(svrconn) MCAUSER(app) replace
AMQ8014I: IBM MQ channel created.
# サーバ接続チャネル作成確認
display channel(DEV*) all
4 : display channel(DEV*) all
AMQ8414I: Display Channel details.
CHANNEL(DEV.APP.SVRCONN) CHLTYPE(SVRCONN)
ALTDATE(2024-12-14) ALTTIME(12.26.14)
CERTLABL( ) COMPHDR(NONE)
COMPMSG(NONE) DESCR( )
DISCINT(0) HBINT(300)
KAINT(AUTO) MAXINST(999999999)
MAXINSTC(999999999) MAXMSGL(4194304)
MCAUSER(APP) MONCHL(QMGR)
RCVDATA( ) RCVEXIT( )
SCYDATA( ) SCYEXIT( )
SENDDATA( ) SENDEXIT( )
SHARECNV(10) SSLCAUTH(REQUIRED)
SSLCIPH( ) SSLPEER( )
TRPTYPE(TCP)
ユーザに権限を追加
appユーザが、キューマネージャに接続、キューにput,get する権限を追加する。
※ユーザー自体はコンテナ構築時に作成されている模様?ただどこに作成されているのわからず。
# appユーザがキューマネージャに接続する権限を追加
bash-4.4$ setmqaut -m QM1 -t qmgr -p app +inq +connect
The setmqaut command completed successfully.
# 想定通りQM1に対してconnectする権限が追加されたことを確認
bash-4.4$ dspmqaut -m QM1 -t qmgr -p app
Entity app has the following authorizations for object QM1:
inq
connect
# appユーザがキューを操作する権限を追加
bash-4.4$ setmqaut -m QM1 -t queue -n DEV.QUEUE.1 -p app +get +browse +put +inq
The setmqaut command completed successfully.
# 想定通りキュー操作権限がついたことを確認。デフォルトで最初からいくつかついていた模様?
bash-4.4$ dspmqaut -m QM1 -t queue -n DEV.QUEUE.1 -p app
Entity app has the following authorizations for object DEV.QUEUE.1:
get
browse
put
inq
set
dlt
chg
dsp
passid
passall
setid
setall
clr
javaアプリの実装
ここからが本題。
jdkのインストール
jdk11が必要とのことで、以下を参考に導入する。
せっかくなのでjdkの取得元は、チュートリアル内でIBMから紹介されているIBM Semeru を利用。
# IBM semaru のtarをwgetして展開
[root@my-instance ~]# mkdir /usr/java
[root@my-instance ~]# cd /usr/java
[root@my-instance java]# wget https://github.com/ibmruntimes/semeru11-binaries/releases/download/jdk-11.0.25%2B9_openj9-0.48.0/ibm-semeru-open-jdk_x64_linux_11.0.25_9_openj9-0.48.0.tar.gz
[root@my-instance java]# tar zxvf ibm-semeru-open-jdk_x64_linux_11.0.25_9_openj9-0.48.0.tar.gz
# bashrc に以下を記載
[root@my-instance tmp]# cat ~/.bashrc | tail -3
export JAVA_HOME=/usr/java/jdk-11.0.25+9/
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib
[root@my-instance ~]# source .bashrc
[root@my-instance ~]# java --version
openjdk 11.0.25 2024-10-15
IBM Semeru Runtime Open Edition 11.0.25.0 (build 11.0.25+9)
Eclipse OpenJ9 VM 11.0.25.0 (build openj9-0.48.0, JRE 11 Linux amd64-64-Bit Compressed References 20241107_1233 (JIT enabled, AOT enabled)
OpenJ9 - 1d5831436e
OMR - d10a4d553
JCL - edded3f65c based on jdk-11.0.25+9)
資材のダウンロード
[root@my-instance ~]# mkdir MQClient
[root@my-instance ~]# cd MQClient
# 資材のDL。MQのclient、JMSのAPIと、後で使うのであろうjsonのライブラリをダウンロード
[root@my-instance MQClient]# curl -o com.ibm.mq.allclient-9.3.0.0.jar https://repo1.maven.org/maven2/com/ibm/mq/com.ibm.mq.allclient/9.3.0.0/com.ibm.mq.allclient-9.3.0.0.jar
[root@my-instance MQClient]# curl -o javax.jms-api-2.0.1.jar https://repo1.maven.org/maven2/javax/jms/javax.jms-api/2.0.1/javax.jms-api-2.0.1.jar
[root@my-instance MQClient]# curl -o json-20220320.jar https://repo1.maven.org/maven2/org/json/json/20220320/json-20220320.jar
資材をDLする。
-
com.ibm.mq.allclient-9.3.0.0.jar
がくだんのIBM MQ MQI Clientと思われる。 -
javax.jms-api-2.0.1.jar
はJMS(java messaging service) なるメッセージングサービスを利用するための標準API。
JMSの仕様に沿ったメッセージングAPIの実装の一つとして(?)MQI Clientがある、という立ち位置。
先に↓のサンプルコードの一部を抜粋すると、
最初に以下のようにJMSの引数にMQI Client(WMQConstants.WMQ_PROVIDER
)を与える形でJMSのコネクションを作成し、
// Create a connection factory
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
以降はJMSのAPIを叩く形で通信できる。
// Create JMS objects
context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
(略)
//メッセージをエンキュー
producer = context.createProducer();
producer.send(destination, message);
サンプルコードのダウンロード
[root@my-instance MQClient]# mkdir -p com/ibm/mq/samples/jms
[root@my-instance MQClient]# cd com/ibm/mq/samples/jms
[root@my-instance jms]# pwd
/root/MQClient/com/ibm/mq/samples/jms
# サンプルコードのDL
[root@my-instance jms]# curl -o JmsPutGet.java https://raw.githubusercontent.com/ibm-messaging/mq-dev-samples/master/gettingStarted/jms/com/ibm/mq/samples/jms/JmsPutGet.java
コードの詳細は以下を参照。
main関数内だけ抜粋したものが以下。対象キューに「Your lucky number today is XXX」というメッセージをPUTして、その後すぐGETする処理。putされた直後、デキュー→チャネル切断される前のキューとチャネルの状態が見たいので、★箇所のみ追記している
public static void main(String[] args) {
// Sanity check main() arguments and warn user
if (args.length > 0) {
System.out.println("\n!!!! WARNING: You have provided arguments to the Java main() function. JVM arguments (such as -Djavax.net.ssl.trustStore) must be passed before the main class or .jar you wish to run.\n\n");
Console c = System.console();
System.out.println("Press the Enter key to continue");
c.readLine();
}
// Variables
JMSContext context = null;
Destination destination = null;
JMSProducer producer = null;
JMSConsumer consumer = null;
try {
// Create a connection factory
// JMSの引数にMQIClientを渡し、JMSのconnection factoryを生成する
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
// Set the properties
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, HOST);
cf.setIntProperty(WMQConstants.WMQ_PORT, PORT);
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, CHANNEL);
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, QMGR);
cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "JmsPutGet (JMS)");
cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
cf.setStringProperty(WMQConstants.USERID, APP_USER);
cf.setStringProperty(WMQConstants.PASSWORD, APP_PASSWORD);
//cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER");
// Create JMS objects
// JMS経由でキューマネージャーと接続。
// この時点で、javaアプリ(これ)側からMQIチャネルが確立されるものと思われる
context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
long uniqueNumber = System.currentTimeMillis() % 1000;
TextMessage message = context.createTextMessage("Your lucky number today is " + uniqueNumber);
// メッセージをエンキュー
producer = context.createProducer();
producer.send(destination, message);
System.out.println("Sent message:\n" + message);
// エンキュー後、コネクション確立中の状態を確認したいので1分sleepを追記
try {
System.out.println("60秒停止します");
Thread.sleep(60000);
System.out.println("一時停止を解除しました。");
} catch (InterruptedException e){
e.printStackTrace();
}
// デキュー操作
consumer = context.createConsumer(destination); // autoclosable
String receivedMessage = consumer.receiveBody(String.class, 15000); // in ms or 15 seconds
System.out.println("\nReceived message:\n" + receivedMessage);
// チャネルをクローズ。今回もjavaアプリからMQIチャネルを切断
context.close();
recordSuccess();
} catch (JMSException jmsex) {
recordFailure(jmsex);
}
System.exit(status);
}
接続情報のみ修正する。
[root@my-instance jms]# cat JmsPutGet.java
(略)
// Create variables for the connection to MQ
private static final String HOST = "localhost"; // Host name or IP address
private static final int PORT = 1414; // Listener port for your queue manager
private static final String CHANNEL = "DEV.APP.SVRCONN"; // Channel name
private static final String QMGR = "QM1"; // Queue manager name
private static final String APP_USER = "app"; // User name that application uses to connect to MQ
private static final String APP_PASSWORD = "passw0rd"; // Password that the application uses to connect to MQ
private static final String QUEUE_NAME = "DEV.QUEUE.1"; // Queue that the application uses to put and get messages to and from
コンパイル・実行
先ほどの資材をクラスパスに指定してコンパイル・実行。
期待通りエンキュー・デキューが実行できた。
[root@my-instance MQClient]# cd /root/MQClient/
[root@my-instance MQClient]# ll
合計 8284
drwxr-xr-x. 3 root root 17 12月 8 05:48 com
-rw-r--r--. 1 root root 8339503 12月 8 05:45 com.ibm.mq.allclient-9.3.0.0.jar
-rw-r--r--. 1 root root 64009 12月 8 05:45 javax.jms-api-2.0.1.jar
-rw-r--r--. 1 root root 70939 12月 8 05:45 json-20220320.jar
# コンパイル
[root@my-instance MQClient]# javac -cp ./com.ibm.mq.allclient-9.3.0.0.jar:./javax.jms-api-2.0.1.jar:./json-20220320.jar com/ibm/mq/samples/jms/JmsPutGet.java
[root@my-instance MQClient]# ll com/ibm/mq/samples/jms/
合計 24
-rw-r--r--. 1 root root 4450 12月 8 06:00 JmsPutGet.class #classファイルが生成されている
-rw-r--r--. 1 root root 5726 12月 8 05:55 JmsPutGet.java
-rw-r--r--. 1 root root 5738 12月 8 05:48 JmsPutGet.java_bk
# 実行。期待通りエンキュー・デキューが実行できた。
[root@my-instance MQClient]# java -cp ./com.ibm.mq.allclient-9.3.0.0.jar:./javax.jms-api-2.0.1.jar:./json-20220320.jar:. com.ibm.mq.samples.jms.JmsPutGet
Sent message:
JMSMessage class: jms_text
JMSType: null
JMSDeliveryMode: 2
JMSDeliveryDelay: 0
JMSDeliveryTime: 1733606825651
JMSExpiration: 0
JMSPriority: 4
JMSMessageID: ID:414d5120514d312020202020202020203b6a5467016a0240
JMSTimestamp: 1733606825651
JMSCorrelationID: null
JMSDestination: queue:///DEV.QUEUE.1
JMSReplyTo: null
JMSRedelivered: false
JMSXAppID: JmsPutGet (JMS)
JMSXDeliveryCount: 0
JMSXUserID: app
JMS_IBM_PutApplType: 28
JMS_IBM_PutDate: 20241207
JMS_IBM_PutTime: 21270581
Your lucky number today is 513 ★送信成功
60秒停止します ★この間にチャネル状態・滞留メッセージの内容を確認
一時停止を解除しました。
Received message:
Your lucky number today is 513 ★同メッセージの受信も成功
SUCCESS
エンキュー直後のキュー・チャネルの状態
エンキュー直後、60秒の停止中に確認したキューの状態が以下。
期待通り「Your lucky number today is 513」のメッセージがエンキューされている様子がわかる。
この時、チャネルの状態を確認すると、MQI clientから接続を確立されたことで、キューマネージャー側から見ても接続が実行中の状態になっている。
デキューまで完了した後のキュー・チャネルの状態
デキューしたのでメッセージは想定通りなくなっている
チャネル状態は再度非アクティブ状態に。context.close();
でMQI client側からチャネルをクローズしたことを契機に非アクティブ状態になるものと思われる。
補足
エラーは主に以下から確認できる。以下はappユーザーへ権限付与する前に疎通しようとして出力されたエラー。
# appユーザーへ権限付与する前に疎通しようとして認証エラー。
[root@my-instance MQClient]# java -cp ./com.ibm.mq.allclient-9.3.0.0.jar:./javax.jms-api-2.0.1.jar:./json-20220320.jar:. com.ibm.mq.samples.jms.JmsPutGet
Exception in thread "main" com.ibm.msg.client.jms.DetailedJMSSecurityRuntimeException: JMSWMQ2013: 接続モード 'Client' およびホスト名 'localhost(1414)' のキュー・マネージャー 'QM1' に指定されたセキュリティー認証は無効でした。
指定したユーザー名およびパスワードが接続先のキュー・マネージャー上で正しいかどうかを確認してください。 詳しくは、キュー・マネージャーのエラー・ログ、および IBM 資料内の「Securing IBM MQ」トピックを参照してください。
at com.ibm.msg.client.jms.DetailedJMSSecurityException.getUnchecked(DetailedJMSSecurityException.java:270)
at com.ibm.msg.client.jms.internal.JmsErrorUtils.convertJMSException(JmsErrorUtils.java:173)
at com.ibm.msg.client.jms.admin.JmsConnectionFactoryImpl.createContext(JmsConnectionFactoryImpl.java:515)
at com.ibm.mq.samples.jms.JmsPutGet.main(JmsPutGet.java:106)
Caused by: com.ibm.mq.MQException: JMSCMQ0001: IBM MQ 呼び出しは完了コード '2' ('MQCC_FAILED')、理由 '2035' ('MQRC_NOT_AUTHORIZED') で失敗しました。
at com.ibm.msg.client.wmq.common.internal.Reason.createException(Reason.java:203)
at com.ibm.msg.client.wmq.internal.WMQConnection.<init>(WMQConnection.java:458)
at com.ibm.msg.client.wmq.factories.WMQConnectionFactory.createV7ProviderConnection(WMQConnectionFactory.java:8683)
at com.ibm.msg.client.wmq.factories.WMQConnectionFactory.createProviderConnection(WMQConnectionFactory.java:8023)
at com.ibm.msg.client.jms.admin.JmsConnectionFactoryImpl._createConnection(JmsConnectionFactoryImpl.java:322)
at com.ibm.msg.client.jms.admin.JmsConnectionFactoryImpl.createContext(JmsConnectionFactoryImpl.java:481)
... 1 more
# コンテナのログにも認証エラーのむねが出力されていた
[root@my-instance MQClient]# docker logs QM1 | tail
2024-12-14T12:31:33.455Z AMQ8077W: Entity 'app' has insufficient authority to access object QM1 [qmgr]. [CommentInsert1(app), CommentInsert2(QM1 [qmgr]), CommentInsert3(connect)]
2024-12-14T12:31:33.465Z AMQ9557E: Queue Manager User ID initialization failed for 'app'. [ArithInsert1(2), ArithInsert2(2035), CommentInsert1(app), CommentInsert2(app), CommentInsert3(app)]
# コンテナにログイン
[root@my-instance MQClient]# docker exec -it QM1 bash
# キューマネージャのエラーログに認証エラーのむね詳しく出力されていた
bash-4.4$ ll /var/mqm/qmgrs/QM1/errors/AMQERR01.LOG
-rw-rw----. 1 1001 root 50226 Dec 14 12:31 /var/mqm/qmgrs/QM1/errors/AMQERR01.LOG
12/14/24 12:31:33 - Process(285.17) User(mqm) Program(amqzlaa0)
Host(326e7b5e7739) Installation(Installation1)
VRMF(9.2.4.0) QMgr(QM1)
Time(2024-12-14T12:31:33.455Z)
CommentInsert1(app)
CommentInsert2(QM1 [qmgr])
CommentInsert3(connect)
AMQ8077W: Entity 'app' has insufficient authority to access object QM1 [qmgr].
EXPLANATION:
The specified entity is not authorized to access the required object. The
following requested permissions are unauthorized: connect
ACTION:
Ensure that the correct level of authority has been set for this entity against
the required object, or ensure that the entity is a member of a privileged
group.
----- amqzfubx.c : 1640 -------------------------------------------------------
12/14/24 12:31:33 - Process(440.5) User(mqm) Program(amqrmppa)
Host(326e7b5e7739) Installation(Installation1)
VRMF(9.2.4.0) QMgr(QM1)
Time(2024-12-14T12:31:33.465Z)
ArithInsert1(2) ArithInsert2(2035)
CommentInsert1(app)
CommentInsert2(app)
CommentInsert3(app)
AMQ9557E: Queue Manager User ID initialization failed for 'app'.
EXPLANATION:
The call to initialize the User ID 'app' failed with CompCode 2 and Reason
2035. If an MQCSP block was used, the User ID in the MQCSP block was 'app'. If
a userID flow was used, the User ID in the UID header was 'app' and any CHLAUTH
rules applied prior to user adoption were evaluated case-sensitively against
this value.
ACTION:
Correct the error and try again.
----- cmqxrsrv.c : 2595 -------------------------------------------------------
# こちらは起動時の出力のみだったためあまり参考にならず
bash-4.4$ ll /var/mqm/errors/AMQERR01.LOG
-rw-rw-r--. 1 1001 root 1813 Dec 14 12:24 /var/mqm/errors/AMQERR01.LOG
おわりに
MQIクライアント接続でのMQ操作を行った。
次回は、もう一つの接続形態である、メッセージチャネルを経由したキューの伝搬を確認する。