Spring Cloud项目(三): 服务注册与发现(Eureka)、原理及其常见问题

好不容易能空出时间整理一下这几个月的所得了

Eureka

一、 简介

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

注:AWS(亚马逊云计算服务),其中重要组成部分有Region(区域)和Available Zone(可用区),可以把AWS理解为一个书店,有很多个书架(region),书架相对独立,每个书架都有都有很多层(Available Zone),每个层的书要保持一致就需要用管理员(通过高带宽、低延迟网络与完全冗余的专用城域光纤互连,为可用区之间提供高吞吐量和低延迟的网络。网络性能足以完成可用区之间的同步复制)。

二、 组成部分

Eureka包含两个组件:Eureka Server和Eureka Client

  1. Eureka Server(多个server可以互相注册)
    Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
    Eureka Server本身也是一个服务,默认情况下会自动注册到Eureka注册中心。
    如果搭建单机版的Eureka Server注册中心,则需要配置取消Eureka Server的自动注册逻辑。毕竟当前服务注册到当前服务代表的注册中心中是一个说不通的逻辑。
    Eureka Server通过Register、Get、Renew等接口提供服务的注册、发现和心跳检测等服务。
  2. Eureka Client(注册在server上)
    Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
    Eureka Client分为两个角色,分别是:Application Service(Service Provider 服务提供方)Application Client(Service Consumer服务消费方)

三、Eureka Server架构图

在这里插入图片描述

  • Register(服务注册):把自己的IP和端口注册给Eureka。
  • Renew(服务续约):发送心跳包,每30秒发送一次。告诉Eureka自己还活着。
  • Cancel(服务下线):当provider关闭时会向Eureka发送消息,把自己从服务列表中删除。防止consumer调用到不存在的服务。
  • Get Registry(获取服务注册列表):获取其他服务列表。
  • Replicate(集群中数据同步):eureka集群中服务端之间的数据复制与同步。
  • Make Remote Call(远程调用):完成服务的远程调用。

四、单机版本的Eureka server 和 Eureka client(客户端提供方)和 Eureka client(客户端消费方)

Eureka server项目

pom.xml

<!-- 新增eureka 服务端依赖-->
<dependency>
	 <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

application.yml

#设置eureka server 服务的端口号
server:
  port : 8761
spring:
  application:
    name: eurekazz
eureka:
  client:
    fetch-registry: false #是否从Eureka服务中获取注册信息,由于是单机,所以不需要同步
    register-with-eureka: false #由于是单机版本就不向注册中心注册自己
  service-url:
     defaultZone: http://127.0.0.1:8761/eureka #注册到注册中心,如果不是单机,则需要将其他的Eureka server地址用逗号隔开

启动类

@SpringBootApplication
@EnableEurekaServer  //开启eureka服务
public class EurekazzApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekazzApplication.class, args);
    }
}

当控制台出现如下提示,则可以打开http://localhost:8761/,就能看到eureka服务页面
在这里插入图片描述

Eureka client(客户端提供方)

pom.xml

		<dependency>
 			<!-- eureka依赖   -->   
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
        	<!-- 客户端依赖,注册在eureka上   -->   
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
        	<!-- 由于客户端会提供web访问,所以添加此依赖  -->  
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

bootstrap.yml(一般将重要参数放入该文件里,优先级高,bootstrap一定会被读取(覆盖application.yml的相同配置))

server:
  port: 6666 #端口号
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka #注册到注册中心
  instance:
    prefer-ip-address: true #显示ip

启动类

//@EnableDiscoverClient 与@EnableEurekaClient相同效果
@EnableEurekaClient //加入注册中心 也可以不需要写,Springcloud Edgware以上,引入依赖就会自动加载配置文件,自动注册。
@SpringCloudApplication
/**
 * author : ww
 */

public class ConfigseverApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigseverApplication.class, args);
    }
}
Eureka client(客户端消费方)

与Eureka client(客户端提供方)配置一样。

五、集群的Eureka server

Eureka client 会定期的连接Eureka server获取该server下注册信息,并缓存在本地,单机情况下,如果Eureka server宕机了,client端还是会根据缓存信息去访问,但无法更新缓存信息,很容易造成问题,所以一般都会使用高可用的Eureka server集群。

集群的Eureka server

新建两个Eureka server项目(eurekaone,eurekatwo),作为集群。除了配置文件不同,其他都和上面Eureka server项目相同
1.如果是windows,则修改一下本地映射地址(C:\Windows\System32\drivers\etc\hosts),目的是为了方便在注册中心中辨别
在这里插入图片描述
2.application.yml(Eureka server 01项目)

#设置eureka server 服务的端口号
server:
  port : 8761
spring:
  application:
    name: eurekaone
eureka:
  client:
    fetch-registry: true #同步注册中心的信息
    register-with-eureka: true #注册中心注册自己
    service-url:
      #基本上把集群的所有的注册中心地址都放进来,避免当前服务地址宕机后,其他Eureka client项目注册无法同步
      defaultZone: http://peer2:8762/eureka,http://127.0.0.1:8761/eureka 
  instance:
    hostname: peer1
    instance-id: peer1

3.application.yml(Eureka server 02项目)

