上节按照生产环境的结构对集群进行了搭建,这节使用java代码对集群进行同步消息与异步消息j接收的测试
1. 集群节点接收消息的代码
为方便测试,添加配置文件工具类,读取配置文件顺序的优先级如下:1.程序打包后jar文件同级文件夹config下的config.properties文件。2.程序打包后jar文件的同级config.properties文件。3.classpath:config.properties
package com.qinke.mqcluster;
import com.ibm.mq.*;
import com.qinke.mqcluster.utils.ConfigUtil;
import java.io.IOException;
public class CCQMReceiver {
private static String host;
private static Integer port;
private static String channel;
private static String qmgr;
private static String asyncQueueName;
private static String syncQueueName;
private static void init() {
host = ConfigUtil.getConfig("HOST");
port = Integer.valueOf(ConfigUtil.getConfig("PORT"));
channel = ConfigUtil.getConfig("CHANNEL");
qmgr = ConfigUtil.getConfig("QMGR");
asyncQueueName = ConfigUtil.getConfig("ASYNCQUEUE");
syncQueueName = ConfigUtil.getConfig("SYNCQUEUE");
StringBuffer lisLog = new StringBuffer();
lisLog.append(host)
.append(" : ")
.append(port)
.append(" : ")
.append(channel)
.append(" : ")
.append(qmgr)
.append(" : ")
.append(asyncQueueName)
.append(" : ")
.append(syncQueueName);
//配置要使用的队列管理器的信息,这里使用队列管理器CQM1的信息
MQEnvironment.hostname = host;
MQEnvironment.port = port;
MQEnvironment.channel = channel;
System.out.println("初始化");
System.out.println("监听参数:" + lisLog.toString());
}
//异步消息接收
private static void asyncReceive() {
try {
//创建队列管理器对象,在实例化的时候会隐式连接队列管理器CQM1
MQQueueManager mqQueueManager = new MQQueueManager(qmgr);
//定义打开方式
int openOption = MQC.MQOO_INPUT_SHARED; //以读取方式打开
//直接循环,每次发10条消息
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(asyncQueueName, openOption);
//定义获取消息时的一些操作
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = gmo.options + MQC.MQGMO_WAIT; //无消息时等待
gmo.options = gmo.options + MQC.MQGMO_FAIL_IF_QUIESCING; //队列管理器停止时退出等待
gmo.waitInterval = 300000; //无消息时等待时长
System.out.println("开始监听");
for (int i = 0; i <= 1000; i++) {
//创建简单消息对象
MQMessage mqMessage = new MQMessage();
queue.get(mqMessage, gmo);
String msgContent = mqMessage.readUTF();
System.out.println(msgContent);
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//同步消息接收
private static void syncReceive() {
try {
//创建队列管理器对象,在实例化的时候会隐式连接队列管理器CQM1
MQQueueManager mqQueueManager = new MQQueueManager(qmgr);
//定义打开方式
int openOption = MQC.MQOO_INPUT_SHARED; //以读取方式打开
//直接循环,每次发10条消息
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(syncQueueName, openOption);
//定义获取消息时的一些操作
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = gmo.options + MQC.MQGMO_SYNCPOINT;//在同步点控制下获取消息
gmo.options = gmo.options + MQC.MQGMO_WAIT; //无消息时等待
gmo.options = gmo.options + MQC.MQGMO_FAIL_IF_QUIESCING; //队列管理器停止时退出等待
gmo.waitInterval = 300000; //无消息时等待时长
/*
这里为解决回执队列不同的情况,每次根据请求队列中携带的回执队列名称重新打开回执队列,若回执队列是确定的,可将回执队列的定义放到循环外,以减少队列开关次数来提高性能
*/
for (int i = 0; i <= 1000; i++) {
//创建简单消息对象
MQMessage mqMessage = new MQMessage();
//接收消息
queue.get(mqMessage, gmo);
//读取消息内容
String msgContent = mqMessage.readUTF();
System.out.println(msgContent);
//判断,如果消息标识为请求消息,则进行回应
if (mqMessage.messageFlags == MQC.MQMT_REQUEST) {
//获取回应队列
String replyQueueName = mqMessage.replyToQueueName;
//获取回应队列管理器
String replyQueueManager = mqMessage.replyToQueueManagerName;
//使用回应队列,回应队列管理器创建回执消息队列的对象
MQQueue sendQueue = mqQueueManager.accessQueue(replyQueueName, MQC.MQOO_OUTPUT | MQC.MQOO_FAIL_IF_QUIESCING, replyQueueManager, null, null);
//创建回执消息对象
MQMessage sendMsg = new MQMessage();
//设置消息对应参数,发送端使用此参数进行消息关联
sendMsg.correlationId = mqMessage.messageId;
//将请求消息内容写入响应消息(按照自己的业务写入相应的数据,测试时为测试方便,将消息回写)
sendMsg.writeUTF(msgContent);
//设置消息类型为响应消息
sendMsg.messageFlags = MQC.MQMT_REPLY;
//消息发送
sendQueue.put(sendMsg);
//关闭发送队列
sendQueue.close();
}
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
init();
asyncReceive();
}
}
2.集群外节点发送消息的代码
package com.qinke.mqcluster;
import com.ibm.mq.*;
import com.qinke.mqcluster.utils.ConfigUtil;
import java.io.IOException;
public class CCQMSender {
private static String host;
private static Integer port;
private static String channel;
private static String qmgr;
private static String asyncQueueName;
private static String syncQueueName;
private static String repeictQueueName;
private static void init() {
host = ConfigUtil.getConfig("HOST");
port = Integer.valueOf(ConfigUtil.getConfig("PORT"));
channel = ConfigUtil.getConfig("CHANNEL");
qmgr = ConfigUtil.getConfig("QMGR");
asyncQueueName = ConfigUtil.getConfig("ASYNCQUEUE");
syncQueueName = ConfigUtil.getConfig("SYNCQUEUE");
repeictQueueName = ConfigUtil.getConfig("REPEICTQUEUENAME");
StringBuffer lisLog = new StringBuffer();
lisLog.append(host)
.append(" : ")
.append(port)
.append(" : ")
.append(channel)
.append(" : ")
.append(qmgr)
.append(" : ")
.append(asyncQueueName)
.append(" : ")
.append(syncQueueName)
.append(" : ")
.append(repeictQueueName);
//配置要使用的队列管理器的信息,这里使用队列管理器CQM1的信息
MQEnvironment.hostname = host;
MQEnvironment.port = port;
MQEnvironment.channel = channel;
System.out.println("初始化");
System.out.println("监听参数:" + lisLog.toString());
}
public static void sendAsync() {
try {
//创建队列管理器对象,在实例化的时候会隐式连接队列管理器CQM1
MQQueueManager mqQueueManager = new MQQueueManager(qmgr);
//定义打开方式
int openOption = MQC.MQOO_OUTPUT; //以写入方式打开
openOption = openOption + MQC.MQOO_BIND_NOT_FIXED;//写入消息方式为不绑定方式(想要负载均衡必须为此方式)
//直接循环,每次发10条消息
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(asyncQueueName, openOption);
for (int i = 0; i <= 10; i++) {
//创建简单消息对象
MQMessage mqMessage = new MQMessage();
//将数据写入消息对象中(可自行尝试其他write方法)
String msg = "简单消息:" + i;
mqMessage.writeUTF(msg);
//使用队列发送消息
queue.put(mqMessage);
System.out.println("send:" + msg);
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void sendSync() {
try {
//创建队列管理器对象,在实例化的时候会隐式连接队列管理器CQM1
MQQueueManager mqQueueManager = new MQQueueManager(qmgr);
//定义打开方式
int openOption = MQC.MQOO_OUTPUT; //以写入方式打开
openOption = openOption + MQC.MQOO_BIND_NOT_FIXED;//写入消息方式为不绑定方式(想要负载均衡必须为此方式)
//直接循环,每次发10条消息
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(syncQueueName, openOption);
MQPutMessageOptions pmo = new MQPutMessageOptions();
pmo.options = pmo.options + MQC.MQPMO_NEW_MSG_ID;
pmo.options = pmo.options + MQC.MQPMO_SYNCPOINT;
for (int i = 0; i <= 10; i++) {
//创建简单消息对象
MQMessage sendMsg = new MQMessage();
//将数据写入消息对象中(可自行尝试其他write方法)
sendMsg.writeUTF("简单消息:" + i);
//设置消息标识为请求消息
sendMsg.messageFlags = MQC.MQMT_REQUEST;
//设置回执队列与回执队列管理器(当前队列管理器)
sendMsg.replyToQueueName = repeictQueueName;
sendMsg.replyToQueueManagerName = qmgr;
//使用队列发送消息
queue.put(sendMsg, pmo);
byte[] messageId = sendMsg.messageId;
//设置回执队列打开方式
int getOpenOption = MQC.MQOO_INPUT_SHARED | MQC.MQOO_FAIL_IF_QUIESCING;
//创建回执队列对象
MQQueue mqQueue = mqQueueManager.accessQueue(repeictQueueName, getOpenOption);
//创建获取消息的对象,并设置消息选择器
MQMessage getMsg = new MQMessage();
getMsg.correlationId = messageId;
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = gmo.options + MQC.MQGMO_SYNCPOINT;//Get messages under sync point control(在同步点控制下获取消息)
gmo.options = gmo.options + MQC.MQGMO_WAIT; // Wait if no messages on the Queue(如果在队列上没有消息则等待)
gmo.options = gmo.options + MQC.MQMO_MATCH_CORREL_ID;// Fail if Qeue Manager Quiescing(如果队列管理器停顿则失败)
gmo.waitInterval = 2000;
//获取响应消息
mqQueue.get(getMsg, gmo);
String getMsgContent = getMsg.readUTF();
System.out.println(getMsgContent);
mqQueue.close();
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
init();
sendAsync();
}
}
3. 代码测试
3.1 异步消息发送与接收测试
在使用代码接收异步消息时,由于我们需要对集群中的三个节点进行监听,而IDE每次执行代码的进程只有一个,所以这里将代码进行打包后使用cmd运行。
3.1.1 代码打包方式
构建完成之后在项目的根目录找到out文件夹,打包完成的程序在这个地方
将jar考走放到单独的文件夹中,按下shift再按下鼠标右键,点击在此处打开powershell窗口,即可在当前目录下打开cmd窗口,使用 java -jar .\mqcluster.jar 命令执行程序。
3.1.2 启动监听
注意: 打包前请确定接收程序的main方法调用的是否为异步消息接收的方法。打包后将jar文件复制到三个文件夹,文件夹下分别拷贝config.properties.想要当前程序监听哪个队列,只需修改配置文件即可。
三个节点异步消息监听启动完毕的效果
3.1.3 使用外部MQ节点向集群发送异步消息
- 将程序配置修改为CCQM6的相关配置
- CCQMSender的main方法调用修改为asyncSend
- 启动CCQMSender.main,查看控制台的消息发送情况
3.1.4 查看监听程序的消息接收情况
由图可以看出,异步消息正常发送且进行了负载均衡,集群外部对集群的异步消息发送与接收到此结束
3.2 同步消息的发送与接收
3.2.1 程序打包
程序打包方式同 3.1.1 。注意打包时将CCQMReceiver.main()中调用的方法修改为syncReceive();
3.2.2 启动监听
同 3.2.1。
3.1.3 使用外部MQ节点向集群发送同步消息
- 将程序配置修改为CCQM6的相关配置
- CCQMSender的main方法调用修改为syncSend
3.2.4 查看监听程序的消息接收情况
由图可以看出,同步消息到集群时也经过了负载均衡,且发送无问题,从集群外部到集群的同步消息测试到此结束。
3.2.5 附代码链接
4. 测试中发现的问题
集群结构是CCQM1主网关,CCQM2备网关,当与集群外部进行交互时,首选应为CCQM1,当网关CCQM1发生问题不能正常工作时,才进行切换CCQM2.
在测试中发现,从集群外部向集群发送同步消息时,入集群的消息全部都由CCQM1网关接收,但是出集群的消息,却是因为两个网关都有消息回执的集群共享队列,导致回执消息在两个网关上进行了负载均衡,使用过程中确实是没有什么问题,但回执消息的返回路径却跟想象的不太一样。当前还未找到解决办法,哪位大牛有相关的解决方案,可以在一块交流一下。