How to return response immediate to client in spring flux by controlling the no of thread using ExecutorService and CompletableFuture? - spring-boot

I needed to call two downstream systems parallelly with non-blocking io from Spring flux-based my rest service API. But the first downstream system capacity is 10 requests at a time and the second downstream system is 100.
The first downstream system out is input to the second downstream system so I can make a more parallel request to the second system to expedite the process.
The second downstream system response is very large so unable to hold in memory to concrete all the response So immediate want to return the response to the client.
Ex workflow:
Sample Code:
#GetMapping(path = "/stream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<String> getstream() {
ExecutorService executor = Executors.newFixedThreadPool(10);
List<CompletableFuture> list = new ArrayList<>();
AtomicInteger ai = new AtomicInteger(1);
RestTemplate restTemplate = new RestTemplate();
for (int i = 0; i < 100; i++) {
CompletableFuture<Object> cff = CompletableFuture.supplyAsync(
() -> ai.getAndAdd(1) + " first downstream web service " +
restTemplate.getForObject("http://dummy.restapiexample.com/api/v1/employee/" + ai.get(), String.class)
).thenApplyAsync(v -> {
Random r = new Random();
Integer in = r.nextInt(1000);
return v + " second downstream web service " + in + " " + restTemplate.getForObject("http://dummy.restapiexample.com/api/v1/employee/" + ai.get() + 1, String.class) + " \n";
}, executor);
list.add(cff);
}
return Flux.fromStream(list.stream().map(m -> {
try {
return m.get().toString();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return "";
})
);
}
This code only working for the first five threads after I am getting a response all threads completed the process. But I needed to get a response immediately to the client once I am getting the response from the second downstream system.
Note: The above code is not implemented with a second level thread pool.
Thank you in advance.

If you're building non-blocking system using Spring-Webflux it's better to utilise capabilities of WebClient in your example. I've created a simple test application where the below code snippet worked for me:
private final WebClient w = WebClient.create("http://localhost:8080/call"); // web client for external system
#GetMapping(path = "/stream", produces = MediaType.APPLICATION_STREAM_JSON_VALUE)
public Flux<MyClass> getstream() {
return Flux
.range(0, 100) // prepare initial 100 requests
.window(10) // combine elements in batch of 10 (probably buffer will fit better, have a look)
// .delayElements(Duration.ofSeconds(5)) for testing purpose you can use this function as well
.doOnNext(flow -> log.info("Batch of 10 is ready")) // double check tells that batch is ready
.flatMap(flow -> flow
// perform an external async call for each element in batch of 10
// they will be executed sequentially but there will not be any performance issues because
// calls are async. If you wish you can add .parallel() to the flow to make it parallel
.flatMap(element -> w.get().exchange())
.map(r -> r.bodyToMono(MyClass.class))
)
// subscribe to each response and throw received element further to the stream
.flatMap(response -> Mono.create(s -> response.subscribe(s::success)))
.window(1000) // batch of 1000 is ready
.flatMap(flow -> flow
.flatMap(element -> w.get().exchange())
.map(r -> r.bodyToMono(MyClass.class))
)
.flatMap(response -> Mono.create(s -> response.subscribe(s::success)));
}
public static class MyClass {
public Integer i;
}
UPDATE:
I've prepared a small application to reproduce your case. You can find it in my repository.

Related

Spring Integration Channel works for first time, then does not the second time

