场景二:测试高可用HA下绑定VIP地址做IP地址漂移(QMRGRECGC->QMFX),实现在单节点故障情况下,自动虚IP漂移到另一个节点上实现服务高可用
目的:1.虚IP在2个节点间自动漂移;2.VIP漂移后应用程序能够自动重连实现消息重复不丢失,服务高可用
回执发送在两个网关管理器前面加了一层高可用Keepalived来实现故障IP切换,对程序连接是透明的。
多个网关队列管理器做HA,绑定VIP实现在回执发送过程中因主网关队列管理器宕机后,还能继续发送回执给远端MQ。
本场景实验可以和场景一的实验一起合并后形成一个完整的从远端发送消息到接收端集群,然后通过消息消费处理后又返回回执结果给远端队列管理器,这个过程中涉及到远端发送消息的高可用和本地集群发送回执的高可用,而由于远端发送消息的高可用在实验一中已测试,本实验只测试第二个步骤本地集群发送回执的高可用。
HA VIP:192.168.153.200
HA采用手工切换方式,利用LInux的绑定虚IP命令
ifconfig eth0:1 192.168.153.200 netmask 255.255.255.0 up
ifconfig eth0:1 down
首先在主网关上绑定VIP
绑定前要确认网卡的名字
192.168.153.129的网卡为eth1,因此执行
ifconfig eth1:1 192.168.153.200 netmask 255.255.255.0 up
192.168.153.129 QMRGRECGC连接远端MQ
192.168.153.128 QMRGRECGC连接远端MQ
192.168.153.1回执接收端MQFX的通道状态
通过以上确认各个通道都已启用,下面启动回执发送程序。
在主网关上停止VIP地址绑定
ifconfig eth1:1 down
然后在备用网关上绑定VIP地址
执行绑定命令必须在root用户下执行
切换过程中观察代码的异常捕捉
已发送:18
已发送:19
已发送:20
已发送:21
====2============
====网络故障,正在切换备用服务============
com.ibm.mq.MQException: MQJE001: 完成代码为 '2',原因为 '2009'。
at com.ibm.mq.MQDestination.internalMQPUT(MQDestination.java:1304)
at com.ibm.mq.MQDestination.put(MQDestination.java:1152)
at com.datatech.jmstest.Test_HUIZHI.sendMsg(Test_HUIZHI.java:71)
at com.datatech.jmstest.Test_HUIZHI.main(Test_HUIZHI.java:164)
Caused by: com.ibm.mq.jmqi.JmqiException: CC=2;RC=2009
at com.ibm.mq.jmqi.remote.impl.RemoteSession.receiveAsyncTsh(RemoteSession.java:617)
at com.ibm.mq.jmqi.remote.impl.RemoteSession.receiveTSH(RemoteSession.java:796)
at com.ibm.mq.jmqi.remote.impl.RemoteSession.receiveMQIFlow(RemoteSession.java:1425)
at com.ibm.mq.jmqi.remote.api.RemoteFAP.jmqiPutMessageWithProps(RemoteFAP.java:10025)
at com.ibm.mq.jmqi.remote.api.RemoteFAP.MQPUT(RemoteFAP.java:8877)
at com.ibm.mq.ese.jmqi.InterceptedJmqiImpl.MQPUT(InterceptedJmqiImpl.java:679)
at com.ibm.mq.ese.jmqi.ESEJMQI.MQPUT(ESEJMQI.java:491)
at com.ibm.mq.MQDestination.internalMQPUT(MQDestination.java:1297)
... 3 more
Caused by: com.ibm.mq.jmqi.JmqiException: CC=2;RC=2009;AMQ9213: 发生 'TCP' 的通信错误。 [1=java.net.SocketException[Connection reset],4=TCP,5=sockInStream.read]
at com.ibm.mq.jmqi.remote.impl.RemoteTCPConnection.receive(RemoteTCPConnection.java:1540)
at com.ibm.mq.jmqi.remote.impl.RemoteRcvThread.receiveBuffer(RemoteRcvThread.java:786)
at com.ibm.mq.jmqi.remote.impl.RemoteRcvThread.receiveOneTSH(RemoteRcvThread.java:749)
at com.ibm.mq.jmqi.remote.impl.RemoteRcvThread.run(RemoteRcvThread.java:141)
at com.ibm.msg.client.commonservices.workqueue.WorkQueueItem.runTask(WorkQueueItem.java:263)
at com.ibm.msg.client.commonservices.workqueue.SimpleWorkQueueItem.runItem(SimpleWorkQueueItem.java:99)
at com.ibm.msg.client.commonservices.workqueue.WorkQueueItem.run(WorkQueueItem.java:284)
at com.ibm.msg.client.commonservices.workqueue.WorkQueueManager.runWorkQueueItem(WorkQueueManager.java:312)
at com.ibm.msg.client.commonservices.j2se.workqueue.WorkQueueManagerImplementation$ThreadPoolWorker.run(WorkQueueManagerImplementation.java:1193)
Caused by: java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.ibm.mq.jmqi.remote.impl.RemoteTCPConnection.receive(RemoteTCPConnection.java:1530)
... 8 more
已发送:22
已发送:23
已发送:24
已发送:25
在代码中捕捉到异常后,发起重连,从代码中看发送消息的数量没有跳跃,没有丢失,然后在MQFX回执接收端查看当前队列深度也为100,消息没有丢失。
通过HA可以使用多个网关下对程序透明情况下的自动切换发送消息到远端,但程序中要注意的是需要捕捉到连接的异常,并进行判断然后发起通道的重连,若是程序没有异常处理环节则就达不到高可用的服务状态。
package com.datatech.jmstest;
import java.io.IOException;
import com.ibm.mq.MQC;
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.mq.constants.MQConstants;
public class Test_HUIZHI {
static MQQueueManager qMgr;
static int CCSID = 1381;
static String queueString = "TO.HUIZHI";
static int openOptions = MQConstants.MQOO_OUTPUT | MQConstants.MQOO_FAIL_IF_QUIESCING;
static MQQueue queue = null;
public static void connect() throws MQException {
MQEnvironment.hostname = "192.168.153.200";// MQ服务器IP
MQEnvironment.channel = "MY.SVRCONN"; // 队列管理器对应的服务器连接通道
MQEnvironment.CCSID = 1381; // 字符编码
MQEnvironment.port = 1417; // 队列管理器的端口号
// //MQ中拥有权限的用户名
MQEnvironment.userID = "mqm";
//用户名对应的密码
MQEnvironment.password = "123456";
try {
qMgr = new MQQueueManager("QMRGRECGC");// 队列管理器名称
queue = qMgr.accessQueue(queueString, openOptions, null, null, null);
} catch (MQException e) {
e.printStackTrace();
}
}
public static void sendMsg(String msgStr) {
//设置将要连接的队列属性
//目标为远程队列,所有这里不可以用MQOO_INPUT_AS_Q_DEF属性
//int openOptions = MQC.MQOO_INPUT_AS_Q_DEF | MQC.MQOO_OUTPUT;
//以下选项可适合远程队列与本地队列
//连接队列
//MQQueue provides inquire, set, put and get operations for WebSphere MQ queues.
//The inquire and set capabilities are inherited from MQManagedObject.
/*关闭了就重新打开*/
// 建立Q1通道的连接
MQMessage msg = new MQMessage();// 要写入队列的消息
msg.format = MQConstants.MQFMT_STRING;
msg.characterSet = CCSID;
msg.encoding = CCSID;
// msg.writeObject(msgStr); //将消息写入消息对象中;
boolean Flag;
Flag=true;
int count=1;
while(Flag)
{
try {
MQPutMessageOptions pmo = new MQPutMessageOptions();
msg.expiry = -1; // 设置消息用不过期
msg.writeString(msgStr);
queue.put(msg, pmo);// 将消息放入队列
}catch(java.net.SocketException e){
System.out.println("网络故障,正在切换备用服务");
} catch(IOException e){
System.out.println("IO故障");
//msg.writeString(msgStr);
}
catch (MQException e) {
System.out.println("===="+e.getCompCode()+"============");
System.out.println("====网络故障,正在切换备用服务============");
try {
connect();
MQPutMessageOptions pmo = new MQPutMessageOptions();
msg.expiry = -1; // 设置消息用不过期
try {
msg.writeString(msgStr);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
queue.put(msg, pmo);// 将消息放入队列
} catch (MQException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
}
System.out.println("已发送:"+count);
if(count==100){
Flag=false;
}
count++;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (queue != null) {
try {
queue.close();
} catch (MQException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void receiveMsg() {
int openOptions = MQC.MQOO_INPUT_AS_Q_DEF | MQC.MQOO_OUTPUT | MQC.MQOO_INQUIRE;
MQQueue queue = null;
try {
queue = qMgr.accessQueue(queueString, openOptions, null, null, null);
System.out.println("该队列当前的深度为:" + queue.getCurrentDepth());
System.out.println("===========================");
int depth = queue.getCurrentDepth();
// 将队列的里的消息读出来
while (depth-- > 0) {
MQMessage msg = new MQMessage();// 要读的队列的消息
MQGetMessageOptions gmo = new MQGetMessageOptions();
queue.get(msg, gmo);
System.out.println("消息的大小为:" + msg.getDataLength());
System.out.println("消息的内容:\n" + msg.readStringOfByteLength(msg.getDataLength()));
System.out.println("---------------------------");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (queue != null) {
try {
queue.close();
} catch (MQException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws MQException {
System.out.println("========回执高可用服务发送测试========");
connect();
sendMsg("我来测试一下");
// receiveMsg();
}
}