Spring Cloud 之Eureka(一)

简介

  Eureka是Spring cloud 的基本套件之一,是基于Netflix 的Eureka做的二次封装,主要是负责完成微服务架构中的服务治理功能。它是微服务架构中最为核心和基础的模块,它主要是用来实现各个微服务市里额自动化注册和发现。

  服务注册:在服务治理框架中,都会有一个注册中心,每个服务单元都要想服务注册中心登记自己提供的服务,将主机与端口号、版本号、通讯协议等一些附加信息告诉注册中心,注册中心按服务名分类组织服务清单。并且注册中心还有一心跳的方式去监测清单中的服务是否可用,如不可用需要从服务清单中剔除,达到配出故障服务的效果。

  例如:我们有两个提供服务A分别位于:192.168.0.100:8000和192.168.0.101:8000,有三个提供服务B的实例分别位于192.168.0.100:9000,192.168.0.101:9000,192.168.0.102:9000位置上。注册中心会维护类似的服务清单:

  

服务名 位置
服务A 192.168.0.100:8000和192.168.0.101:8000
服务B 192.168.0.100:9000,192.168.0.101:9000,192.168.0.102:9000位置上

  服务发现:在服务治理框架下运作,不在需要制定具体的实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供方接口的时候,并不知道,具体服务实例的位置。因此需要向服务注册中心咨询服务,并获取服务实例的所有清单,然后再通过某种策略(也就是负载均衡)来决定访问访问某个具体的服务实例。但是,在实际的框架中为了性能的考虑等因素,不会采用每次都向服务注册中心获取服务的方式,并且不同的应用在缓存和服务剔除等机制上也会有一些不用的实现策略。

名词解释

服务提供者

  服务注册:“服务提供者”在启动的时候回通过发送rest请求的方式将自己注册到注册中心,同事带上自身服务的一些元数据信息。注册中心接受到这个rest请求之后,将元数据信息存储在一个双层结构Map中,其中第一层的key是服务名,第二层的key是具体服务的实例名。

  在服务注册时,需要确定eureka.client.register-with-eureka=true参数是否正确,改制默认为true。若设置为false竟不能启动注册操作。

  服务同步:将不同的服务提供者分别注册到两个不同的注册中心上,也就是他们的信息被两个服务注册中心所维护。此时,由于服务注册中心之间因相互注册为服务,当服务提供者发送注册其请求到一个服务注册中心时,它会将该请求转发给集群中相连的其他的注册中心,从而实现注册中心之间的服务同步。通过服务同步,这些服务提供者的注册信息就可以通过这两个注册中心的任意一天获取到。

  服务续约:这里类似于心跳检测机制,默认是每隔90秒,服务提供者需要向注册中心进行续约,目的是隔一段时间Service Provider调用接口,告诉Eureka Server它还活着没挂,不要把它T了。通俗的说就是它们两之间的心跳检测,避免服务提供者被剔除掉

服务消费者

  获取服务:当我们启动服务消费者时,她会发送一个rest请求给注册中心,来获取服务提供者在注册中心的所有的实例清单。为了性能考虑,注册中心会维护一份制度的服务清单来返回给客户端,同事这个缓存清单会每个30秒更新一次。

  获取服务:服务消费者额基础,所以必须确保eureka.client.fentch-registry=ture。如果想要修改缓存清单的更新时间可以通过修改此参数:eureka.client.registry-fetch-interval-sencouds=30。

服务调用:服务消费者在获取到服务清单后,通过服务名可以获取具体提供服务的实例名和对应实例的元数据信息,因为有这些服务实例的详细信息,所以客户端可以根据自己额需要,通过负载均衡策略或者实例。

  对于访问实例的选择,Eureka中有REginon和zone的概念,一个REgion中可以包含多个Zone,每个服务客户端徐亚被注册到一个Zone中,所以每个客户端对应一个REgion和一个Zone。在进行服务调用时,有限访问同处于一个zone中的服务提供者,若访问不到在访问其他的zone。

  服务下线:在系统下运行过程中必然会面临关闭或者重启服务某个实例的情况,在服务关闭期间,我们自然不希望客户端会继续调用关闭了的实例。所以在客户端程序中,当服务实例进行正常关闭操作时,她会出发一个服务下线的rest到注册中心,告诉注册中心:我要下线了。注册中心在收到请求之后,将该服务状态置为下线,并把该下线的事件传播出去。