When using Spring Integration and Channel to another integration flow, it only works the first time.
Then after that it skips over the channel and returns.
From first integration flow:
.handle((p, h) -> {
System.out.println("Payload Before Channel" + p.toString());
return p;
})
.channel(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.getChannelName())
.handle((p, h) -> {
System.out.println("Payload After Channel" + p.toString());
return p;
})
Then on the next integration flow:
#Bean
public IntegrationFlow jamsSubmitJob() {
return IntegrationFlows.from(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.getChannelName())
.handle((p, h) -> {
try {
jamsToken = authMang.getJamsAuth().getTokenWithTokenType();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JAMS_SUBMIT_JOB_INTGRTN
.info(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Integration called.");
JAMS_SUBMIT_JOB_INTGRTN.info(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Headers:= " + h);
JAMS_SUBMIT_JOB_INTGRTN.debug(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Payload:= " + p);
return p;
})
.handle((p, h) -> {
// hail mary to get new token
return MessageBuilder
.withPayload(p)
.removeHeaders("*")
.setHeader(HttpHeaders.AUTHORIZATION.toLowerCase(), jamsToken)
.setHeader(HttpHeaders.CONTENT_TYPE.toLowerCase(), "application/json")
.build();
})
.handle((p, h) -> {
JAMS_SUBMIT_JOB_INTGRTN
.info(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Submitting payload to JAMS:");
JAMS_SUBMIT_JOB_INTGRTN.info(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Headers:= " + h);
JAMS_SUBMIT_JOB_INTGRTN.info(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.toString() + " Payload:= " + p);
return p;
})
.handle(Http.outboundGateway(JAMS_SUBMIT_ENDPOINT)
.requestFactory(alliantPooledHttpConnection.get_httpComponentsClientHttpRequestFactory())
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.extractPayload(true))
.logAndReply();
}
The behavior is that every other message gets through, basically skipping over the channel until the next time around. Strangely if I duplicate the jamsSubmitJob Bean, then it will work twice, then fail, then start over again.
Thanks!
This is a misunderstanding what is channel and what is a subscriber to that channel.
So, you have this:
.channel(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.getChannelName())
.handle((p, h) -> {
This way you declare a channel (if that does not exist in the application context yet) and subscriber to it.
Then you have this:
return IntegrationFlows.from(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.getChannelName())
.handle((p, h) -> {
This way you declare a channel (if that does not exist in the application context yet) and subscriber to it.
Did you notice a duplication in my explanation? I did that deliberately to take your attention to the problem.
So, if channel does not exist it is created. In the other place an existing object is used. In the end you end up with two subscribers to the same channel. The framework by default creates for us an instance of a DirectChannel, which come with a round-robin dispatching strategy. That means that the firsts message is going to a first subscriber, the second - to second, the third to the first and so on.
What you want is probably a request-reply pattern, and you better look into the .gateway(IntegrationNamesEnum.JAMS_SUBMIT_JOB_INTGRTN.getChannelName()) instead of .channel() in that your first IntegrationFlow.
See more info in docs:
https://docs.spring.io/spring-integration/docs/current/reference/html/core.html#channel-implementations-directchannel
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-channels
https://docs.spring.io/spring-integration/docs/current/reference/html/dsl.html#java-dsl-gateway

Completable Future inside completable future

I have couple of completable future in my code. Inside one of completable future I want another completable future (completable future inside completable future) e.g.
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
Map<String, List<EmployeeReward>> rewards = rewards(employeeInfo);
Map<String, List<EmployeePoints>> points = points(employeeInfo);
}, executor);
}
In above method rewards and points are two independent sequential call, I want it to parallel call for them for that I tried -
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
CompletableFuture<Map<String, List<EmployeeReward>>> rewards = reward(employeeInfo);
CompletableFuture<Map<String, List<EmployeePoints>>> points = points(employeeInfo);
CompletableFuture<Void> futures = allOf(rewards, points);
}, executor);
}
Is is correct way to do this? How can I improve it if not correct way?
I am building <List<Employee>> as below
employeeInfo.stream.map(employee -> Employee.builder().
.<someEmplyInfo>
.points(points.getOrDefault(employee.getEmpId, newArrayList()))
);
It is important to handle any exceptions in exceptionally block for individual futures.
Ideally, the flow of control should not be dependent on the exception handling logic, it should be wrapped in a status object that can be used to evaluate if further processing should happen.
Adding a thenApply post the allOf method and then fetching the results within the thenApply block should do the trick
public CompletableFuture<List<Employee>> buildEmployee(List<EmployeeResponse> employeeInfo) {
//Build the future instance for reward
CompletableFuture<Map<String, List<EmployeeReward>>> rewardsFuture = reward(employeeInfo)
.exceptionally(throwable -> {
//Handle the error
return null;
});
//Build the future instance for points
CompletableFuture<Map<String, List<EmployeePoints>>> pointsFuture = points(employeeInfo)
.exceptionally(throwable -> {
//Handle the error for rewards
return null;
});
return CompletableFuture.allOf(rewardsFuture, pointsFuture).thenApply(v -> {
try {
Map<String, List<EmployeeReward>> rewardsResult = rewardsFuture.get();
Map<String, List<EmployeePoints>> pointsResult = pointsFuture.get();
//Convert the above map to the desired list of string
List<Employee> buildEmployeeResult = null;
return buildEmployeeResult;
}
catch (Exception e) {
//Handle exception
return null;
}
}, executor);
}
private CompletableFuture<Map<String, List<EmployeePoints>>> points(List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
//Logic for points synchronous
});
}
private CompletableFuture<Map<String, List<EmployeeReward>>> reward(List<EmployeeResponse> employeeInfo) {
return supplyAsync(() -> {
//Logic for rewards synchronous
});
}
In the above approach you are using Async thread to execute buildEmployee method, which means Async thread is responsible to make two API calls rewards and points and then it will combine the result. so in the above approach this method is executing asynchronously but not the API calls.
But you can do it another way by making API calls Asynchronously, do the reward call asynchronously by using supplyAsync and then do the points call using Main thread. Finally block the main thread until async call get finished and then combine the result
public CompletableFuture<List<Employee>> buildEmployee (List<EmployeeResponse> employeeInfo) {
// First call is Async call
CompletableFuture<Map<String, List<EmployeeReward>>> rewards = CompletableFuture.supplyAsync(()->reward(employeeInfo), executor);
//Second call by main thread
Map<String, List<EmployeePoints>>> points = points(employeeInfo);
// main thread is Blocked and get the result of the future.
rewards.get(); //throws InterruptedException,ExecutionException
// Now combine the result and return list
return CompletableFuture.completedFuture(result);
}

TPL Performance With Apache Kafka

Halo,
I'm unable to get any improved performance with TPL DataFlow and wondering if I'm using it incorrectly.
The application below does the following:
Pulls message from a Kafka topic
Parses this message into an Foo object with ParseData()
Serializes this Foo into JSON
Then publishes the JSON to a new Kafka topic.
Some single threaded stats:
ParseData can parse strings into Foo at 100 msg/sec (single threaded test)
SerializeMessage can do 200 Foos/sec (single threaded test)
Consuming Kafka messages (skipping all the parsing/serializing) can handle over 2000 msgs/sec
Based on this, I had hopes to leverage TPL for improving throughput. My max throughput should be close to the Kafka limit of 2000 msgs/sec.
However, I'm not seeing any improvements in throughput and I'm running the application on a machine with 12 physical cores (24 w HT). When I print out the size of the queue for each block, the transformBlock is always around 1000, but the others are under 10 which leads me to believe that the transformBlock isn't leveraging multi-core system.
Have I setup TPL DataFlow to leverage paralellism correctly?
app = new App();
await app.Start(new[]{"consume-topic"}, cancelSource);
// App class
async Task Start(IEnumerable<string> topics, CancellationTokenSource cancelSource) {
transformBlock = new TransformBlock<string, Foo>(TransformKafkaMessage,
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 8,
BoundedCapacity = 1000,
SingleProducerConstrained = true,
});
serializeBlock = new TransformBlock<Foo, string>(SerializeMessage,
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 4,
BoundedCapacity = 1000,
SingleProducerConstrained = true,
});
publishBlock = new ActionBlock<JsonMessage>(PublishJson,
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 1,
BoundedCapacity = 1000,
SingleProducerConstrained = true
});
// Setup the pipeline
transformBlock.LinkTo(serializeBlock);
serializeBlock.LinkTo(publishBlock);
// Start Kafka Listener loop
consumer.Subscribe(topics);
while(true) {
var result = consumer.Consume(cancelSource.Token);
await ProcessMessage<Ignore, string>(result);
}
}
// send the content of the kafka message to transform block
async Task ProcessMessage<TKey, TValue>(ConsumeResult<TKey, string> msg) {
var result = await transformBlock.SendAsync(msg.Value);
}
// Convert the raw string data into an object
Foo TransformKafkaMessage(string data) {
// Note this ParseData() function can process about 100 items per sec
// in local single threaded testing
Foo foo = ParseData(data);
return foo;
}
// Serialize the new Foo into JSON
string SerializeMessage(Foo foo) {
// The serializer can process about 200 msgs/sec (single threaded test)
var json = foo.Serialize();
return json;
}
// publish new message back to Kafka
void PublishJson(string json) {
// Create a Confluent.Kafka Message
var kafkaMessage = new Message<Null, string> {
Value = json
};
producer.Produce("produce-topic", kafkaMessage);
}

