Contents
  1. 1. Eureka
    1. 1.1. 1 服务发现的由来
      1. 1.1.1. 1.1 单体架构
      2. 1.1.2. 1.2 SOA架构
      3. 1.1.3. 1.3 微服务时代
    2. 1.2. 2 Eureka简介
      1. 1.2.1. 2.1 服务发现技术选型
    3. 1.3. 3 Eureka入门
      1. 1.3.1. 3.1 单机版
        1. 1.3.1.1. 3.1.1 Eureka Server
          1. 1.3.1.1.1. 3.1.1.1 引入Eureka Server依赖
          2. 1.3.1.1.2. 3.1.1.2 配置启动类
          3. 1.3.1.1.3. 3.1.1.3 配置properties或yml文件
        2. 1.3.1.2. 3.1.2 Eureka Client
          1. 1.3.1.2.1. 3.1.2.1 引入依赖
          2. 1.3.1.2.2. 3.1.2.2 配置启动类
          3. 1.3.1.2.3. 3.1.2.3 配置properties或yml文件
      2. 1.3.2. 3.2 Peer to Peer架构
        1. 1.3.2.1. 3.2.1 主从复制
        2. 1.3.2.2. 3.2.2 对等复制
        3. 1.3.2.3. 3.2.3 代码示例

Eureka

Spring Cloud Eureka是spring cloud 微服务套件中的一部分,她基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能

在了解Eureka之前,我们先来讲下服务发现的由来

1 服务发现的由来

服务发现及注册中心是与软件开发的架构方式演化而来的,大概归纳如下

1.1 单体架构

最早的开发中,大多使用单体架构,服务自成一体。对于依赖的少数外部服务,会采用配置地址的方式进行访问。比如第三方接口,会使用appId和appKey来进行调用。

1.2 SOA架构

随着SOA架构流行,有些公司的内部开始将单体应用拆分成粒度较为粗的服务化架构。

查看: https://blog.csdn.net/chenleixing/article/details/44926955

1.3 微服务时代

在微服务时代,底层运维方式发生了很大的变化,并且随着docker和kubernetes的流行,业务服务不在部署在固定的虚拟机上,其ip地址也不再固定,这个时候前面的解决方式就显得有点儿吃力了。针对这个问题,提出了一些解决方案,例如

1:以nginx为例,在没有引入服务注册中心的时候,那么需要我们手工或是通过脚本的方式,在部署的受去更新nginx的配置文件,然后reload。或是使用ngx_http_dyups_module通过rest api在运行的受进行更新upstream而不需要reload

2: 将服务中心作为一个标配的分布式服务组件,网管等都从服务注册中心获取相关服务的实例信息,实现动态路由。例如consule-template+nginx方案,然后通过consul监听服务实例变化,再更新nginx的配置文件。

从上面的例子我们可以看出,随着服务架构模式以及底层运维方式的变化,服务注册中心逐步再分布式系统架构中占据了一个重要的地位。

2 Eureka简介

Eureka是Netflix公司开源的一款服务发现组件,该组件提供的服务发现可以为负载均衡、failover等提供了支持。Eureka包括Eureka Server和Eureka Client。Eureka Server提供了RESTFul服务,而Eureka Client则是使用Java编写的客户端,用于简化Eureka Server的交互。

2.1 服务发现技术选型

名称 类型 AP或CP 语言 依赖 集成 一致性算法
Zookeeper General CP Java JVM Client Binding Paxos
Doozer General CP Go Client Binding Paxos
Consul General CP GO HTTP/DNS Library Raft
Etcd General Mixed (1) Go Client Binding/HTTP Raft
SmartStack Dedicated AP Ruby haproxy/Zookeeper Sidekick (nerve/synapse)
Eureka Dedicated AP Java JVM Java Client
NSQ (lookupd) Dedicated AP Go Client Binding
Serf Dedicated AP Go Local CLI
Spotify (DNS) Dedicated AP N/A Bind DNS Library
SkyDNS Dedicated Mixed (2) Go HTTP/DNS Library

CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得 。

查看:http://www.ruanyifeng.com/blog/2018/07/cap.html

img

从列表中我们可以看出,有很多服务组件可以选择,针对AP及CP这里我们主要选择Eureka来进行讲解

EurekaServer采用P2P(Peer To Peer)模式https://www.cnblogs.com/linsanshu/p/5546948.html,不保证复制操作一定能成功,因此提供的是一个最终一致性的服务实例视图。Client端再Server端的注册信息有一个带期限的租约,一旦Server端再指定期间没有收到Client端发送的心跳,则Server端会认为Client端注册的服务是不健康的,定时任务会将从注册表中删除。

