At the end of my Spark app (Containerized Spark v2.4.7), I publish a message to RabbitMQ. The app runs successfully and even the message is published to my containerized RabbitMQ. The problem is that the process doesn't really finish... I need to ctrl c from the terminal to abort the process.
Loglines that I write to console after publishing message are written, which means the process didn't stuck in the RabbitMQ client.
I tried to close the RabbitMQ channel and connection, but it didn't help.
Also, I tried to close the SparkSession at the end, but it didn't help either.
I wrote a test with Junit where I create the same queue with the same configuration and write the same message with the same client configuration and it finishes successfully, not stuck or anything.
My RabbitMQ publishing implementation:
private Connection getConnection() throws KeyManagementException, NoSuchAlgorithmException, URISyntaxException, IOException, TimeoutException{
ConnectionFactory factory = new ConnectionFactory();
String uri = getURI();
factory.setUri(new URI(uri));
factory.setConnectionTimeout(10000);
return factory.newConnection();
}
#Override
public void publish(String msg) {
try{
Connection connection = getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("rabbitQueue", true, false, false, null);
channel.basicPublish("exchange", "rabbitKey", null, msg.getBytes());
} catch (Exception ex){
logger.error("Error, failed to create connection to RabbitMQ", ex);
}
}
What should be the reason that my process doesn't finish?
Thanks!
Related
I have a JAVA application which creates consumers that listen to rabbitmq . I need to know the started consumer is still working fine and if not then i need to restart the consumer.
Is their any way i can do that. Currently my main application creates an Executor thread pool and passes this executor while creating new connection.
ExecutorService executor = Executors.newFixedThreadPool(30);
Connection connection = factory.newConnection(executor);
The main method then create 30 consumerApp object by calling constructor with new channel as argument and call the listen() method
for(int i=0;i<=30;i++) {
ConsumerApp consumer = new ConsumerApp(i,connection.createChanell());
consumer.listen() }
The listen method in consumerApp listen to a queue and start a DefaultConsumer Object which simply prints the received message
listen() {
try {
channel.queueDeclare("test-queue-name", false, false, false, null);
}
catch {
System.out.println("Exception on creating Queue")
}
Consumer consumer = new DefaultConsumer(this.channel) {
#Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received Message in consumer '"+consumerId+" "+ message + "'");
}
};
//Now starting the consumer
try {
channel.basicConsume(QUEUE_NAME, true, consumer);
}
catch (ShutdownSignalException | IOException ex) {
ex.printStackTrace();
}
}
I want to know is their any way i can check the consumer is active . My idea is to catch the shutdown signal exception and recreate the consumer object and recall the listen method . Is this necessary as rabbitmq auto recovers and connnect back. ? But how can i ensure this ?
Is this any way achievable using the threadpool passed to rabbitmq connector.
I am using latest version of rabbitmq client 5.3.0
Consumer has different methods that can help you track the state of your consumer. You're likely to be interested in handleConsumeOk and in handleCancel.
Automatic connection recovery will indeed re-register consumers after a connection failure, but that doesn't prevent you from following their state manually to e.g. expose some information on JMX.
I have a Java rich client application that registers a durable subscription on a remote HornetQ JMS Topic on startup.
However if the server restarts the connection is lost and can only be restored by restarting the client application.
This leads to confusing situations where JMS messages are not received and after a client restart a lot of messages are received at once.
A simple solution to restore connection would be to run a timer to periodically check if the connection is still valid and try to reconnect otherwise.
Alternatively the server could send a heartbeat to the client and try to reconnect if no heartbeat is received after a certain period (like mentioned in this answer).
But both seem like clumsy approaches to this problem and therefore I would like to know if there is a better solution to find out that the connection is not available anymore?
To get notified on the disconnection you have to register an ExceptionListener on your TopicConnection before you start the connection.
private void subscribe() throws JMSException {
// get context / create connection and session / etc.
TopicConnection connection = ...
connection.setExceptionListener(this::handleExceptions);
connection.start();
}
In the ExceptionListener you can check the error code of the received JMSException. (The error codes are vendor-specific)
In the case of HornetQ the error code DISCONNECT is received after the connection is lost.
private static final String HORNETQ_DISCONNECT_ERROR_CODE = "DISCONNECT";
private void handleExceptions(final JMSException jmsException) {
final String errorCode = jmsException.getErrorCode();
if (HORNETQ_DISCONNECT_ERROR_CODE.equals(errorCode)) {
tryConnect();
}
}
Then you can start a self-canceling Timer that tries to reconnect every x seconds until it succeeds.
private static final long SUBSCRIBE_RETRY_TIME_OUT_IN_MILLIS = 60000;
private void tryConnect() {
final Timer timer = new Timer("JMS-Topic-Reconnection-Timer", true);
final TimerTask timerTask = new TimerTask() {
#Override
public void run() {
try {
subscribe();
// cancel the timer, after the subscription succeeds
timer.cancel();
}
catch (final Exception e) {
logger.info("reconnect to jms topic failed: {}", e.getMessage());
}
}
};
timer.schedule(timerTask, SUBSCRIBE_RETRY_TIME_OUT_IN_MILLIS, SUBSCRIBE_RETRY_TIME_OUT_IN_MILLIS);
}
We're using hornetQ, and got a separate java application which connects to the queue to read from it.
We're having a Connection to it, creating a JMS session from the connection,
then obtaining a JMS MessageConsumer from the session, then assigning a custom MessageListener to the MessageConsumer.
We call message.acknowledge() for every processed message.
This all works very well, but in the case where we kill our java-application in the middle of processing, before hitting acknowledge(), the message is removed from hornetQ, yet never processed, and lost forever.
It seems that our current java application JMS setup somehow works by removing from the queue, and then rolling back/re-inserting it, if something goes wrong.
But if we kill our application, there won't be any roll-back or re-inserting, as it happens within our client/java-code.
Is there a setting or delivery-mode or something I can configure in the client, that causes the HornetQ to treat every message as "not consumed"(and still in the queue) untill the client sends the ack, so that a rollback arent neccecary?
EDIT: Also tried using transactional mode, calling:
connection.createSession(true, acknowledgementType)
Our MessageListener:
#Override
public void onMessage(Message message) {
LOGGER.debug("Message received. Going to handle message.");
try {
final TextMessage messageReceived = (TextMessage) message;
final String text = messageReceived.getText();
LOGGER.debug(String.format("Message content:\n%s", text));
final boolean isMessageSent = eventDispatcher.handleEvent(text);
if (isMessageSent) {
LOGGER.error("Behold!");
message.acknowledge();
session.commit();
} else {
session.rollback();
}
} catch (JMSException jmse) {
LOGGER.error("Failed to get text from a message from the events queue.", jmse);
} catch (JAXBException jaxbe) {
LOGGER.error("Failed to deserialize contents of the message from the events queue.", jaxbe);
} catch (Exception ex) {
LOGGER.error("An error occurred when receiving an event from the events queue:", ex);
}
Situation: Given the telnet client & server example of the official repo (https://github.com/netty/netty/tree/4.0/example/src/main/java/io/netty/example/telnet), I've change this a little bit using a fake blocking task: https://github.com/knalli/netty-with-bio-task/tree/master/src/main/java/de/knallisworld/poc
This is Netty 4!
Instead of echo replying the message (like the telnet server demo does), the channel handler blocks the thread for some time (in real world with things like JDBC or JSch, ...).
try { Thread.sleep(3000); } catch (InterruptedException e) {};
future = ctx.write("Task finished at " + new Date());
future.addListener(ChannelFutureListener.CLOSE);
This actually works: I'm testing this with a echo "Hello" | nc localhost $port) and the thread will be blocked (and nc waits) until it returns 3 seconds later.
However, this means I'm blocking a thread of Netty's event loop worker group with an unrelated task.
Therefor, I've changed the channel registration and applied a custom executor:
public class TelnetServerInitializer extends ChannelInitializer<SocketChannel> {
private static final StringDecoder DECODER = new StringDecoder();
private static final StringEncoder ENCODER = new StringEncoder();
private TelnetServerHandler serverHandler;
private EventExecutorGroup executorGroup;
public TelnetServerInitializer() {
executorGroup = new DefaultEventExecutorGroup(10);
serverHandler = new TelnetServerHandler();
}
#Override
protected void initChannel(final SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(
8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", DECODER);
pipeline.addLast("encoder", ENCODER);
// THIS!
pipeline.addLast(executorGroup, "handler", serverHandler);
}
}
Unfortunately, after this configuration the socket will be closed immediately after exiting handler's channelRead0(). I can see that the task itself will be processed including calling the handler's event methods. But the corresponding channel is already disconnected to the client (my nc command as already exited).
How does integrating another executor work? Am I missing a detail?
Your netty server is working as expected, it's the echo | nc command you are testing with that is exiting early.
Try using 'telnet localhost 3000' for an interactive session with your test server, enter some text and you'll see that the correct response is written after a delay, then the channel is closed.
Or just use 'nc -v -w10 localhost 3000', write some text, hit enter, again you'll see the expected output after a delay and the channel closed.
I'm testing open MQ for send and receive messages in my project. I have no problem to configure it to send a synchronous message, but i can't find any way in the official documentation to configure the message to be consumed 15 minutes after the producer send a message, and continue call the consumer if an error appears.
offical documentation: http://dlc.sun.com/pdf/819-7757/819-7757.pdf
my method whom send a message
public void sendMessage(EntradaPrecomven entrada){
try{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL, "file:///C:/mqteste");
// Create the initial context.
Context ctx = new InitialContext(env);
// Look up the connection factory object in the JNDI object store.
autenticisFactory = (ConnectionFactory) ctx.lookup(CF_LOOKUP_NAME);
mdbConn = autenticisFactory.createConnection();
mdbSession = mdbConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = (Destination) ctx.lookup(DEST_LOOKUP_NAME);
MessageProducer myProducer = mdbSession.createProducer(destination);
ObjectMessage outMsg = mdbSession.createObjectMessage(entrada);
outMsg.setJMSRedelivered(Boolean.TRUE);
myProducer.send(outMsg);
consumidor = mdbSession.createConsumer(destination);
MessageMDB myListener = new MessageMDB();
consumidor.setMessageListener(myListener);
mdbConn.start();
mdbConn.close();
}catch(Exception e){
try {
mdbSession.rollback();
} catch (JMSException e1) {}
}
}
My listener:
#Override
public void onMessage(Message msg) {
ObjectMessage objMessage = (ObjectMessage) msg;
try {
System.out.println("Received Phone Call:" + objMessage.getJMSRedelivered());
throw new JMSException("TESTE");
} catch (JMSException e) {
e.printStackTrace();
}
}
So, when i call mdbConn.start() the sendMessage() is called, but i want to call 15 minutes after the call. And whatever it sendMessage() does, the message is always removed from the queue. How can i keep the messagen in queue to be called later ?
Thanks!
The message is removed from the broker queue due to the fact that the session you are using is set to auto acknowledge.
mdbSession = mdbConn.createSession(false, Session.AUTO_ACKNOWLEDGE);
This will automatically send an acknowledgement to the broker that the listener has received a message for the consumer with which it is associated once the onMessage() method has executed to completion. This then results in the message being removed from the queue.
If you manually take over the acknowledgement process you can choose to only acknowledge the receipt of the message at a time of your choosing (be that 15 minutes later or whatever criteria you have for the consuming client).
Setting the Session Session.CLIENT_ACKNOWLEDGE will allow you to do this but then you will have to manually send an acknowledge in your consumer code. By calling acknowledge on the message msg.acknowledge() inside your onMessage() method within your listener.
This will then acknowledge the receipt of messages consumed within that session and remove them from the queue.
Pages 46 and 65 in the pdf you quoted are useful for more information on this as is the api