目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Eureka

服务发现的由来

单体架构时代
 服务自成一体,对于依赖的少数外部服务采用配置域名的方式访问。
SOA 架构时代
 单体架构拆分为较粗粒度的服务化架构,依赖内部服务较多,内部服务间相互调用较多,以基于 HTTP 形式暴露服务为例,B 服务需要调用 A 服务:

  • B 服务自己维护 A 服务的所有实例 IP,耦合度高。
  • A 服务自己恢复其所有实例的 IP,暴露统一的内网域名供调用者消费,解耦合。

微服务架构时代
 底层运维方式发生了巨大的变化,随着 Docker 的流行,业务服务不再部署在固定的虚拟机上,其 ip 地址也不再固定,此时前面的解决方式就显得捉襟见肘,以 Nginx 为例:

  • 在没有引入注册中心时候,需要手工或者通过脚本的方式,在部署的时候去更新 Nginx 的配置文件,然后 reload,或者是使用 ngx_http_dyups_module 通过 rest API 来在运行时直接更新 upstream 而不需要 reload.
  • 将注册中心作为一个标配的分布式服务组件,网关等中间件服务都可以从注册中心获取相关的实例信息,实现动态路由。比如 consul-template + Nginx 的方法,通过 consul 监听服务实例变化,然后更新 Nginx 的配置文件,通过 reload 实现服务列表动态更新。随着服务架构模式以及运维方式的变化,服务注册中心逐步在分布式系统架构中占据了一个重要的位置。

C、A、P

一致性(Consistency):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)

  • 强一致性(strong consistency)。任何时刻,任何用户都能读取到最近一次成功更新的数据。 ​
  • 单调一致性(monotonic consistency)。任何时刻,任何用户一旦读到某个数据在某次更新后的值,那么就不会再读到比这个值更旧的值。也 就是说,可获取的数据顺序必是单调递增的。
  • 会话一致性(session consistency)。任何用户在某次会话中,一旦读到某个数据在某次更新后的值,那么在本次会话中就不会再读到比这值更旧的值会话一致性是在单调一致性的基础上进一步放松约束,只保证单个用户单个会话内的单调性,在不同用户或同一用户不同会话间则没有保障。
  • 最终一致性(eventual consistency)。用户只能读到某次更新后的值,但系统保证数据将最终达到完全一致的状态,只是所需时间不能保障。
  • 弱一致性(weak consistency)。用户无法在确定时间内读到最新更新的值。

可用性(Availability):在任何时候客户端对集群进行读写操作时候,请求能够正常响应。(服务一直可用,而且是正常响应时间)

分区容错性(Partition tolerance):发生通信故障的时候,整个集群被分割为多个无法相通信的分区时,集群依然可用。(100 个节点,挂了几个,不影响服务,越多机器越好)

CAP 定理

CAP 定理
 三者不可同时获得,最多只能实现上面的两点。
 而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡。

CAP 无法同时满足的原因

CA 满足的情况下,P 不能满足的原因:
 数据同步(C)需要时间,也要正常的时间内响应(A),那么机器数量就要少(机器数量规模较大同步需要耗费大量时间就无法满足 A),所以 P 就不满足。
CP 满足的情况下,A 不能满足的原因:
 数据同步(C)需要时间, 机器数量也多(P),但是同步数据需要时间,所以不能再正常时间内响应,所以 A 就不满足。
AP 满足的情况下,C 不能满足的原因:
 机器数量也多(P),正常的时间内响应(A),那么数据就不能及时同步到其他节点,无法保证每个节点都可以读取到最新的数据,所以 C 不满足。

AP 优于 CP

 对于分布式系统来说,网络条件相对不可控,因此系统必须具备分区容忍性。在此前提下分布式系统的设计需要在 AP 及 CP 之间进行选择。

