Spring Cloud Eureka 基于 Netflix Eureka 进行了封装,增加了Spring Boot 特有的自动化配置风格,主要负责微服务中的服务治理功能,包括服务注册服务发现

我们希望有一个模块可以自动注册存在的微服务,而不是手动配置。在调用的时候,在这个模块中查找被注册的服务的位置,然后直接调用,这就是服务治理。

Eureka 主要用于服务治理,主要包含两大部分,服务注册中心客户端。客户端又可以根据需要分为服务提供者和服务消费者。

服务治理机制

服务注册中心

  • 失效剔除:
    在正常情况下,服务提供者下线时,将会提醒服务注册中心下线,然后服务注册中心会将服务的实例从服务列表剔除。
    如果由于网络故障,没有将下线的请求信息发送给注册中心,服务中心不知道具体的服务提供者已经下线,在Eureka Server 启动的时候,一个定时任务将会被创建,每60s(默认)便去清单中剔除超过90s(默认) 的服务实例。
  • 自我保护:
    Eureka Server 会维持一个心跳,如果运行时,统计心跳的比例在15分钟内低于85%,则服务中心将会自动启动自我保护机制。
    在自我保护机制中,当前的注册信息会被保护起来,使得这些信息不会过期。在实际情况中可能由于网络不稳定导致心跳失败,所以自我保护机制还是很有必要。在调试中会调用到不存在的服务实例,需要将自我保护机制关掉。
//自我保护机制
	private boolean enableSelfPreservation = true;

	private double renewalPercentThreshold = 0.85;
eureka:
  server:
  	# 关掉自我保护
    enable-self-preservation: false

服务提供者

  • 服务注册:
    只要加上注解就可以使得客户端到服务注册中心注册。客户端启动之后,会马上发送请求,并按照配置配置信息注册到服务注册中心,在请求中会带上服务的元数据信息。
  • 服务续约:
    在服务注册之后,服务提供者需要使用心跳来维护注册的服务。为了防止剔除,服务提供者使用心跳进行维护,这个过程称为服务续约。
euewka:
  instance:
    # 续约时间,也是心跳时间
    lease-renewal-interval-in-seconds: 30
    # 服务时效时间 超时时间
    lease-expiration-duration-in-seconds: 90

服务消费者

  • 服务下线
    当服务进行关闭,客户端就会触发一个服务消息给Eureka Server, 提醒这是一个正常的服务关闭操作,然后服务注册中心就可以把服务实例从列表删除。
  • 获取服务:
    启动服务消费者,将会触发一个请求给服务注册中心,将服务注册中心的服务清单获取下来,并在客户端维护。也可以定期更新本地维护的清单,默认30s。
eureka:
  client:
    # 定期更新本地维护的清单
    registry-fetch-interval-seconds: 30

查看自动配置类

服务注册中心配置

org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean

@ConfigurationProperties(EurekaServerConfigBean.PREFIX)
public class EurekaServerConfigBean implements EurekaServerConfig {

	//服务注册中心的配置都是以 eureka.server 开头
	public static final String PREFIX = "eureka.server";

	private static final int MINUTES = 60 * 1000;

	@Autowired(required = false)
	PropertyResolver propertyResolver;

	private String aWSAccessId;

	private String aWSSecretKey;

	private int eIPBindRebindRetries = 3;

	private int eIPBindingRetryIntervalMs = 5 * MINUTES;

	private int eIPBindingRetryIntervalMsWhenUnbound = 1 * MINUTES;
	
	//自我保护机制
	private boolean enableSelfPreservation = true;
	//心跳失败比例
	private double renewalPercentThreshold = 0.85;
	//续约数阀值更新频率
	private int renewalThresholdUpdateIntervalMs = 15 * MINUTES;
	
