服务注册与服务发现

服务注册与服务发现

Eureka的架构

Eureka客户端:使用了@EnableEurekaClient注解的应用服务,如订单服务等,甚至Eureka本身也是一个客户端

Eureka服务端:使用了@EnableEurekaServer注解的应用服务,该服务提供了注册表以及对服务节点的操作

服务提供者:服务启动后,可以向注册中心发起register请求,将服务信息注册进去

服务消费者:服务启动后,可以从注册中心拉取服务信息,并根据所得的服务信息调用服务

他们之间的关系为:

Eureka-server你可以当作他是一个单独的服务,而跟它对接的都是Eureka-client,而Eureka-client中则包含了服务消费者和服务提供者等角色。例如订单服务中包含一个消费者A,商品服务中包含一个提供者B,现在消费者A想查询该订单对应的商品信息,那就是查询提供者B的服务,那么首先A和B都必须先把自己作为一个eureka客户端,然后B将自己的服务信息发送到eureka的服务端中,A再从eureka服务端中拉取B的服务信息(地址),然后发起请求。

服务端

所需依赖

复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

启动服务时需要添加两个注解

复制代码
@EnableEurekaServer
@SpringBootApplication

客户端

所需依赖

复制代码
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

启动服务需要配置

复制代码
@EnableEurekaClient
@SpringBootApplication

关系

1、服务提供者、服务消费者、服务发现组件三者之间的关系大致如下:

1)各个微服务在启动时时,将自己的网络地址等信息注册到服务发现组件上(eureka,zookeeper,Consul,spring cloud alibaba的nacos),服务发现组件会存储这些信息。

2)服务消费者会从服务发现组件查询服务提供者的网络地址,然后将服务地址列表缓存到本地,然后根据服务名负载均衡调用服务提供者的接口。

3)各个微服务与服务发现组件使用一定的机制来维持心跳,服务发现组件若发现有服务没有提供心跳,那么服务发现组件会将该服务剔除。

4)微服务网络地址发生变更(例如实例增减或者IP端口发生变化等),会重新注册到服务发现组件上,使用这种方式,可以避免因网络变化导致服务之间的通讯停止,服务消费者也无须人工的修改网络地址。

Eureka简介

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

1.Eureka包含两个组件:Eureka Server和Eureka Client。

Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询负载算法的负载均衡器。

2.在应用启动后,Eureka Client会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

3.Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

三、Eureka一些特性及配置过程时需要注意的问题

1.Eureka能够保证AP,即当Eureka中的某个节点挂掉后,剩余的节点继续仍然可以提供服务的发现与注册服务。而Eureka的客户端在向某个Eureka或者发现了当前的Eureka不可用时,会自动切换到其他的节点,也就是说Eureka的节点是平等的,只要有一台Eureka服务器在,就能保证服务的可以继续被使用。

2.Eureka的自我保护机制,我们在注册服务时,如发生断网的情况,Eureka不能接收到当前服务的任何心跳请求,Eureka会在默认的90s后,将该服务进行强制剔除,这样就能保证到网络故障时,虽然失去了部分节点,但不会像zookeeper那样会使整个注册服务瘫痪。当网络稳定时,新的实例会同步到其他节点中。

3.相关配置问题

服务端的配置如下,不注册自身

复制代码
security:
  basic:
    enabled: true
  user:
    name: user
    password: password123
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://user:password123@localhost:8761/eureka

引入依赖

复制代码
<!-- eureka-server-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId>
    <version>1.2.3.RELEASE</version>
</dependency>
 <!--eureka-client-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka</artifactId>
     <version>1.2.3.RELEASE</version>
</dependency>

Eureka客户端应用服务注册流程

1、应用集成eureka客户端依赖,配置eureka相关信息;

2、当应用启动完成后,应用会自动向eureka-server服务发请求,将自身的示例信息注册到eureka-server;

3、会启动心跳进程和服务状态检测时间;心跳每隔30s一次;应用服务状态发生变更,也会想eureka-server服务进行服务重新注册;

4、当应用服务进行关闭时,会调用shutdown方法,进行服务下线的操作;当服务因断电/网络问题/意外停机,无法正确的下线服务时,这时心跳就会起到相对应的作用,如果eureka-server服务发现应用超过三次(90s)没有心跳,那么就会将服务进行保护/下线;

源码分析

源码分析:

应用在服务启动时自动注册的实现:配置类:EurekaClientAutoConfiguration里面向IOC容器内注册了EurekaAutoServiceRegistration.class对象

