In the previous chapter, we learned from the overall Nacos Project module architecture, do have a good idea, now, we go gradually to dig inside the code details, a lot of people in the study of open source when, do not know, code so much, from where to start to see? We can start from an interface, this interface is you used, know what it probably does, there is a sense of the body, we remember the first chapter when we wrote the HelloWorld, right, from the inside of the interface began to peel Onions.
This is Nacos GitHub code address, start before starting to focus on, plus watch, follow-up Nacos mailing list will also notify you, you can focus on Nacos's latest real-time news, and the wonderful discussion between Daniel.
The following code, which is the first section of the code that publishes a service:
public static void main(String[] args) throws NacosException, InterruptedException { //发布的服务名 String serviceName = "helloworld.services"; //构造一个Nacos实例,入参是Nacos server的ip和服务端口 NamingService naming = NacosFactory.createNamingService("100.81.0.34:8080"); //发布一个服务,该服务对外提供的ip为127.0.0.1,端口为8888 naming.registerInstance(serviceName, "100.81.0.35", 8080); Thread.sleep(Integer.MAX_VALUE);}
Among them, the first step is to construct a Nacos service instance, construct an instance of the parameter, is a string, the value of the specification is ip:port, this IP, is any one of our Nacos server address, we click to see this method:
public static NamingService createNamingService(String serverAddr) throws NacosException { return NamingFactory.createNamingService(serverAddr); }
Let's also look at the code that creates the Configuration service instance:
public static ConfigService createConfigService(String serverAddr) throws NacosException { return ConfigFactory.createConfigService(serverAddr);}
As we can see, nacosfactory is actually a unified portal for service discovery and configuration management interfaces, and then it does not make sense to create instances of different services, we can use namingfactory directly, or configfactory directly create a Nacos service instance, or work
Next, take a look at how this Nacos naming instance is constructed:
public static NamingService createNamingService(String serverList) throws NacosException { try { Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService"); Constructor constructor = driverImplClass.getConstructor(String.class); NamingService vendorImpl = (NamingService) constructor.newInstance(serverList); return vendorImpl; } catch (Throwable e) { throw new NacosException(-400, e.getMessage()); }}
An instance of Namingservice is instantiated by reflection Nacosnamingservice, the constructor is a string entry, and as we look down, the constructor does something:
public NacosNamingService(String serverList) { this.serverList = serverList; init(); eventDispatcher = new EventDispatcher(); serverProxy = new NamingProxy(namespace, endpoint, serverList); beatReactor = new BeatReactor(serverProxy); hostReactor = new HostReactor(eventDispatcher, serverProxy, cacheDir);}
The input serverlist is the server address that we just passed in, the value is assigned to the ServerList field of the instance, and then an Init method is called, and the method is as follows:
private void init() { namespace = System.getProperty(PropertyKeyConst.NAMESPACE); if (StringUtils.isEmpty(namespace)) { namespace = UtilAndComs.DEFAULT_NAMESPACE_ID; } logName = System.getProperty(UtilAndComs.NACOS_NAMING_LOG_NAME); if (StringUtils.isEmpty(logName)) { logName = "naming.log"; } cacheDir = System.getProperty("com.alibaba.nacos.naming.cache.dir"); if (StringUtils.isEmpty(cacheDir)) { cacheDir = System.getProperty("user.home") + "/nacos/naming/" + namespace; }}
This side did 3 things, give Namespace,logname,cachedir assignment, namespace we have passed in, the default is Default,namespace in Nacos, is used for local cache isolation, on a machine, Start a Nacos client process, the default local cache path is defaulted, if you start one, you need to reset a namespace, otherwise the previous cache will be reused, causing conflicts; LogName and Cachedir, these 2 fields do not explain, literally understand. To say more, the settings for these values can be passed in as a system parameter at Java startup, and are the first priority.
After the Init method executes, the next step is to instantiate some framework components, Eventdispatcher, which is a classic event distribution component that works as follows:
There will be a separate thread from Blockqueue to get the event, this event in Nacos here, is the server pushed down the data, listener when we subscribe to a data, will be generated from a listener instance, in the event to the queue, to find the corresponding listener, To execute the callback function onevent inside the listener. If this model is not familiar with the classmate, you can look at the next Eventdispatcher code, this is the basic knowledge, and business has no relationship, here is not too much detailed explanation, the length is too long.
Next, instantiate a nameproxy component, what does this thing do? Let's look at the code inside:
public namingproxy (string namespace, string endpoint, string serverlist) {This.names Pace = namespace; This.endpoint = endpoint; if (Stringutils.isnotempty (serverlist)) {this.serverlist = Arrays.aslist (Serverlist.split (",")); if (this.serverList.size () = = 1) {this.nacosdomain = serverlist; }} executorservice = new Scheduledthreadpoolexecutor (1, New Threadfactory () {@Override public Thread Newthread (Runnable r) {Thread t = new Thread (r); T.setname ("Com.taobao.vipserver.serverlist.updater"); T.setdaemon (TRUE); return t; } }); Executorservice.schedulewithfixeddelay (New Runnable () {@Override public void run () {Refreshsrvi Fneed (); }}, 0, Vipsrvrefintermillis, timeunit.milliseconds); Refreshsrvifneed ();}
There is a lot of logic in this, I summarize, mainly started a thread, every 30s, to execute refreshsrvifneed () This method,
Refreshsrvifneed () This method inside, do things, is through an HTTP request, Go to Nacos server to get a list of addresses for the Nacos server cluster, with the following code:
private void Refreshsrvifneed () {try {if (! Collectionutils.isempty (serverlist)) {LogUtils.LOG.info ("server list provided by User:" + serverlist); Return } if (System.currenttimemillis ()-lastsrvreftime < Vipsrvrefintermillis) {return; } list<string> List = Getserverlistfromendpoint (); if (List.isEmpty ()) {throw new Exception ("Can not acquire Vipserver list"); } if (! Collectionutils.isequalcollection (list, serversfromendpoint)) {LogUtils.LOG.info ("server-list", "SERVER Li ST is updated: "+ list); } serversfromendpoint = list; Lastsrvreftime = System.currenttimemillis (); } catch (Throwable e) {LogUtils.LOG.warn ("Failed to update server list", e); } }
After getting the address list, assign the value to Serversfromendpoint, and record the current update time, at the next update, less than 30s, do not update, avoid frequent updates, in general, The purpose of Nameproxy is to periodically maintain the latest address list on the client side of the Nacos server.
We continue to look down, then initialize the Beatreactor component, from the name can guess, it should be related to the heartbeat thing, it initializes the code as follows:
public BeatReactor(NamingProxy serverProxy) { this.serverProxy = serverProxy; executorService.scheduleAtFixedRate(new BeatProcessor(), 0, clientBeatInterval, TimeUnit.MILLISECONDS);}
A scheduled interval of 10s tasks, to execute beatprocessor inside the logic, Beatprocessor code inside, is the loop to fetch the current client registered good instance, and then send an HTTP heartbeat notification request to the server, tell the client, The health status of this service, the specific code is as follows:
class BeatTask implements Runnable { BeatInfo beatInfo; public BeatTask(BeatInfo beatInfo) { this.beatInfo = beatInfo; } @Override public void run() { Map<String, String> params = new HashMap<String, String>(2); params.put("beat", JSON.toJSONString(beatInfo)); params.put("dom", beatInfo.getDom()); try { String result = serverProxy.callAllServers(UtilAndComs.NACOS_URL_BASE + "/api/clientBeat", params); JSONObject jsonObject = JSON.parseObject(result); if (jsonObject != null) { clientBeatInterval = jsonObject.getLong("clientBeatInterval"); } } catch (Exception e) { LogUtils.LOG.error("CLIENT-BEAT", "failed to send beat: " + JSON.toJSONString(beatInfo), e); } }}
Here is the Naocs client to actively report the health of the logic of the service, is the service discovery function, a more important concept, the service health check mechanism, often also has the service side actively to probe the client interface return.
The final step is to initialize an instance called Hostreactor, and let's see what it did:
public HostReactor(EventDispatcher eventDispatcher, NamingProxy serverProxy, String cacheDir) { this.eventDispatcher = eventDispatcher; this.serverProxy = serverProxy; this.cacheDir = cacheDir; this.serviceInfoMap = new ConcurrentHashMap<>(DiskCache.read(this.cacheDir)); this.failoverReactor = new FailoverReactor(this, cacheDir); this.pushRecver = new PushRecver(this);}
The next step is to load the data from the cache file into the Serviceinfomap memory map, and then initialize a failoverreactor component, which is associated with the Nacos client cache disaster, with the following initialization code:
public void init () {Executorservice.schedulewithfixeddelay (New Switchrefresher (), 0L, 5000L, timeunit.milliseconds); Executorservice.schedulewithfixeddelay (New Diskfilewriter (), day_period_minutes, timeunit.minutes); Backup file on the startup if failover directory is empty. Executorservice.schedule (New Runnable () {@Override public void run () {try {File Cachedir = new File (failoverdir); if (!cachedir.exists () &&!cachedir.mkdirs ()) {throw new IllegalStateException ("Failed to Crea Te cache dir: "+ failoverdir); } file[] files = cachedir.listfiles (); if (files = = NULL | | files.length <= 0) {new Diskfilewriter (). run (); }} catch (Throwable e) {LogUtils.LOG.error ("NA", "Failed to backup file on startup.", E); }}}, 10000L, timeunit.milliseconds);}
The
Initializes 3 scheduled tasks, the code for the first task is as follows:
Class Switchrefresher implements Runnable {long lastmodifiedmillis = 0L; @Override public void Run () {try {file Switchfile = new File (Failoverdir + UTILANDCOMS.FAILOVER_SWI TCH); if (!switchfile.exists ()) {switchparams.put ("Failover-mode", "false"); LogUtils.LOG.debug ("Failover switch is not found," + switchfile.getname ()); Return } Long Modified = Switchfile.lastmodified (); if (Lastmodifiedmillis < modified) {Lastmodifiedmillis = modified; String failover = concurrentdiskutil.getfilecontent (Failoverdir + Utilandcoms.failover_switch, Charset.defaultcharset (). toString ()); if (! Stringutils.isempty (failover)) {list<string> lines = arrays.aslist (Failover.split (diskcache.getl Ineseperator ())); for (string line:lines) {string line1 = Line.trim (); if ("1". Equals (line1)) {switchparams.put ("Failover-mode", "true"); LogUtils.LOG.info ("Failover-mode is on"); New Failoverfilereader (). run (); } else if ("0". Equals (line1)) {switchparams.put ("Failover-mode", "false"); LogUtils.LOG.info ("Failover-mode is Off"); }}}} else {switchparams.put ("Failover-mode", "false"); }}} catch (Throwable e) {LogUtils.LOG.error ("NA", "failed to read failover switch.", E ); } }}
First determine whether the disaster tolerant switch is a disk file form exists, through the disaster tolerant switch file name, determine whether the disaster tolerant switch is open, 1 means open, 0 is off, read to the disaster tolerant switch, the value is updated to memory, the subsequent resolution address list, the first will determine whether the disaster tolerant switch is open, If open, read the cached data, otherwise get the latest data from the server.
The second scheduled task, do the following things:
class Diskfilewriter extends TimerTask {public void run () {map<string, Ser viceinfo> map = Hostreactor.getserviceinfomap (); For (map.entry<string, serviceinfo> entry:map.entrySet ()) {ServiceInfo serviceinfo = Entry.getvalue (); if (Stringutils.equals (Serviceinfo.getkey (), utilandcoms.all_ips) | | Stringutils.equals (Serviceinfo.getname (), Utilandcoms.env_list_key) | | Stringutils.equals (Serviceinfo.getname (), "00-00---000-env_configs-000---00-00") | | Stringutils.equals (Serviceinfo.getname (), "Vipclient.properties") | | Stringutils.equals (Serviceinfo.getname (), "00-00---000-all_hosts-000---00-00")) {continue; } diskcache.write (ServiceInfo, Failoverdir); } }}
Every 24 hours, the memory of all the service data, write over to the disk, which need to filter out some non-domain data special data, specific to see the description in the code. The last scheduled task, every 10s, is to check if the cache directory exists, and if the value of the cache is not present, actively trigger a cache of write disk operations.
The above is the client to construct a Nacos instance of the initialization of all processes, mostly in the initialization of a number of thread pool or scheduled tasks, their respective roles, this is our writing back-end program some basic routines, improve the system's concurrency, while in the distribution and execution of tasks, The introduction of some common asynchronous programming models, such as queue model event distribution, is a good learning material for asynchronous and concurrency, which is also the basic requirement for writing high-performance programs in 2 points.
Summarize
In this chapter, we construct a Nacos service instance as a pointcut through Nacos's nacosfactory, and we string through the initialization process of the client, outlining a few things that the client initialization process does:
- Initializes the event distribution component for handling change data that is actively notified by the service side
- Initializes the Nacos service cluster address List Update component for client-side maintenance of the latest address list on the Nacos server
- Initialize the service health Check module to proactively escalate the health of the service to the server
- Initialize the client's cache, 10s check once, and if not, create
- 24-hour backup of client-side cache files
- 5s check a disaster recovery switch, update to in-memory, disaster tolerant mode, the service address reads the cache
The above is the Nacos client instance initialization of the overall process, is not feeling to do a lot of things, there are some details of the code, we have more intensive reading, if there is nothing to understand, you can leave a message, or in the community to find @ Super elder brother to help you answer, if you can find bugs or other suggestions, Issue can be raised in the community.
Reprint please contact: (zjjxg2018)
High performance Service discovery, configuration Framework Nacos Series 3: Service discovery: Nacos Client initialization process