	..........

服务注册类的配置

org.springframework.cloud.netflix.eureka.EurekaClientConfigBean

@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {

	//客户端配置都是以 eureka.client 开头
	public static final String PREFIX = "eureka.client";

	/**
	 * Default Eureka URL.
	 */
	public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX + "/";

	//默认可用分区。
	public static final String DEFAULT_ZONE = "defaultZone";

	private static final int MINUTES = 60;

	@Autowired(required = false)
	PropertyResolver propertyResolver;

	// 是否启动客户端
	private boolean enabled = true;
	
	// 获取服务的时间间隔
	private int registryFetchIntervalSeconds = 30;
	
	//更新实例到 Server 的时间
	private int instanceInfoReplicationIntervalSeconds = 30;

	//初始化实例到Server 的时间
	private int initialInstanceInfoReplicationIntervalSeconds = 40;

	//轮询Server地址更改时间
	private int eurekaServiceUrlPollIntervalSeconds = 5 * MINUTES;

	//读取 Server 超时时间
	private int eurekaServerReadTimeoutSeconds = 8;

	// 连接 Server 时间
	private int eurekaServerConnectTimeoutSeconds = 5;

	//Client 到Server 的连接总数
	private int eurekaServerTotalConnections = 200;

	//Client 到每个主机的连接总数
	private int eurekaServerTotalConnectionsPerHost = 50;
	........

服务实例类的配置

org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean

//实力类的配置以 eureka.instance 开头
@ConfigurationProperties("eureka.instance")
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {

	private static final String UNKNOWN = "unknown";

	//配置前缀
	private String actuatorPrefix = "/actuator";

	// 应用名
	private String appname = UNKNOWN;

	// 应用组名
	private String appGroupName;

	// 实例注册之后,是否马上开启通信,默认 false
	private boolean instanceEnabledOnit;
	
	// 非安全端口
	private int nonSecurePort = 80;
	
	// 安全端口
	private int securePort = 443;

	..............

服务注册中心仪表盘配置

服务注册中心的仪表盘用于服务注册中心的可视化展示。
org.springframework.cloud.netflix.eureka.server.EurekaDashboardProperties

@ConfigurationProperties("eureka.dashboard")
public class EurekaDashboardProperties {

	// 仪表盘访问路径
	private String path = "/";
	
	//	是否启用仪表盘
	private boolean enabled = true;

eureka:
  dashboard:
    path: /dashboard

springcloud eureka源码编译 springcloud eureka底层原理_eureka

# 查看源码

DiscoveryClient 实例

/**
 * Convenience annotation for clients to enable Eureka discovery configuration
 * (specifically). Use this (optionally) in case you want discovery and know for sure that
 * it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
 * find the eureka classes if they are available (i.e. you need Eureka on the classpath as
 * well).
 *
 * @author Dave Syer
 * @author Spencer Gibb
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient {

}

通过注释发现,@EnableEurekaClient的作用就是启动Eureka Discovery configuration.

public class EurekaDiscoveryClient implements DiscoveryClient {

EurekaDiscoveryClient类的作用就是与Eureka Server 相互协作/

Eureka Client 负责向Server 注册服务实例、向Server 租约续期、向Server取消租约、查询Server中的服务实例列表,Eureka Client 还需要配置一个Eureka Server 的URL列表。

服务发现

从要与eureka客户机对话的属性文件中获取所有eureka服务url的列表。

public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
        List<String> orderedUrls = new ArrayList<String>();
        // 读取region 并返回,每个微服务对应一个 region
        String region = getRegion(clientConfig);
        String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
        if (availZones == null || availZones.length == 0) {
            availZones = new String[1];
            availZones[0] = DEFAULT_ZONE;
        }
        logger.debug("The availability zone for the given region {} are {}", region, availZones);
        int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);

        List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);
        if (serviceUrls != null) {
            orderedUrls.addAll(serviceUrls);
        }
        int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 : (myZoneOffset + 1);
        while (currentOffset != myZoneOffset) {
            serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[currentOffset]);
            if (serviceUrls != null) {
                orderedUrls.addAll(serviceUrls);
            }
            if (currentOffset == (availZones.length - 1)) {
                currentOffset = 0;
            } else {
                currentOffset++;
            }
        }

        if (orderedUrls.size() < 1) {
            throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
        }
        return orderedUrls;
    }

获取实例

@Override
	public List<ServiceInstance> getInstances(String serviceId) {
		List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false);
		List<ServiceInstance> instances = new ArrayList<>();
		for (InstanceInfo info : infos) {
			instances.add(new EurekaServiceInstance(info));
		}
		return instances;
	}