注册中心 org.springframework.cloud.netflix.eureka

在分布式服务中,服务注册中心主要承担的是登记服务“我是谁,我从哪里来”的重要职责。Spring Cloud架构中,org.springframe.netflix.eureka是大家最为熟知的一种实现方式。。

org.springframe.netflix.eureka是基于com.netflix.eureka拓展的,可视为Spring Boot方式的实现。Eureka服务构建如下:

  1. 新建一个Maven项目,命名eureka,步骤略;
  2. 在项目pom.xml添加如下配置:
<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>

</dependency>
  1. 在项目主函数类添加@EnableEurekaServer注解
@SpringBootApplication

@EnableEurekaServer

public class EurekaApplication {

    public static void main(String[] args) {

        SpringApplication.run(EurekaApplication.class, args);

    }

}
  1. 一般为保证服务安全,我们会要求访问服务时进行身份校验,可以添加security来鉴权。

在pom.xml添加security依赖

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency>

在bootstrap.yml添加security和eureka配置。

spring:

  application:

    name: eureka

  security:

    basic:

      enabled: true

    user:

      name: admin

      password: 666666
eureka:

  instance:

    hostname: localhost

    lease-renewal-interval-in-seconds: 10

    prefer-ip-address: false

  client:

    registerWithEureka: false

    fetchRegistry: false

    serviceUrl:

      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/

  server:

    eviction-interval-timer-in-ms: 5000

    renewal-percent-threshold: 0.49

      单实例Eureka到此就已经完成了。在开发环境或测试环境中,单实例已经满足性能需求,但实际生产环境一般采用主从实例,防止意外宕机造成的影响,主从实例需将registerWithEureka和fetchRegistry置为true,并修改defaultZone配置两个eureka的访问路径,即自己和另一个eureka的地址(这里用端口号作为区分,端口8761和端口8762,也可以通过域名作为区分,形式不限),两个路径之间通过英文逗号进行分隔。

eureka:

  instance:

    hostname: localhost

    lease-renewal-interval-in-seconds: 10

    prefer-ip-address: false

  client:

    registerWithEureka: true

    fetchRegistry: true

    serviceUrl:

      defaultZone: http://admin:666666 @localhost:8761/eureka/, http://admin:666666 @localhost:8762/eureka/

  server:

    eviction-interval-timer-in-ms: 5000

    renewal-percent-threshold: 0.49

            其他服务注册到eureka的实例如下:

  1. 创建一个Maven项目,步骤略;
  2. 在pom.xml添加依赖
<dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

</dependency>
  1. 在项目bootstrap.yml添加eureka配置
eureka:

  instance:

    lease-renewal-interval-in-seconds: 10 # 表示eureka间隔多久去拉取服务注册信息,默认为30秒,对于gateway,如果要迅速获取服务注册状态,可以缩小该值,比如1

    lease-expiration-duration-in-seconds: 20 # 表示eureka至上一次收到心跳之后,等待下一次心跳的超时时间,在这个时间内没收到下一次心跳,则将移除该instance

  client:

    security:

      basic:

        user: admin# eureka服务账号

        password: 666666 # eureka服务密码

    serviceUrl:

      defaultZone: http://${eureka.client.security.basic.user}:${eureka.client.security.basic.password}@localhost:8761/eureka/
  1. 在项目主函数类添加@SpringCloudApplication或SpringBootApplication、@EnableEurekaClient
@SpringCloudApplication

public class UserApplication {

    public static void main(String[] args) {

        SpringApplication.run(UserApplication.class, args);

    }

}

 

/** 小白解读时间,若有不妥之处,望请不吝赐教 **/

通过IDEA我们可以查看到org.springframe.netflix.eureka的源码,其项目结构如下:

其中,我们看到在eureka服务添加的注解@EnableEurekaServer的类中有@Import(EurekaServerMarkerConfiguration.class):

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Import(EurekaServerMarkerConfiguration.class)

public @interface EnableEurekaServer {



}

而在对应的EurekaServerMarkerConfiguration的类中则是实例一个Marker的Bean:

@Configuration

public class EurekaServerMarkerConfiguration {



   @Bean

   public Marker eurekaServerMarkerBean() {

      return new Marker();

   }



   class Marker {

   }

}

            接着,我在EurekaServerAutoConfiguration类中找到@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)中有Marker.class,查询手册得知@ConditionalOnBean的含义是当给定的Bean存在时实例化当前Bean,其次,在EurekaServerAutoConfiguration类中@Import(EurekaServerInitializerConfiguration.class),其他的则是向Spring IoC容器注入相关的Bean,实例化com.netflix.eureka和可视化监控等。

@Configuration

@Import(EurekaServerInitializerConfiguration.class)

@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)

@EnableConfigurationProperties({ EurekaDashboardProperties.class,

      InstanceRegistryProperties.class })

@PropertySource("classpath:/eureka/server.properties")