Zookeeper
 默认"C"P,C 并不是严格的强一致性,
 如果需要强一致性,则需要在读取数据的时候先执行以下 sync 操作,即 leader 节点先同步下数据。在发生极端的网络分区的时候,如果 leader 节点不再 non-quorum 分区,那么对 non-quorum 分区上节点的读写请求将会报错,无法满足 Availability 特性。
 在某个节点失效时,则会进行选举新的 leader,选举需要花费时间,选举期间服务不可用,无法满足 Availability 特性。
 半数以上节点不可用,则无法提供服务,因此无法满足 Availability 特性。

Eureka
 Eureka 是部署在 AWS 背景下设计的,其设计者认为,在云端,特别是在大规模部署的情况下,是吧是不可避免的,可能因为 Eureka 自身部署失败,注册的服务不可用,或者由于网络分区导致服务不可用。
 故此需要 Eureka 在网络分区的时候还能够正常提供服务注册及发现的功能,因此 Eureka 选择满足 Availability 这个特性。
 Peter Kelley 在《Eureka! Why You Shouldn't Use ZooKeeper for Service Discovery》一文中指出,在实际生产实践中,服务注册及发现中保留可用及过期的数据总比服务不可用要好。
 集群的所有节点在同一时刻持有的本地注册表内信息并不是强一致性的,这就需要客户端能够支持负载均衡及失败重试。

Eureka 简介

 Eureka 是 Netflix 公司开源的一款服务发现组件,该组件提供的服务发现功能可以为负载均衡、failover 等提供支持。Eureka 包括 Eureka Server 及 Eureka Client。Server 提供 REST 服务,Client 则使用 Java 编写用于简化与 Server 的交互。注册中心的主要职能:

  • 为内部所有服务提供服务发现功能
  • 结合 ribbon 组件提供各种个性化的负载均衡算法

客户端发现模式
 客户端从一注册中心查询所有可用服务实例的库,并缓存到本地。服务调用时,客户端使用负载均衡算法从多个后端服务实例中选择出一个,然后发出请求。
 Eureka 分为 Eureka Server 和 Eureka client, Eureka Server 是一个服务注册中心,为服务实例注册管理和查询可用实例提供了 REST API,并可以用其定位、负载均衡、故障恢复后端服务的中间层服务。

  • Eureka Client 向服务注册中心注册服务
  • Eureka Clien 会定时拉去注册中心注册表副本;
  • 在服务停止的时候,Eureka Client 向服务注册中心注销服务;
  • 服务注册后,Eureka Client 会定时的发送心跳来刷新服务的最新状态。

Peer to Peer 架构
 一般来说,分布式系统的数据在多个副本节点之间的复制方式可分为:主从复制和对等复制。

  • 主从复制:写操作交 Master 处理,最后由 Master 同步到其他 Slave,具体复制可细分为:同步更新、异步更新、同步及异步混合更新。
     缺点:写操作的压力都会作用在 Master 节点,从而形成系统的瓶颈。
  • 对等复制:集群中所有节点均可接收读写请求,不存在写操作压力瓶颈。
     缺点:peer 节点之间数据复制的冲突的问题。
     Eureka 采用如下两个方式来解决 lastDirtyTimestamp 标识、heartbeat。

SELF PRESERVATION 设计
 Eureka Client 端与 Server 端之间有个租约,Client 要定时发送心跳为维持这个租约,表示自己还存活。Eureka 通过当前注册的实例数,去计算每分钟应该从应用实例接收到的心跳数,如果最近一分钟接收到的续约次数小于等于指定的阈值,则关闭租约失效剔除功能,禁止定时任务剔除失效的实例,从而防止因网络抖动造成的误判,从而保护注册中心的注册信息可以。

EurekaServer 搭建

新建 Maven 项目使用 Spring Initializr 添加依赖 Spring Cloud Discovery => Eureka Server

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

启动类中添加注解 @EnableEurekaServer

 1import org.springframework.boot.SpringApplication;
 2import org.springframework.boot.autoconfigure.SpringBootApplication;
 3import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
 4
 5@SpringBootApplication
 6@EnableEurekaServer
 7public class EurekaServerApplication {
 8    public static void main(String[] args) {
 9        SpringApplication.run(EurekaServerApplication.class, args);
10    }
11}

