Martian'S Blog 码而不思则罔,思而不码则殆

微服务之服务发现 Eureka的介绍与使用

Eureka是什么?

Eureka是一个感叹词,传说是古希腊数学家和发明家阿基米德发现浮力时的呐喊o(╯□╰)o。它是一个基于REST的服务,在Netflix主要用于AWS云上的服务发现,以实现中间层服务器的负载均衡和容错。Netflix是一家成功实践微服务架构的互联网公司,除了部署在AWS云上,Eureka也适用于基于Docker等虚拟化技术的微服务架构环境中。

Eureka

客户端服务发现模式

基于云的微服务,由于可能的机器故障、升级,弹性扩缩等原因,服务的每个实例可能会动态变化,实例IP也可能动态改变。不同于传统的运行在IP地址固定的物理机上的服务,需要一套更好的服务发现机制。Eureka即是Netflix提供的一种高可用的服务发现解决方案。

解决的问题

首先,从整体上看,服务发现有客户端发现模式服务端发现模式两种。区别在于服务发现和Balance策略是由使用方自己实现还是作为一项服务来供使用方调用。服务发现依赖于服务注册表,系统中每个服务实例启动时,会将自己的网络位置信息发送到服务注册表,服务注册表利用心跳机制即时更新。实例关闭或者服务注册表检测到实例心跳超时情况下,实例信息就会从服务注册表移出。

具体到这两种不同的服务发现模式,客户端发现模式是由服务请求方负责发现所有可用实例在网络中的具体位置,并根据具体的Balance策略将请求路由到具体的实例处理。而服务端发现模式则是请求方把请求经由Load Balancer,Load Balancer查询服务注册表后根据自己的Balance策略将请求路由到目标服务的一台具体实例上进行处理。两种实现方式的优缺点这里不做讨论。

Eureka 是一种客户端服务发现模式,提供Server和Client两个组件。Eureka Server作为服务注册表的角色,提供REST API管理服务实例的注册和查询。POST请求用于服务注册,PUT请求用于实现心跳机制,DELETE请求服务注册表移除实例信息,GET请求查询服务注册表来获取所有的可用实例。Eureka Client是Java实现的Eureka客户端,除了方便集成外,还提供了比较简单的Round-Robin Balance。配合使用Netflix Ribbon ,可以实现更复杂的基于流量、资源占用情况、请求失败等因素的Banlance策略,为系统提供更为可靠的弹性保证。

enter image description here

整体结构和特性介绍

Netflix 在AWS云环境上的Eureka部署如下:

整体结构 每个region上部署一个Eureka集群,这个Eureka集群管理当前region的所有服务实例。region的每个zone上至少部署一台Eureka服务器,保证即使当前zone挂掉了,其他zone的服务仍然可用。(region和zone的概念见AWS官网说明:Regions and Availability Zones)。如果当前zone的Eureka Server连接不上,Eureka Client会尝试连接其他zone的Eureka Server来故障转移。

服务注册在Eureka Server上,每30秒发送心跳来维持注册状态。客户端90s内都没有发心跳,Eureka Server就会认为服务不可用并将其从服务注册表移除。服务的注册和更新信息会同步到Eureka集群的其他节点。所有zone的Eureka Client每30秒查询一次当前zone的服务注册表获取所有可用服务,然后采用合适的Balance策略向某一个服务实例发起请求。

Eureka是一个AP的系统,具备高可用性和分区容错性。每个Eureka Client本地都有一份它最新获取到的服务注册表的缓存信息,即使所有的Eureka Server都挂掉了,依然可以根据本地缓存的服务信息正常工作。Eureka Server没有基于quorum 机制实现,而是采用P2P的去中心化结构,这样相比于zookeeper,集群不需要保证至少几台Server存活才能正常工作,增强了可用性。但是这种结构注定了Eureka不可能有zookeeper那样的一致性保证,同时因为Client缓存更新不及时、Server间同步失败等原因,都会导致Client访问不到新注册的服务或者访问到过期的服务。

