0

For a given request like /api/user/1, suppose it will take 10s for querying from db.

So at first there is one request accepted by the server, and the service start to query the db. Then during the query, there maybe some more incoming same requests(all for /api/user/1).

How to avoid the later requests query the database? We do not mean cache, we just want to avoid the exactly same query occur at the same time.

Is this make any sense? If yes, how to you make that (Take Java or node application as example)?

hguser
  • 35,079
  • 54
  • 159
  • 293

1 Answers1

0

You could use a ConcurrentHashMap (see here) which maps route strings like "/api/user/1" to a List of callbacks taking a single parameter of type T (replace T with whatever class you use to store request results). Your request method will need to use a Consumer<T> for the callback. If you are not familiar with Consumer<T> you can read about it here. It is simply an interface representing a function which takes one parameter of type T and which returns nothing.

When a thread wants the result of the request to a route it should call a synchronized method register which takes a route as a String and a callback and does the following:

It should check if the route is a key in the Map. If it isn't, it should add the route as a key to the Map with its value being a List containing one value, the callback supplied in the parameter, and then it should initiate the request with a callback to a method resolve which I will discuss below. If the route was already a key in the Map then the callback of the thread should simply be added to the List in the Map where the route is the key.

The resolve function should take the route as a String and the request result of type T. It should then get the List at the route key, remove the route key from Map, and finally iterate over all of the callbacks and call them with the request result.

I have written up some code with an example, but I have not tested it.

CallbackHandler.java

public abstract class CallbackHandler<T> {
    private QueryRepetitionHandler<T> handler;
    private CountDownLatch latch;
    private T result;

    public CallbackHandler(QueryRepetitionHandler<T> handler) {
        this.handler = handler;
        latch = new CountDownLatch(1);
    }

    public void resolve(T result) {
        this.result = result;
        latch.countDown();
    }

    public void request(String route) {
        handler.register(route);
        latch.await();
    }
}

QueryRepetitionHandler.java

public abstract class QueryRepetitionHandler<T> {
    private Map<String, List<CallbackHandler<T>> map = new ConcurrentHashMap<>();

    protected abstract void request(String route, Consumer<T> consumer);

    public synchronized void register(String route, CallbackHandler<T> handler) {
        if (map.containsKey(route)) {
            map.get(route).add(callback);
        } else {
            List<CallbackHandler<T>> list = new ArrayList<>();
            list.add(callback);
            map.put(route, list);
            request(route, result -> resolve(route, result));
        }
    }

    private void resolve(String route, T result) {
        List<Consumer<T>> list = map.remove(route);

        // Sanity check
        if (list != null) {
            list.forEach(handler -> handler.resolve(result));
        }
    }
}

You'll want to instantiate one QueryRepetitionHandler<T> to be shared by all of your threads. When a thread wants to make a request it should instantiate a CallbackHandler<T> using the shared QueryRepetitionHandler<T>. Of course, you can't instantiate QueryRepetitionHandler<T> without implementing the request method. The request method should simply make the request and then call the callback provided as a Consumer<T>. It should then call the request method of CallbackHandler<T> with the desired route as a String argument. That thread will then be waiting (using latch.await()) for the result of the request until QueryRepetitionHandler<T> calls its resolve method with the result and calls latch.countDown().

Tomer Aberbach
  • 536
  • 3
  • 11
  • Tha's right, you does not misunderstand me. While I am not sure how to use that since I am not familiar with the function programming in java. – hguser Dec 05 '18 at 05:55
  • This is what I tried to use the handler in an interceptor: https://i.imgur.com/gYtVcNBg.png – hguser Dec 05 '18 at 06:12
  • I made some changes after thinking about the problem and added a description at the end of the answer regarding how to use the code. Does this clear it up? – Tomer Aberbach Dec 05 '18 at 18:00