CompletableFuture across microservices(JVM) - java

Step 1: I would like to have one CompletableFuture<String> asyncFuture starts in microservice A running a async task via say supplyAsync.
Step 2: Then manually complete the same future object by calling manually calling asyncFuture.complete(T value) from a DIFFERENT microservice B which would be triggered by some async event.
Apparently microservice A and microservice B have different JVMs.
In reality microservice A and microservice B are different instance of the same microservice running on different pods in kubernetes.
Between Step 1 and Step 2 , the future object will be stored in Redis which microservice B can retrieve safely.
After some quick googling, I think I am gonna try a couple of solution below:
1> HazelCast's Distributed Executor Service which I can pass in as a second parameter when calling
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
Ref:
http://docs.hazelcast.org/docs/2.3/manual/html/ch09.html
2>Use shared executorService from apache ignite
Ref: https://apacheignite.readme.io/v1.2/docs/executor-service
Not sure if either would work ? Also I am wondering has anyone has dealt with something like this before ? If so I would appreciate if you could share your solution with me.

Regarding Apache Ignite, there are a lot of options how to cooperate nodes (microservices). One of them is Continuous Queries [1] which allow listening to data modifications occurring on caches.
For example, on service A you can create ContinuousQuery and wait for value is changed in the cache:
private String waitForValueChanged(IgniteCache<Integer, String> cache, Integer key) throws InterruptedException {
ContinuousQuery<Integer, String> qry = new ContinuousQuery<>();
qry.setInitialQuery(new ScanQuery<>((k, v) -> k == key));
final CountDownLatch waitForValueChanged = new CountDownLatch(1);
final AtomicReference<String> result = new AtomicReference<>();
CacheEntryUpdatedListener<Integer, String> listener = new CacheEntryUpdatedListener<Integer, String>() {
#Override public void onUpdated(Iterable<CacheEntryEvent<? extends Integer, ? extends String>> iterable) throws CacheEntryListenerException {
for (CacheEntryEvent<? extends Integer, ? extends String> entry: iterable) {
result.set(entry.getValue());
}
waitForValueChanged.countDown();
}
};
qry.setLocalListener(listener);
try (QueryCursor<Cache.Entry<Integer, String>> cur = cache.query(qry);) {
waitForValueChanged.await(60000, TimeUnit.MILLISECONDS);
}
return result.get();
}
On service B you just need to put the value into the cache to "complete the future":
private void completeFuture(IgniteCache<Integer, String> cache, Integer key, String value) {
cache.put(key, value);
}
Here is an example project which shows how Continuous Queries work [2].
[1] https://apacheignite.readme.io/docs#section-continuous-queries
[2] https://github.com/gromtech/ignite-continuous-query-example

Related

Implementing a cache within a Repository using HashMap question

I got this question on an interview and I'm trying to learn from this.
Assuming that this repository is used in a concurrent context with billions of messages in the database.
public class MessageRepository {
public static final Map<String, Message> cache = new HashMap<>();
public Message findMessageById(String id) {
if(cache.containsKey(id)) {
return cache.get(id);
}
Message p = loadMessageFromDb(id);
cache.put(id, p);
return p;
}
Message loadMessageFromDb(String id) {
/* query DB and map row to a Message object */
}
}
What are possible problems with this approach?
One I can think of is HashMap not being a thread safe implementation of Map. Perhaps ConcurrentHashMap would be better for that.
I wasn't sure about any other of the possible answers which were:
1) Class MessageRepository is final meaning it's immutable, so it can't have a modifiable cache.
(AFAIK HashMap is mutable and it's composed into MessageRepository so this wouldn't be an issue).
2) Field cache is final meaning that it's immutable, so it can't be modified by put.
(final doesn't mean immutable so this wouldn't be an issue either)
3) Field cache is static meaning that it will be reset every time an instance of MessageRepository will be built.
(cache will be shared by all instances of MessageRepository so it shouldn't be a problem)
4) HashMap is synchronized, performances may be better without synchronization.
(I think SynchronizedHashMap is the synced implementation)
5) HashMap does not implement evict mechanism out of the box, it may cause memory problems.
(What kind of problems?)
I see two problems with this cache. If loadMessageFromDb() is an expensive operation, then two threads can initiate duplicate calculations. This isn't alleviated even if you use ConcurrentHashMap. A proper implementation of a cache that avoid this would be:
public class MessageRepository {
private static final Map<String, Future<Message>> CACHE = new ConcurrentHashMap<>();
public Message findMessageById(String id) throws ExecutionException, InterruptedException {
Future<Message> messageFuture = CACHE.get(id);
if (null == messageFuture) {
FutureTask<Message> ft = new FutureTask<>(() -> loadMessageFromDb(id));
messageFuture = CACHE.putIfAbsent(id, ft);
if (null == messageFuture) {
messageFuture = ft;
ft.run();
}
}
return messageFuture.get();
}
}
(Taken directly from JCIP By Brian Goetz et. al.)
In the cache above, when a thread starts a computation, it puts the Future into the cache and then patiently waits till the computation finishes. Any thread that comes in with the same id sees that a computation is already ongoing and will again wait on the same future. If two threads call exactly at the same time, putIfAbsent ensures that only one thread is able to initiate the computation.
Java does not have any SynchronizedHashMap class. You should use ConcurrentHashMap. You can do Collections.synchronisedMap(new HashMap<>()) but it has really bad performance.
A problem with the above cache is that it does not evict entries. Java provides LinkedHashMap that can help you create a LRU cache, but it is not synchronised. If you want both functionalities, you should try Guava cache.