那么我们为什么要选择Eureka呢?主要有以下几点

  • Eureka是基于AP而不是CP
  • 团队是Java开发,除了问题好排查
  • Eureka能和zuul ribbon整合性好

3 Eureka入门

eureka配置参数

配置参数 默认值 说明
服务注册中心配置 Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
eureka.server.enable-self-preservation false 关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除
服务实例类配置 Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
eureka.instance.prefer-ip-address false 不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址
eureka.instance.ip-address IP地址
eureka.instance.hostname 设置当前实例的主机名称
eureka.instance.appname 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.lease-renewal-interval-in-seconds 30 定义服务续约任务(心跳)的调用间隔,单位:秒
eureka.instance.lease-expiration-duration-in-seconds 90 定义服务失效的时间,单位:秒
eureka.instance.status-page-url-path /info 状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.status-page-url 状态页面的URL,绝对路径
eureka.instance.health-check-url-path /health 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.health-check-url 健康检查页面的URL,绝对路径
服务注册类配置 Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
eureka.client.service-url. 指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。如果服务注册中心加入了安全验证,这里配置的地址格式为:http://:@localhost:8761/eureka 其中 为安全校验的用户名; 为该用户的密码
eureka.client.fetch-registery true 检索服务
eureka.client.registery-fetch-interval-seconds 30 从Eureka服务器端获取注册信息的间隔时间,单位:秒
eureka.client.register-with-eureka true 启动服务注册
eureka.client.eureka-server-connect-timeout-seconds 5 连接 Eureka Server 的超时时间,单位:秒
eureka.client.eureka-server-read-timeout-seconds 8 读取 Eureka Server 信息的超时时间,单位:秒
eureka.client.filter-only-up-instances true 获取实例时是否过滤,只保留UP状态的实例
eureka.client.eureka-connection-idle-timeout-seconds 30 Eureka 服务端连接空闲关闭时间,单位:秒
eureka.client.eureka-server-total-connections 200 从Eureka 客户端到所有Eureka服务端的连接总数
eureka.client.eureka-server-total-connections-per-host 50 从Eureka客户端到每个Eureka服务主机的连接总数

3.1 单机版

3.1.1 Eureka Server

3.1.1.1 引入Eureka Server依赖
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3.1.1.2 配置启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
3.1.1.3 配置properties或yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#服务名称
spring.application.name=smartke-cloud-eureka-server
#服务端口
server.port=8761
#eureka设置
eureka.instance.hostname=localhost
# 表示是否注册自身到eureka服务器
eureka.client.register-with-eureka=false
# 是否从eureka上获取注册信息
eureka.client.fetch-registry=false
#关闭eureka自我保护机制
eureka.server.enable-self-preservation=false
#剔除失效服务间隔
eureka.server.eviction-interval-timer-in-ms=2000
#设置访问eureka的时候需要使用用户名和密码进行登录
#security.basic.enabled=true
#spring.security.user.name=smartke
#spring.security.user.password=smartke
#日志设置
logging.level.root=error
logging.level.org.springframework=info
logging.file=${spring.application.name}.log

3.1.2 Eureka Client

3.1.2.1 引入依赖
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.1.2.2 配置启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
3.1.2.3 配置properties或yml文件
1
2
3
4
5
6
7
8
9
10
#port
server.port=8002
###application name
spring.application.name=smartke-portal
###eureka
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:8761/eureka/
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)默认为30
eureka.instance.lease-renewal-interval-in-seconds=1
#Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己)默认为90
eureka.instance.lease-expiration-duration-in-seconds=2

说明一下,这里需要指定spring.application.name不然会再Eureka Server界面显示为UNKNOW

20190106102425

eureka错误解释

自我保护机制

当我们再本地调式基于eureka的程序时,基本上都会碰到这样一个问题,再服务注册中心的信息面板中出现以下红色的警告信息

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 server的自我保护机制。服务注册到eureka server之后,会维护一个心跳连接,告诉eureka server自己还活着。eureka server再运行期间,会统计心跳失败比例再15分钟之内是否低于85%,如果出现低于的情况(在本地单机调试的时候十分容易满足,生产环境的情况下,通常是由于网络不稳定导致),eureka server 会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息(这就是为什么选择Eureka,因为eureka是AP的)但是,在这段时间之内实例若出现问题,那么客户端很容易拿到实际上已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。

