Deferred Result literally means deferring results. spring encapsulates support after the introduction of asynchronous requests after servlet 3.0, and is also an old feature. DeferredResult can allow container threads to release quickly so that they can accept more requests and improve throughput, so that the real business logic can be completed in other working threads.
Recent review of the implementation principle of apollo configuration center shows that the publishing configuration push change message of apollo is implemented with Deferred Result. apollo client will send a long-round training http request like the server, with a timeout of 60 seconds. When the timeout is over, a 304 httpstatus will be returned to the client, indicating that the configuration has not changed, and the client will continue this step. The server will call DeferredResult.setResult to return 200 status codes when there is a publishing configuration, and then the rotation training request will return immediately (no timeout). When the client receives the response result, it will initiate a request to obtain the changed configuration information.
Let's write a simple demo ourselves to demonstrate this process.
@SpringBootApplication public class DemoApplication implements WebMvcConfigurer { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @Bean public ThreadPoolTaskExecutor mvcTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setQueueCapacity(100); executor.setMaxPoolSize(25); return executor; } //Configure asynchronous support, set up a workthread pool for asynchronous execution of business logic, and set the default timeout time to 60 seconds @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { configurer.setTaskExecutor(mvcTaskExecutor()); configurer.setDefaultTimeout(60000L); } }
@RestController public class ApolloController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); //Multimap in guava, multi-value map, enhancement of map, one key can keep multiple values private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create()); //Analog long polling @RequestMapping(value = "/watch/{namespace}", method = RequestMethod.GET, produces = "text/html") public DeferredResult<String> watch(@PathVariable("namespace") String namespace) { logger.info("Request received"); DeferredResult<String> deferredResult = new DeferredResult<>(); //When deferred Result completes (whether it is timeout or abnormal or normal), remove the corresponding watch key from watchRequests deferredResult.onCompletion(new Runnable() { @Override public void run() { System.out.println("remove key:" + namespace); watchRequests.remove(namespace, deferredResult); } }); watchRequests.put(namespace, deferredResult); logger.info("Servlet thread released"); return deferredResult; } //Simulated release namespace configuration @RequestMapping(value = "/publish/{namespace}", method = RequestMethod.GET, produces = "text/html") public Object publishConfig(@PathVariable("namespace") String namespace) { if (watchRequests.containsKey(namespace)) { Collection<DeferredResult<String>> deferredResults = watchRequests.get(namespace); Long time = System.currentTimeMillis(); //Notify all watch es of this namespace change as a result of long-term training configuration changes for (DeferredResult<String> deferredResult : deferredResults) { deferredResult.setResult(namespace + " changed:" + time); } } return "success"; } }
AsyncRequestTimeoutException is generated when the request timeouts, and we need to capture it in global exceptions
Then we send the request http://localhost:8080/watch/mynamespace through the postman tool. The request will hang up. After 60 seconds, Deferred Result timeout, the client normally receives 304 status codes, indicating that the configuration has not changed during this period.
Then we simulate the configuration change, initiate a request for http://localhost:8080/watch/mynamespace again, wait for 10 seconds (no more than 60 seconds), and then call http://localhost:8080/publish/mynamespace to publish the configuration change. At this point, postman receives the response immediately, indicating that configuration changes occurred during the rotation training.
mynamespace changed:1538880050147
Here we use a MultiMap to store all round training requests. Key corresponds to namespace and value corresponds to Deferred Result, an asynchronous request for all changes in namespace. It should be noted that when Deferred Result is completed, remember to remove the corresponding key from MultiMap to avoid memory overflow requests.
The advantage of this kind of long polling is that, compared with the request server which has been circulating all the time, more instances will exert great pressure on the server. The way of http long polling will be pushed to the client when the server changes actively, and the client will suspend the request at other time, so that the performance and real-time performance are satisfied at the same time.