复制代码
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry registry,
                                                                       EurekaRegistration registration) {
    return new EurekaAutoServiceRegistration(context, registry, registration);
}

EurekaAutoServiceRegistration.class实现了SmartLifecycle.class

SmartLifecycle.class这个类是在应用启动后,自动调用start()

复制代码
@Override
public void start() {
    // 端口配置
    if (this.port.get() != 0) {
           if (this.registration.getNonSecurePort() == 0) {
                this.registration.setNonSecurePort(this.port.get());
           }
            if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
                this.registration.setSecurePort(this.port.get());
            }
        }

        // only initialize if nonSecurePort is greater than 0 and it isn't already running
        // because of containerPortInitializer below
         if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
          // 进行主动注册
            this.serviceRegistry.register(this.registration);
            // 发布注册事件
            this.context.publishEvent(
                   new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
            this.running.set(true);
         }
    }

EurekaServiceRegistry#register() 注册代码如下:

复制代码
@Override
public void register(EurekaRegistration reg) {
     maybeInitializeClient(reg);
     if (log.isInfoEnabled()) {
          log.info("Registering application " + reg.getApplicationInfoManager().getInfo().getAppName()
              + " with eureka with status "
              + reg.getInstanceConfig().getInitialStatus());
     }
     reg.getApplicationInfoManager()
        .setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
      // 进行注册健康检查
     reg.getHealthCheckHandler().ifAvailable(healthCheckHandler ->
     reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
}

进行安全健康检查同时注册信息 DiscoveryClient#registerHealthCheck()

复制代码
@Override
    public void registerHealthCheck(HealthCheckHandler healthCheckHandler) {
        if (instanceInfo == null) {
            logger.error("Cannot register a healthcheck handler when instance info is null!");
        }
        if (healthCheckHandler != null) {
            this.healthCheckHandlerRef.set(healthCheckHandler);
            // schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered
            if (instanceInfoReplicator != null) {
                // 进行注册
                instanceInfoReplicator.onDemandUpdate();
            }
        }
    }

在上述的onDemandUpdate()方法中有个register方法,代码如下

DiscoveryClient#register()

复制代码
boolean register() throws Throwable {
        logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
        EurekaHttpResponse<Void> httpResponse;
        try {            // 发请求给配置url进行注册 
            httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
        } catch (Exception e) {
            logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
            throw e;
        }
        if (logger.isInfoEnabled()) {
            logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
        }
        // 返回204码代表注册成功
        return httpResponse.getStatusCode() == 204;
    }

AbstractJerseyEurekaHttpClient#register()

复制代码
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
   String urlPath = "apps/" + info.getAppName();
   ClientResponse response = null;
   try {
       Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
       addExtraHeaders(resourceBuilder);
       response = resourceBuilder.header("Accept-Encoding", "gzip").type(MediaType.APPLICATION_JSON_TYPE).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, info);
       return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
       if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(), response == null ? "N/A" : response.getStatus());
     }
     if (response != null) {
          response.close();
      }
   }
}
相关推荐
努力也学不会java8 小时前
【Spring Cloud】 服务注册/服务发现
人工智能·后端·算法·spring·spring cloud·容器·服务发现
廋到被风吹走10 小时前
【配置中心】Nacos 配置中心与服务发现深度解析
开发语言·服务发现·php
MengFly_5 天前
Java广播 —如何利用广播做服务发现
java·网络·服务发现
rchmin9 天前
Nacos服务与配置管理平台介绍
架构·服务发现·配置管理
oMcLin11 天前
如何在 CentOS 7.9 上配置并调优 Docker Swarm 集群,确保跨多个节点的高效服务发现与负载均衡?
docker·centos·服务发现
喵叔哟12 天前
19.服务集成与通信
后端·docker·容器·服务发现
喵叔哟19 天前
15.故障排查与调试
后端·docker·容器·服务发现
没有bug.的程序员24 天前
Spring Cloud Gateway 架构与执行流程:从原理到性能优化的深度探索
微服务·云原生·eureka·性能优化·架构·sentinel·服务发现
没有bug.的程序员1 个月前
Sentinel 流控原理深度解析:从SlotChain到热点参数限流的设计哲学
jvm·微服务·云原生·eureka·sentinel·服务发现
运维开发小白1 个月前
服务发现中间件ConSul的操作指南
中间件·服务发现·consul