那么出现上面的情况,我们该怎么解决呢?可以在本地配置文件中加入eureka.server.enable-self-preservation=false参数来关闭,以确保注册中心可以将不可用的实例及时剔除。

关闭自我保护之后,启动eureka server会出现以下提示,可以忽略。

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS

3.2 Peer to Peer架构

一般而言,分布式系统的数据在多个副本之间的复制方式,可以分为主从复制和对等复制

3.2.1 主从复制

主从复制就是我们熟知的Master-Slave模式,即有一个主副本,其他副本为从副本。所有对数据的写操作都提交到主副本,最后再由主副本更新到其他从副本。具体更新的方式,还可以细分为同步更新、异步更新、同步及异步混合。

对于主从复制模式来讲,写操作的压力都在主副本上面,她是整个系统的瓶颈,但是从副本可以帮主副本分担读请求。

3.2.2 对等复制

即P2P的模式,副本之间部分主从,任何副本都可以接受写操作,然后每个副本之间相互进行数据更i性能。对于对等复制模式来讲,由于任何副本都可以进行写操作,各个副本之间的数据哦同步及冲突处理是一个比较棘手的问题。

Eureka Server就是采用的Peer to Peer 模式。

Eureka Server从一开始设计就考虑了高可用的问题,在Eureka的服务治理设计中,所有的节点即是服务提供方,又是服务消费方,服务注册中心也不例外。Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步。达到高可用的效果。

3.2.3 代码示例

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.com.bmsmart</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>

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

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>${spring-cloud.version}</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>
  • 创建application-peer1.properties,作为peer1服务中心的配置,并将serviceUrl指向peer2:
1
2
3
4
5
spring.application.name=eureka-server
server.port=8761

eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:8762/eureka/
  • 创建 application-peer2.properties,作为peer2服务中心的配置,并将serviceUrl指向peer1
1
2
3
4
5
6
spring.application.name=eureka-server
server.port=8762

eureka.instance.hostname=peer2

eureka.client.serviceUrl.defaultZone=http://peer1:8761/eureka/
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.com.bmsmart.eurekaserver;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

}
  • 通过spring.profiles.active属性来分别启动peer1和peer2
1
2
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2

此时访问peer1的注册中心http://localhost:8761,我们可以看到`registered-replicas`中有peer2的节点`http://peer2:8762/eureka/` 同样访问peer2 http://lcoalhost:8762也能看到`registered-replicas`中有peer1的节点`http://peer1:8761/eureka/`

1546746477978

20190106114838

eureka-client

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.com.bmsmart</groupId>
<artifactId>eureka-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-client</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>${spring-cloud.version}</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>
</repository>
</repositories>

</project>
  • 配置文件
1
2
3
4
5
6
7
8
9
10
11
spring.application.name=eureka-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://peer1:8761/eureka/,http://peer2:8762/eureka/
logging.file=${spring.application.name}.log
eureka.instance.prefer-ip-address=true
# 健康检查
#eureka.client.healthcheck.enabled=true
# 随机端口配置
#eureka.instance.instance-id=${spring.application.name}:${random.int}
#server.port=0
#server.port=${random.int[10000,19999]}
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.com.bmsmart.eurekaclient;

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

@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
Contents
  1. 1. Eureka
    1. 1.1. 1 服务发现的由来
      1. 1.1.1. 1.1 单体架构
      2. 1.1.2. 1.2 SOA架构
      3. 1.1.3. 1.3 微服务时代
    2. 1.2. 2 Eureka简介
      1. 1.2.1. 2.1 服务发现技术选型
    3. 1.3. 3 Eureka入门
      1. 1.3.1. 3.1 单机版
        1. 1.3.1.1. 3.1.1 Eureka Server
          1. 1.3.1.1.1. 3.1.1.1 引入Eureka Server依赖
          2. 1.3.1.1.2. 3.1.1.2 配置启动类
          3. 1.3.1.1.3. 3.1.1.3 配置properties或yml文件
        2. 1.3.1.2. 3.1.2 Eureka Client
          1. 1.3.1.2.1. 3.1.2.1 引入依赖
          2. 1.3.1.2.2. 3.1.2.2 配置启动类
          3. 1.3.1.2.3. 3.1.2.3 配置properties或yml文件
      2. 1.3.2. 3.2 Peer to Peer架构
        1. 1.3.2.1. 3.2.1 主从复制
        2. 1.3.2.2. 3.2.2 对等复制
        3. 1.3.2.3. 3.2.3 代码示例