#设置eureka server 服务的端口号
server:
  port : 8762
spring:
  application:
    name: eurekatwo
eureka:
  service-url:
     defaultZone: http://peer2:8762/eureka,http://127.0.0.1:8761/eureka 
  client:
    fetch-registry: true 
    register-with-eureka: true 

4.运行两个项目,分别访问:http://peer1:8761/http://peer2:8762/
DS Replicas 为同步注册信息的列表
在这里插入图片描述
在这里插入图片描述
5.开启一个Eureka client项目任意注册 一个Eureka server项目,关闭一个Eureka server项目,依然能访问到它的注册信息

eureka启动执行逻辑:

EurekaServerMarkerConfiguration(创建maker)–>EurekaServerAutoConfiguration(加载配置文件)–>EurekaServerInitializerConfiguration(初始化eureka)–>eurekaServerBootstrap(启动eureka加载,相邻节点注册信息)

  1. 从spring-cloud-netflix-eureka-server包中,可以看到在@EurekaServerMarkerConfiguration装载Marker后台,依赖中会先自动加载EurekaServerAutoConfiguration的bean,主要功能是将eureka-server中的功能bean加载在beanfactory(实体工厂),比如说通过jersey向外提供获取server信息的接口配置节点信息启动eureka
@Configuration //表明这是一个配置类
@Import(EurekaServerInitializerConfiguration.class) //导入启动EurekaServer的bean
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class) //标识只有在EurekaServerMarkerConfiguration.Marker.class加载完才会加载当前这个bean,然后开启eureka
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
      InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties") //加载配置文件
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {

    // 此处省略大部分代码,仅抽取一些关键的代码片段

    // 加载EurekaController, spring-cloud 提供了一些额外的接口,用来获取eurekaServer的信息
    @Bean
    @ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
    public EurekaController eurekaController() {
       return new EurekaController(this.applicationInfoManager);
    }
    // 配置服务节点信息,这里的作用主要是为了配置Eureka的peer节点,也就是说当有收到有节点注册上来
    //的时候,需要通知给那些服务节点, (互为一个集群)
    @Bean
    @ConditionalOnMissingBean
    public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
        ServerCodecs serverCodecs) {
        return new PeerEurekaNodes(registry, this.eurekaServerConfig,
         this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
    }
    // EurekaServer的上下文
    @Bean
    public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
      PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
        return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
         registry, peerEurekaNodes, this.applicationInfoManager);
    }
    // 这个类的作用是spring-cloud和原生eureka的胶水代码,通过这个类来启动EurekaSever
    // 后面这个类会在EurekaServerInitializerConfiguration被调用,进行eureka启动
    // EurekaServerBootstrap类会初始化Eureka的环境变量,并且也会初始化eureka的上下文(复制节点,状态修改为up,开启定时任务清理心跳失效60s的客户端)
    @Bean
    public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
      EurekaServerContext serverContext) {
    return new EurekaServerBootstrap(this.applicationInfoManager,
         this.eurekaClientConfig, this.eurekaServerConfig, registry,
         serverContext);
    }
// 配置拦截器,ServletContainer里面实现了jersey框架,通过他来实现eurekaServer对外的restFull接口
@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;
}

// 拦截器实例
@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<Class<?>>();
   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<String, Object>();
   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;
}
}

  1. 加载@EurekaServerInitializerConfiguration标签,会开启一个线程去初始化EurekaServer,同时启动Eureka Server
/**
 * @author Dave Syer
 */
@Configuration
@CommonsLog
public class EurekaServerInitializerConfiguration
      implements ServletContextAware, SmartLifecycle, Ordered {

   @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 {
               //初始化EurekaServer,同时启动Eureka Server ,
               eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
               log.info("Started Eureka Server");
                // 发布EurekaServer的注册事件
               publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                // 设置启动的状态为true
               EurekaServerInitializerConfiguration.this.running = true;
                // 发送Eureka Start 事件 , 其他还有各种事件,我们可以监听这种时间,然后做一些特定的业务需求,后面会讲到。
               publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
            }
            catch (Exception ex) {
               // Help!
               log.error("Could not initialize Eureka servlet context", ex);
            }
         }
      }).start();
   }

  
}

六、常见问题

  1. 注册服务慢
    原因:由于心跳机制,默认30s,只有当实例、服务器、客户端的本地缓存数据一致时才能被其他服务发现(3次心跳),可以修改心跳时间间隔(eureka.instance.leaseRenewal-IntervalInSeconds)
  2. 已经停止的微服务节点注销慢或者不住校
    关闭自我保护、修改清理间隔时间(修改配置文件)
  3. 如何主动关闭
    postman调用:localhost:8761/eureka/apps/{application.name}/
    写接口
@RestController
public class HelloController {
    @Autowired
    private DiscoveryClient client;
    
    @RequestMapping(value = "/offline", method = RequestMethod.GET)
    public void offLine(){
    	DiscoveryManager.getInstance().shutdownComponent();
    }   
}

注:如果集群中有一个eureka server宕机。不会有类似选举的过程,客户端请求会自动切换到新的eureka server节点,维持强一致性和高可用性,对网络分区等问题采用自我保护机制,保留信息不过期。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值