public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {

   /**

    * List of packages containing Jersey resources required by the Eureka server

    */

   private static final String[] EUREKA_PACKAGES = new String[] { "com.netflix.discovery",

         "com.netflix.eureka" };



   @Autowired

   private ApplicationInfoManager applicationInfoManager;



   @Autowired

   private EurekaServerConfig eurekaServerConfig;



   @Autowired

   private EurekaClientConfig eurekaClientConfig;



   @Autowired

   private EurekaClient eurekaClient;



   @Autowired

   private InstanceRegistryProperties instanceRegistryProperties;



   public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();



   @Bean

   public HasFeatures eurekaServerFeature() {

      return HasFeatures.namedFeature("Eureka Server",

            EurekaServerAutoConfiguration.class);

   }



   @Configuration

   protected static class EurekaServerConfigBeanConfiguration {

      @Bean

      @ConditionalOnMissingBean

      public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {

         EurekaServerConfigBean server = new EurekaServerConfigBean();

         if (clientConfig.shouldRegisterWithEureka()) {

            // Set a sensible default if we are supposed to replicate

            server.setRegistrySyncRetries(5);

         }

         return server;

      }

   }



   @Bean

   @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)

   public EurekaController eurekaController() {

      return new EurekaController(this.applicationInfoManager);

   }



   static {

      CodecWrappers.registerWrapper(JACKSON_JSON);

      EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());

   }



   @Bean

   public ServerCodecs serverCodecs() {

      return new CloudServerCodecs(this.eurekaServerConfig);

   }



   private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {

      CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());

      return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;

   }



   private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {

      CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());

      return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)

            : codec;

   }



   class CloudServerCodecs extends DefaultServerCodecs {



      public CloudServerCodecs(EurekaServerConfig serverConfig) {

         super(getFullJson(serverConfig),

               CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),

               getFullXml(serverConfig),

               CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));

      }

   }



   @Bean

   public PeerAwareInstanceRegistry peerAwareInstanceRegistry(

         ServerCodecs serverCodecs) {

      this.eurekaClient.getApplications(); // force initialization

      return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,

            serverCodecs, this.eurekaClient,

            this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),

            this.instanceRegistryProperties.getDefaultOpenForTrafficCount());

   }



   @Bean

   @ConditionalOnMissingBean

   public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,

         ServerCodecs serverCodecs) {

      return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,

            this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);

   }

   

   /**

    * {@link PeerEurekaNodes} which updates peers when /refresh is invoked.

    * Peers are updated only if

    * <code>eureka.client.use-dns-for-fetching-service-urls</code> is

    * <code>false</code> and one of following properties have changed.

    * </p>

    * <ul>

    * <li><code>eureka.client.availability-zones</code></li>

    * <li><code>eureka.client.region</code></li>

    * <li><code>eureka.client.service-url.&lt;zone&gt;</code></li>

    * </ul>

    */

   static class RefreshablePeerEurekaNodes extends PeerEurekaNodes

         implements ApplicationListener<EnvironmentChangeEvent> {



      public RefreshablePeerEurekaNodes(

            final PeerAwareInstanceRegistry registry,

            final EurekaServerConfig serverConfig,

            final EurekaClientConfig clientConfig, 

            final ServerCodecs serverCodecs,

            final ApplicationInfoManager applicationInfoManager) {

         super(registry, serverConfig, clientConfig, serverCodecs, applicationInfoManager);

      }



      @Override

      public void onApplicationEvent(final EnvironmentChangeEvent event) {

         if (shouldUpdate(event.getKeys())) {

            updatePeerEurekaNodes(resolvePeerUrls());

         }

      }

      

      /*

       * Check whether specific properties have changed.

       */

      protected boolean shouldUpdate(final Set<String> changedKeys) {

         assert changedKeys != null;

         

         // if eureka.client.use-dns-for-fetching-service-urls is true, then

         // service-url will not be fetched from environment.

         if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {

            return false;

         }

         

         if (changedKeys.contains("eureka.client.region")) {

            return true;

         }

         

         for (final String key : changedKeys) {

            // property keys are not expected to be null.

            if (key.startsWith("eureka.client.service-url.") ||

               key.startsWith("eureka.client.availability-zones.")) {

               return true;

            }

         }

         

         return false;

      }

   }



   @Bean

   public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,

         PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {

      return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,

            registry, peerEurekaNodes, this.applicationInfoManager);

   }



   @Bean

   public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,

         EurekaServerContext serverContext) {

      return new EurekaServerBootstrap(this.applicationInfoManager,

            this.eurekaClientConfig, this.eurekaServerConfig, registry,

            serverContext);

   }



   /**

    * Register the Jersey filter

    */

   @Bean

   public FilterRegistrationBean jerseyFilterRegistration(

         javax.ws.rs.core.Application eurekaJerseyApp) {

      FilterRegistrationBean bean = new FilterRegistrationBean();

      bean.setFilter(new ServletContainer(eurekaJerseyApp));

      bean.setOrder(Ordered.LOWEST_PRECEDENCE);

      bean.setUrlPatterns(

            Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));



      return bean;

   }



   /**

    * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources

    * required by the Eureka server.

    */

   @Bean

   public javax.ws.rs.core.Application jerseyApplication(Environment environment,

         ResourceLoader resourceLoader) {



      ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(

            false, environment);



      // Filter to include only classes that have a particular annotation.

      //

      provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));

      provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));



      // Find classes in Eureka packages (or subpackages)

      //

      Set<Class<?>> classes = new HashSet<>();

      for (String basePackage : EUREKA_PACKAGES) {

         Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);

         for (BeanDefinition bd : beans) {

            Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),

                  resourceLoader.getClassLoader());

            classes.add(cls);

         }

      }



      // Construct the Jersey ResourceConfig

      //

      Map<String, Object> propsAndFeatures = new HashMap<>();

      propsAndFeatures.put(

            // Skip static content used by the webapp

            ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,

            EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");



      DefaultResourceConfig rc = new DefaultResourceConfig(classes);

      rc.setPropertiesAndFeatures(propsAndFeatures);



      return rc;

   }



   @Bean

   public FilterRegistrationBean traceFilterRegistration(

         @Qualifier("httpTraceFilter") Filter filter) {

      FilterRegistrationBean bean = new FilterRegistrationBean();

      bean.setFilter(filter);

      bean.setOrder(Ordered.LOWEST_PRECEDENCE - 10);

      return bean;

   }

}