Best way to call serial , batch wise requests in Rx Java

I'm new to RxJava. Currently, I'm trying out samples and converting existing codes to Rx.
I have an existing API, which takes a large list of objects,
since server takes time to process a large number of inputs and also due to timeout issues I'm sending inputs to the API in batch wise. if I have 300 objects, I will pass it as batches of 10 objects. As in, first I call the API with first 10 items, waiting for the response, once I received the response, I will take the next 10, till I reach 300 items. Righ now I am using so many nested callbacks and flags to keep track of items and results. I need to convert it to Rx Java.
I tried something with buffer operator and its working as expected. Just wanted to know is there any better solution or is this the exact way to do. My code is given below.
Observable.range(1, 300)
.buffer(10)
.flatMap((integers) -> mockServerResult(integers))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableObserver<String>() {
#Override
public void onNext(#NonNull String s) {
Log.d(TAG, "onNext: " + s);
}
#Override
public void onError(#NonNull Throwable e) {
}
#Override
public void onComplete() {
}
});
}
public Observable<String> mockServerResult(List<Integer> integers) {
StringBuilder stringBuilder = new StringBuilder("server Results for ");
for (Integer integer : integers) {
stringBuilder.append(integer.toString()).append(",");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return Observable.just(stringBuilder.toString());
}

wait for multiple volley responses in a for loop

I am using volley singleton and add all volley request to it.
sample code of adding volley request to queue
MyApplication.getInstance().addToReqQueue(jsObjRequest, "jreq1");
I have an onclick function.
buttonId.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
for(int i=0;i<4;i++){
//....... here i call for asycn volley requests which get added to the queue of volleysingleton
}
// ******how to ensure all my volley requests are completed before i move to next step here.*****
//calling for new intent
Intent m = new Intent(PlaceActivity.this, Myplanshow.class);
m.putExtra("table_name", myplansLists.get(myplansLists.size() - 1).table_name);
m.putExtra("table_name_without_plan_number", myplansLists.get(myplansLists.size() - 1).place_url_name);
m.putExtra("changed", "no");
m.putExtra("plannumber", myplansLists.size());
//moving to new intent;
v.getContext().startActivity(m);
}
});
Inside onclick i have a for loop which will execute multiple volley requests.
After the for loop it will start a new activity through intent.
But for my new activity to show, i need the data of all the volley requests in the for loop to be completed before, it leaves this activity and goes to new activity.
My approach basically is to set up 2 int variables: successCount and errorCount that I use to monitor the volley requests. In the onResponse of each request, I increment the successCount variable, then in the onErrorResponse, I increment the errorCount. At the end, I check if the sum of both variables equals the number of requests made, if its not, the thread waits in a loop.
check this:
buttonId.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Runnable(){
#Override
public void run() {
int successCount=0;
int errorCount=0;
for(int i=0;i<4;i++){
//....... here i call for asycn volley requests which get added to the queue of volleysingleton
//in the onResponse of each of the volley requests, increment successCount by 1;
// i.e successCount++;
//also in onErrorResponse of each of the volley requests, increment
// errorCount by 1
}
// ******how to ensure all my volley requests are completed before i move to next step here.*****
// wait here till all requests are finished
while (successCount+errorCount<4)
{
Log.d("Volley"," waiting");
}
//calling for new intent
Intent m = new Intent(PlaceActivity.this, Myplanshow.class);
m.putExtra("table_name", myplansLists.get(myplansLists.size() - 1).table_name);
m.putExtra("table_name_without_plan_number", myplansLists.get(myplansLists.size() - 1).place_url_name);
m.putExtra("changed", "no");
m.putExtra("plannumber", myplansLists.size());
//moving to new intent;
v.getContext().startActivity(m);
}
}.run();
}
});

Resources