I have used .net C# (IBM MQ version 9.1.5) to pull messages from the queue. So I have no issues connecting to the queue and getting messages.
I have read that there is the concept of transactions Distributed Transactions.
I tried the following:
var getMessageOptions = new MQGetMessageOptions();
getMessageOptions = new MQGetMessageOptions();
getMessageOptions.Options += MQC.MQGMO_WAIT + MQC.MQGMO_SYNCPOINT;
getMessageOptions.WaitInterval = 20000; // 20 seconds wait
Transaction oldAmbient = Transaction.Current;
using (var tx = new CommittableTransaction())
{
try
{
int i = queue.CurrentDepth;
Log.Information($"Current queue depth is {i} message(s)");
var message = new MQMessage();
queue.Get(message, getMessageOptions);
string messageStr = message.ReadString(message.DataLength);
Log.Information(messageStr);
tx.Commit();
}
catch (MQException e) when (e.Reason == 2033)
{
// Report exceptions other than "no messages in the queue"
Log.Information("No messages in the queue");
tx.Rollback();
}
catch (Exception ex)
{
Log.Error($"Exception when trying to capture a message from the queue: {ex.Message}");
tx.Rollback();
}
I am getting an error code of 2035.
Looking at the documents on Recovering Transactions, where does the "SYSTEM.DOTNET.XARECOVERY.QUEUE" live, is it on the queuemanger?
Do I need to get permissions enabled on this?
Also I see that Microsoft Distributed Transaction Manager is mentioned, is this something that we need to have running on the local host in order for distributed transactions to work?
If MQ Distributed transactions feature is being used then the user running the application should have the authority to "SYSTEM.DOTNET.XARECOVERY.QUEUE".If a transaction is incomplete "SYSTEM.DOTNET.XARECOVERY.QUEUE" queue holds the information of incomplete transaction as message in that queue,which later can be used to resolve the transaction.
Based on your scenario which you had put in comments i.e "we want to just save the message to a file. My thinking is if there is a problem with that, I could roll back the transaction." .If MQ is the only resource manager then you don't have to use Distributed transactions. Getting a message under syncpoint can also be used instead of Distributed Transactions. Distributed Transactions will be useful if more than one resource manager is being used.
To get a message under syncpoint following sample code can be used by updating hostname,channel,port,queue and queue manager name:
var getMessageOptions = new MQGetMessageOptions();
getMessageOptions = new MQGetMessageOptions();
getMessageOptions.Options += MQC.MQGMO_WAIT + MQC.MQGMO_SYNCPOINT;
getMessageOptions.WaitInterval = 20000; // 20 seconds wait
Hashtable props = new Hashtable();
props.Add(MQC.HOST_NAME_PROPERTY, "localhost");
props.Add(MQC.CHANNEL_PROPERTY, "DOTNET.SVRCONN");
props.Add(MQC.PORT_PROPERTY, 3636);
MQQueueManager qm = new MQQueueManager("QM", props);
MQQueue queue = qm.AccessQueue("Q1", MQC.MQOO_INPUT_AS_Q_DEF);
try
{
var message = new MQMessage();
queue.Get(message, getMessageOptions);
//to commit the message
qm.Commit();
string messageStr = message.ReadString(message.DataLength);
}
catch (MQException e) when (e.Reason == 2033)
{
// Report exceptions other than "no messages in the queue"
Log.Information("No messages in the queue");
}
catch (Exception ex)
{
Log.Error($"Exception when trying to capture a message from the queue:
}
Related
I'm trying to reuse a JMSContext to send multiple messages using the same context as shown in this IBM MQ tutorial.
context = cf.createContext();
destination = context.createQueue(QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
try {
TextMessage message = context.createTextMessage("Message " + i + ".\n");
producer.send(destination, message);
} catch (Exception ignore) {}
}
context.close();
Say the connection is dropped at some point. Will the context auto recovers or will I need to reconstruct the context again?
UPDATE --
This is how the current connection factory is being constructed:
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(JmsConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
cf.setStringProperty (CommonConstants.WMQ_HOST_NAME, config.getHost());
cf.setIntProperty (CommonConstants.WMQ_PORT, config.getPort());
cf.setStringProperty (CommonConstants.WMQ_CHANNEL, config.getChannel());
cf.setIntProperty (CommonConstants.WMQ_CONNECTION_MODE, CommonConstants.WMQ_CM_CLIENT);
cf.setStringProperty (CommonConstants.WMQ_QUEUE_MANAGER, config.getQueueManager());
cf.setBooleanProperty (JmsConstants.USER_AUTHENTICATION_MQCSP, false);
cf.setIntProperty (JmsConstants.PRIORITY, 0);
return cf.createContext();
Reconnect works like this (see also comment of #JoshMc):
On the client, set the reconnect option like this:
cf.setIntProperty(CommonConstants.WMQ_CLIENT_RECONNECT_OPTIONS, CommonConstants.WMQConstants.WMQ_CLIENT_RECONNECT);
On the server, stop the queue manager like this:
endmqm -r
Have u tried with creating JMSContext from existing one?
JMSContext#createContext(int sessionMode)
It will create new JMSContext but reuse the same connection.
Reference:
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.pro.doc/intro_jms_model.htm
https://docs.oracle.com/javaee/7/api/javax/jms/JMSContext.html
Consumer queues are allocated in client side, broker knows nothing about this.
So how can we monitor which queue is allocated to which consumer client?
Though there is no exiting command, for each message queue per consumer group, You can find out the client using provided admin infrastructure. Here is the snippet achieving this:
private Map<MessageQueue, String> getClientConnection(DefaultMQAdminExt defaultMQAdminExt, String groupName){
Map<MessageQueue, String> results = new HashMap<MessageQueue, String>();
try{
ConsumerConnection consumerConnection = defaultMQAdminExt.examineConsumerConnectionInfo(groupName);
for (Connection connection : consumerConnection.getConnectionSet()){
String clinetId = connection.getClientId();
ConsumerRunningInfo consumerRunningInfo = defaultMQAdminExt.getConsumerRunningInfo(groupName, clinetId, false);
for(MessageQueue messageQueue : consumerRunningInfo.getMqTable().keySet()){
results.put(messageQueue, clinetId + " " + connection.getClientAddr());
}
}
}catch (Exception e){
}
return results;
}
In case you have not used the RocketMQ-Console project, please try and run it: https://github.com/rocketmq/rocketmq-console-ng
In the Consumer tab, Click "consumer detail" button, you will see message queue allocation result visually as below:
Message queues allocation result
I am currently trying to retrieve a specific message from a session.
To do so I use want to use the .Receive(Int64) on the MessageSession where I pass in the sequence number of the message.
Here is my code -
long msgSequenceNr = 1337;
QueueClient queueClient = QueueClient.CreateFromConnectionString(Constants.ServiceBusConnectionString, Constants.TestQueueEntityName, ReceiveMode.PeekLock);
MessageSession msgSession = queueClient.AcceptMessageSession(Constants.TestSessionId);
var peekedMsg = msgSession.Peek(msgSequenceNr); // <-- Works fine!
var receivedMsg = msgSession.Receive(msgSequenceNr); // <-- MessageNotFoundException
Unfortunately the Receive will result in a MessageNotFoundException while the Peek works fine.
Is this a limitation that I missed or is there another way to achieve this.
Note that it is possible that there are multiple messages in the session
Receive with the SequenceNumber can only be used in combination with the Defer method. This is how you would implement it:
Message received, but it can't be processed right now (maybe it's waiting for a different process to complete).
Persist the SequenceNumber in some persistent storage (Table Storage, SQL Database, ...)
When you know that processing can continue (eg: the dependent process is complete), load all SequenceNumbers from your persistent storage.
Use Receive(int sequenceNumber) or ReceiveBatch(int[] sequenceNumbers) to received and process your deferred messages.
Sample application: https://code.msdn.microsoft.com/windowsazure/Brokered-Messaging-ccc4f879#content
Update:
Form your comment I noticed that "undeferring" a deferred message could be a solution. Here's some sample code to undefer the message which copies the deferred message to a new message, Completes the deferred message and sends the new message back in the queue. This uses a TransactionScope to transactionally Complete and Resend the message to avoid the risk of losing the message:
var messageId = "12434539828282";
// Send.
var msg = new BrokeredMessage {SessionId = "user1", MessageId = messageId };
msg.Properties.Add("Language", "Dutch");
queue.Send(msg);
// Receive.
var session = queue.AcceptMessageSession();
msg = session.Receive();
// Store the sequence number.
var sequenceNumber = msg.SequenceNumber;
// Defer.
msg.Defer();
// Change to true to test if the transaction worked.
var shouldThrow = false;
// Later processing of deferred message.
msg = session.Receive(sequenceNumber);
try
{
using (var ts = new TransactionScope())
{
// Create a new message.
var undeferredMessage = new BrokeredMessage {SessionId = msg.SessionId, MessageId = msg.MessageId};
foreach (var prop in msg.Properties)
undeferredMessage.Properties.Add(prop);
// Complete and send within the same transaction.
msg.Complete();
if (shouldThrow)
throw new InvalidOperationException("Some error");
queue.Send(undeferredMessage);
// Complete the transaction.
ts.Complete();
}
}
catch (Exception ex)
{
msg.Abandon();
}
if (shouldThrow)
{
msg = session.Receive(sequenceNumber);
Console.WriteLine(msg.MessageId + " should match: " + messageId);
}
else
{
try
{
msg = session.Receive(sequenceNumber);
}
catch (Exception ex)
{
Console.WriteLine("Message not found, transaction worked OK.");
}
}
Note: here I'm simply taking a copy of the Properties. Keep into account that you might want to copy the Body and any other additional information.
I want to get the Queue Depth for a Transmission Queue (XMIT queue) using WebSphere MQ Classes for .Net , can someone kindly help me giving a specific link/Pseudocode or .Net Classes/API to identify the XMIT queue depth. I have gone through the .Net API but didn't find any info on XMIT queue.
You can use the MQ .NET PCF interface to query queue attributes. Below is the sample code snippet.
Note: MQ .NET PCF interface is undocumented interface and may not be supported. You will need to consult IBM.
public static void InquireQueue()
{
PCFMessageAgent messageAgent = null;
try
{
// Create connection to queue manager
messageAgent = new PCFMessageAgent("QM3");
// Build Inquire command to query attributes a queue
PCFMessage pcfMsg = new PCFMessage(MQC.MQCMD_INQUIRE_Q);
pcfMsg.AddParameter(MQC.MQCA_Q_NAME, "TO.QM2");
// Send request and receive response
PCFMessage[] pcfResponse = messageAgent.Send(pcfMsg);
// Process and print response.
int pcfResponseLen = pcfResponse.Length;
for (int pcfResponseIdx = 0; pcfResponseIdx < pcfResponseLen; pcfResponseIdx++)
{
PCFParameter[] parameters = pcfResponse[pcfResponseIdx].GetParameters();
foreach (PCFParameter pm in parameters)
{
// We just want to print current queue depth only
if (pm.Parameter == MQC.MQIA_CURRENT_Q_DEPTH)
Console.WriteLine("Queue Depth" + " - " + pm.GetValue());
}
}
}
catch (PCFException pcfEx)
{
Console.Write(pcfEx);
}
catch (MQException ex)
{
Console.Write(ex);
}
finally
{
if (messageAgent != null)
messageAgent.Disconnect();
}
}
I have to update my existing JMS Receiver program to as follows.
Existing Functionality:
My receiver class will read a message and calls a web service to process the job in one of the server once the message is received as xml.
New Functionality:
The receiver should wait for sometime until the job server is free to process a job. I tried using MessageSelectors but which is only applicable for message headers.I tried this option "message = (JMSTextMessage) mqQueueReceiver.receive(100000000000000);" but whenever i posted a message those message is read after posted into queue. But i want to keep receiver to wait for some interval which i am getting from Job server through web service call.
My Code is below:
connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setHostName(config.getValue("host"));
connectionFactory.setPort(Integer.parseInt(config.getValue("port")));
connectionFactory.setTransportType(JMSC.MQJMS_TP_CLIENT_MQ_TCPIP);
connectionFactory.setQueueManager(config.getValue("manager"));
connectionFactory.setChannel(config.getValue("channel"));
queueConnection = (MQQueueConnection) connectionFactory.createQueueConnection();
queueSession = (MQQueueSession) queueConnection.createQueueSession(true, Session.CLIENT_ACKNOWLEDGE);
queue = (MQQueue) queueSession.createQueue(config.getValue("queue"));
mqQueueReceiver = (MQQueueReceiver) queueSession.createReceiver(queue);
while(true) {
if(this.stopListener) {
System.out.println("stopListener variable is changed ");
break;
}
try {
message = (JMSTextMessage) mqQueueReceiver.receive(1000);
String response = "";
if(this.nullCheckJMSTextObject(message)) {
response= soapClient.invokeWebService(message.getText(),message.getJMSCorrelationID());
if(this.nullCheckSoapResponse(response)) {
queueSession.commit();
} else {
queueSession.rollback();
queueSession.commit();
Thread.sleep(receiverWaitTime);
}
}
} catch (JMSException e) {
System.err.println("Linked Exception");
e.getLinkedException();
System.err.println("Error Code");
e.getErrorCode();
System.err.println("Cause ");
e.getCause();
System.err.println("fillTrackTrace ");
e.fillInStackTrace();
e.printStackTrace();
break;
}catch(IllegalStateException e) {
e.printStackTrace();
break;
}catch(InterruptedException e) {
e.printStackTrace();
break;
}catch(Exception e) {
e.printStackTrace();
break;
}
}
The receive(timeout) method will wait for the specified timeout period for a message to arrive on a queue. If a message arrives on a queue before the timeout, the method will return immediately with a message otherwise the method will wait till the timeout period and then return with no message. You will see a 2033 exception.
The timeout specified for the receive() call indicates how long the receive method must wait for messages before it can return. The timeout specified is not to delay the message delivery. If there is a message, the method will return immediately.
I think your logic can be modified to alter the order of execution. Change the code to receive messages only when your web service is ready to process messages.