Spring Cloud Eureka 全方位深入解析

前言:

spring-cloud-starter-netflix-eureka 和 eureka的区别: 前者是对后者的封装,以保证其在spring cloud中的运行。后者为纯粹的servlet应用,需要打war运行。

注:netflix   eureka 2.x 目前已停更

一.介绍

1.eureka介绍:略

2.CAP原理

在分布式环境中,存在一个著名的CAP原理,C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。

eureka 注册中心符合AP,而zookeeper则符合CP,具体两者的区别会在最后常见问题下进行详细说明。

一.参数配置

1.eureka server 配置

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

1.1 yml配置 (此配置并未囊括所有,如需要其他则自行配置)

#eureka server刷新readCacheMap的时间,注意,client读取的是readCacheMap,这个时间决定了多久会把readWriteCacheMap的缓存更新到readCacheMap上
#默认30s
eureka.server.responseCacheUpdateIntervalMs=3000
#eureka server缓存readWriteCacheMap失效时间,这个只有在这个时间过去后缓存才会失效,失效前不会更新,过期后从registry重新读取注册服务信息,registry是一个ConcurrentHashMap。
#由于启用了evict其实就用不太上改这个配置了
#默认180s
eureka.server.responseCacheAutoExpirationInSeconds=180

#启用主动失效,并且每次主动失效检测间隔为3s
#默认60s
eureka.server.eviction-interval-timer-in-ms=3000

#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchIntervalSeconds=5
#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000
eureka.instance.preferIpAddress=true
#关闭自我保护
eureka.server.enable-self-preservation=false
eureka.client.serviceUrl.defaultZone=http://localhost:8211/eureka/,http://localhost:8211/eureka/

1.2 main启动注解

package com.my.test.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer  //开启eureka服务
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication .class, args);
    }

}

2.eureka client 配置

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2.1 yml 配置 (此配置并未囊括所有,如需要其他则自行配置)

#服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除
#注意,EurekaServer一定要设置eureka.server.eviction-interval-timer-in-ms否则这个配置无效,这个配置一般为服务刷新时间配置的三倍
#默认90s
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次
#默认30s
eureka.instance.lease-renewal-interval-in-seconds=5
#eureka client刷新本地缓存时间
#默认30s
eureka.client.registryFetchIntervalSeconds=5
#eureka客户端ribbon刷新时间
#默认30s
ribbon.ServerListRefreshInterval=1000
eureka.instance.preferIpAddress=true
#关闭自我保护
eureka.server.enable-self-preservation=false
#最好每个实例不同顺序,按照离自己最近的Eureka地址放到最前面
eureka.client.serviceUrl.defaultZone=http://localhost:8211/eureka/,http://localhost:8211/eureka/

2.2 main 配置

package com.my.test.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient  //此处也可以用 @EnableDiscoveryClient 替代
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication .class, args);
    }

}

二.运行流程

1.服务调用关系

服务提供者

1.1 启动后,向注册中心发起register请求,注册服务

1.2 在运行过程中,定时向注册中心发送renew心跳,证明“我还活着”。

1.3 停止服务提供者,向注册中心发起cancel请求,清空当前服务注册信息。

服务消费者

1.4 启动后,从注册中心拉取服务注册信息

1.5 在运行过程中,定时更新服务注册信息。

1.6 服务消费者发起远程调用:

2.服务续约

Application Service内的Eureka Client后台启动一个定时任务,跟Eureka Server保持一个心跳续约任务,每隔一段时间(默认30S)向Eureka Server发送一次renew请求,进行续约,告诉Eureka Server我还活着,防止被Eureka Server的Evict任务剔除。

3.服务下线

Application Service应用停止后,向Eureka Server发送一个cancel请求,告诉注册中心我已经退出了,Eureka Server接收到之后会将其移出注册列表,后面再有获取注册服务列表的时候就获取不到了,防止消费端消费不可用的服务。

4.服务剔除

Eureka Server启动后在后台启动一个Evict任务,对一定时间内没有续约的服务进行剔除。

三.数据存储结构

1.接口服务层

负责接收并转换对象

2.二级缓存层

2.1.一级缓存

无过期时间,保存服务信息的对外输出数据结构。

2.2.二级缓存

本质上是guava的缓存,包含失效机制,保存服务信息的对外输出数据结构。

3.数据存储层

包含了服务详情和服务治理相关的属性

 

四.附属功能

1.身份验证

注册eureka 的时候,通过Spring Security(注:旧版本和新版本的配置会在差异)增加账号密码验证。

导入jar包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

增加Spring Security 配置类(注:再此类中也可以增加配置其他的安全操作,比如失败或成功等之后的一些处理逻辑,有兴趣的可以深入学习Spring Security)

package com.my.test.eureka.config;

import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * 注册中心(带-账号安全)
 * @ClassName SecurityConfig
 * @Description
 * @Auther Hari
 * @Date 2019/1/21 9:35
 * @Version 1.0
 **/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        super.configure(http);
    }
}

yml中增加账号密码(账号密码也可以在上面的配置类中进行设置)

spring:
  security:
    user:
      name: admin
      password: admin

