Asynchronous Programming Completable Future for Request Merging in High Concurrent System Optimization

Keywords: Java Programming Redis Database

Asynchronous Programming Completable Future for Request Merging in High Concurrent System Optimization
First of all, the scene:

According to Redis's official website, the read-write performance of single-machine Redis is 120,000/sec, and batch processing can reach 700,000/sec. Whether it's a cache or a database, it has the function of batch processing. When our system reaches the bottleneck, we consider squeezing cache and database performance adequately to cope with larger concurrent requests. It is suitable for the special high concurrency scenarios such as double-eleven e-commerce promotion, so that the system can support higher concurrency.

Thoughts:

When a user requests to the background, I do not process them immediately. Instead, I pile them up in a queue for 10 milliseconds. Because of the high concurrency scenario, a certain number of requests are piled up.

I define a timed task to send requests in the queue to the Redis cache at the back end, or to the database in batch mode, to get the batch results, and then distribute the results to the corresponding requesting users.

For a single user, it is imperceptible that his request slows down by 10 milliseconds. But for our system, it can improve the anti-concurrency ability several times.

The function of merging requests and distributing results is to use a kind of Completable Future to realize asynchronous programming and data interaction between different threads.

How does thread 1 create asynchronous tasks?

// Creating Asynchronous Tasks
CompletableFuture> future = new CompletableFuture<>();

// Blocking awaits the result.
Map result = future.get();
How does thread 2 assign data to thread 1?

// Processing results of thread 2
Object result = result;
// The result of thread 2 is assigned to thread 1
future.complete(result);
Completable Future is a class provided by Daniel Doug Lea in JDK 1.8. Let's look at the source code of the complete() method.

Copy code

/**
 * If not already completed, sets the value returned by {@link
 * #get()} and related methods to the given value.
 *
 * @param value the result value
 * @return {@code true} if this invocation caused this CompletableFuture
 * to transition to a completed state, else {@code false}
 */
public boolean complete(T value) {
    boolean triggered = completeValue(value);
    postComplete();
    return triggered;
}

Copy code
Translation:

If not, set the returned value and related method get() to a given value.

That is to say,

The get() method of thread 1 gets the value given by the complete() method of thread 2.

See here, you should basically understand the meaning of this exception programming. Its core is thread communication and data transmission. Code directly:

Copy code
package www.itbac.com;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

public class CompletableFutureTest {

//Concurrent and secure blocking queues to accumulate requests. (batch processing every N milliseconds)
LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue();

// The implementation of timing tasks, processing data every N milliseconds.
@PostConstruct
public void init() {
    // Timing Task Thread Pool
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
    scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {

// Catching exceptions

            try {
                //1. Remove queue requests from blocked queues and generate a batch query.
                int size = queue.size();
                if (size == 0) {
                    return;
                }
                List<Request> requests = new ArrayList<>(size);
                for (int i = 0; i < size; i++) {
                    // Remove the queue and return.
                    Request poll = queue.poll();
                    requests.add(poll);
                }
                //2. Assemble a batch query request parameter.
                List<String> movieCodes = new ArrayList<>();
                for (Request request : requests) {
                    movieCodes.add(request.getMovieCode());
                }
                //3. http request, or dubbo request. Batch request, get the result list.
                System.out.println("The number of consolidation requests:"+movieCodes.size());
                List<Map<String, Object>> responses = new ArrayList<>();

                //4. Converting list to map is convenient and fast to find.
                HashMap<String, Map<String, Object>> responseMap = new HashMap<>();
                for (Map<String, Object> respons : responses) {
                    String code = respons.get("code").toString();
                    responseMap.put(code,respons);
                }
                //4. Respond the results to each individual user request.
                for (Request request : requests) {
                    //According to the unique parameters carried in the request, the response is found in the result of batch query.
                    Map<String, Object> result = responseMap.get(request.getMovieCode());

                    //Returns the result to the corresponding request thread. Two threads communicate, asynchronous programming assignment.
                    //complete(), source comment translation: If not, set the values returned by methods and related methods to a given value
                    request.getFuture().complete(result);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        // Tasks are executed immediately and repeated at intervals of 10 milliseconds.
    }, 0, 10, TimeUnit.MILLISECONDS);

}

// Ten thousand user requests, ten thousand concurrent requests, inquiries for movie information
public Map<String, Object> queryMovie(String movieCode) throws ExecutionException, InterruptedException {
    //Request merging reduces the number of interface calls and improves performance.
    //Idea: Combine similar requests from different users.
    //Not immediately initiate interface calls, requests. First collect and then make batch requests.
    Request request = new Request();
    //Request parameters
    request.setMovieCode(movieCode);
    //Asynchronous programming, creating the task of the current thread, asynchronous operation by other threads, to obtain the results of asynchronous processing.
    CompletableFuture<Map<String, Object>> future = new CompletableFuture<>();
    request.setFuture(future);

    //Request parameters are queued. Timing tasks to digest requests.
    queue.add(request);

    //Blocking awaits the result.
    Map<String, Object> stringObjectMap = future.get();
    return stringObjectMap;
}

}

//Request wrapper class
class Request {

//Request parameter: movie id.
private String movieCode;

// Multithread future s receive return values.
//Each request object has a future that receives the request.
private CompletableFuture<Map<String, Object>> future;
public CompletableFuture<Map<String, Object>> getFuture() {
    return future;
}

public void setFuture(CompletableFuture<Map<String, Object>> future) {
    this.future = future;
}

public Request() {
}

public Request(String movieCode) {
    this.movieCode = movieCode;
}

public String getMovieCode() {
    return movieCode;
}

public void setMovieCode(String movieCode) {
    this.movieCode = movieCode;
}

}
Copy code
In this way, request merging, batch processing and result distribution response are realized. Let the system support higher concurrency.

Of course, because it's not every day, and there's not that much concurrency, add a dynamic configuration, only when a specific time, the request stack. The rest of the time is normal. This part of the logic is not written out.

Original address https://www.cnblogs.com/itbac/p/11298626.html

Posted by herschen on Sun, 04 Aug 2019 23:32:54 -0700