0%

SpringCloud(四)Eureka服务注册与发现

Eureka基本架构

SpringCloud 封装了Netflix 公司开发的Eureka模块来实现服务注册和发现。

Server&Client

Eureka 采用 C-S的设计架构(客户端就好比要入驻楼层的企业,服务端就好比楼层物业中心), Eureka Server作为服务注册功能的服务器,它是服务注册中心

Eureka Server 提供服务注册,服务各个节点(启用 Eureka的应用)启动后,会自动在EurekaServer中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到

注册和发现

系统中的其他微服务(如上一章 SpringCloud(三)Rest微服务案例中的 provider),使用Eureka 的客户端连接到Eureka Server 并维持心跳连接,这样系统的维护人员就可以通过Eureka Server监控系统中各个微服务是否正常运行, SpringCLoud的一些其他模块(比如Zuul)就可以通过Eureka Server发现系统中的其他微服务,并执行相关的逻辑。


Eureka实战

延续上一章SpringCloud(三)Rest微服务案例的项目,创建一个子模块 clouddemo-eureka(端口7001)来作为 Eureka Server ,并将 clouddemo-provider注册上去。

clouddemo-eureka

  • pom,xml,引入 Spring Cloudeureka-server组件
1
2
3
4
5
6
7
8
9
10
11
12
<parent>
<groupId>top,zhenganwen</groupId>
<artifactId>clouddemo</artifactId>
<version>1,0-SNAPSHOT</version>
</parent>

<dependencies>
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
  • application,yml,添加整合 Eureka Server的配置信息
1
2
3
4
5
6
7
8
9
10
11
server:
port: 7001

eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka,instance,hostname}:${server,port}/eureka/

eureka,instance,hostname,此服务端部署在哪个主机上(主机名)

eureka,client

register-with-eureka,不向注册中心注册当前服务(因为当前服务就是注册中心)

fetch-registry,是否检索抓取服务(注册中心的职责是维护注册上来的服务实例)

defaultZone,服务注册和发现要通过该地址

访问 localhost:7001,出现如下界面则注册中心部署成功:

provider

  • pom,xml,添加 eureka客户端依赖和自动配置依赖:
1
2
3
4
5
6
7
8
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

如果 pom文件报错 Failed to read artifact descriptor for xxxjar,多半是依赖包/文件夹没有完整下载成功,此时你需要到 ctrl alt shift s中的 Libraries中查看划红线的依赖包的路径,删除该路径对应的文件夹,然后在 pom文件中重新添加依赖。

这类问题多半是添加依赖时,坐标不对;或者下载依赖包过程中某些操作导致下载异常终止;又或者是你的IDEA设置了 enable autoimport,当你还没写完坐标时,autoimport引入一个 unkown的依赖(因此最好在其他编辑器写好依赖后再复制到IDEA中)。

无论是哪种情况,你都应该找到异常依赖包的位置将其删除,并重启IDEA,重新复制依赖到 pom,xml

  • 添加 @EnableEurekaClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top,zhenganwen,clouddemoprovider;

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

@SpringBootApplication
@EnableEurekaClient
public class ClouddemoProviderApplication {

public static void main(String[] args) {
SpringApplication,run(ClouddemoProviderApplication,classargs);
}
}

这也是 Spring Boot的风格特点之一,在使用非 Spring家族的技术如 EurekaZuul时,需要添加支持/整合注解如 @EnableEureka@EnableZuul@EnableXxx的注解以将对应的组件/技术整合到Spring中。

  • 启动 clouddemo-provider,测试 localhost:7001,发现服务已部署上去:

actuator与注册微服务信息完善

现在 provider服务虽然已经注册上去了,但是有些注册信息需要完善(简洁化、规范化),如:

  • 上图 Status中的实例ID( localhost:clouddemo-dept:8001)中的 localhost无实际意义,应省略
  • 鼠标悬浮localhost:clouddemo-dept:8001时显示的超链接 localhost:8001/info中的 localhost应替换成具体IP,方便定位服务所在主机
  • 访问 localhost:8001/info404,这是因为没有集成 actuator

自定义实例ID和显示实例所在主机IP

这两个属性都跟实例有关,因此在 application,ymlprovider工程)中的 eureka,instance下设置:

1
2
3
4
5
6
7
eureka:
instance:
instance-id: clouddemo-dept-8001
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka/

重启后效果如下:

自定义应用信息info

需求:通过 /infoSpringBoot1,x)或 /actuator/infoSpringBoot2,x)暴露服务对应的模块和服务的 maven坐标,使得同事能够据此调用我们的服务。效果实例:

  1. application,yml中配置要暴露的应用信息:
1
2
3
4
5
info:
app,name: clouddemo-dept-provider
company,name: www,zhenganwen,top
build,artifactId: @project,artifactId@
build,version: @project,version@
  1. 我们需要在 clouddemo-providerclouddemo(父工程)添加一个构建工具:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<build>
<finalName>clouddemo</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>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>

此构建工具的作用是在构建项目的 src/main/resources下的文件时,将文件中的以 @开头和结尾的字符串替换成 pom,xml中的对应标签的属性值。例如我们在对 clouddemo-provider执行 mvn package命令时,其中 application,yml中的 @project,artifactId@就会被替换成当前项目 pom,xml中的 <project,artifactId>标签值 clouddemo-provider

  1. 最后需要添加 actuator依赖:
1
2
3
4
5
<dependency>
<groupId>org,springframework,boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
<scope>runtime</scope>
</dependency>

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

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

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

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


服务发现

