Eureka - это децентрализованное приложение для управления обслуживанием, и ее отличительная особенность заключается в том, что она может быть зарегистрирована по адресу, который вы настроили как сервер, так и сервис. Затем в этой статье давайте обсудим процесс регистрации Эврики.
1. Сервер Эврики
Основным классом сервера Eureka является Eurekabootstrap, который реализует слушателя ServletContextListener. Поэтому мы можем сделать вывод, что Эврика реализована на основе контейнеров сервлетов. Ключевой код заключается в следующем:
Общедоступный класс Eurekabootstrap реализует ServletContextListener {//...mit, связанный с кодом/*** Инициализирует Eureka, включая синхронизацию с другими коллегами Eureka и публикация реестра. * * @see * javax.servlet.servletcontextlistener#contextinitialized (javax.servlet.servletcontextevent) */ @@override public void contextInitialized (EventContexTexTevent Event) {try {initeureKaenvironment (); initeurekaservercontext (); ServletContext sc = event.getServletContext (); sc.setattribute (eurekaservercontext.class.getName (), serverContext); } catch (throwable e) {logger.error ("Не может загрузить сервер Eureka:", e); Бросьте новое runtimeexception («не может загрузить сервер Eureka:», E); }} // Опустить связанный код ......}Мы можем видеть, что когда инициализация ServletContext будет завершена, среда Eureka будет инициализирована, а затем будет инициализирована EurekaserverContext. Затем мы рассмотрим метод initeurekaservercontext:
/*** init Hook для контекста сервера. Переопределить для пользовательской логики. */ Protected void initeureKaserverContext () бросает исключение {// ...... ApplicationInfomanager ApplicationInfomanager = null; if (eurekaclient == null) {eurekainstanceconfig instanceconfig = iscloud (configurationalmanager.getDeploymentContext ())? new CloudInstanceConfig (): новый mydatacenterinstanceconfig (); ApplicationInfomanager = new ApplicationInfomanager (InstanceConfig, New EurekaconfigBasedInStanceInfoProvider (InstanceConfig) .get ()); EurekaclientConfig eurekaclientConfig = new DeLaulteKaclientConfig (); eurekaclient = new DiscoveryClient (ApplicationInfomanager, EurekaclientConfig); } else {ApplicationInfomanager = eurekaclient.getApplicationInfomanager (); } Peerawareinstanceregistry Registry; if (isaws (ApplicationInfomanager.getInfo ())) {Registry = new Awsinstanceregistry (eurekaserverconfig, eurekaclient.geteurekaclientConfig (), ServerCodecs, eurekaclient); awsbinder = new awsbinderdelegate (eurekaserverconfig, eurekaclient.geteurekaclientconfig (), реестр, ApplicationInfomanager); awsbinder.start (); } else {Registry = new PeeraWareinStancereGistryImpl (eurekaserverconfig, eurekaclient.geteurekaclientconfig (), servercodecs, eurekaclient); } //...Omit часть кода}В этом методе будут созданы многие объекты, связанные с сервисом Eureka. Здесь я перечисляю два основных объекта, а именно Eurekaclient и Peerawareinstanceregistry. Мы поговорим о клиентской части позже. Давайте посмотрим на то, для чего используется Peerawareinstanceregistry. Здесь я пишу классную диаграмму об этом классе:
Согласно классовой диаграмме, мы можем четко обнаружить, что интерфейс верхнего уровня PeeraWareinStanceregistry является LeaseManager и LookupService, где LookupService определяет поведение самого основного примера обнаружения, в то время как LeaseManager определяет обработку регистрации, обновления и отмены клиента. Таким образом, в этой статье давайте сосредоточимся на реализации соответствующих интерфейсов LeaseManager. Оглядываясь назад, мы смотрим на Peerawareinstanceregistry. Фактически, этот класс используется для копирования соответствующей информации по нескольким узлам. Например, если узел регистрируется для обновления и офлайн, то соответствующая копия (уведомление) будет скопирована в каждый узел через этот класс. Посмотрим, как это обрабатывает регистрацию клиента:
/** * Регистрирует информацию о {@link ExanceinInfo} и Replicas * Эта информация для всех узлов Eureka. Если это событие репликации * из других узлов реплики, то оно не реплицируется. * * @param info * {@link ancessionInfo} для зарегистрированного и воспроизведения. * @param Isreplication * true, если это событие репликации из других узлов реплики, * false в противном случае. */ @Override public void Register (окончательный экземпляр Info, Final Boolean Isreplication) {int ardedudeDuration = lease.default_duration_in_secs; if (info.getLeaseInfo ()! = null && info.getLeaseInfo (). getDurationInsecs ()> 0) {ardedudeCuration = info.getLeaseInfo (). getDurationInsecs (); } super.register (информация, арендованная, iSreplication); Replicatetopeers (action.register, info.getAppName (), info.getId (), info, null, isreplication); }Мы видим, что он вызывает метод регистра родительского класса, а затем повторяет соответствующее поведение на другие узлы через репликатетопеи. Конкретная репликация не будет обсуждаться здесь. Давайте сосредоточимся на методе регистрации. Мы находим метод Register () в родительском классе:
/*** Регистрирует новый экземпляр с данной продолжительностью. * * @see com.netflix.eureka.lease.leasemanager#Register (java.lang.object, int, boolean) */ public void Register (exanceInfo Registration, int ardedudeDuration, boolean isreplication) {try {read.lock (); Map <string, аренда <exantianInfo >> gmap = registry.get (registrant.getAppname ()); Register.Increment (iSreplication); if (gmap == null) {final concurrenthashmap <string, lease <exanceinfo >> gnewmap = new concurrenthashmap <string, аренда <exantianInfo >> (); gmap = registry.putifabsent (registrant.getAppname (), gnewmap); if (gmap == null) {gmap = gnewmap; }} Аренда <exantianInfo> существующая точка = gmap.get (registrant.getid ()); // сохранить последнюю грязную метку времени, не перезаписывая ее, если уже есть аренда, если (существующая лиза! Long RegistrationLastDirtyTimestAmp = recared.getLastDirtyTimestAmp (); logger.debug («существующий аренда найден (существующий = {}, предоставленный = {}», существующая lastdirtytimestamp, registrationlastdirtytimestamp); // Это a> вместо a> = потому что, если временные метки равны, мы все еще принимаем удаленное // exactorinfo вместо локальной копии, если (существует, если существует. RegistrationLastDirtyTimestAmp) {logger.warn («Существует существующая аренда, а грязная временная метка существующей аренды {} больше" + ", чем та, которая зарегистрирована {}", EssuestLastDirtyRtyTimestAmp, RegistrationLastDirtyTimestAmp); Registration = EssureLease.getholder (); This.ExectedNumberOfRENEWSPERMIN + 2; Lease <ExanseInfo> (Регистрация, арендованная);Через исходный код давайте кратко рассмотрим процесс:
1) Во -первых, получите несколько столбцов объектов экземпляра службы на основе AppName. Если он нулевой, создайте новую карту и добавьте текущую зарегистрированную информацию приложения на эту карту. Здесь есть объект аренды. Этот класс описывает атрибуты времени общего, например, время регистрации, время запуска службы, время окончательного обновления и т. Д. Вы можете обратить внимание на ее реализацию:
/ * * Copyright 2012 Netflix, Inc. * * Лицензирован по лицензии Apache, версия 2.0 («Лицензия»); * Вы не можете использовать этот файл, кроме как в соответствии с лицензией. * Вы можете получить копию лицензии по адресу * * http://www.apache.org/licenses/license-2.0 * *, если не требуется применимый закон или не согласен в письменной форме, программное обеспечение *, распределено по лицензии, распределяется на «как это», * без гарантий или условий любого рода, либо экспрессии, либо подразумевается. * См. Лицензию на конкретные разрешения на управление языком и * ограничения по лицензии. */package com.netflix.eureka.lease; import com.netflix.eureka.registry.abstractinstanceregistry;/*** Описывает доступность на основе времени {@link T}. Цель состоит в том, чтобы избежать * накопления экземпляров в {@link Abstractinstanceregistry} в результате неровных * остановков, которые не редкость в средах AWS. * * Если аренда истекает без обновления, он в конечном итоге непрерывно истекает *, отмечая связанное {@link T} для немедленного выселения - это похоже на * явную отмену, за исключением того, что между * {@link T} и {@link Leasemanager}. * * @author Karthik Ranganathan, Greg Kim */Public Class Lease <t> {enum action {Register, Cancel, renew}; Public Static Final int default_duration_in_secs = 90; частный держатель T; Частный Long EvictionTimestamp; частный длинный регистрация,; Частный Long Serviceuptimestamp; // Сделать его нестабильным, чтобы задача с истечением срока действия увидела бы эту более быструю частную летучих долгосрочных долгов; частный длительный срок; публичная аренда (t r, int durationinsecs) {holder = r; RegistrationTimestAmp = System.CurrentTimeMillis (); LASTUPDATETIMESTAMP = RegistrationTimeStamp; Продолжительность = (продолжительность. * 1000); } /** * Обновление аренды, используйте продолжительность обновления, если он был указан * Assocated {@link T} во время регистрации, в противном случае продолжительность по умолчанию - * {@link #default_duration_in_secs}. */ public void renew () {lastupDateTimestAmp = System.currentTimeMillis () + продолжительность; } /*** Отменяет аренду, обновляя время выселения. */ public void cancel () {if (evictiontimestamp <= 0) {evictionTimestAmp = System.currentTimeMillis (); }} /*** Отметьте службу как Up. Это потребуется только в первый раз, вызову, * последующие вызовы будут проигнорированы. */ public void serviceUp () {if (serviceUptimestAmp == 0) {serviceuptimestAmp = System.currentTimeMillis (); }} /*** Установите листья службы вверх. */ public void setServiceuptimestAmp (long serviceuptimestamp) {this.serviceuptimestamp = serviceuptimestamp; } /*** Проверяет, истек ли аренда данного {@link com.netflix.appinfo.instanceinfo}. */ public boolean isexpired () {return isexpread (0l); } /*** Проверяет, истек ли аренда данного {@link com.netflix.appinfo.instanceinfo}. * * Обратите внимание, что из -за renew (), выполняющего «неправильную» вещь и настройку LastUpdateTimestAmp до +продолжительность, больше, чем * каким он должен быть, истечение на самом деле будет 2 * продолжительности. Это незначительная ошибка, и он должен только влиять на * экземпляры, которые невырасно отключены. Из -за возможного широкого воздействия на существующее использование, это не будет фиксированным. * @Param. Boolean Isexpired (long AddizeAlaSemes) {return (evictionTimeStamp> 0 || system.currentTimeMillis ()> (LastuPDateTimestAmp + Duration + AddieAlaleasems);} / ** * Получает миллионы секунды с момента регистрации аренды. * * @return. {return Registrationtymp; Миллионы с тех пор, как эпоха была высечена Long Getserviceuptimest () {return serviceuptimestamp;2) Согласно в настоящее время зарегистрированного идентификатора, если вы можете получить его на карте, сделайте следующее:
2.1) В соответствии с временем прикосновения существующего узела в данный момент и временем прикосновения зарегистрированного узла, если первое время будет позже, чем последнее время, в настоящее время зарегистрированный экземпляр должен быть подлежит существующему экземпляру.
2.2) В противном случае обновите его ожидаемый номер обновления в минуту и его порог
3) Сохраните текущий узел регистрации в карте, и наш процесс регистрации в основном подошел к концу
2. Eureka Client
Когда сервер ServletContext будет инициализирован, будет создан DiscoveryClient. Друзья, которые знакомы с Эврикой, должны быть знакомы с этими двумя атрибутами: Fetchregistry и Registerwithureka. При запуске в Integrated Eureka Independent Mode в Springcloud, если эти два значения не являются ложными, то стартап сообщит об ошибке. Почему он сообщает об ошибке? На самом деле, ответ заключается в конструкторе DiscoveryClient:
@Inject DiscoveryClient (ApplicationInfomanager ApplicationInfomanager, EurekaclientConfig, AbstractDiscoveryclientoptionalArgs args, провайдер <bastupregistry> BackupRegistryProvider) {//......... ни запрос данных. "); планировщик = null; HeartbeatExecutor = null; cacherefreshexecutor = null; eurekatransport = null; instanceregionchecker = new Instanceregionchecker (New PropertyBasedaStoreGionMapper (config), clientConfig.getRegion ()); // Это немного взлома, чтобы разрешить существующий код с использованием DiscoveryManager.getInstance () // Работать с Di'D DiscoveryClient DiscoveryManager.getInstance (). SetDiscoveryClient (это); DiscoveryManager.getInstance (). SeteurekaclientConfig (config); inittimestampms = System.currentTimeMiLsis (); logger.info ("Discovery Client инициализируется в TimestAmp {} с начальными экземплярами count: {}", inittimestampms, this.getApplications (). size ()); возвращаться; // Нет необходимости настраивать сетевые задачи, и мы готовы} try {// Размер по умолчанию 2 - 1 каждый для Heartbeat и Cacherefresh Scheduler = Executors.newschedledThreadpool (2, New ThinkfactoryBuilder () .SetNameFormat ("DiscoveryClient-%d") .semon (true). (TrueyClient-%d ") .semon (true). heartbeatExecutor = new ThreadPoolExecutor( 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d") .setDaemon(true) .строить() ); // Использовать прямую передачу cacherefreshexecutor = new ThreadPoolexeCutor (1, ClientConfig.getCachereFresheCutorthReadPoolsize (), 0, TimeUnit.seconds, New Synchronousqueue <Reannable>, New TreatkFactoryBuilder () .SetNameformat ("DiscoveryClefre-cacherefresefexecexecexemec .setDaemon (true) .build ()); // Использовать прямую передачу eurekatransport = new eurekatransport (); PradeRulerEndPointTask (Eurekatransport, Args); //BOMIT какой -то код initscheduledTasks (); // ....}На основе исходного кода мы можем сделать следующие выводы:
1) Если оба должны регистрировать withureka и должны быть неверными, то вернитесь напрямую.
2) Создайте бассейн потоков, который посылает сердцебиение и обновляет кэши
3) Инициализировать созданные задачи
Затем давайте посмотрим на следующий код в методе initschedudtasks ():
// Heartbeat Timer Scheduler.schedule (New Timedsupervisortask («Heartbeat», планировщик, Heartbeatexecutor, rewallintervalinsecs, TimeUnit.seconds, ExpbackOffBound, New HeartbeatThread ()), rewangalIntervalinsecs, TimeUnit.seconds);
Вот поток, которая запускает временное выполнение, за несколько секунд и выполняет отправку сердцебиения в соответствии с значением RenewAlIntervalinsecs. Поток HeartbeatThread выполняется следующим образом:
/*** Задача сердцебиения, которая возобновляет аренду в заданных интервалах. */ private Class HeartBeatThread реализует runnable {public void run () {if (rewall ()) {lastSuccessureHeartBeattimEStamp = System.currentTimeMillis (); }}}Мы видим, что метод выполнения очень прост в выполнении метода обновления, и если время успешно записано. Метод обновления:
/ ** * Возобновить с помощью службы Eureka, сделав соответствующий вызов отдыха */ boolean rene () {eurekahttpresponse <concenceInfo> httpresponse; try {httpresponse = eurekatransport.registrationclient.sendheartbeat (exanteminfo.getAppName (), exanceinInfo.getId (), exanceInfo, null); logger.debug ("{} - Статус Heartbeat: {}", prefix + apppathidentifier, httpresponse.getstatuscode ()); if (httpresponse.getStatuscode () == 404) {reregister_counter.increment (); logger.info ("{} - повторная регистрация приложений/{}", префикс + apppathidentifier, exanceinInfo.getAppName ()); Long TimeStamp = exactionInfo.setisDirtyWithTime (); логический успех = register (); if (success) {exanceinInfo.unsetisDirty (timeStamp); } вернуть успех; } return httpresponse.getStatuscode () == 200; } catch (throwable e) {logger.error ("{} - не смог отправить сердцебиение!", Prefix + apppathidentifier, e); вернуть ложь; }}Если сердцебиение отправлено здесь, если возврат составляет 404, будет выполнена регистрационная операция. Обратите внимание, что на основе возвращаемого значения httpresponse мы можем сделать вывод, что все эти операции основаны на HTTP -запросах. Это правда? Давайте продолжим посмотреть на метод регистра:
/*** Зарегистрируйтесь в службе Eureka, сделав соответствующий вызов отдыха. */ boolean Register () бросает Throwable {logger.info (prefix + apppathidentifier + ": регистрация службы ..."); Eurekahttpresponse <void> httpresponse; try {httpresponse = eurekatransport.registrationClient.register (exanceInfo); } catch (Exception e) {logger.warn ("{} - Регистрация не удалась {}", префикс + apppathidentifier, e.getmessage (), e); бросить E; } if (logger.isinfoenabled ()) {logger.info ("{} - Состояние регистрации: {}", префикс + apppathidentifier, httpresponse.getStatUscode ()); } return httpresponse.getStatuscode () == 204; }Здесь метод RegistrationClient в Eurekatransport называется:
Частный статический финальный класс Eurekatransport {private ClosableSolver Boottrapresolver; Частный транспортный клиент TransportClientFactory; Частный Eurekahttpclient RegistrationClient; Частный eurekahttpclientFactory RegistrationClientFactory; Частный eurekahttpclient QueryClient; Частный eurekahttpclientFactory QueryClientFactory; void shutdown () {if (RegistrationClientFactory! = null) {RegistrationClientFactory.shutDown (); } if (QueryClientFactory! = null) {QueryClientFactory.shutdown (); } if (RegistrationClient! = null) {registrationClient.shutdown (); } if (QueryClient! = null) {QueryClient.shutdown (); } if (transportClientFactory! = null) {transportClientFactory.shutdown (); }}}Здесь мы видим, что клиент Eureka использует HTTP -запрос для регистрации службы, что означает, что при создании DiscoveryClient мы зарегистрируем экземпляр с сервером.
3. Service Service, предоставленная сервером
Мы уже видели код, предоставленный сервером для обработки запросов на регистрацию клиентов. Поскольку клиент регистрируется через протокол HTTP, сервер должен иметь адрес для обработки этого HTTP -запроса. Фактически, сервер Eureka использует стандарт JAX-RS для предоставления метода REST для обнаружения службы. Мы можем взглянуть на метод AddInstance этого приложения:
/** * Регистрирует информацию о конкретном экземпляре для * {@link com.netflix.discovery.shared.application}. * * @param info * {@link ancessineinfo} Информация о экземпляре. * @param Isreplication * Параметр заголовка, содержащий информацию, будь то * реплицируется из других узлов. */@Post @consumes ({"application/json", "application/xml"}) публичный ответ addInstance (instanceInfo info, @headerparam (peerurekanode.header_replication) string isreplication) {logger.debug ("Регистрирующий экземпляр {} (replication = {})", info. // Утвердите, что экземпляры serainfo содержит все необходимые поля if (isblank (info.getid ())) {return response.status (400) .Entity ("отсутствующий экземплярный"). build (); } else if (isblank (info.gethostname ())) {return response.status (400) .Entity ("отсутствующий имени хоста"). build (); } else if (isblank (info.getipaddr ())) {return response.status (400) .entity ("отсутствующий IP -адрес"). build (); } else if (isblank (info.getAppname ()))) {return response.status (400) .Entity ("отсутствующий appname"). build (); } else if (! appname.equals (info.getAppName ()))) {return response.status (400) .Entity ("несоответствующее appName, ожидая" + appname + ", но был" + info.getAppName ()). build (); } else if (info.getDataCenterInfo () == null) {return response.status (400) .Entity ("отсутствующий datacenterinfo"). build (); } else if (info.getDataCenterInfo (). getName () == null) {return response.status (400) .Entity ("отсутствующий DataCenterInfo"). Build (); } else if (info.getDataCenterInfo (). getName () == null) {return response.status (400) .Entity ("отсутствующее имя DataCenterInfo"). Build (); } // Обработка случаев, когда клиенты могут регистрироваться в Bad DataCenterInfo с отсутствующими данными dataCenterinfo dataCenterInfo = info.getDataCenterInfo (); if (DataCenterInfo EncementOf inkeicleIdentifier) {string dataCenterInfoid = (((уникальный идентификатор) dataCenterInfo) .getId (); if (isblank (dataCenterInfoid)) {boolean experimental = "true" .EqualsIgnoreCase (serverConfig.getExperimental ("registration.validation.datacenterinfoid")); if (Experimental) {String entity = "dataCenterInfo типа" + datacenterinfo.getClass () + "должен содержать действительный идентификатор"; return response.status (400) .Entity (Entity) .build (); } else if (dataCenterInfo EncementOf AmazonInfo) {Amazoninfo Amazoninfo = (Amazoninfo) dataCenterinfo; String EffectiDID = amazoninfo.get (amazoninfo.metadatakey.instanceid); if (EffectiveD == null) {amazoninfo.getMetAdata (). put (amazoninfo.metadatakey.instanceId.getName (), info.getId ()); }} else {logger.warn ("Регистрация данных datacenterinfo of type {} без соответствующего идентификатора", datacenterinfo.getClass ()); }}}} Registry.register (info "true" .equals (isreplication)); return response.status (204) .build (); // 204 быть обратно совместимым}Выше всего содержание этой статьи. Я надеюсь, что это будет полезно для каждого обучения, и я надеюсь, что все будут поддерживать Wulin.com больше.