爲了開發效率高效和業務邏輯清晰,愈來愈多的項目採用分佈式系統。分佈式最重要的就是註冊中心了。Eureka是SpringCloud原生提供的註冊中心,來look一波吧。
引入依賴:spring
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>Greenwich.SR1</version> </dependency>
給啓動類加上註解@EnableEurekaServerbootstrap
@EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
配置一下yml文件:瀏覽器
#端口號 server: port: 8331 #Eureka實例名,集羣中根據這裏相互識別 eureka: instance: hostname: eureka #客戶端 client: #是否開啓註冊服務,做爲註冊中心,就不註冊了 register-with-eureka: false #是否拉取服務列表,這裏我只提供服務給別的服務。 fetch-registry: false #註冊中心地址 service-url: defaultZone: http://localhost:8331/eureka/
啓動項目EurekaApplication ,瀏覽器訪問http://localhost:8331/,Euerka 服務器搭建成功了。安全
如今尚未東西註冊進來。服務器
引入依賴:數據結構
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>Greenwich.SR1</version> </dependency>
這回啓動類註解變了,@EnableDiscoveryClientapp
@EnableDiscoveryClient @SpringBootApplication public class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
接下來是配置文件:分佈式
#端口號 server: port: 8332 #Eureka實例名,集羣中根據這裏相互識別 spring: application: name: first-service eureka: #客戶端 client: #註冊中心地址 service-url: defaultZone: http://localhost:8331/eureka/
而後啓動項目,過一下子,刷新頁面:ide
註冊中心有了剛纔那個服務了。這個叫作first-service註冊到註冊中心,它既能夠叫作生產者,也能夠被叫作消費者,由於它能夠爲別的服務提供接口,也能夠調用其餘服務提供的接口。總之,不管是生產者仍是消費者,它都被叫作Client,要用@EnableDiscoveryClient註解。我不當心點進去這個註解裏面,發現還有個參數,boolean autoRegister() default true
。這是是否項目已啓動,該服務自動註冊到註冊中心。默認爲自動。微服務
除了@EnableDiscoveryClient這個註解之外,還可使用另一個註解@EnableEurekaClient。效果相同,若是是Eureka作註冊中心的話,建議使用@EnableEurekaClient,若是是其餘註冊中心的話(例如阿里的nacos),建議使用@EnableDiscoveryClient。
要想運行起來一個個微服務,造成分佈式系統,做爲註冊中心和其中服務,應該實現一下需求:
一個服務client註冊到註冊中心eureka,該client的信息會被存在一個Map中,實現了第一步。同時,client會拉取一份名單,名單裏面有其餘註冊服務的信息,而且爲了保證明時性,每30s會再從註冊中心那邊拉取一份名單信息,實現了第二步。爲了確保註冊中心實時知道哪些服務還存活着,須要每一個client,每隔一段時間(默認30s)向註冊中心發送一個心跳,告訴註冊中心,我還在,註冊中心那份名單拿上還會記錄着這個client還能夠用,實現了第三步。
先看一眼註冊中心,也就是服務端Service,有個啓動引導類EurekaBootStrap,其中有個方法:
@Override public void contextInitialized(ServletContextEvent event) { try { initEurekaEnvironment(); initEurekaServerContext(); ServletContext sc = event.getServletContext(); sc.setAttribute(EurekaServerContext.class.getName(), serverContext); } catch (Throwable e) { logger.error("Cannot bootstrap eureka server :", e); throw new RuntimeException("Cannot bootstrap eureka server :", e); } }
這方法是初始化Eureka方法的,如今我特別想知道註冊中心是用什麼數據結構存下客戶端client信息的,因此我得去找註冊中心爲客戶端client提供的註冊接口,因而乎,點進initEurekaServerContext()這個方法看看,有個PeerAwareInstanceRegistry這個接口,再點進去看看,發現了
void register(InstanceInfo info, boolean isReplication);
看下它的實現類
@Override public void register(final InstanceInfo info, final boolean isReplication) { int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS; if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) { leaseDuration = info.getLeaseInfo().getDurationInSecs(); } super.register(info, leaseDuration, isReplication); replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); }
replicateToPeers() 這個方法用於註冊中心是集羣的狀況,主要是註冊完以後,同步該服務給其餘eureka節點。
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) { try { read.lock(); Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); REGISTER.increment(isReplication); if (gMap == null) { final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>(); gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap); if (gMap == null) { gMap = gNewMap; } } Lease<InstanceInfo> existingLease = gMap.get(registrant.getId()); ... 彷彿有好多代碼... } finally { read.unlock(); } }
目測 registry 應該就是儲存着全部的服務,點一下看其結構。
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
最外層是線程安全的ConcurrentHashMap,key值是registrant.getAppName(),也就是實例中的應用名稱 first-service。 裏面又是一個ConcurrentHashMap(代碼裏面是Map接口,但其實確定是ConcurrentHashMap,你能夠看gNewMap 對象怎麼new的)。裏面這個key是registrant.getId()實例id,value 是Lease<InstanceInfo>,這裏面存着服務實例和過時時間什麼的。ok,具體註冊,今天找到地方,先不看了。
關於client端,須要定時拉取服務名單,定時發送註冊中心一個心跳。因此用了兩個定時器。
在DiscoveryClient 類中,有個initScheduledTasks() 這個方法,是初始化那兩個定時器的,簡略代碼以下:
private void initScheduledTasks() { if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); scheduler.schedule( new TimedSupervisorTask( "cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ), registryFetchIntervalSeconds, TimeUnit.SECONDS); } if (clientConfig.shouldRegisterWithEureka()) { int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs); // Heartbeat timer scheduler.schedule( new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ), renewalIntervalInSecs, TimeUnit.SECONDS); } else { logger.info("Not registering with Eureka server per configuration"); }
SpringBoot讓集成Eureka很是的簡單,本篇提供了快速入門的示例。從此還要考慮到註冊中心集羣的問題。固然,如今還有更好用的註冊中心,阿里的nacos,不只有註冊中心的功能,同時還繼承了配置中心的功能。瞭解Eureka工做原理,有助於幫助咱們更好的理解分佈式系統中的註冊中心,爲了未來學習瞭解其餘註冊中心提供理論基礎。