现在 clouddemo-provider工程已经注册到 eureka上了,如何在 clouddemo-cosumer工程中发现该服务呢?

  1. 要想获取 eureka上的服务信息,当然还是要通过 eureka客户端连接服务端,因此同样需要引入 eureka依赖:
1
2
3
4
5
6
7
8
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
  1. 接着我们需要配置 Eureka Server的地址:
1
2
3
4
eureka:
client:
serviceUrl:
defaultZone: http://localhost:7001/eureka/
  1. 添加 @EnableDiscoveryClient注解或 @EnableEurekaClient注解(后者包含了前者),接着我们就可以注入 DiscoveryClient
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
package top,zhenganwen,clouddemoconsumer,controller;

import org,springframework,beans,factory,annotation,Autowired;
import org,springframework,cloud,client,ServiceInstance;
import org,springframework,cloud,client,discovery,DiscoveryClient;
import org,springframework,web,bind,annotation,RequestMapping;
import org,springframework,web,bind,annotation,RestController;

import java,util,HashMap;
import java,util,List;
import java,util,Map;

@RestController
@RequestMapping("discovery")
public class DiscoveryController {

@Autowired
private DiscoveryClient discoveryClient;

@RequestMapping("dept")
public Map discovery() {
Map resultMap = new HashMap();
//获取所有微服务名(spring,application,name)
resultMap,put("services", discoveryClient,getServices());
//根据serviceId(spring,application,name大写)获取服务实例集合
List<ServiceInstance> serviceInstances = discoveryClient,getInstances("CLOUDDEMO-DEPT");
resultMap,put("CLOUDDEMO-DEPT", serviceInstances);
return resultMap;
}
}

注意 DiscoveryClient导入的是 Spring提供的,SpringCloudNetflixDiscoveryClient进行了再封装。

  1. 访问 localhost/discovery/dept
1
2
3
4
5
6
7
8
9
10
11
{ 
services: [
"clouddemo-dept"
],
CLOUDDEMO-DEPT: [
{
host: "192,168,25,1",
port: 8001,
uri: "http://192,168,25,1:8001",
metadata: { },
,,,

Eureka集群

现在我们将服务的注册与发现交给了 eureka,如果 eureka出了问题,那么将会导致所有注册在 eureka上的服务不能被发现并调用,因此我们还需要搭建 eureka集群来保证其高可用

添加域名映射模拟分布式环境

  1. hosts文件中添加如下内容:
1
2
3
127001 eureka1,com
127001 eureka2,com
127001 eureka3,com
  1. 创建子模块 clouddemo-eureka2clouddemo-eureka3
  2. 分别添加依赖:
1
2
3
4
<dependency>
<groupId>org,springframework,cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
  1. 分别修改配置文件(端口、主机名、defaultZone):

    clouddemo-eureka

1
2
3
4
5
6
7
8
9
10
11
server:
port: 7001

eureka:
instance:
hostname: eureka1,com
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://eureka2,com:7002/eureka/,http://eureka3,com:7003/eureka/

clouddemo-eureka2

1
2
3
4
5
6
7
8
9
10
11
server:
port: 7002

eureka:
instance:
hostname: eureka2.com
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka3.com:7003/eureka/

clouddemo-eureka3

1
2
3
4
5
6
7
8
9
10
11
server:
port: 7003

eureka:
instance:
hostname: eureka3.com
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://eureka1.com:7001/eureka/,http://eureka2.com:7002/eureka/
  1. 分别添加 @EnableEurekaServer

  2. 分别启动三个 eureka

    访问 eureka1.com:7001,出现如下图所示集群其他节点信息(访问 eureka2.com:7002eureka3.com:7003也是如此):

  3. 修改 clouddemo-provider配置

1
defaultZone: http://eureka1,com:7001/eureka/,http://eureka1,com:7002/eureka/,http://eureka1,com:7003/eureka/
  1. 启动 clouddemo-provider
  2. 访问 eureka1,com:7001eureka2,com:7002eureka3,com:7003均可发现注册的服务 CLOUDDEMO-DEPT

CAP

CAP指的是:Consistency(强一致性)、Availability(可用性)、Partition tolerance(分区容错性)

ACID指的是:A(Atomicity)原子性、C(Consistency)一致性、I(Isolation)独立性、D(Durability)持久性

关系型数据库(如Mysql、Oracle、SqlServer)遵循CA;redis、mongdb则遵循CP。

CAP理论的核心是:分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此根据CAP原理将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则的三大类:

  • CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大
  • CP-满足一致性,分区容忍性的系统,通常不是特别高可用
  • AP-满足可用性,分区容忍性的系统,通常可能对一致性要求低一些

CAP的三进二

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须要实现的,所以我们只能在一致性和可用性之间进行权衡,没有nosql系统能同时保证这三点。

  • CA 传统Oracle数据库
  • AP 大多数网站架构的选择
  • CP Redis,Mongod

案例

淘宝双十一应遵循AP还是CP?

Consistency如保证PV(商品浏览量)、保证商品点赞数等,Availability如承受数亿客户访问服务器不能瘫痪。此时两害相权取其轻,应选AP。等过了双十一流量高峰后则应选CP。

作为服务中心,Eureka比ookeeper好在哪儿?

Zookeeper保证的是CP,Eureka则是AP

Zookeeper保证的是CP

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

Eureka保证AP

Eureka看明白了这一点,因此在设计时就优先保证可用性,Eureka各个节点都是平等的几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端在向某个Eureka注册时如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)

除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳(注册的服务没有被访问),那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  • Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务(好死不如赖活着,高可用)
  • Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其节点上(即先保证当前节点依然可用,高可用)
  • 当网络稳定时,当前实例新的注册信息会被同步到其他节点中(弱一致性)

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

鼓励一下~