I'm trying to create a network of embedded brokers, the topology I'm trying to achieve is as follows:
So I want to run Broker 1 which would be the initial receiver of all the messages and hold a topic %X%. Then I want to connect Broker 2 and Broker 3 to Broker 1 and make them listen to Broker 1 through a network connection.
Eventually I want to allow consumers to receive messages from %X% topic connecting to Broker 2 and Broker 3.
So far I've written the following code:
Broker 1:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Broker 2:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61617");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Broker 3:
BrokerService broker = new BrokerService();
broker.addConnector("tcp://localhost:61618");
broker.addNetworkConnector("static:(tcp://localhost:61616)");
broker.start();
Producer:
public class Producer {
private Connection connection;
public Producer() throws JMSException {
// Create a ConnectionFactory
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://localhost:61616");
connection = connectionFactory.createConnection();
connection.start();
....
}
public void produceMessage(int x) {
try {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createTopic("Testtopic");
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
String text = "Hello world " + x + "! From: " + Thread.currentThread().getName() + " : " + this.hashCode();
TextMessage message = session.createTextMessage(text);
System.out.println("Sent message: "+ message.hashCode() + " : " + Thread.currentThread().getName());
producer.send(message);
session.close();
}
......
}
}
Consumer:
public class Consumer {
public Consumer() throws JMSException {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL("tcp://localhost:61618"); // BROKER 3
Connection connection = connectionFactory.createConnection();
connection.start();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageConsumer consumer = session.createConsumer(session.createTopic("Testtopic"));
consumer.setMessageListener(new HelloMessageListener());
}
private static class HelloMessageListener implements MessageListener {
public void onMessage(Message message) {
TextMessage textMessage = (TextMessage) message;
try {
System.out.println("Consumer " + Thread.currentThread().getName() + " received message: " + textMessage.getText());
.......
}
}
}
However, when I connect to tcp://localhost:61618 (which is Broker 3) by consumer, I cannot receive any message. In the meantime if a connect directly to
tcp://localhost:61616 (the initial receiver, Broker 1), consumer receives messages and everything goes well. I think I missed something in connectors configuration. Could you please help me with this?
Thanks,
Cheers
The networkConnectors look to have the wrong port in the uri.
should be:
On broker1:
.addNetworkConnector(tcp://localhost:61617)
.addNetworkConnector(tcp://localhost:61618)
You don't need the network connectors from 2, 3 back to 1.
I also suggest configuring the full object and adding some parameters to bolster the config... duplex="false", networkTTL=1, etc.
Related
I just finished a tutorial on JMS, so it's super new to me and I'm trying to understand the basics. I'm using ActiveMQ Artemis if that matters. I created two simple applications, one named Producer and the other named Consumer. I run Producer first then run Consumer. The Consumer application never terminates and does not print the messages to the console. Weirdly, if I do not manually terminate Consumer and run Producer a second time, then I see the messages that Consumer should have received printed on the console. What's going on here? How do I get Consumer to receive and print messages from Producer?
Here's Producer:
public class Producer {
public static void main(String[] args) throws Exception {
InitialContext initialContext = null;
Connection connection = null;
initialContext = new InitialContext();
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
connection = cf.createConnection();
Session session = connection.createSession();
Queue queue = (Queue) initialContext.lookup("queue/myQueue");
Topic topic = (Topic) initialContext.lookup("topic/myTopic");
MessageProducer queueProducer = session.createProducer(queue);
MessageProducer topicProducer = session.createProducer(topic);
TextMessage queueMessage = session.createTextMessage("This message is for the queue2");
TextMessage topicMessage = session.createTextMessage("This message is for the topic2");
queueProducer.send(queueMessage);
topicProducer.send(topicMessage);
System.out.println("Message to queue sent: "+ queueMessage.getText());
System.out.println("Message to topic sent: "+ topicMessage.getText());
initialContext.close();
}
}
Here's Consumer:
public class Consumer {
public static void main(String[] args) throws Exception {
InitialContext initialContext = null;
Connection connection = null;
initialContext = new InitialContext();
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("ConnectionFactory");
connection = cf.createConnection();
Session session = connection.createSession();
Queue queue = (Queue) initialContext.lookup("queue/myQueue");
Topic topic = (Topic) initialContext.lookup("topic/myTopic");
MessageConsumer queueConsumer = session.createConsumer(queue);
MessageConsumer topicConsumer1 = session.createConsumer(topic);
MessageConsumer topicConsumer2 = session.createConsumer(topic);
connection.start();
TextMessage messageReceivedByQueueConsumer = (TextMessage) queueConsumer.receive();
TextMessage messageReceivedByTopicConsumer1 = (TextMessage) topicConsumer1.receive();
TextMessage messageReceivedByTopicConsumer2 = (TextMessage) topicConsumer2.receive();
System.out.println("Message received by queue consumer: "+ messageReceivedByQueueConsumer.getText());
System.out.println("Message received by topic consumer 1: "+ messageReceivedByTopicConsumer1.getText());
System.out.println("Message received by topic consumer 2: "+ messageReceivedByTopicConsumer2.getText());
connection.close();
initialContext.close();
}
}
What you're observing is, in fact, the expected behavior.
Since you run the Producer application first a message is being sent to a queue and a topic when no consumer/subscriber exists on either. The message sent to the queue is stored in the queue because that's how JMS queues work. The message sent to the topic is discarded since there are no subscriptions to receive the message. Again, this is how JMS topics work.
Then when your Consumer application runs the queueConsumer receives the message sent to the queue, but since you're invoking receive() with no timeout on topicConsumer1 the application will simply block forever since there are no messages in the topic consumer's subscription. This blocking prevents the message received from the queue from being printed.
You should run your consuming application first and then run your producer while the consuming application is still running. Then you should see all the messages received and printed as you assumed they would be.
I listened to an OpenMQ queue asynchronously. If I have an exception in the process of consuming a message, is there a way to get OpenMQ to push that message to me again?
#Bean
public JmsListenerContainerFactory jmsQueueListenerContainerFactory() {
DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
jmsListenerContainerFactory.setConnectionFactory(connectionFactory());
jmsListenerContainerFactory.setPubSubDomain(false);
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return jmsListenerContainerFactory;
}
MessageConsumer receiver = session.createConsumer(destination);
receiver.setMessageListener(new MessageListener() {
public void onMessage(Message message) {
TextMessage text = (TextMessage) message;
System.out.println("Received message: " + message.getText());
//The connection timed out when saving the message to the database
repository.save(text);
}
});
The reason it is not backed out is that you have acknowledged mode set to AUTO_ACKNOWLEDGE.
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
Change this to CLIENT_ACKNOWLEDGE like below:
jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
Use message.acknowledge() to commit the message.
Use session.recover() to back out the message.
I have a kubernetes cluster with an activeMQ Artemis Queue and I am using hpa for autoscaling of micro services. The messages are send via QpidSender and received via JMSListener.
Messaging works, but I am not able to configure the Queue/Listener in a way, that autoscaling works as expacted.
This is my Qpid sender
public static void send(String avroMessage, String task) throws JMSException, NamingException {
Connection connection = createConnection();
connection.start();
Session session = createSession(connection);
MessageProducer messageProducer = createProducer(session);
TextMessage message = session.createTextMessage(avroMessage);
message.setStringProperty("task", task);
messageProducer.send(
message,
DeliveryMode.NON_PERSISTENT,
Message.DEFAULT_PRIORITY,
Message.DEFAULT_TIME_TO_LIVE);
connection.close();
}
private static MessageProducer createProducer(Session session) throws JMSException {
Destination producerDestination =
session.createQueue("queue?consumer.prefetchSize=1&heartbeat='10000'");
return session.createProducer(producerDestination);
}
private static Session createSession(Connection connection) throws JMSException {
return connection.createSession(Session.AUTO_ACKNOWLEDGE);
}
private static Connection createConnection() throws NamingException, JMSException {
Hashtable<Object, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
env.put("connectionfactory.factoryLookup", amqUrl);
Context context = new javax.naming.InitialContext(env);
ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("factoryLookup");
PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setConnectionFactory(connectionFactory);
pooledConnectionFactory.setMaxConnections(10);
return connectionFactory.createConnection(amqUsername, amqPassword);
}
This is my Listener config
#Bean
public JmsConnectionFactory jmsConnection() {
JmsConnectionFactory jmsConnection = new JmsConnectionFactory();
jmsConnection.setRemoteURI(this.amqUrl);
jmsConnection.setUsername(this.amqUsername);
jmsConnection.setPassword(this.amqPassword);
return jmsConnection;
}
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(jmsConnection());
return factory;
}
And here is my Listener
#JmsListener(
destination = "queue?consumer.prefetchSize=1&heartbeat='10000'",
selector = "task = 'myTask'"
)
public void receiveMsg(Message message) throws IOException, JMSException {
message.acknowledge();
doStuff();
}
I send the message like this
QpidSender.send(avroMessage, "myTask");
This setting works. I can send different messages and as soon than there are more then 2, the second instance of my service starts and consumes the message. If later the message count is below 2, the service is terminated.
The problem is: I don't want the message to be acknowledged before the doStuff(). Because if something goes wrong or if the service is terminated before finishing doStuff(), the message is lost (right?).
But if I reorder it to
doStuff();
message.acknowledge();
the second instance can not receive a message from the broker, as long as the first service is still in doStuff() and hasn't acknowledged the message.
How do I configure this in a way, that more than one instance can consume a message from the queue, but the message isn't lost, if the service gets terminated or something else fails on doStuff()?
Use factory.setSessionTransacted(true).
See the javadocs for DefaultMessageListenerContainer:
* <p><b>It is strongly recommended to either set {#link #setSessionTransacted
* "sessionTransacted"} to "true" or specify an external {#link #setTransactionManager
* "transactionManager"}.</b> See the {#link AbstractMessageListenerContainer}
* javadoc for details on acknowledge modes and native transaction options, as
* well as the {#link AbstractPollingMessageListenerContainer} javadoc for details
* on configuring an external transaction manager. Note that for the default
* "AUTO_ACKNOWLEDGE" mode, this container applies automatic message acknowledgment
* before listener execution, with no redelivery in case of an exception.
I have a java program I run to write messages to Mid-Tier IBM MQ's to test functionality before attaching our main programs to them. The write method looks like the following below:
private static void sendSingleMessage(ConnectionFactory connectionFactory,
String[] messages, String destination) throws Exception {
Connection connection = null;
try {
connection = connectionFactory.createConnection();
for (String payload : messages) {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(destination);
MessageProducer producer = session.createProducer(queue);
Message msg = session.createTextMessage(payload);
System.out.println("Sending text '" + payload + "'");
producer.send(msg);
session.close();
System.out.println("Message sent");
}
} finally {
if (connection != null) {
connection.close();
}
}
}
The connectionFactory is setup before this method executes, but within that method I set the MQConncetionFactory properties(host,port,channel,queuemanager, etc...) This send method works and I can see the queue depth increasing on my IBM MQ Explorer when I call it from my main method.
When I run a similar readSingleMessage method, the code gets stuck on the consumer.receive() and never finishes executing. See below:
private static void readSingleMessage(ConnectionFactory connectionFactory,
String[] messages, String destination) throws Exception {
Connection connection = null;
try {
connection = connectionFactory.createConnection();
for (String payload : messages) {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue(destination);
MessageConsumer consumer = session.createConsumer(queue);
System.out.println("Recieving text '" + payload + "'");
consumer.receive();
session.close();
System.out.println("Received message");
}
} finally {
if (connection != null) {
connection.close();
}
}
}
Is there anyway I can further debug this, or find why I am able to write to the queue, but unable to read a message off of it?
You have to start the JMS Connection by calling the start() method on it. You cannot receive any messages until the connection is started. This is noted in the JMS Specification and Javadoc.
As an aside, if you use the JMS 2.0 "simplified" API and create a JMSContext object (an object which is essentially a combined Connection and Session) you do not need to call start to receive messages. A consumer crated from it can be used to receive messages without being explicitly started.
I am using spring activemq for sending jms messages to activemq message queue.
The messagequeue is created on a remote server.I have to set a proxy to reach to that message queue server from my local machine.
Can anyone help me on how to add proxy to activemqConnectionFactory class.
I tried using HttpClientTransport, ProxyConnector, NetworkConnector classes where I set the broker URL, but I am not sure how to add those to the activemqconnectionFactory.
Please find the details below:
message.queue.broker.url=ssl://<HOSTNAME>:61617
HttpClientTransport httpClientTransport = new HttpClientTransport(null, new URI(brokerUrl));
httpClientTransport.setProxyHost("<proxyHost>");
httpClientTransport.setProxyPort(3128);
httpClientTransport.start();
ProxyConnector proxy = new ProxyConnector();
proxy.setBind(new URI(brokerUrl));
proxy.setRemote(new URI(brokerUrl));
proxy.start();
NetworkConnector netConnector = new NetworkConnector() {
};
netConnector.setBrokerURL(brokerUrl);
netConnector.start();
ActiveMQConnectionFactory activeMQConnectionfactory = new ActiveMQConnectionFactory(userName, password, brokerUrl);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(activeMQConnectionfactory);
cachingConnectionFactory.setSessionCacheSize(50);
ActiveMQQueue activeMqQueue = new ActiveMQQueue(destination);
jmsTemplate = new JmsTemplate(sslConnectionFactory);
jmsTemplate.setDefaultDestination(activeMqQueue);
#Override
public void sendMessage(final String text) {
// TODO Auto-generated method stub
this.jmsTemplate.send(new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
Message message = session.createTextMessage(text);
return message;
}
});
}
When I run the above code, I get ConnectionTimedOutException:
Caused by: javax.jms.JMSException: Could not connect to broker URL: ssl://:61617. Reason: java.net.SocketTimeoutException: connect timed out
I also tried with
ssl://<URL>?transport.proxyHost=<PROXYHOST>amp;transport.proxyPort=3128
Thanks in Advance.