EurekaServerInitializerConfiguration类实现了ServletContextAware, SmartLifecycle, Ordered三个接口,在start()方法中调用了eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);其实现在EurekaServerBootstrap类。

@Configuration

public class EurekaServerInitializerConfiguration

      implements ServletContextAware, SmartLifecycle, Ordered {



   private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class);



   @Autowired

   private EurekaServerConfig eurekaServerConfig;



   private ServletContext servletContext;



   @Autowired

   private ApplicationContext applicationContext;



   @Autowired

   private EurekaServerBootstrap eurekaServerBootstrap;



   private boolean running;



   private int order = 1;



   @Override

   public void setServletContext(ServletContext servletContext) {

      this.servletContext = servletContext;

   }



   @Override

   public void start() {

      new Thread(new Runnable() {

         @Override

         public void run() {

            try {

               //TODO: is this class even needed now?

               eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);

               log.info("Started Eureka Server");



               publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));

               EurekaServerInitializerConfiguration.this.running = true;

               publish(new EurekaServerStartedEvent(getEurekaServerConfig()));

            }

            catch (Exception ex) {

               // Help!

               log.error("Could not initialize Eureka servlet context", ex);

            }

         }

      }).start();

   }



   private EurekaServerConfig getEurekaServerConfig() {

      return this.eurekaServerConfig;

   }



   private void publish(ApplicationEvent event) {

      this.applicationContext.publishEvent(event);

   }



   @Override

   public void stop() {

      this.running = false;

      eurekaServerBootstrap.contextDestroyed(this.servletContext);

   }



   @Override

   public boolean isRunning() {

      return this.running;

   }



   @Override

   public int getPhase() {

      return 0;

   }



   @Override

   public boolean isAutoStartup() {

      return true;

   }



   @Override

   public void stop(Runnable callback) {

      callback.run();

   }



   @Override

   public int getOrder() {

      return this.order;

   }



}

            在EurekaServerBootstrap类中找到contextInitialized方法,主要是初始化环境配置和上下文。

public void contextInitialized(ServletContext context) {

   try {

      initEurekaEnvironment();

      initEurekaServerContext();



      context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);

   }

   catch (Throwable e) {

      log.error("Cannot bootstrap eureka server :", e);

      throw new RuntimeException("Cannot bootstrap eureka server :", e);

   }

}

            其中,初始化上下文的实现主要是向后兼容、拷贝注册邻近节点信息、注册所有监控统计信息。

protected void initEurekaServerContext() throws Exception {

   // For backward compatibility

   JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),

         XStream.PRIORITY_VERY_HIGH);

   XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),

         XStream.PRIORITY_VERY_HIGH);



   if (isAws(this.applicationInfoManager.getInfo())) {

      this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,

            this.eurekaClientConfig, this.registry, this.applicationInfoManager);

      this.awsBinder.start();

   }



   EurekaServerContextHolder.initialize(this.serverContext);



   log.info("Initialized server context");



   // Copy registry from neighboring eureka node

   int registryCount = this.registry.syncUp();

   this.registry.openForTraffic(this.applicationInfoManager, registryCount);



   // Register all monitoring statistics.

   EurekaMonitors.registerAllStats();

}

            在EurekaServerInitializerConfiguration类start()后还发布了可用事件EurekaRegistryAvailableEvent、服务启动事件EurekaServerStartedEvent,结合InstanceRegistry类(在EurekaServerAutoConfiguration类被实例化

,继承PeerAwareInstanceRegistryImpl,而PeerAwareInstanceRegistryImpl又是继承AbstractInstanceRegistry

)重写的注册事件EurekaInstanceRegisteredEvent、下线事件EurekaInstanceCanceledEvent、续约事件EurekaInstanceRenewedEvent,我们可以通过监听这些事件自定义服务的健康监控;

EurekaController则是使用freemarker模板引擎实现了可视化管理页面。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值