Call a WebService and a REST API using JDK8 Streams and CompletableFuture

I have a SOAP call that I need to make and then process the results from the SOAP call in a REST call. Each set of calls is based on a batch of records. I am getting completely lost in trying to get this to run using JDK8 streams as asynchronous as possible. How can I accomplish this?
SOAP Call:
CompletableFuture<Stream<Product>> getProducts(final Set<String> criteria)
{
return supplyAsync(() -> {
...
return service.findProducts(request);
}, EXECUTOR_THREAD_POOL);
}
REST Call:
final CompletableFuture<Stream<Result>> validateProducts(final Stream<Product> products)
{
return supplyAsync(() -> service
.submitProducts(products, false)
.stream(), EXECUTOR_THREAD_POOL);
}
I am trying to invoke the SOAP call, pass the result into the REST call, and collect the results using a JDK8 stream. Each SOAP->REST call is a "set" of records (or batch) similar to paging. (this is totally not working right now but just an example).
#Test
public void should_execute_validations()
{
final Set<String> samples = generateSamples();
//Prepare paging...
final int total = samples.size();
final int pages = getPages(total);
log.debug("Items: {} / Pages: {}", total, pages);
final Stopwatch stopwatch = createStarted();
final Set<Result> results = range(0, pages)
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset)
.thenApply(this::validateProducts);
})
.flatMap(CompletableFuture::join)
.collect(toSet());
log.debug("Executed {} calls in {}", pages, stopwatch.stop());
assertThat(results, notNullValue());
}
I think there are two usage that are incorrect in your example: thenApply and join.
To chain the 1st call (SOAP) and the 2nd call (REST), you need to use thenCompose instead of thenApply. This is because method "validateProducts" returns completable futures, using "thenApply" will create CompletableFuture<CompletableFuture<Stream<Result>>> in your stream mapping. But what you need is probably CompletableFuture<Stream<Result>>. Using thenCompose can resolve this problem, because it is analogous to "Optional.flatMap" or "Stream.flatMap":
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset)
.thenCompose(this::validateProducts);
})
The 2nd incorrect usage is join. Using join blocks the current thread waiting for the result of that CompletableFuture. In your cases, there are N completable futures, where N is the number of pages. Instead of waiting them one by one, the better solution is to wait all the them use CompletableFuture.allOf(...). This method returns a new CompletableFuture that is completed when all of the given CompletableFutures complete. So I suggest that you modify your stream usage and return a list of futures. Then, wait the completion. And finally, retrieve the results:
List<CompletableFuture<Stream<Result>>> futures = range(0, pages)
.mapToObj(index -> {
final Set<String> subset = subset(index, samples);
return getProducts(subset).thenCompose(this::validateProducts);
})
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for (CompletableFuture<Stream<Result>> cf : futures) {
// TODO Handle the results and exceptions here
}
You can see the complete program on GitHub.

Java annotation to mark a function argument should be thread-safe

Let's say I have a function signature as follows -
class CustomerStore {
public void processCustomerInfo(final BiConsumer<Integer, CustomerInfo.V> consumer) throws Exception
}
consumer is executed in parallel threads inside processCustomerInfo.
Can someone let me know is there any annotation we should be adding to denote that passed in consumer should be threadsafe (other than comments)?
For example, the below caller code is not quite correct.
final Map<Integer, CustomerInfo.V> cust = new HashMap<>(); // Should be ConcurrentHashMap
fdbCustomerStore.processCustomerInfo((cid, v) -> cust.put(cid, v));

