I have an application that sends AMQP messages via RabbitMQ. message sending is triggered on an http request. Recently I have noticed that some messages appear to be getting lost (as in never delivered). I also noticed that the list of channels being managed by the server is steadily increasing. The first thing I have corrected is to close channels after they are no longer required. However, I am still not sure my code is correctly structured to ensure delivery. Two sections of code are below; the first is a section of a singleton that manages the connection (does not recreate on every call), the second is the sending code. Any advice / guidance would be appreciated.
#Service
public class PersistentConnection {
private static Connection myConnection = null;
private Boolean blocked = false;
#Autowired ApplicationConfiguration applicationConfiguration;
#Autowired ConfigurationService configurationService;
#PostConstruct
private void init() {
}
#PreDestroy
private void destroy() {
try {
myConnection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Connection getConnection( ) {
if (myConnection == null) {
start();
}
else if (!myConnection.isOpen()) {
log.warn("AMQP Connection closed. Attempting to start.");
start();
}
return myConnection;
}
private void start() {
log.debug("Building AMQP Connection");
ConnectionFactory factory = new ConnectionFactory();
String ipAddress = applicationConfiguration.getAMQPHost();
String password = applicationConfiguration.getAMQPUser();
String user = applicationConfiguration.getAMQPPassword();
String virtualHost = applicationConfiguration.getAMQPVirtualHost();
String port = applicationConfiguration.getAMQPPort();
try {
factory.setUsername(user);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setPort(Integer.parseInt(port));
factory.setHost(ipAddress);
myConnection = factory.newConnection();
}
catch (Exception e) {
e.printStackTrace();
}
myConnection.addBlockedListener(new BlockedListener() {
public void handleBlocked(String reason) throws IOException {
// Connection is now blocked
blocked = true;
}
public void handleUnblocked() throws IOException {
// Connection is now unblocked
blocked = false;
}
});
}
public Boolean isBlocked() {
return blocked;
}
}
/*
* Sends ADT message to AMQP server.
*/
private void send(String routingKey, String message) throws Exception {
String exchange = applicationConfiguration.getAMQPExchange();
String exchangeType = applicationConfiguration.getAMQPExchangeType();
Connection connection = myConnection.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, exchangeType);
channel.basicPublish(exchange, routingKey, null, message.getBytes());
// Close the channel if it is no longer needed in this thread
channel.close();
}
Try this code:
#Service
public class PersistentConnection {
private Connection myConnection = null;
private Boolean blocked = false;
#Autowired ApplicationConfiguration applicationConfiguration;
#Autowired ConfigurationService configurationService;
#PostConstruct
private void init() {
start(); /// In this way you can initthe connection and you are sure it is called only one time.
}
#PreDestroy
private void destroy() {
try {
myConnection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Connection getConnection( ) {
return myConnection;
}
private void start() {
log.debug("Building AMQP Connection");
ConnectionFactory factory = new ConnectionFactory();
String ipAddress = applicationConfiguration.getAMQPHost();
String password = applicationConfiguration.getAMQPUser();
String user = applicationConfiguration.getAMQPPassword();
String virtualHost = applicationConfiguration.getAMQPVirtualHost();
String port = applicationConfiguration.getAMQPPort();
try {
factory.setUsername(user);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setPort(Integer.parseInt(port));
factory.setHost(ipAddress);
myConnection = factory.newConnection();
}
catch (Exception e) {
e.printStackTrace();
}
myConnection.addBlockedListener(new BlockedListener() {
public void handleBlocked(String reason) throws IOException {
// Connection is now blocked
blocked = true;
}
public void handleUnblocked() throws IOException {
// Connection is now unblocked
blocked = false;
}
});
}
public Boolean isBlocked() {
return blocked;
}
}
/*
* Sends ADT message to AMQP server.
*/
private void send(String routingKey, String message) throws Exception {
String exchange = applicationConfiguration.getAMQPExchange();
String exchangeType = applicationConfiguration.getAMQPExchangeType();
Connection connection = myConnection.getConnection();
if (connection!=null){
Channel channel = connection.createChannel();
try{
channel.exchangeDeclare(exchange, exchangeType);
channel.basicPublish(exchange, routingKey, null, message.getBytes());
} finally{
// Close the channel if it is no longer needed in this thread
channel.close();
}
}
}
This could be enough, you have an connection with rabbitmq when the system starts.
If you an lazy singleton, the code is just a bit different.
I suggest to not use isOpen() method, please read here:
isOpen
boolean isOpen() Determine whether the component is currently open.
Will return false if we are currently closing. Checking this method
should be only for information, because of the race conditions - state
can change after the call. Instead just execute and try to catch
ShutdownSignalException and IOException Returns: true when component
is open, false otherwise
EDIT**
Question 1:
What are you looking for is the HA client.
RabbitMQ java client by default doesn't support this features, since the version 3.3.0 supports only the reconnect,read this:
...allows Java-based clients to reconnect automatically after network
failure. If you want be sure about your messages you have to create an
robust client able to resists to all fails.
Generally you should consider the fails, for example:
what happen if there is an error during the message publish?
In your case you simply lose the message,You should re-queue the message manually.
Question 2:
I don’t know your code, but connection == null shouldn’t happen, because this procedure is called for first:
#PostConstruct
private void init() {
start(); /// In this way you can initthe connection and you are sure it is called only one time.
}
Anyway you can raise an exception, the question is:
What do I have to do with the message that I was trying to send?
See the question 1
I’d like to suggest to read more about the HA, for example this:
http://www.rabbitmq.com/ha.html
https://www.rabbitmq.com/reliability.html
And this for the client:
https://github.com/jhalterman/lyra (I never used it)
Create a reliable system with rabbitmq is not complex, but you should know some basic concept.
Anyway .. Let me know!
Related
I am trying to log any information or exception that occurs during message sending in RabbitMQ, for that I tried to add ConnectionListener on the existing connection factory.
kRabbitTemplate.getConnectionFactory().addConnectionListener(new ConnectionListener() {
#Override
public void onCreate(Connection connection) {
System.out.println("Connection Created");
}
#Override
public void onShutDown(ShutdownSignalException signal) {
System.out.println("Connection Shutdown "+signal.getMessage());
}
});
kRabbitTemplate.convertAndSend(exchange, routingkey, empDTO);
To test the exception scenario, I unbind and even deleted the queue from RabbitMQ console. But I did not get any exception or any shutdown method call.
Although, When I stopped RabbitMQ service, I got
Exception in thread "Thread-5" org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused: connect
But this exception is not from the listener I added.
I want to know
Why I did not get any exception or call from shutdown method
How can I use ConnectionListner and/or ChannelListner for logging failure/success of message delivery.
Can we use the AMQP appender, if yes how can we do that? (any example / tutorial)
What are the other approaches to ensure the message is sent?
Note: I do not want to use the publisher confirm the approach.
Connection Refused is not a ShutdownSignalException - the connection was never established because the broker is not present on the server/port.
You can't use the listeners to confirm delivery or return of individual messages; use publisher confirms and returns for that.
https://docs.spring.io/spring-amqp/docs/current/reference/html/#publishing-is-async
See the documentation for how to use the appenders.
https://docs.spring.io/spring-amqp/docs/current/reference/html/#logging
EDIT
To get notified of failures to connect, you currently need to use other techniques, depending on whether you are sending or receiving.
Here is an example that shows how:
#SpringBootApplication
public class So66882099Application {
private static final Logger log = LoggerFactory.getLogger(So66882099Application.class);
public static void main(String[] args) {
SpringApplication.run(So66882099Application.class, args);
}
#RabbitListener(queues = "foo")
void listen(String in) {
}
// consumer side listeners for no connection
#EventListener
void consumerFailed(ListenerContainerConsumerFailedEvent event) {
log.error(event + " via event listener");
if (event.getThrowable() instanceof AmqpConnectException) {
log.error("Broker down?");
}
}
// or
#Bean
ApplicationListener<ListenerContainerConsumerFailedEvent> eventListener() {
return event -> log.error(event + " via application listener");
}
// producer side - use a RetryListener
#Bean
RabbitTemplate template(ConnectionFactory cf) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(cf);
RetryTemplate retry = new RetryTemplate();
// configure retries here as needed
retry.registerListener(new RetryListener() {
#Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}
#Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
log.error("Send failed " + throwable.getMessage());
}
#Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
Throwable throwable) {
}
});
rabbitTemplate.setRetryTemplate(retry);
return rabbitTemplate;
}
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
try {
template.convertAndSend("foo", "bar");
}
catch (Exception e) {
e.printStackTrace();
}
};
}
}
I am new to spring-amqp. I am trying to manually acknowledge the messages instead of using auto-ack.
I am seeing that the last message is being unacked in the management console.
image for unacked message in managemnet console.
but the queue is empty.
As soon as I stop the server the last message gets acknowledged. How do I handle this and how can I print in logs ,the message id/information which has been unacknowledged..
Here is the code which I have implemented.
RabbitConfig.java:
public class RabbitMQConfig {
final static String queueName = "spring-boot";
#Bean
Queue queue() {
return new Queue(queueName, true,false,false,null);
}
#Bean
TopicExchange exchange() {
return new TopicExchange("spring-boot-exchange");
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueName);
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(queueName);
container.setMessageListener(listenerAdapter);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return container;
}
#Bean
Consumer receiver() {
return new Consumer();
}
#Bean
MessageListenerAdapter listenerAdapter(Consumer receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
Consumer.java
public class Consumer implements ChannelAwareMessageListener{
#RabbitListener(queues = "spring-boot")
public void receiveMessage(String message, Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) long tag)
throws IOException, InterruptedException {
Thread.sleep(500);
channel.basicAck(tag, true);
System.out.println(tag + "received");
}
#Override
public void onMessage(Message arg0, Channel arg1) throws Exception {
// TODO Auto-generated method stub
}
Producer endpoints:
#RestController
public class HelloController {
private final RabbitTemplate rabbitTemplate;
public HelloController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
// Call this end point from the postman or the browser then check in the
// rabbitmq server
#GetMapping(path = "/hello")
public String sayHello() throws InterruptedException {
// Producer operation
for (int i = 0; i < 100; i++) {
Thread.sleep(500);
rabbitTemplate.convertAndSend(RabbitMQConfig.queueName, "Hello World");
}
return "hello";
}
#GetMapping(path = "/hellotwo")
public String sayHellotwo() throws InterruptedException {
// Producer operation
for (int i = 0; i < 50; i++) {
Thread.sleep(500);
rabbitTemplate.convertAndSend(RabbitMQConfig.queueName, "SEcond message");
}
return "hellotwo";
}
You have two listener containers; the container bean and one created by the framework for the #RabbitListener.
I am not entirely sure what's happening without running a test myself, but I suspect the problem is your attempt to call receiveMessage from the simple MessageListenerAdapter.
That adapter is only designed to call a method with one argument (converted from the Message). Also, that adapter doesn't know how to map #Header parameters. I suspect that delivery fails and since you are using MANUAL acks, no more deliveries are attempted to that container because of the unack'd delivery and the default qos (1).
You don't need your container bean; instead configure the message listener container factory to set the ack mode. See the documentation.
If you are new to spring-amqp; why do you think you need manual acks? The default mode (auto) means the container will ack/nack for you (NONE is traditional rabbit auto-ack). It is not common to use manual acks with Spring.
Based on the suggested solusion and following the example, I'm trying to delete a key right after I get a notification that another key has expired.
The problem is that under stress test with heavy load of seting 600K new keys and setting half of them with expiration time of 2 seconds, I get the following exception:
Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: Unknown reply: t
The question is what will be the best practice to write such listener? (thread pool? if so in what context to implement it?)
Jedis version: 2.7.2
Redis version: 2.8.19
My code so far:
Subscriber class:
public class Subscriber {
public static void main(String[] args) {
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
Jedis jedis = pool.getResource();
jedis.psubscribe(new KeyExpiredListener(), "__key*__:*");
}
}
Listener class:
public class KeyExpiredListener extends JedisPubSub {
private String generalKeyTimeoutPrefix = "TO_";
#Override
public void onPMessage(String pattern, String channel, String message) {
String originalKey = null;
try {
if(channel.endsWith("__keyevent#0__:expired") && message.startsWith(generalKeyTimeoutPrefix)) {
originalKey = message.substring(generalKeyTimeoutPrefix.length());
del(originalKey);
}
} catch(Exception e) {
logger.error("..", e);
}
}
private void del(String key) {
Jedis jedis = new Jedis("localhost");
jedis.connect();
try {
jedis.del(key);
} catch (Exception e) {
logger.error("...");
} finally {
jedis.disconnect();
}
}
}
Key generator:
public class TestJedis {
public static void main(String[] args) throws InterruptedException {
JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost");
Jedis jedis = pool.getResource();
String prefixForlKeys = "token_";
String prefixForTimeoutKeys = "TO_";
for (int i = 0; i < 300000; i++) {
String key = prefixForlKeys + i;
String timeoutKey = prefixForTimeoutKeys + key;
jedis.set(key, "some_data");
jedis.set(timeoutKey, "");
jedis.expire(timeoutKey, 2);
}
System.out.println("Finished to create the keys");
}
}
the problem is with your implementation of del() method: it does not use connection pooling, does not reuse connections, so it finally occupies all available local ports. Try to use something like this:
private void del(String key) {
Jedis jedis = pool.getResource();
jedis.connect();
try {
jedis.del(key);
} catch (Exception e) {
e.printStackTrace( );
} finally {
jedis.close();
}
}
instead of opening/closing a connection for each expired key.
I use RabbitMQ for connection between parts my program. Version of RMQ(3.3.5). It used with java client from repo.
// Connection part
#Inject
public AMQService(RabbitMQConfig mqConfig) throws IOException {
this.mqConfig = mqConfig;
connectionFactory.setHost(mqConfig.getRABBIT_HOST());
connectionFactory.setUsername(mqConfig.getRABBIT_USERNAME());
connectionFactory.setPassword(mqConfig.getRABBIT_PASSWORD());
connectionFactory.setAutomaticRecoveryEnabled(true);
connectionFactory.setPort(mqConfig.getRABBIT_PORT());
connectionFactory.setVirtualHost(mqConfig.getRABBIT_VHOST());
Connection connection = connectionFactory.newConnection();
channel = connection.createChannel();
channel.basicQos(1);
}
//Consume part
private static void consumeResultQueue() {
final QueueingConsumer consumer = new QueueingConsumer(channel);
Future resultQueue = EXECUTOR_SERVICE.submit((Callable<Object>) () -> {
channel.basicConsume("resultQueue", true, consumer);
while (true) {
try {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String message = new String(delivery.getBody(), "UTF-8");
resultListener.onMessage(message);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
});
}
I want leave use inifinty loop. Can RMQ notify client while message can read from queue? Without check?
You can create a class which extends DefaultConsumer and override handleDelivery.
public class MyConsumer extends DefaultConsumer {
public MyConsumer(Channel channel) {
super(channel);
}
#Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties, byte[] body) throws IOException {
// do your computation
}
}
And register this consumer with channel.basicConsume(queueName, myConsumerInstance);
Note that by doing this, handleDelivery will run inside rabbitmq client thread pool so you should avoid any long computation inside this function.
some configurations here:
non-durable consumer,non-persistent message,disabled flow control,default prefetch size,optimizeAcknowledge = true,asynsend = true, use jms to connect ActiveMQ
for example,
one producer、one consumer,
Producer————Topic————consumer
the producer send rate can reach 6k/s
but,in this case:
one producer three consumer,
/——consumer
Producer——-Topic——-consumer
\——consumer
the producer send rate drop down to 4k/s
Here is my some of the key code:
sender class:
public class sender {
public Boolean durable=false;
public String clientID=null;
public Boolean transacted=false;
public int ackMode=Session.AUTO_ACKNOWLEDGE;
public int timeToLive=0;
public String queuename = "";
public int persistent = DeliveryMode.NON_PERSISTENT;
public Connection createConnection(String user,String pwd,String url) throws JMSException, Exception {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(user, pwd, url);
connectionFactory.setDispatchAsync(true);
//connectionFactory.setAlwaysSessionAsync(false);
Connection connection = connectionFactory.createConnection();
if (durable && clientID!=null) {
connection.setClientID(clientID);
}
connection.start();
return connection;
}
public Session createSession(Connection connection) throws Exception {
Session session = connection.createSession(transacted, ackMode);
return session;
}
public MessageProducer createProducer(Session session) throws JMSException {
Queue destination = session.createQueue(queuename);
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(persistent);
if( timeToLive!=0 )
producer.setTimeToLive(timeToLive);
return producer;
}
public void onMessage(Message message) {
//process message
}
}
sendmain method:
public static void main(String[] args) throws JMSException, Exception {
// TODO Auto-generated method stub
sender s = new sender();
s.persistent = DeliveryMode.NON_PERSISTENT;
Connection c = s.createConnection("","","tcp://localhost:61616?jms.useAsyncSend=true");
Session sess = s.createSession(c);
Topic topic = sess.createTopic("topic.test");
MessageProducer mp = sess.createProducer(topic);
StringBuffer tmpsb=new StringBuffer();
for (int j=0;j<1024;j++)
{
tmpsb.append("0");
}
Message m = sess.createTextMessage(tmpsb.toString());
long pre=System.currentTimeMillis();
for (int i=0;i<10000;i++)
{
mp.send(m);
}
long post=System.currentTimeMillis();
mp.close();
System.out.println("sendtime:"+(post-pre));
System.out.println("sendrate:"+10000000/(float)(post-pre));
System.out.println("timenow:"+(post));
}
receiver class code:
public class receiver implements MessageListener
{
public int receivetimes=0;
public long pretime;
public void onMessage(Message msg)
{
//TextMessage tm = (TextMessage) msg;
try {
if (receivetimes==0)
{
pretime=System.currentTimeMillis();
}
receivetimes+=1;
if (receivetimes==10000)
{
long now=System.currentTimeMillis();
System.out.println("time:"+(now-pretime)+"\nrecive rate:"+9999999/(float)(now-pretime));
System.out.println("timenow:"+(now));
receivetimes=0;
}
} catch(Throwable t) {
t.printStackTrace();
}
}
}
receiver class code here has hide some methods,such as createConnection,createSession or something just like sender class does.
receiver main method:
public static void main(String[] args) throws JMSException, Exception {
// TODO Auto-generated method stub
receiver s = new receiver();
Connection c = s.createConnection("","","tcp://localhost:6151?jms.optimizeAcknowledge=true");
Session sess = s.createSession(c);
Topic destination = sess.createTopic("topic.test");
MessageConsumer consumer = sess.createConsumer(destination);
consumer.setMessageListener(new receiver());
}
Every consumer is in a standalone process. I ran three consumer and one producer then I got a result of bad performance. Does any one knows why I get this?
Just as #TimBish said.
The issue is 'producer,consumer,activemq-server on same machine'.When separated,the issue never shows up.
It is so important to test something in a strict way.......