The article directories
-
- preface
- Question 1. How to implement configuration hot refresh
-
- 1. @ RefreshScope principle
- 2. ContextRefresher.refresh()
- 3. RefreshScope.refreshAll()
- 4. Simulate building a wheel
- Question 2. How does the Nacos client listen in real time when the Nacos server configuration is updated
-
- 1. Apollo implementation
- 2. What is DeferredResult
- 3. Simulate building a wheel
- conclusion
preface
How did the last Nacos or Config article load remote configuration? And make a wheel out of it? We’ve figured out how Nacos loads the remote configuration. In this article, we’ll take a look at how Nacos implements configuration hot flush.
When I saw that Nacos can achieve hot refresh, I had a few questions in my mind ❓, which should be everyone’s questions.
- How to implement configuration hot refresh?
- How do you dynamically refresh the properties in your Bean?
- How do you dynamically sense remote configuration updates?
With these questions answered, you understand how to configure hot flusher.
The article roughly introduces the key points of the implementation technology, and how to imitate a simple wheel (wheel is very important, only think about their own wheel, will ask a lot of principle questions), specific source details, please take the keywords in the article to Google, and then follow the DEBUG.
Question 1. How to implement configuration hot refresh
Key PRINCIPLES of Nacos:
- 1. Use Spring Cloud native annotations on beans that need a hot refresh
@RefreshScope
- 2. Called when the configuration is updated
contextRefresher.refresh()
The code is as follows:
@RestController
@RequestMapping("/config")
@RefreshScope / / the key
public class ConfigController {
@Value("${laker.name}") // Attributes to be refreshed
private String lakerName;
@RequestMapping("/get")
public String get(a) {
returnlakerName; }... }Copy the code
1. @ RefreshScope principle
RefreshScope = spring-Cloud-context
Can put @ Bean definitions in org. Springframework. Cloud. Context. The scope. The refresh. RefreshScope. Beans annotated in this way can be refreshed at run time, and any components that use them will get a new instance before the next method call, which will be fully initialized and injected with all dependencies.
To understand RefreshScope, one must first understand Scope
The Scope (org. Springframework. Beans. Factory. Config. The Scope) was 2.0 core concepts beginning in the Spring
RefreshScope (org. Springframework. Cloud. Context. The scope. The refresh), namely @ scope (” refresh “) is a spring cloud provides a special scope of implementation, It is used to implement configuration and instance hot loading.
Similar ones are:
- RequestScope: An instance that gets the instance from the current Web Request
- SessionScope: The instance that gets the instance from the Session
- ThreadScope: Instance fetched from ThreadLocal
RefreshScope is fetched from the built-in cache.
2. ContextRefresher.refresh()
Trigger contextrefresher.refresh when there is a configuration update
RefreshScope Refresh process
The entrance ContextRefresher. Refresh
public synchronized Set<String> refresh(a) {① Map<String, Object> extractthis.context.getEnvironment().getPropertySources()); (2) updateEnvironment (); Set<String> keys = changes(before, ③extract(this.context.getEnvironment().getPropertySources())).keySet(); 5.this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys)); 6.this.scope.refreshAll();
}
Copy the code
① Extract all parameter variables other than standard parameters (SYSTEM,JNDI,SERVLET)
(2) Re-load the Environment parameters into a new Spring Context container, and then close the new container (important: you can debug it and restart the SpringApplication).
(3) Mention the updated parameters (excluding standard parameters)
④ Compare the change items
Release environment change events
(6) The RefreshScope regenerates the Bean with the new environment parameters. The process of regenerating is simple. Clear the RefreshScope cache and destroy the Bean, and the next time a new instance is fetched from the BeanFactory.
3. RefreshScope.refreshAll()
RefreshScope. RefreshAll ()
public void refreshAll(a) {
super.destroy();
this.context.publishEvent(new RefreshScopeRefreshedEvent());
}
Copy the code
The RefreshScope class has a member variable cache, which is used to cache all beans that have already been generated. When the get method is called, a new object is generated and placed in the cache, and its corresponding Bean is initialized via getBean:
public Object get(String name, ObjectFactory
objectFactory) {
BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));
this.locks.putIfAbsent(name, new ReentrantReadWriteLock());
try {
return value.getBean();
}
catch (RuntimeException e) {
this.errors.put(name, e);
throwe; }}Copy the code
Therefore, you only need to clear the entire cache during destruction, and the next time you fetch an object, you can create a new object, which will naturally bind the new attribute:
public void destroy(a) {
List<Throwable> errors = new ArrayList<Throwable>();
Collection<BeanLifecycleWrapper> wrappers = this.cache.clear();
for (BeanLifecycleWrapper wrapper : wrappers) {
try {
Lock lock = this.locks.get(wrapper.getName()).writeLock();
lock.lock();
try {
wrapper.destroy();
}
finally{ lock.unlock(); }}catch(RuntimeException e) { errors.add(e); }}if(! errors.isEmpty()) {throw wrapIfNecessary(errors.get(0));
}
this.errors.clear();
}
Copy the code
Once the cache is cleared, a new object is created and placed in the cache the next time the object is accessed.
And after clear the cache, it will send a RefreshScopeRefreshedEvent event, in some Spring Cloud components will be listening to this event and make some feedback.
4. Simulate building a wheel
Here we can simulate building a thermal regenerative wheel;
The code and configuration are as follows:
-
The project relies on spring-Cloud-Context
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-context</artifactId> </dependency> Copy the code
-
Configuration bean
@Component @RefreshScope public class User { @Value("${laker.name}") privateString name; . }Copy the code
-
Refresh interfaces and view interfaces
@RestController @RequestMapping("/config") public class ConfigController { @Autowired User user; @Autowired ContextRefresher contextRefresher; @RequestMapping("/get") public String get(a) { return user.getName(); } @RequestMapping("/refresh") public String[] refresh() { Set<String> keys = contextRefresher.refresh(); return keys.toArray(new String[keys.size()]); } Copy the code
-
application.yml
laker: name: laker Copy the code
The operation process is as follows:
-
Browser browser http://localhost:8080/config/get – result: laker
-
Change the contents of application.yml to:
laker: name: lakerupdate Copy the code
-
Browser http://localhost:8080/config/refresh – browser results: laker. Name
-
Browser browser http://localhost:8080/config/get – result: lakerupdate (not restart, realized the configuration updates)
Question 2. How does the Nacos client listen in real time when the Nacos server configuration is updated
Take a look at the Nacos source code here. It uses long polling. What is long polling and its alternatives? System design basics Long polling, WebSockets, server send Events (SSEs) protocols
There are many open source components that use long polling “push + pull” messages, to name a few:
- RocketMQ
- Nacos
- Apollo
- Kafka
I spent a few hours looking at the Nacos long round source code, too much not very easy to understand, interested to Google. Generally, we are based on the background of Spring Boot. After various Google searches, we found that The implementation of Apollo was relatively simple, so we directly used the code of Apollo for reference.
1. Apollo implementation
The implementation is as follows:
- The client sends an Http request to the Config Service
notifications/v2
The interface, that isNotificationControllerV2, seeRemoteConfigLongPollService - NotificationControllerV2 does not return the result immediately, but suspends the request via Spring DeferredResult
- If no configuration of interest to the client is published within 60 seconds, Http status code 304 is returned to the client
- NotificationControllerV2 calls the setResult method of DeferredResult if a configuration is published that the client is interested in, passing in namespace information with the configuration change, and the request is returned immediately. The client requests the Config Service to obtain the latest configuration of the namespace after obtaining the newly configured namespace.
Interpretation:
-
The keyword DeferredResult uses this feature to implement long polling
-
When a timeout is returned, the status Code returned is Http Code 304
The requested page has not been modified since the last request. When the server returns this response, it does not return the web page content, thus saving bandwidth and overhead.
2. What is DeferredResult
Asynchronous support was introduced in Servlet 3.0 and, simply put, allows HTTP requests to be processed in a thread other than the request receiver thread.
The DeferredResult available since Spring 3.2 helps unload long-running computations from http-worker threads into separate threads.
Although another thread will occupy some resources to do the computation, it will not block the worker thread and can handle incoming client requests.
The asynchronous request processing model is useful because it helps to scale applications well during high loads, especially for IO intensive operations.
DeferredResult is a wrapper around asynchronous servlets
For details, see Spring Boot, which I wrote in CSDN, using DeferredResult to implement long polling
Here’s a picture on the Internet that makes it a little bit clearer.
Servlet asynchronous flow chart
Upon receiving the request, the Tomcat worker thread obtains an asynchronous context AsyncContext object from HttpServletRequest. The Tomcat worker thread passes the AsyncContext object to the business processing thread. At the same time, the Tomcat worker thread is returned to the worker thread pool, which is the asynchronous start. The processing of business logic is completed in the business processing thread, and response is generated and returned to the client.
3. Simulate building a wheel
Here we will use Spring Boot to simulate how to implement long polling service push through Spring Boot DeferredResult.
The code is as follows for your reference only:
/** * Echo Config Service notification client long polling implementation principle */
@RestController
@RequestMapping("/config")
public class LakerConfigController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// A key can hold multiple values
private Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());
/** * simulate long polling */
@RequestMapping(value = "/get/{dataId}")
public DeferredResult<String> watch(@PathVariable("dataId") String dataId) {
logger.info("Request received");
ResponseEntity<String>
NOT_MODIFIED_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
The 304 status code is returned to tell the client that the configuration file of the current namespace is not updated
DeferredResult<String> deferredResult = new DeferredResult<>(30 * 1000L, NOT_MODIFIED_RESPONSE);
// When deferredResult completes (whether timed out, abnormal, or normal), remove the corresponding Watch key from watchRequests
deferredResult.onCompletion(() -> {
logger.info("remove key:" + dataId);
watchRequests.remove(dataId, deferredResult);
});
deferredResult.onTimeout(() -> {
logger.info("onTimeout()");
});
watchRequests.put(dataId, deferredResult);
logger.info("Servlet thread released");
return deferredResult;
}
/** * simulate publish configuration */
@RequestMapping(value = "/update/{dataId}")
public Object publishConfig(@PathVariable("dataId") String dataId) {
if (watchRequests.containsKey(dataId)) {
Collection<DeferredResult<String>> deferredResults = watchRequests.get(dataId);
Long time = System.currentTimeMillis();
// Inform all watch of the configuration change result of namespace change
for (DeferredResult<String> deferredResult : deferredResults) {
//deferredResult Once the setResult() method is executed, deferredResult is finished and the result is immediately returned to the client
deferredResult.setResult(dataId + " changed:"+ time); }}return "success"; }}Copy the code
The operation process is as follows:
For the sake of simplicity I use the browser simulation, the actual use of Java Http Client, such as: OKHTTP, Apache Http Client, etc
Normal process:
- client1The browser
http://localhost:8080/config/get/laker
, blocking in ing - client2The browser
http://localhost:8080/config/update/laker
To return tosuccess
- client1The browser
http://localhost:8080/config/get/laker
To return tolaker changed:1611022736865
Timeout process:
- client1The browser
http://localhost:8080/config/get/laker
, blocking in ing - After the 30 s
- client1Browser, back
http code 304
conclusion
- Nacos use
Long polling
Solution to monitor remote configuration changes in real time - Nacos use
spring-cloud-context
Configuration hot refresh is implemented by @refreshScope and Contextrefresher.refresh
How does Nacos or Config load remote configuration? And make a wheel out of it? , a streamlined stand-alone version of Laker-Config (shanzhai wheel) has been basically manufactured 😁.
Reference:
- Ctripcorp. Making. IO/Apollo / # / useful…
- Blog.csdn.net/liuccc1/art…
- Blog.csdn.net/wangxindong…
- Blog.csdn.net/u012410733/…
- www.cnblogs.com/javastack/p…
Contents of this series
- An overview of the Spring Cloud microservices series of articles, which will be updated in real time
🍎QQ group [837324215] 🍎 pay attention to my public number [Java Factory interview officer], learn together 🍎🍎🍎