Correct use of Hazelcast EntryProcessor

We're trying to work out the best way to use Hazelcast's IMap without using pessimistic locking.
EntryProcessor seems like the correct choice, however we need to apply two different types of operations: 'create' when containsKey is false, and 'update' when containsKey is true.
How can I utilise EntryProcessor to support these logic checks?
If two threads hit the containsKey() at the same time and it returns false to both of them, I don't want both of them to create the key. I'd want the second thread to apply an update instead.
This is what we have so far:
public void put(String key, Object value) {
IMap<String, Object> map = getMap();
if (!map.containsKey(key)) {
// create key here
} else {
// update existing value here
// ...
map.executeOnKey(key, new TransactionEntryProcessor({my_new_value}));
}
}
private static class MyEntryProcessor implements
EntryProcessor<String, Object>, EntryBackupProcessor<String, Object>, Serializable {
private static final long serialVersionUID = // blah blah
private static final ThreadLocal<Object> entryToSet = new ThreadLocal<>();
MyEntryProcessor(Object entryToSet) {
MyEntryProcessor.entryToSet.set(entryToSet);
}
#Override
public Object process(Map.Entry<String, Object> entry) {
entry.setValue(entryToSet.get());
return entry.getValue();
}
#Override
public EntryBackupProcessor<String, Object> getBackupProcessor() {
return MyEntryProcessor.this;
}
#Override
public void processBackup(Map.Entry<String, Object> entry) {
entry.setValue(entryToSet.get());
}
}
You can see that two threads can enter the put method and call containsKey at the same time. The second will overwrite the outcome of the first.
EntryProcessor by definition is a processing logic that gets executed on the entry itself, eliminating the need of serializing/deserializing the value. Internally, EPs are executed by partition threads, where one partition thread takes care of multiple partitions. When an EP comes to HC, it is picked by the owner thread of the partition where the key belongs. Once the processing is completed, the partition thread is ready to accept and execute other tasks (which may well be same EP for same key, submitted by another thread). Therefore, it may seem so but EPs should not be used as alternatives to pessimistic locking.
If you are insistent and really keen on using EP for this then you could try putting a null check inside process method. Something like this:
public Object process(Map.Entry<String, Object> entry) {
if(null == entry.getValue()) {
entry.setValue("value123");
}
return entry.getValue();
}
This way two things will happen:
1. The other thread will wait for partition thread to be available again
2. Since the value already exists, you wont overwrite anything

Rxjava - Can I invoke `Subject.onNext` method from different threads?

I have a Spring web server and I want to create a chat room for every N (for example 10) clients that requests my controller.
Every request to server has its own thread, how to collect every N request and create for example a room for it? I think that Rxjava has a solution for this so how can I implement this and if I can't do this, what's the best solution?
Update 1:
With help of #pavan-kumar answer I created this:
#RestController
public class GameController {
private final PublishSubject<Integer> subject;
private AtomicInteger counter = new AtomicInteger(0);
#Autowired
public GameController(PublishSubject<Integer> subject) {
this.subject = subject;
}
#PostConstruct
public void init() {
subject.buffer(10).subscribe(
integers -> {
StringBuilder builder = new StringBuilder("[ ");
for (Integer integer : integers) {
builder = builder.append(integer).append(", ");
}
String s = builder.append("]").toString();
System.out.println(s);
});
}
#RequestMapping(value = "/game", method = RequestMethod.GET)
public void findNewGame() {
int i = counter.addAndGet(1);
subject.onNext(i);
}
}
So current question is "Can I invoke Subject.onNext method from different threads?"
Not directly. You have to provide serialization in some way or use the toSerialized() method and communicate with the returned Subject<T, R> instance.
PublishSubject<Integer> ps = PublishSubject.create();
Subject<Integer, Integer> subject = ps.toSerialized();
subject.subscribe(System.out::println);
subject.onNext(1);
Though the use case is not very clear for me, the below approach would probably help.
Every request to server has its own thread - Use a static Observable shared amongst all threads. May be you can call onNext every time a new user establishes a connection.
Once you have such Observable, you can subscribe to it via a buffer as depicted below.
Observable.range(1, 50).buffer(10).subscribe(n -> System.out.println(n.get(0)));
Observable.range(1, 50) is similar to your static Observable which emits events every time a connection is established. buffer takes care of merging all the items into a List of items and emits that one List when the said number of items(10) are emitted. You can subscribe on this and take appropriate actions as needed.

Categories

Resources