SpringCloud篇二之Eureka

/ 微服务SpringCloudJava技术 / 没有评论 / 129浏览

SpringCloud Eureka是什么?

Eureka[juˈri:kə] 字面意思是发现、找到的意思。

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

Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器,它是服务注册中心。

EurekaDubbo的架构对比图:

eureka

dubbo

Eureka的三大角色

  1. Eureka Server 提供服务注册和发现。
  2. Service Provider服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到。
  3. Service Consumer服务消费方从Eureka获取注册服务列表,从而能够消费服务。

Eureka的构建和使用

创建Eureka注册中心模块

  1. POM文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>user-management</artifactId>
        <groupId>com.zealzhangz</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>microservice-cloud-eureka-7001</artifactId>

    <dependencies>
        <!--eureka-server服务端 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <!-- 修改后立即生效,热部署 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>springloaded</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
</project>
  1. 入口代码
/**
 * @version Version: 1.0
 * @date DateTime: 2018/08/18 23:42:00<br/>
 */
@SpringBootApplication
@EnableEurekaServer // EurekaServer服务器端启动类,接受其它微服务注册进来
public class EurekaServer7001App {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServer7001App.class,args);
    }
}
  1. 启动并验证启动成功
# 直接浏览器访问端口
http://127.0.0.1:7001

将已有的服务注册到注册中心

microservice-cloud-provider-user-8001服务注册到注册中心

  1. 修改POM文件添加依赖
<!-- 将微服务provider侧注册进eureka -->
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-eureka</artifactId>
   </dependency>
  1. 修改配置文件
eureka:
  client: #客户端注册进eureka服务列表内
    service-url: 
      defaultZone: http://localhost:7001/eureka
  1. 添加服务发现注解@EnableEurekaClient
package com.zealzhangz;

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

/**
 * @version Version: 1.0
 * @date DateTime: 2018/08/15 00:44:00<br/>
 */
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@SpringBootApplication
public class UserProvider8001App {
    public static void main(String[] args) {
        SpringApplication.run(UserProvider8001App.class,args);
    }
}

  1. 测试服务注册结果 直接在浏览器访问下面url,确认服务正确注册
http://localhost:7001

完善我们的微服务

目前我们已经完成了注册功能,但是此时我们刷新一下http://localhost:7001/ 这个页面就会看到一段红色的字

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

我们先来完善我们的微服务再来解决这种Eureka自我保护的机制

  1. 修改主机名 在microservice-cloud-provider-user-8001application.yml文件里面最后一行添加如下配置:
eureka:
    instance:
        instance-id: microservicecloud-user-8001
        prefer-ip-address: true                                 #访问路径可以显示IP地址

微服务info内容详细信息

  1. 修改microservice-cloud-provider-user-8001 POM文件
<!-- actuator监控信息完善 -->
   <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
   </dependency>
  1. 修改总工程pom文件添加构建build信息 添加内容:
<build>
   <finalName>micro-service-cloud</finalName>
   <resources>
     <resource>
       <directory>src/main/resources</directory>
       <filtering>true</filtering>
     </resource>
   </resources>
   <plugins>
     <plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-resources-plugin</artifactId>
       <configuration>
         <delimiters>
          <delimit>@</delimit><!--注意不要使用$,原因是${}会被maven处理-->
         </delimiters>
       </configuration>
     </plugin>
   </plugins>
  </build>
  1. 再修改microservice-cloud-provider-user-8001工程下的application.yml文件,来添加我们这个微服务的一些描述
info:
  app.name: zhanga-micro-service-cloud
  company.name: www.zhangaoo.com                           #我的博客地址
  build.artifactId: @project.artifactId@                   #注意不要使用$,原因是${}会被maven处理
  build.version: @project.version@

访问对应的接口http://192.168.50.95:8001/info,就会返回以下JSON结果

{
    "app": {
        "name": "zhanga-micro-service-cloud"
    },
    "company": {
        "name": "www.zhangaoo.com"
    },
    "build": {
        "artifactId": "microservicecloud-provider-user-8001",
        "version": "1.0-SNAPSHOT"
    }
}

Eureka的自我保护机制介绍

默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。

综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。

server:
   enable-self-preservation: false

但是eureka自我保护模式希望大家在没有其他特殊业务需求的话就不要去禁用它的自我保护模式

Eure的服务发现