当Eureka Server节点间某次信息同步失败时,同步失败的操作会在客户端下次心跳发起时再次同步;如果Eureka Server和Eureka Client间有网络分区存在(默认的检测机制是15分钟内低于85%的心跳汇报),Eureka Server会进入自我保护模式,不再把过期服务从服务注册表移除(这种情况下客户端有可能获取已经停止的服务,配合使用Hystrix通过熔断机制来容错和降级,弥补基于客户端服务发现的时效性的缺点)。更复杂的情况见在Eureka官网文档上有详细说明:Understanding Eureka Peer to Peer Communication

简单使用

Eureka Wiki上有Demo应用可以编译后直接运行,把编译后生成的war包放在tomcat web容器的webapps目录下运行即可。Eureka可以和Spring Cloud无缝集成,这里主要说明一下和Spring Cloud的集成。

需要

  • JDK 1.8及以上的JDK版本
  • Maven 3.0+
  • IntelliJ IDEA

依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>eureka-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR5</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

spring-cloud-starter-eureka-server是Eureka Server的POM依赖,spring-cloud-starter-eureka是Eureka Client的Pom依赖。依赖里面把Server和Client都包含进去了,Eureka 服务端工程和Eureka 客户端工程可以用同一份依赖文件。

实现

借助于Spring Cloud的强大,服务端和客户端实现只需要很少的代码。 服务端:

@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServiceApplication.class, args);
    }
}

客户端:

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

    @RestController
    class ServiceInstanceRestController {

        @Autowired
        private DiscoveryClient discoveryClient;

        @RequestMapping("/service-instances/{applicationName}")
        public List<ServiceInstance> serviceInstancesByApplicationName(
                @PathVariable String applicationName) {
            return this.discoveryClient.getInstances(applicationName);
        }
    }
}

@EnableEurekaServer 注解把当前类标注为Eureka Server作为服务注册表。依靠Spring Cloud的自动配置@EnableDiscoveryClient 注解会把classpath下的Eureka DiscoveryClient 作为客户端服务发现的实现。

配置

服务端需要以下配置:

server.port=8000 #服务启动端口
eureka.client.register-with-eureka=false  #单机启用,不注册到其他Eureka Server
eureka.client.fetch-registry=false  #单机启用,不从其他Eureka Server获取注册表信息

客户端需要以下配置:

server.port=8113 #服务启动端口
spring.application.name=a-bootiful-client #服务名
eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/ #Eureka Server服务地址

运行

先启动Eureka服务端,服务端页面加载成功后再启动客户端。默认情况下,客户端会在一分钟左右后把自己注册在服务注册表,并从服务注册表拉取可用服务,只有一个客户端运行的情况下会只有一个。 成功

因为本身是REST的服务,在浏览器访问http://localhost:8113/service-instances/a-bootiful-client 也能获取到可用服务。

总结

Eureka作为一个轻量的服务发现实现,具有AP的保证。基于客户端的发现模式需要自己去实现客户端Balance策略,而Netflix已经给出了Ribbon这样的解决方案。 借助Spring Cloud,可以比较方便、快速的集成进微服务架构中。如果使用Spring Cloud作为微服务框架,Eureka会是一个很好的选择。

相关参考

http://www.infoq.com/cn/articles/basis-frameworkto-implement-micro-service

https://github.com/Netflix/eureka/wiki

http://blog.abhijitsarkar.org/technical/netflix-eureka/

http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_cloud_netflix

http://www.jiagoushuo.com/article/1000415.html

https://tech.knewton.com/blog/2014/12/eureka-shouldnt-use-zookeeper-service-discovery/

https://technologyconversations.com/2015/09/08/service-discovery-zookeeper-vs-etcd-vs-consul/

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-auto-configuration.html

http://dockone.io/article/771

https://spring.io/guides/gs/service-registration-and-discovery/