配置完成后,访问注册中心的网页就可以看到提示输入账号密码了。

服务注册到eureka上的url则如下

http://账号:密码@host:port/eureka

2.健康检查

默认情况下,Eureka使用客户端心跳来确定客户端是否启动。除非另有规定,否则发现客户端将不会根据Spring Boot执行器传播应用程序的当前运行状况检查状态。这意味着成功注册后Eureka将永远宣布申请处于“UP”状态。通过启用Eureka运行状况检查可以改变此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序将不会在“UP”之外的状态下将流量发送到应用程序。

开启健康检查

eureka:
  client:
    healthcheck:
      enabled: true

3.状态页和健康指标

eureka:
  instance:
    status-page-url-path: /actuator/info #eureka注册中心的url link
    health-check-url-path: /actuator/health #健康检查的url

4.负载均衡

 

5.高可用性,区域和地区

当存在多个机房时,同一个机房的服务调同一个机房的服务,如同一个机房的服务调不通,则再调用其他机房的服务。

配置

Server 1

eureka:
  instance:
    metadata-map:
      zone: zone-1  #服务所属zone
  client:
    prefer-same-zone-eureka: true
    region: shenzhen #地区
    availability-zones:
      shenzhen: zone-1,zone-2 #机房12
    service-url:
      zone-1: http://localhost:8761/eureka/
      zone-2: http://localhost:8762/eureka/

Server 2

eureka:
  instance:
    metadata-map:
      zone: zone-2  #服务所属zone
  client:
    prefer-same-zone-eureka: true
    region: shenzhen #地区
    availability-zones:
      shenzhen: zone-2,zone-1 #机房21
    service-url:
      zone-1: http://localhost:8761/eureka/
      zone-2: http://localhost:8762/eureka/

Client 1

eureka:
  instance:
    metadata-map:
      zone: zone-1  #服务所属zone
  client:
    prefer-same-zone-eureka: true
    region: shenzhen #地区
    availability-zones:
      shenzhen: zone-1,zone-2 #机房12
    service-url:
      zone-1: http://localhost:8761/eureka/
      zone-2: http://localhost:8762/eureka/

根据如上配置,则client 会先调用server1,如不通则再次调用server2

6.ip地址偏好

在某些情况下,Eureka优先发布服务的IP地址而不是主机名。将eureka.instance.preferIpAddress设置为true,并且当应用程序向eureka注册时,它将使用其IP地址而不是其主机名。

7.同行意识

通过运行多个实例并请求他们相互注册,可以使Eureka更具弹性和可用性。事实上,这是默认的行为,所以你需要做的只是为对方添加一个有效的serviceUrl,例如

---
spring:
  profiles: peer1
eureka:
  instance:
    hostname: peer1
  client:
    serviceUrl:
      defaultZone: http://peer2/eureka/

---
spring:
  profiles: peer2
eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: http://peer1/eureka/

8.独立模式

只要存在某种监视器或弹性运行时间(例如Cloud Foundry),两个高速缓存(客户机和服务器)和心跳的组合使独立的Eureka服务器对故障具有相当的弹性。在独立模式下,您可能更喜欢关闭客户端行为,因此不会继续尝试并且无法访问其对等体。例

server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

请注意,serviceUrl指向与本地实例相同的主机。

9.自我保护机制

自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:

1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。

2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用

3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。

配置

eureka:
  server: 
    enable-self-preservation: false  #开启或者禁用自我保护
    eviction-interval-timer-in-ms: 3000 #心跳检测时间

四.集群部署

简单的多eureka配置(可采取多区域多机房配置,具体参考上:5.高可用性,区域和地区)

server 1

spring:
  profiles: peer1
eureka:
  instance:
    hostname: peer1
  client:
    serviceUrl:
      defaultZone: http://peer2/eureka/

server 2

spring:
  profiles: peer2
eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: http://peer1/eureka/

client 1

eureka:
  instance:
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 15
    prefer-ip-address: true
  client:
    serviceUrl:
      defaultZone: http://peer2/eureka/,http://peer1/eureka/  #同时注册多个注册中心

五.其他

1.实现对eureka监听

package com.my.test.eureka.listener;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Applications;
import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent;
import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName EurekaListener
 * @Description 监听eureka 服务注册情况
 * @Auther Hari
 * @Date 2019/1/21 10:55
 * @Version 1.0
 **/
@Configuration
@Slf4j
@EnableScheduling
public class EurekaListener implements ApplicationListener {

