I have an Flux endpoint that I provide to clients (subscribers) to receive updated prices. I'm testing it accessing the URL (http://localhost:8080/prices) though the browser and it works fine. The problem I'm facing (I'm maybe missing some concepts here) is when I open this URL in many browsers and I expect to receive the notification in all of them, but just one receives. It is working as a queue instead of a topic (like in message Brokers). Is that correct behavior?
#GetMapping(value = "prices", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<Collection<Price>>> prices() {
return Flux.interval(Duration.ofSeconds(5))
.map(sec -> pricesQueue.get())
.filter(prices -> !prices.isEmpty())
.map(prices -> ServerSentEvent.<Collection<Price>> builder()
.event("status-changed")
.data(prices)
.build());
}
get isn't a standard queue operation, but this is almost certainly because your pricesQueue.get() method isn't idempotent. With every request (with every browser window you open in this case), you'll get a new flux that calls pricesQueue.get() every 5 seconds. Now if pricesQueue.get() just retrieves the latest item in the queue and does nothing with it, all is good - all your subscribers receive the same item, and the same item is displayed. But if it acts more like a poll() where it removes the item in the queue after it's retrieved it, then only the first flux will get that value - the rest won't, as by that point it will have been removed.
You've really two main options here:
Change your get() implementation (or implement a new method) so that it doesn't mutate the queue, only retrieves a value.
Turn the flux into a hot flux. Store Flux.interval(Duration.ofSeconds(5)).map(sec -> pricesQueue.get()).publish().autoConnect() somewhere as a field (let's say as queueFlux), then just return queueFlux.filter(prices -> !prices.isEmpty()).map(...) in your controller method.
Related
I'm quite new to Mono and Flux. I'm trying to join several downstream API responses. It's a traditional blocking application. I don't wish to collect a list of Mono, I want a List of the payloads returned from the downstream APIs, which I fetch from the Mono. However the 'result' being returned to the controller at times only has some or none of the downstream API responses. What is the correct way to do this? I've read several posts How to iterate Flux and mix with Mono states
you should not call subscribe anywhere in a web application. If this is bound to an HTTP request, you're basically triggering the
reactive pipeline with no guarantee about resources or completion.
Calling subscribe triggers the pipeline but does not wait until it's
complete
Should I be using CompletableFuture?
In my Service I attempted
var result = new ArrayList<List<>>();
List<Mono<X>> monoList = apiCall();
Flux.fromIterable(monoList)
.flatMap(m -> m.doOnSuccess(
x -> {
result.add(x.getData());
}
)).subscribe();
I also attempted the following in controller, but the method returns without waiting for subscribe to complete
var result = new ArrayList<List<X>>();
Flux.concat(
this.service.callApis(result, ...)
).subscribe();
return result;
In my service
public Mono<Void> callApis(List<List<x>> result, ..) {
...
return Flux.fromIterable(monoList)
.flatMap(m -> m.doOnSuccess(
x -> {
result.add(x.getData()...);
}
)).then();
The Project Reactor documentation (which is very good) has a section called Which operator do I need?. You need to create a Flux from your API calls, combine the results, and then return to the synchronous world.
In your case, it looks like all your downstream services have the same API, so they all return the same type and it doesn't really matter what order those responses appear in your application. Also, I'm assuming that apiCall() returns a List<Mono<Response>>. You probably want something like
Flux.fromIterable(apiCall()) // Flux<Mono<Response>>
.flatMap(mono -> mono) // Flux<Response>
.map(response -> response.getData()) // Flux<List<X>>
.collectList() // Mono<List<List<X>>>
.block(); // List<List<X>>
The fromIterable(...).flatMap(x->x) construct just converts your List<Mono<R>> into a Flux<R>.
map() is used to extract the data part of your response.
collectList() creates a Mono that waits until the Flux completes, and gives a single result containing all the data lists.
block() subscribes to the Mono returned by the previous operator, and blocks until it is complete, which will (in this case) be when all the Monos returned by apiCall() have completed.
There are many possible alternatives here, and which is most suitable will depend on your exact use case.
I am trying to do live streaming example app, where I can live update the list in the browser. I want to return all elements and then still listening (don't stop the stream) when new item is add to the database. Then I want to show new item in the browser. My current solution all the time prints all items (second by second) but I think there is better solution, when I can a) find the difference in list from last processing repository.findAll() and return only currList - prevList b) I can listen to some kind of events? Like inserting to table and add new item to still opened stream.
Here is my current code:
#RestController
#RequestMapping("/songs")
public class SongController {
private final SongRepository songRepository;
public SongController(SongRepository songRepository) {
this.songRepository = songRepository;
}
#GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Song> getAllSongs() {
return Flux.interval(Duration.ofSeconds(1))
.flatMap(x -> songRepository.findAll());
}
#PostMapping
public Mono<Song> addSong(#RequestBody Song song) {
return songRepository.save(song);
}
}
Here is how it looks like now:
As you can see, Its obviously looped, and I just need plain list with 7 elements on begining and then +1 element every time I post new song (by addSong()).
I don't need a entire ready solution, I just don't know what should I use.
Thank you in advance, cheers
In my experience there are three options that have different pros and cons.
You could create a web socket connection from the browser to your backend service. This will create a bi-directional connection that will allow you push updates from the server to your browser. In this instance whenever you add a song you would then write that song to the web socket connection and handle that on the browser side, so adding it to the list in the browser.
The cons of this are in my experience web socket connections are finicky and aren't the most stable or reliable.
You could use server side events. I haven't used this personally but I have heard this can be a viable options for pushing events from the server to the browser. https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
You could poll the endpoint. I know this approach gets a lot of hate in recent years but it is a viable options. The benefit with polling the endpoint is that it is resilient to failures. If your backend is overloaded and can't respond for one request it will likely be able to respond to a subsequent request. Also there are ways of improving commonly used endpoints so you're not hammering your database like a cache or something of that nature.
I am using webclient for the rest call and what i need is, if the primary URL is failing for the n'th time do the next re-try on Secondary URL . Please find below sample code for the logic which i am using. But it seems we cannot change the URL once he client is created and even if i change the URL its not getting effected and still requests are been fired to the initial URL.
ClientHttpConnector connector;//initiate
WebClient webClient = WebClient.builder().clientConnector(connector).build();
WebClient.RequestBodyUriSpec client = webClient.post();
client.uri("http://primaryUrl/").body(BodyInserters.fromObject("hi")).retrieve().bodyToMono(String.class).retryWhen(Retry.anyOf(Exception.class)
.exponentialBackoff(Duration.ofSeconds(2), Duration.ofSeconds(10)).doOnRetry(x ->
{
if (x.iteration() == 2) {
client.uri("http://fail_over_url/");//this does not work
}
})
.retryMax(2)).subscribe(WebClientTest::logCompletion, WebClientTest::handleError);
Is there any way to change the URL at the middle of re-try cycle ?
But it seems we cannot change the URL once he client is created
You cannot - it's immutable.
even if i change the URL its not getting effected
You're not actually changing the URL. Take a look at the uri() method - it's returning a new instance with a URI set. Since you're not doing anything with that new instance, nothing happens (as expected.)
The route I'd probably suggest is to create a separate method to form & return your basic WebClient publisher:
private Mono<String> fromUrl(String url) {
return WebClient.builder().clientConnector(connector).build()
.post()
.body(BodyInserters.fromValue("hi"))
.uri(url)
.retrieve()
.bodyToMono(String.class);
}
...and then do something like:
fromUrl("https://httpstat.us/400").retryWhen(Retry.backoff(2, Duration.ofSeconds(1)))
.onErrorResume(t -> Exceptions.isRetryExhausted(t), t -> fromUrl("https://httpstat.us/500").retryWhen(Retry.backoff(5, Duration.ofSeconds(1))))
.onErrorResume(t -> Exceptions.isRetryExhausted(t), t -> fromUrl("https://httpstat.us/200").retryWhen(Retry.backoff(7, Duration.ofSeconds(1))))
...which will try /400 3 times, then try /500 5 times, then /200 up to 7 times (but unless it's down, that will of course return on the first try.)
Note that the above example uses the latest version of reactor-core which has the retry functionality built in, rather than the retry functionality in reactor addons. Translating it to the reactor addons functionality should be reasonably straightforward.
This doesn't not strictly changing the URL in the same retry cycle, but instead chaining requests together with configurable retries per request. This then allows you to set different retry strategies on different URLs, which is advantageous if you don't necessarily want the retry to "carry on" from its previous point (It could make sense to set the backoff back to one second for a fresh URL, for example.)
So my use-case is to consume messages from Kafka in a Spring Webflux application while programming in the reactive style using Project Reactor, and to perform a non-blocking operation for each message in the same order as the messages were received from Kafka. The system should also be able to recover on its own.
Here is the code snippet that is setup to consume from :
Flux<ReceiverRecord<Integer, DataDocument>> messages = Flux.defer(() -> {
KafkaReceiver<Integer, DataDocument> receiver = KafkaReceiver.create(options);
return receiver.receive();
});
messages.map(this::transformToOutputFormat)
.map(this::performAction)
.flatMapSequential(receiverRecordMono -> receiverRecordMono)
.doOnNext(record -> record.receiverOffset().acknowledge())
.doOnError(error -> logger.error("Error receiving record", error))
.retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
.subscribe();
As you can see, what I do is: take the message from Kafka, transform it into an object intended for a new destination, then send it to the destination, and then acknowledge the offset to mark the message as consumed and processed. It is critical to acknowledge the offset in the same order as the messages being consumed from Kafka so that we don't move the offset beyond messages that were not fully processed (including sending some data to the destination). Hence I'm using a flatMapSequential to ensure this.
For simplicity let's assume the transformToOutputFormat() method is an identity transform.
public ReceiverRecord<Integer, DataDocument> transformToOutputFormat(ReceiverRecord<Integer, DataDocument> record) {
return record;
}
The performAction() method needs to do something over the network, say call an HTTP REST API. So the appropriate APIs return a Mono, which means the chain needs to be subscribed to. Also, I need the ReceiverRecord to be returned by this method so that the offset can be acknowledged in the flatMapSequential() operator above. Because I need the Mono subscribed to, I'm using flatMapSequential above. If not, I could have used a map instead.
public Mono<ReceiverRecord<Integer, DataDocument>> performAction(ReceiverRecord<Integer, DataDocument> record) {
return Mono.just(record)
.flatMap(receiverRecord ->
HttpClient.create()
.port(3000)
.get()
.uri("/makeCall?data=" + receiverRecord.value().getData())
.responseContent()
.aggregate()
.asString()
)
.retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
.then(Mono.just(record));
I have two conflicting needs in this method:
1. Subscribe to the chain that makes the HTTP call
2. Return the ReceiverRecord
Using a flatMap() means my return type changes to a Mono. Using doOnNext() in the same place would retain the ReceiverRecord in the chain, but would not allow the HttpClient response to be subscribed to automatically.
I can't add .subscribe() after asString(), because I want to wait till the HTTP response is completely received before the offset is acknowledged.
I can't use .block() either since it runs on a parallel thread.
As a result, I need to cheat and return the record object from the method scope.
The other thing is that on a retry inside performAction it switches threads. Since flatMapSequential() eagerly subscribes to each Mono in the outer flux, this means that while acknowledgement of offsets can be guaranteed in order, we can't guarantee that the HTTP call in performAction will be performed in the same order.
So I have two questions.
Is it possible to return record in a natural way rather than returning the method scope object?
Is it possible to ensure that both the HTTP call as well as the offset acknowledgement are performed in the same order as the messages for which these operations are occurring?
Here is the solution I have come up with.
Flux<ReceiverRecord<Integer, DataDocument>> messages = Flux.defer(() -> {
KafkaReceiver<Integer, DataDocument> receiver = KafkaReceiver.create(options);
return receiver.receive();
});
messages.map(this::transformToOutputFormat)
.delayUntil(this::performAction)
.doOnNext(record -> record.receiverOffset().acknowledge())
.doOnError(error -> logger.error("Error receiving record", error))
.retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5))
.subscribe();
Instead of using flatMapSequential to subscribe to the performAction Mono and preserve sequence, what I've done instead is delayed the request for more messages from the Kafka receiver until the action is performed. This enables the one-at-a-time processing that I need.
As a result, performAction doesn't need to return a Mono of ReceiverRecord. I also simplified it to the following:
public Mono<String> performAction(ReceiverRecord<Integer, DataDocument> record) {
HttpClient.create()
.port(3000)
.get()
.uri("/makeCall?data=" + receiverRecord.value().getData())
.responseContent()
.aggregate()
.asString()
.retryBackoff(100, Duration.ofSeconds(5), Duration.ofMinutes(5));
}
I am using the StreamObserver class found in the grpc-java project to set up some bidirectional streaming.
When I run my program, I make an undetermined number of requests to the server, and I only want to call onCompleted() on the requestObserver once I have finished making all of the requests.
Currently, to solve this, I am using a variable "inFlight" to keep track of the requests that have been issued, and when a response comes back, I decrement "inFlight". So, something like this.
// issuing requests
while (haveRequests) {
MessageRequest request = mkRequest();
this.requestObserver.onNext(request);
this.inFlight++;
}
// response observer
StreamObserver<Message> responseObserver = new StreamObserver<Message> {
#Override
public void onNext(Message response) {
if (--this.onFlight == 0) {
this.requestObserver.onCompleted();
}
// work on message
}
// other methods
}
A bit pseudo-codey, but this logic works. However, I would like to get rid of the "inFlight" variable if possible. Is there anything within the StreamObserver class that allows this sort of functionality, without the need of an additional variable to track state? Something that would tell the number of requests issued and when they completed.
I've tried inspecting the object within the intellij IDE debugger, but nothing is popping out to me.
To answer your direct question, you can simply call onComplete from the while loop. All the messages passed to onNext. Under the hood, gRPC will send what is called a "half close", indicating that it won't send any more messages, but it is willing to receive them. Specifically:
// issuing requests
while (haveRequests) {
MessageRequest request = mkRequest();
this.requestObserver.onNext(request);
this.inFlight++;
}
requestObserver.onCompleted();
This ensures that all responses are sent, and in the order that you sent them. On the server side, when it sees the corresponding onCompleted callback, it can half-close its side of the connection by calling onComplete on its observer. (There are two observers on the server side one for receiving info from the client, one for sending info).
Back on the client side, you just need to wait for the server to half close to know that all messages were received and processed. Note that if there were any errors, you would get an onError callback instead.
If you don't know how many requests you are going to make on the client side, you might consider using an AtomicInteger, and call decrementAndGet when you get back a response. If the return value is 0, you'll know all the requests have completed.