对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息

  1. 在UserController中加入下面代码
    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping(value = "/discovery",method = RequestMethod.GET)
    public Object discover(){
        List<String> list = discoveryClient.getServices();
        log.info("list:" + JSONObject.toJSONString(list));
        List<ServiceInstance> srvList = discoveryClient.getInstances("MICROSERVICECLOUD-USER");
        for (ServiceInstance element : srvList) {
            //然后打印你指定要找的微服务的ID和主机和端口以及访问地址等信息
            log.info(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t" + element.getUri());
        }
        return this.discoveryClient;
    }
  1. 测试接口结果 访问接口http://127.0.0.1:8001/user/discovery结果如下
{
    "localServiceInstance": {
        "host": "192.168.50.95",
        "port": 8001,
        "serviceId": "microservicecloud-user",
        "metadata": {},
        "uri": "http://192.168.50.95:8001",
        "secure": false
    },
    "services": [
        "microservicecloud-user"
    ]
}
  1. 给服务定义静态metadata 在上面的JSON返回结果中metadata为空,我们可以通过配置文件定义静态metadata
eureka:
  instance:
    metadata-map:
      fixed-s1: "value_1"

再次请求返回结果如下:

{
    "services": [
        "microservicecloud-eureka"
    ],
    "localServiceInstance": {
        "host": "127.0.0.1",
        "port": 8001,
        "uri": "http://127.0.0.1:8001",
        "serviceId": "microservicecloud-user",
        "metadata": {
            "fixed-s1": "value_1"
        },
        "secure": false
    }
}
  1. Dynamic Service Metadata

可在代码中动态定义metadata

@Component
public class DynamicMetadataReporter {
    @Autowired
    private ApplicationInfoManager applicationInfoManager;

    @PostConstruct
    public void init() {
        Map<String, String> map = applicationInfoManager.getInfo().getMetadata();
        map.put("dynamic-s1", "value_2");
    }
}

结果如下:

[
    {
        "host": "127.0.0.1",
        "port": 8001,
        "uri": "http://127.0.0.1:8001",
        "serviceId": "MICROSERVICECLOUD-USER",
        "metadata": {
            "dynamic-s1": "value_2",
            "fixed-s1": "value_1"
        },
        ....
    }    
]

Eureka集群配置

  1. 按照microservice-cloud-eureka-7001配置分别在创建microservice-cloud-eureka-7002microservice-cloud-eureka-7003两个模块

  2. 修改3台eureka服务器的application.yml配置

server:
  port: 7001
eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      # 单机环境设置为 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
server:
  port: 7002
eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7003/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。

server:
  port: 7003
eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: false #false表示不向注册中心注册自己。
    fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。

  1. 修改microservice-cloud-provider-user-8001配置文件
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/,http://localhost:7002/eureka/,http://localhost:7003/eureka/
  instance:
    instance-id: microservicecloud-user-8001
    prefer-ip-address: true         #访问路径可以显示IP地址
    ip-address:  127.0.0.1

按上述配置配置以后发现会有下面问题

针对上面的两个问题,需要修改两点

  1. Eureka集群默认使用hostname,因此需要在hosts文件映射一下
  2. Eureka集群各个Eureka Server需要互相发现,因此需要设置register-with-eureka: truefetch-registry: true
eureka:
  instance:
    hostname: localhost #eureka服务端的实例名称
  client:
    register-with-eureka: true 
    fetch-registry: true 
  1. 重新配置如下
127.0.0.1  eureka7001.com
127.0.0.1  eureka7002.com
127.0.0.1  eureka7003.com
spring:
  application:
    name: microservicecloud-eureka
server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001.com #eureka服务端的实例名称
#    prefer-ip-address: true
#    ip-address: 127.0.0.1
  client:
    register-with-eureka: true #false表示不向注册中心注册自己。
    fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      # 单机环境设置为 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。

spring:
  application:
    name: microservicecloud-eureka
server:
  port: 7002
eureka:
  instance:
    hostname: eureka7002.com #eureka服务端的实例名称
#    prefer-ip-address: true
#    ip-address: 127.0.0.1
  client:
    register-with-eureka: true #false表示不向注册中心注册自己。
    fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
spring:
  application:
    name: microservicecloud-eureka
server:
  port: 7003
eureka:
  instance:
    hostname: eureka7003.com #eureka服务端的实例名称
#    prefer-ip-address: true
#    ip-address: 127.0.0.1
  client:
    register-with-eureka: true #false表示不向注册中心注册自己。
    fetch-registry: true #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/        #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  instance:
    instance-id: microservicecloud-user-8001
    prefer-ip-address: true         #访问路径可以显示IP地址
    ip-address:  127.0.0.1

Eureka相比Zookeeper有哪些优势呢?

著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性P在是分布式系统中必须要保证的,因此我们只能在AC之间进行权衡。

  1. Zookeeper保证CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。

  1. Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