前言:上篇博客描述的是集群外部节点对集群发送同/异步消息的解决方案,此篇主要是描述对集群向外部节点发送同/异步消息的解决方案,初一想,也没什么区别,但是结合集群的环境,考虑消息的路由,中间开始有区别的,而本篇博客主要就是为了解决这点区别带来的问题。
程序将消息放入外部节点远程队列,远程队列将消息通过传输队列与发送方通道发送至集群网关,网关根据集群内部队列共享情况,对消息进行负载,发送到集群的某个节点,外部向集群发送异步消息结束。
程序将消息通过当前节点发送到集群共享队列中(向集群外部发送消息的集群共享队列定义在两个网关上,且为远程队列),集群网关通过传输队列与发送通道将消息发送到外部节点,集群向外部发送异步消息结束。
集群外部向集群发送异步消息与集群向集群外部发送异步消息没有太大的区别。
程序将消息放入外部节点远程队列,远程队列将消息通过传输队列与发送方通道发送至集群网关,网关根据集群内部队列共享情况,对消息进行负载,发送到集群的某个节点,接收到消息的节点对消息进行处理,并生成回执消息放入集群共享的回执消息队列(回执消息队列为定义在两个网关上的远程队列),网关通过传输队列与发送方通道,将回执消息发送回集群外部节点,外部向集群发送同步消息结束。
程序将消息通过当前节点发送到集群共享队列中(向集群外部发送消息的集群共享队列定义在两个网关上,且为远程队列),集群网关通过传输队列与发送通道将消息发送到外部节点,外部节点对消息进行处理,并生成回执消息放入外部节点的远程队列中,远程队列通过传输队列与发送方通道将消息发送到集群网关,集群网关需要对回执消息确定目标队列管理器,并将消息放入该目标队列管理器中的消息回执队列,集群向外部发送同步消息结束。
集群向外部发送同步消息时,须注意处理回执消息的路由,如果不处理路由,集群会将回执消息进行负载,这样发送同步消息的MQ节点不一定能接收到外部节点返回的回执消息。
由 1得出,只有在集群向外部发送同步消息时,与集群外部向集群发送同步消息有不同,带着这个问题,去查看IBMMQ官方文档,找到了对当前问题的解决方法
大致意思就是集群外部节点若想将消息发送到集群中的某一个节点,需要在外部节点上定义一个与集群中这个节点同名的队列管理器别名(建立队列管理器别名的方法是建立一个RNAME属性为空的远程队列),并且制定传输队列的名称为集群网关别名。我们接下来使用这个方法对当前环境进行修改。
DEFINE QREMOTE(CCQM3) RNAME('') RQMNAME('CCQM3') XMITQ(CLUSTER_CC.GW)
DEFINE QREMOTE(CCQM4) RNAME('') RQMNAME('CCQM4') XMITQ(CLUSTER_CC.GW)
DEFINE QREMOTE(CCQM5) RNAME('') RQMNAME('CCQM5') XMITQ(CLUSTER_CC.GW)
DEFINE QLOCAL(ClUSTER_CC.RECEIVE) CLUSTER(ClUSTER_CC)
DEFINE QREMOTE(CQ3) RNAME(CQ3) RQMNAME(TIPS) CLUSTER(ClUSTER_CC)
DEFINE QREMOTE(CQ4) RNAME(CQ4) RQMNAME(TIPS) CLUSTER(ClUSTER_CC)
DEFINE QREMOTE(CLUSTER_CC.RECEIVE) RNAME(CLUSTER_CC.RECEIVE) RQMNAME(CLUSTER_CC.GW) XMITQ(CLUSTER_CC.GW)
DEFINE QLOCAL(CQ3)
DEFINE QLOCAL(CQ4)
由于异步消息发送与上篇无差别,所以不再进行测试,这里只展示同步消息的发送
package com.qinke.clustertoout;
import com.ibm.mq.*;
import com.ibm.mq.constants.MQConstants;
import com.qinke.mqcluster.utils.ConfigUtil;
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 = MQConstants.MQOO_INPUT_SHARED; //以读取方式打开
//直接循环,每次发10条消息
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(asyncQueueName, openOption);
//定义获取消息时的一些操作
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = gmo.options + MQConstants.MQGMO_WAIT; //无消息时等待
gmo.options = gmo.options + MQConstants.MQGMO_FAIL_IF_QUIESCING; //队列管理器停止时退出等待
gmo.waitInterval = 300000; //无消息时等待时长
System.out.println("异步队列开始监听");
for (int i = 0; i <= 1000; i++) {
//try-catch的目的是捕获获取消息超时抛出的异常,使程序能继续进行下一次获取消息,而不是整个程序直接退出
try{
//创建简单消息对象
MQMessage mqMessage = new MQMessage();
queue.get(mqMessage, gmo);
String msgContent = mqMessage.readUTF();
System.out.println(msgContent);
}catch (Exception e){
e.printStackTrace();
}
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
}
}
//同步消息接收
private static void syncReceive() {
try {
//创建队列管理器对象,在实例化的时候会隐式连接队列管理器CQM1
MQQueueManager mqQueueManager = new MQQueueManager(qmgr);
//定义打开方式
int openOption = MQConstants.MQOO_INPUT_SHARED;
//创建队列实列
MQQueue queue = mqQueueManager.accessQueue(syncQueueName, openOption);
//定义获取消息时的一些操作
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.options = gmo.options + MQConstants.MQGMO_SYNCPOINT;//在同步点控制下获取消息
gmo.options = gmo.options + MQConstants.MQGMO_WAIT; //无消息时等待
gmo.options = gmo.options + MQConstants.MQGMO_FAIL_IF_QUIESCING; //队列管理器停止时退出等待
gmo.waitInterval = 300000; //无消息时等待时长
System.out.println("同步队列开始监听");
/*
这里为解决回执队列不同的情况,每次根据请求队列中携带的回执队列名称重新打开回执队列,若回执队列是确定的,可将回执队列的定义放到循环外,以减少队列开关次数来提高性能
*/
for (int i = 0; i <= 1000; i++) {
try {
//创建简单消息对象
MQMessage mqMessage = new MQMessage();
//接收消息
queue.get(mqMessage, gmo);
//读取消息内容
String msgContent = mqMessage.readUTF();
System.out.println(msgContent);
//判断,如果消息标识为请求消息,则进行回应
if (mqMessage.messageFlags == MQConstants.MQMT_REQUEST) {
//获取回应队列
String replyQueueName = mqMessage.replyToQueueName;
//获取回应队列管理器
String replyQueueManager = mqMessage.replyToQueueManagerName;
//使用回应队列,回应队列管理器创建回执消息队列的对象
MQQueue sendQueue = mqQueueManager.accessQueue(replyQueueName, MQConstants.MQOO_OUTPUT | MQConstants.MQOO_FAIL_IF_QUIESCING, replyQueueManager, null, null);
//创建回执消息对象
MQMessage sendMsg = new MQMessage();
//设置消息对应参数,发送端使用此参数进行消息关联
sendMsg.correlationId = mqMessage.messageId;
//将请求消息内容写入响应消息(按照自己的业务写入相应的数据,测试时为测试方便,将消息回写)
sendMsg.writeUTF(msgContent);
//设置消息类型为响应消息
sendMsg.messageFlags = MQConstants.MQMT_REPLY;
//消息发送
sendQueue.put(sendMsg);
mqQueueManager.commit();
//关闭发送队列
sendQueue.close();
}
} catch(Exception e){
e.printStackTrace();
}
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
init();
syncReceive();
}
}
package com.qinke.clustertoout;
import com.ibm.mq.*;
import com.ibm.mq.constants.MQConstants;
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 = MQConstants.MQOO_OUTPUT; //以写入方式打开
openOption = openOption + MQConstants.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 = MQConstants.MQOO_OUTPUT; //以写入方式打开
openOption = openOption + MQConstants.MQOO_BIND_NOT_FIXED;//写入消息方式为不绑定方式(想要负载均衡必须为此方式)
openOption = openOption + MQConstants.MQOO_FAIL_IF_QUIESCING;
//创建队列对量
MQQueue queue = mqQueueManager.accessQueue(syncQueueName, openOption);
MQPutMessageOptions pmo = new MQPutMessageOptions();
pmo.options = pmo.options + MQConstants.MQPMO_NEW_MSG_ID;
pmo.options = pmo.options + MQConstants.MQPMO_SYNCPOINT;
//直接循环,每次发10条消息
for (int i = 0; i <= 10; i++) {
try {
//创建简单消息对象
MQMessage sendMsg = new MQMessage();
//将数据写入消息对象中(可自行尝试其他write方法)
String sendMsgCon = "简单消息:" + i;
sendMsg.writeUTF(sendMsgCon);
//设置消息标识为请求消息
sendMsg.messageFlags = MQConstants.MQMT_REQUEST;
//设置回执队列与回执队列管理器(当前队列管理器)
sendMsg.replyToQueueName = repeictQueueName;
sendMsg.replyToQueueManagerName = qmgr;
//使用队列发送消息
queue.put(sendMsg, pmo);
mqQueueManager.commit();
System.out.println("发送消息:" + sendMsgCon);
byte[] messageId = sendMsg.messageId;
//设置回执队列打开方式
int getOpenOption = MQConstants.MQOO_INPUT_SHARED | MQConstants.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 + MQConstants.MQGMO_SYNCPOINT;//Get messages under sync point control(在同步点控制下获取消息)
gmo.options = gmo.options + MQConstants.MQGMO_WAIT; // Wait if no messages on the Queue(如果在队列上没有消息则等待)
gmo.options = gmo.options + MQConstants.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();
} catch (Exception e) {
e.printStackTrace();
}
}
//关闭打开的资源,养成好习惯
queue.close();
mqQueueManager.disconnect();
} catch (MQException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
init();
sendSync();
}
}
发送消息的程序连接CCQM3,进行同步消息发送
接收消息的程序连接集群外部节点CCQM6,接收同步消息
通过给外部节点添加匿名队列管理器的方式,解决了集群向集群外部发送同步消息时,回执消息被负载均衡的问题。
代码git地址:mqCluster