服务注册中心

  失效剔除:有些时候我们的服务并不是正常的下线,可能是由于内存溢出,网络故障等原因导致服务不可正常巩固走,而服务注册中心并没有收到“服务下线”的请求。为了从服务列表中将这些无法提供服务的实例剔除,注册中心在启动的时候会创建一个定时任务,默认每隔一段时间(默认是60s)将当前清单中超时没有续约的服务剔除出去。

  自我保护:当我们在本地调试基于Eureka的程序时,基本上都会碰到这样的一个问题,在服务注册中心的信息面板中出现的警告信息。实际上,该警告就是出发了Eureka service的自我保护机制。之前说过,服务注册到Eureka service后,会委会一个心跳连接,告诉Eureka service自己还活着。Eureka service在运行期间,会统计心跳失败的比例在15分钟内是否低于85%,如果低于,Eureka service会将该实例注册信息保护起来,让这些实例不会过期。但是,在这段保护期间实例若出现问题,客户端很容易拿到十几已经不存在的服务实例,会出现调用失败额情况,所以客户端必须要有容错机制,例如:可以使用请求重试,或者断路器等机制。

  在本地调试时很容易出发注册中心的保护机制,这会使注册中心维护的服务实力不那么正确。所以在本地开发时,可以使用eureka.service.enable-self-preservation=false参数来关闭保护机制,一确保注册中心可以将不可用的实例正确剔除。

注意:不同Spring cloud 版本配置会有所不同,本博客中都是基于Brixton.SR5版本。

Eureka之服务端(注册中心)

  服务端也就是注册中心,支持高可用。它依托于强一致性提供良好额的服务实力可用性,可以应对不同的应用场景。如果Eurka以集群的模式部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间据需提供服务的注册和发现,当故障分片恢复运行时,集群中的其他分片会把他们的状态再次同步回来。Netflix推荐在每个可用的区域运行一个Eureka服务端,通过它来形成集群。不同区域的服务注册中心通过异步模式互相复制各各自状态,这就意味着在任意给定的时间点每个实例关于所有服务的状态是有细微差别的。

依赖配置

<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>org.hope</groupId>
  <artifactId>eureka-server</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>eureka-server</name>

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

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
 
  <dependencies>
  //此依赖表示该工程为服务端也就是为注册中心 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>   <!-- Spring boot 测试依赖 --> <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>Brixton.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> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project> 

启动服务注册中心

  通过@EnableEurekaServer来开启一个服务注册中心,这一步很简单。

package org.hope;

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

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

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

application.properties文件配置

//服务名
spring.application.name=eureka-server
//端口号
server.port=1111

eureka.instance.hostname=localhost
//服务注册中心默认将自己作物客户端来尝试注册自己,所以我们要禁止
eureka.client.register-with-eureka=false
//由于注册中心的职责就是维护服务实例,它并不需要去检索服务,所以设置为false
eureka.client.fetch-registry=false
//设置服务注册中心的URL,在服务提供者的项目中这个就是用来指定服务注册中心地址的,在这里由于注册中心不需要向自己注册所以这条配置是无效的,可以去掉
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka

测试

  在完成上面配置后,在浏览器中访问http://loacalhost:1111,就可以看到Eureka信息面板,这里不多赘述。

Eureka之客户端

  客户端主要是处理服务的注册和发现,客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序运行时,Eureka客户端向注册中心注册自身提供的服务并周期性的发送心跳来更新它的服务租约。同时,它也能用服务端查询当前注册的服务信息并把他们缓存到本地并周期性的刷新服务状态。

服务提供者

  服务提供者顾名思义就是对外提供服务的,所以它必须把自己注册到注册中心,以便消费者使用。其实服务提供者也是一个Eureka客户端

  • 依赖配置
<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>Brixton.SR5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
  • application.properties配置
spring.application.name=eureka-provider
server.port=9002
//注册中心的地址
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
  • 主类配置

  注意:这里有两个注解:@EnableEurekaClient(基于spring-cloud-netflix依赖,只能为eureka作用)和@EnableDiscoveryClient(基于spring-cloud-commons依赖,并且在classpath中实现),其实他们两个作用类似,都是用于服务发现的,如果你的classpath中添加了eureka,则它们的作用是一样的。简单来说,如果选用的注册中心是Eureka推荐使用@EnableEurekaClient,如果是其他的注册中心,就用@EnableDiscoveryClient.

@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClientpublic class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
    }
}
  • 测试

@RestController
public class HelloController {
  private final Logger logger = Logger.getLogger(getClass());

  @Autowired
  private DiscoveryClient client;

  @RequestMapping(value = "hello", method = RequestMethod.GET)
  public String index() {
    ServiceInstance instance = client.getLocalServiceInstance();
    logger.info("/hello, host:" + instance.getHost() + ", service_id:" + instance.getServiceId());
    return "Hello World";
  }
}

 同时启动服务注册中心和服务提供者,可以看到服务注册中心的信息面板中有服务提供者的信息。

服务消费者

  服务的消费者,也是一个服务客户端,就是用来调用服务的提供者就行业务处理的,由于这里牵扯到服务消费和负载均衡的内容,所以找个会在Spring cloud之ribbon(一)基本使用和Spring cloud之feign(一)基本使用章节进行具体的具体介绍,这里不再赘述。