I am new to redis and i am using spring data redis for implementing some task queue feautre. Since i wanna do some failover checking, is there any a way to get the number of subscriber like in command "pubsub numsub " for a specific channel. Many thanks
if you use spring reactive-Redis with spring boot you can create a specific topic and get the all subscribers that received the published event as a return by publishing a vent ny using convertAndSend method of ReactiveRedisTemplate. but the spring doesn't provide that in imperative way. the convertAndSend method of RedisTemplate doesn't return anything. the return type is void.
stringStringReactiveRedisTemplate
.convertAndSend(
"your topic",
"event message"
).subscribe(receivedCount -> {
System.out.println("num of subscribers the event received: " + receivedCount);
});
you can another way by using the redis connection. it doesn't matter if you use with spring boot. you can get the connection from the redisTemplate and call the publish method to publish the event like below. then you have to provide your channel name and the message by byte[].
Long receivedCount = redisTemplate.getConnectionFactory().getConnection().publish(
"your channel".getBytes(),
"message".getBytes()
);
now you will receive how many live subscribers that event received. (only for that mentioned topic name).
read more https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:pubsub:publish
Related
I am new to SSE (Server Sent Events) . I found a way to send response using SSE in spring boot. I am able to send response for every 20 seconds. I used below code for the same.
public Flux<UserDto> fetchUserEvent() {
return Flux.interval(Duration.ofSeconds(20)).map(intr -> generateUserEvent()).flatMapIterable(x -> x);
}
generateUserEvent() - verify if new user added in DB. If it found user data, will return the same or will return empty object (new UserDto())
But the problem is , this method being called for every 20 seconds .
But , My requirement is to send the empty response to client every 20 seconds and send the respone whenever new user added to DB.
How can I achieve my goal? Or I am totally wrong conceptually.
You should create an event publisher and listener so you can send an event to the emitter when a new user is registered.
Since you're using spring-boot and probably hibernate you can see example here on how to intercept hibernate events.
Hibernate interceptor or listener with Spring Boot and Spring Data JPA
I would personally not use flux and make a scheduled method in spring to send those empty ping responses to the emitter. example of this can be found here: https://www.roytuts.com/server-sent-events-spring-push-notifications/
More info for spring events in general can be found here:
https://www.baeldung.com/spring-events
CLARIFICATION:
Thanks to #JustinBertram comment I realized that this question does not make sense.
STOMP protocol does not support selectors by itself, you have to use brokers such as ActiveMQ that implement it. STOMP supports headers that can be used by brokers for filtering messages by selectors.
In my case, I'm not using any broker, just frontend with Angular + Stomp + SocksJS and backend with Spring Boot, so I can't use selectors.
The documentation of STOMP protocol does not make this clear to me and I got confused. See these references:
the specification:
Stomp brokers may support the selector header which allows you to
specify an SQL 92 selector on the message headers which acts as a
filter for content based routing.
this article:
The subscribe() method takes an optional headers argument to specify
additional headers when subscribing to a destination:
var headers = {ack: 'client', 'selector': "location = 'Europe'"};
client.subscribe("/queue/test", message_callback, headers);
The client specifies that it will handle the message acknowledgement
and is interested to receive only messages matching the selector
location = 'Europe'.
I'm implementing a backend in Spring Boot. For two-way communications with the frontend I'm using stomp over websockets.
I have followed this Spring Boot + Angular example
It works, but one of my requirements is that the backend has to send messages with selectors so that the frontend subscribes to a topic and only receives the filtered data, to avoid performance issues with real time data.
i.e. { 'selector': "location = 'Europe'" }
For that purpose, I'm trying to make the backend send the messages with selectors, but I can't make it work.
I have followed this article to implement the frontend with selectors and it works correctly, the problem is only the backend.
I tried with #SendTo annotation but it seems it doesn't have any params for that as per the article:
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000);
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
Also I tried with the MessagingTemplate, but I don't know how to set the selector properties in the header:
http://assets.spring.io/wp/WebSocketBlogPost.html
MessageSendingOperations<String> messagingTemplate;
messagingTemplate.convertAndSend(destination, quote);
I really appreciate any help, I have read many articles and docs but I don't find anything talking in particular about this with a solution.
Well, it's possible to use the JMS' selectors with Spring (Boot) Websocket and a STOMP client. I found a native way.
The key thing is that the selector is applied to the org.springframework.messaging.Message instance, and it uses Spring's Spel language to apply the condition (it's not the JMS SQL-like).
So using the default SimpMessagingTemplate, in the backend you can send header variables like this:
this.messagingTemplate.convertAndSend(
"/topic/something", //your destination
payload, //any kind of payload (body)
Map.of("id", 1) //header with key/value
);
In the frontend, to enter a selector that will be evaluated by the org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry.filterSubscriptions, you must declare your Stomp/WebSockets headers as:
{"selector": "headers['nativeHeaders']['id'][0] == '999'"}
Yeah, it's horrible but it works.
As the default Message is GenericMessage, the headers are processed in a new key called "nativeHeaders".
The ['key'],[0] and == are Spring's Spel sintaxes.
Go ahead and filter your messages on the backend, not in the frontend, please!
The latest version of the STOMP specification doesn't include any specific statement about selectors and their syntax because it's really up to the broker implementation as to what is supported here. The specification now just states:
STOMP servers MAY support additional server specific headers to customize the delivery semantics of the subscription. Consult your server's documentation for details.
Brokers like ActiveMQ 5.x and ActiveMQ Artemis support the selector STOMP header and the syntax & behavior of the selector is based on JMS selectors.
Selectors in JMS are for selecting messages on consumption and are configured by the consuming client. You can't set the selector when sending the message.
JMS selectors select messages based on the headers or properties of the message, although some implementations go beyond this and allow selecting based on the content of the message itself. Therefore, if you want to have a selector location = 'Europe' on a consumer then you should set a header on the message when it is sent with the name location and the value of Europe.
The convertAndSend method is overloaded and provides a couple of ways to set a header:
Pass a map of key/value pairs to the convertAndSend method.
Implement a MessagePostProcessor and pass that to the convertAndSend method. Inside your post-processor you can invoke the javax.jms.Message#setStringProperty() method.
I'm using #EnableJms and #JmsListener annotation to register a queue listener in my application, basing on this tutorial. I'm connecting to IBM MQ, getting connection factory using jndi. I have read about acknowledge modes etc. but still it's a new thing to me. And my problem is that the message is not being returned to a queue (the listener is never called again).
#Bean
public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
DefaultJmsListenerContainerFactory factory
= new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setSessionTransacted(true);
factory.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE); //I have tried also CLIENT_ACKNOWLEDGE
return factory;
}
#JmsListener(containerFactory = "jmsListenerContainerFactory", destination = "myQueue")
#SendTo("secondQueue")
public String testListener(String message){
if(true) throw new NullPointerException();
else return message;
}
Any help would be much appreciated.
I would have also a second question. From what I understand if I would like to implement any operation on a database, only way to rollback a commit (if something went wrong after this) would be to create a transaction manager? If not, I would need to detect a duplicated message.
First set the acknowledgement mode to Session.CLIENT_ACKNOWLEDGE
and when receiving the messages, if it's processed correctly then just call message.acknowledge() method or else don't call.
It will automatically stay in the queue and you don't need to resend it.
You need to use
import javax.jms.Message
I created simple Spring Boot app and Docker container of IBM MQ to test your case.
I found good instructions in this tutorial: https://developer.ibm.com/tutorials/mq-jms-application-development-with-spring-boot/
And in your case this environment behaves as expected: endless cycle of receive message -> NullPointerException -> return message -> ...
Than I found feature of IBM MQ called "Backout Queues & Thresholds", you'll found the explanation in this blog post: https://community.ibm.com/community/user/imwuc/browse/blogs/blogviewer?BlogKey=28814801-083d-4c80-be5f-90aaaf81cdfb
Briefly, it is possible to restrict number of times message returned to queue after exception, and after this limit send message to another queue.
May be in your case this feature used on your destination queue.
I'm using direct exchange to publish messages to certain queues with routing keys, all configured in rabbit-server not code, I'm consuming messages with spring micro-service then some failures happens within the receiving method, then the message re-queued causing loop, so I'd like to add a policy with rabbit-server to prevent that kind of re-queuing, could it be added as an argument while binding queue with exchange with specific routing key, or it should be a policy ?
On any exception by default spring send nack with requeue "true". If in your spring consumer application you want to send requeue false, then throw the exception "AmqpRejectAndDontRequeueException". So your consumer code should loook something like this :
`void onMessage(){
try{
// Your Code Here
} catch(Exception e){
throw new AmqpRejectAndDontRequeueException();
}
}`
I am trying to create a durable subscription to a multiple consumer queue in oracle. Using the JMS Spring support. This is a Grails application.
My spring code is as follows:
myQueueConnectionFactory(uk.my.MyQueueConnectionFactory) {
dataSource = ref("dataSourceListener")
}
myMessageListener(uk.my.MyMessageListener)
jms.'listener-container'( 'client-id':'clientid', 'connection-factory':'myQueueConnectionFactory', 'acknowledge':'auto', 'concurrency':'1', 'destination-type': 'topic' ) {
jms.'listener'( destination:'my.queue', ref:'myMessageListener', subscription: 'subscription' )
}
I have specified the client-id and subscription values. But it appears that each time I make a connection to the queue, a new consumer is registered. Showing as something like 'TSUB_....' If I stop and start my application, then another consumer is created, and any messages added to the queue whilst the application was down are not dequeued.
Can anyone point me in the right direction to create a durable subscription. I had thought that my 'client-id' and 'subscription' values would do this for me. Do I need to specify these elsewhere?
Thanks!
Ok, I found the solution to this. Updating my destination-type to 'durableTopic' did the trick.