添加配置文件 application.yaml

 1server:
 2  port: 8761
 3
 4eureka:
 5  instance:
 6    hostname: localhost
 7  client:
 8    #声明自己是个服务端:本身就是高可用集群,找不到同类会报错。
 9    registerWithEureka: false
10    fetchRegistry: false
11    serviceUrl: #注册中心地址,Eureka Client需要指定此处设置的地址
12      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动服务并访问 http://localhost:8761/ Eureka 服务管控台
Eureka 管控台

Eureka Client 之 Provider

新建 Maven 项目使用 Spring Initializr 添加依赖 Spring Cloud Discovery => Eureka Discovery

 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 4    <modelVersion>4.0.0</modelVersion>
 5
 6    <groupId>net.xdclass</groupId>
 7    <artifactId>eureka_server</artifactId>
 8    <version>0.0.1-SNAPSHOT</version>
 9    <packaging>jar</packaging>
10
11    <name>eureka_server</name>
12    <description>Demo project for Spring Boot</description>
13
14    <parent>
15        <groupId>org.springframework.boot</groupId>
16        <artifactId>spring-boot-starter-parent</artifactId>
17        <version>2.0.3.RELEASE</version>
18        <relativePath/> <!-- lookup parent from repository -->
19    </parent>
20
21    <properties>
22        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24        <java.version>1.8</java.version>
25        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
26    </properties>
27
28    <dependencies>
29        <dependency>
30            <groupId>org.springframework.cloud</groupId>
31            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
32        </dependency>
33
34        <dependency>
35            <groupId>org.springframework.boot</groupId>
36            <artifactId>spring-boot-starter-test</artifactId>
37            <scope>test</scope>
38        </dependency>
39    </dependencies>
40
41    <dependencyManagement>
42        <dependencies>
43            <dependency>
44                <groupId>org.springframework.cloud</groupId>
45                <artifactId>spring-cloud-dependencies</artifactId>
46                <version>${spring-cloud.version}</version>
47                <type>pom</type>
48                <scope>import</scope>
49            </dependency>
50        </dependencies>
51    </dependencyManagement>
52
53    <build>
54        <plugins>
55            <plugin>
56                <groupId>org.springframework.boot</groupId>
57                <artifactId>spring-boot-maven-plugin</artifactId>
58            </plugin>
59        </plugins>
60    </build>
61</project>

在启动类添加注解(非必须,最好加,有利于阅读,不加也行,SpringCloud 会帮忙自动配置)

1@EnableDiscoveryClient
23@EnableEurekaClient

添加配置 application.yaml

 1server: #Provider服务监听的端口
 2  port: 8771
 3
 4eureka: #指定注册中心地址
 5  client:
 6    serviceUrl:
 7      defaultZone: http://localhost:8761/eureka/
 8  instance:
 9    prefer-ip-address: true #调试Eureka管控台实例的显示格式
10    instance-id: ${spring.cloud.client.ipAddress}:${server.port}:${spring.application.name}
11
12spring: #服务的名称
13  application:
14      name: product-service
15  cloud:
16    client:
17      ipAddress: 192.168.31.230

查看 Eureka 管控台 http://localhost:8761/ 发现注册注册成功!
Provider 注册成功

Eureka Client 之 Consumer

请自行参考服务消费者 ribbon、feign。

服务发现背后的 DiscoveryClient

服务注册抽象 (不同服务的注册)

  • 提供了 ServiceRegistry 抽象
    客户端发现抽象 (服务的发现,抽象了 Eureka、Zookeeper)
  • 提供了 DiscoveryClient 抽象 @EnableDiscoveryClient
  • 提供了 LoadBalancerClient 抽象 ( 用于客户端的请求做负载,请求时动态拼装 IP 地址)

自动向 Eureka 服务注册(以 Eureka 为例)
ServiceRegistry

  • EurekaServiceRegistry (ServiceRegistry 抽象的实现:服务注册)
  • EurekaRegistration (注册的信息)

作者:Soulboy