Here we use a reply to obtain Zhihu through the unique ID as an example. First of all, let’s make it clear that after an HTTP request is processed on the server, the request will be completed if the response is written back to the connection of this request, as follows:
Public void request(Connection Connection, HttpRequest Request) {// Handle request, omit code connection.write(response); // Complete the response}Copy the code
Given that two interfaces need to be called to get the answer, to get the number of comments and to get the answer information, traditional code might write something like this:
Public void getCommentCount(Connection Connection, HttpRequest Request) {Integer commentCount = null; Try {// Get the number of comments from the cache, blocking IO commentCount = getCommnetCountFromCache(id); } catch(Exception e) {try {// Fail to get from database, block IO commentCount = getVoteCountFromDB(id); } catch(Exception ex) { } } connection.write(commentCount); } public void getAnswer(Connection Connection, HttpRequest Request) {// Obtain the number of likes Integer voteCount = null; IO voteCount = getVoteCountFromCache(id); IO votecountFromcache (id); } catch(Exception e) {try {// voteCount = getVoteCountFromDB(id); } catch(Exception ex) {}} IO Answer Answer = getAnswerFromDB(id); ResultVO Response = new ResultVO(); if (voteCount ! = null) { response.setVoteCount(voteCount); } if (answer ! = null) { response.setAnswer(answer); } connection.write(response); // Complete the response}Copy the code
Under this implementation, your process only needs a single thread pool that holds all the requests. There are two drawbacks to this implementation:
- Thread pool IO is blocked, causing one storage to slow down or the cache to break down, and all services are blocked. Suppose now that the comment cache suddenly hangs, all access to the database, resulting in slow requests. As threads wait for IO responses, the only thread pool is overwhelmed and unable to process requests for answers.
- For getting the answer information, getting the number of likes and getting the answer information can actually be done concurrently. You don’t have to get the likes before you get the answers.
Now, NIO non-blocking IO is very common, and with non-blocking IO, we can do reactive programming so that our threads don’t block, but instead process requests all the time. How does this work?
In traditional BIO, after a thread writes data to a Connection, the current thread enters the Block state until the response returns, and then does whatever it does after the response returns. NIO is a thread that writes data to a Connection, caches in one place what needs to be done after the response is returned, and then returns. So when you get a response back, the Read event of the Selector in NIO is going to be Ready, and the thread that scans the Selector event is going to tell your thread pool that the data is Ready, and then one of the threads in the thread pool, they’re going to take whatever they just cached to do and the parameters, and they’re going to process it.
So, how do you implement caching of what needs to be done and parameters after the response is returned? Java itself provides two types of interfaces, the Callback based Interface (the various Functional interfaces introduced in Java 8) and the Future framework.
Implementation based on Callback:
Public void getAnswer(Connection Connection, HttpRequest Request) {ResultVO ResultVO = new ResultVO(); GetVoteCountFromCache (id, (count, throwable) -> {if (throwable! GetVoteCountFromDB (id, (count2,); throwable2) -> { if (throwable2 == null) { resultVO.setVoteCount(voteCount); GetAnswerFromDB (id, (answer, throwable3) -> {if (throwable3 == null) {resultvo.setanswer (answer); connection.write(resultVO); } else { connection.write(throwable3); }}); }); } else {resultVo.setVotecount (voteCount); GetAnswerFromDB (id, (answer, throwable2) -> {if (throwable2 == null) {resultvo.setanswer (answer); // Return a response connection.write(resultVO); } else {// Return an error response connection.write(throwable2); }}); }}); }Copy the code
As you can see, the deeper the call hierarchy, the more difficult the callback becomes to write, and the more verbose the code. In addition, based on CallBack, it is difficult to obtain the number of likes and obtain the answer information concurrently, so it is still necessary to obtain the number of likes first and then obtain the answer information.
What about future-based? Let’s try this out with CompletableFuture, introduced after Java 8.
Public void getAnswer(Connection Connection, HttpRequest Request) {ResultVO ResultVO = new ResultVO(); Completablefuture. allOf(getVoteCountFromCache(id)) // An exception occurred, Read from the database. ExceptionallyComposeAsync (throwable - > getVoteCountFromDB (id)) / / read after, ThenAccept (VoteCount -> {resultvo.setVotecount (VoteCount); }), getAnswerFromDB(id).thenAccept(answer -> { resultVO.setAnswer(answer); }) ).exceptionallyAsync(throwable -> { connection.write(throwable); }).thenRun(() -> { connection.write(resultVO); }); }Copy the code
This implementation looks much simpler and reads the likes and answers simultaneously. Based on the realization of Completableuture, Project Reactor adds more combination methods and more perfect exception handling mechanism, as well as the handling mechanism in the face of back pressure and retry mechanism.