    private ConcurrentHashMap<String,LostInstance> lostInstanceMap = new ConcurrentHashMap<>();
    private int defalutNotifyInterval[] = {0,60,120,240,480};

    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        // 如果服务退出
        if (applicationEvent instanceof EurekaInstanceCanceledEvent) {
            // 对应执行
            EurekaInstanceCanceledEvent event = (EurekaInstanceCanceledEvent) applicationEvent;
            //获取eureka 注册的服务列表
            PeerAwareInstanceRegistry registry = EurekaServerContextHolder.getInstance().getServerContext().getRegistry();
            Applications applications = registry.getApplications();
            applications.getRegisteredApplications().forEach((registeredApplication) -> {
                registeredApplication.getInstances().forEach((instance) -> {
                    //获取对应的服务信息
                    if (instance.getInstanceId().equals(event.getServerId())) {
                        String id = instance.getInstanceId();
                        log.info("服务ID:{},服务已退出!",id);
                        //放入退出队列
                        lostInstanceMap.remove(id);
                        lostInstanceMap.put(id, new LostInstance(instance));
                    }
                });
            });
        }
        //服务注册
        if (applicationEvent instanceof EurekaInstanceRegisteredEvent) {
            EurekaInstanceRegisteredEvent event = (EurekaInstanceRegisteredEvent) applicationEvent;
            //则移出退出队列
            log.info("服务ID:{},服务注册成功!",event.getInstanceInfo().getInstanceId());
            lostInstanceMap.remove(event.getInstanceInfo().getInstanceId());
        }
    }

    @Scheduled(cron = "0/30 * * * * ?")
    private void notifyLostInstance(){
        lostInstanceMap.entrySet().forEach((lostInstanceMap)->{
            String key = lostInstanceMap.getKey();
            LostInstance lostInstance = lostInstanceMap.getValue();
            DateTime dt = new DateTime(lostInstance.getLostTime());
            if(dt.plusSeconds(defalutNotifyInterval[lostInstance.getCurrentInterval()]).isBeforeNow()){
                log.info("服务:{}已失效,IP为:{},失效时间为:{},请马上重启服务!",new Object[]{lostInstance.getInstanceId(),lostInstance.getIPAddr(),dt.toString()});
            }
        });
    }

    class LostInstance extends InstanceInfo {
        protected int currentInterval = 0;
        protected Date lostTime;
        public LostInstance(InstanceInfo ii) {
            super(ii);
            this.lostTime = new Date();
        }
        public Date getLostTime() {
            return lostTime;
        }
        public void setLostTime(Date lostTime) {
            this.lostTime = lostTime;
        }
        public int getCurrentInterval(){
            return currentInterval++%4;
        }
    }
}

六.常见问题

1.服务器上多网卡的情况下Eureka的部署问题 (详细:https://blog.csdn.net/neosmith/article/details/53126924

场景:服务器上分别配置了eth0, eth1和eth2三块网卡,只有eth1的地址可供其它机器访问,eth0和eth2的 IP 无效。在这种情况下,服务注册时Eureka Client会自动选择eth0作为服务ip, 导致其它服务无法调用。

解决方案:

1.1. 通过上面源码分析可以得知,spring cloud肯定能配置一个网卡忽略列表。通过查文档资料得知确实存在该属性:

spring.cloud.inetutils.ignored-interfaces[0]=eth0 # 忽略eth0, 支持正则表达式

因此,第一种方案就是通过配置application.properties让应用忽略无效的网卡。

1.2.当网查遍历逻辑都没有找到合适ip时会走JDK的InetAddress.getLocalHost()。该方法会返回当前主机的hostname, 然后会根据hostname解析出对应的ip。因此第二种方案就是配置本机的hostname和/etc/hosts文件,直接将本机的主机名映射到有效IP地址。

1.3.添加以下配置:

# 指定此实例的ip
eureka.instance.ip-address=
# 注册时使用ip而不是主机名
eureka.instance.prefer-ip-address=true

2.Eureka服务注册慢的问题

作为一个实例也包括定期心跳到注册表(通过客户端的serviceUrl),默认持续时间为30秒。在实例,服务器和客户端在其本地缓存中都具有相同的元数据(因此可能需要3个心跳)之前,客户端才能发现服务。您可以使用eureka.instance.leaseRenewalIntervalInSeconds更改期限,这将加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器内部有一些计算可以对租赁更新期进行假设。

3.本机Netflix EurekaClient的替代方案

可使用@EnableDiscoveryClient注解

你不必使用原始的Netflix EurekaClient,通常使用一个包装器会更方便。Spring Cloud提供Fegin(REST客户端构建器)和Spring RestTemplate去使用Eureka service的逻辑标识符替代物理URLS。配置带固定的物理服务器集合的Ribbon,你可以简单的设置.ribbon.listOfServers的物理服务器地址(或者hostname)集合,并使用逗号分隔符分开,是客户端的ID。

你也可以使用 org.springframework.cloud.client.discoveryClient,它提供了一个简单的API而不是特定于Netflix。

在类中如下

@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl(){
    List<ServiceInstane> list = discoveryClient.getInstances("STORES");
    if(list!=null && list.siz()>0) {
        return list.get(0).getUri();
    }
    return null;
}

4.eureka 和zookeeper的区别 (详:https://www.cnblogs.com/springsource/p/9379275.htm )

根据CAP原理,eureka属于AP可用性强,zookeeper属于CP为强一致性。在zookeeper出现故障时,会重新选择leader,这需要时间 ,在此期间服务不可用,而且少于三个后,服务同样不可用。

而eureka只要大于等于一个,就可以继续提供服务,并且不存在选择leader等情况,可以立即切换注册中心。

写的不好,还请见谅。部分内容来源网络。如有意见留言交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值