Eureka服务注册中心
关于服务注册中心
服务注册中心本质上是为了解耦服务提供者和服务消费者。
对于任何⼀个微服务,原则上都应存在或者⽀持多个提供者,这是由微服务的分布式属性决定的。
更进⼀步,为了⽀持弹性扩缩容特性,⼀个微服务的提供者的数量和分布往往是动 态变化的,也是⽆法预先确定的。因此,原本在单体应⽤阶段常⽤的静态LB机制就 不再适⽤了,需要引⼊额外的组件来管理微服务提供者的注册与发现,⽽这个组件 就是服务注册中⼼。
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供 者的地址信息。消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置, 这就是透明化路由。
1)服务提供者启动
2)服务提供者将相关服务信息主动注册到注册中心
3)服务消费者获取服务注册信息: pull模式:服务消费者可以主动拉取可用的服务提供者清单 push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的 服务清单给消费者
4)服务消费者直接调用服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除
主流服务中⼼对⽐
-
Zookeeper
Dubbo + Zookeeper
Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决 分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布 式应用配置项的管理等。
简单来说zookeeper本质 = 存储 + 监听通知。
Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相 关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用 Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper 可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,最少节点数为3。
-
Eureka
由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务 注册与发现组件。
-
Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服 务软件, 采用Raft算法保证服务的一致性,且支持健康检查。
-
Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说 Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册 与发 现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册 与发现,还有配置。
组件名 | 语言 | CAP | 对外暴露接口 |
---|---|---|---|
Eureka | Java | AP(自我保护机制,保证可用) | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | Java | CP | 客户端 |
Nacos | Java | 支持AP/CP切换 | HTTP |
Eureka 基础架构
Eureka 交互流程及原理
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是一个Java客户端,用于简化 与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server 进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
1)图中us-east-1c、us-east-1d,us-east-1e代表不同的区也就是不同的机房
2)图中每一个Eureka Server都是一个集群。
3)图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注 册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用。
4)微服务启动后,会周期性地向Eureka Server发送心跳(默认周期为30秒,默认Eureka Server 90S会将还没有续约的给剔除)以续约自己的信息
5)Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微 服务节点(默认90秒)
6)每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务 注册列表的同步
7)Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消 费者依然可以使用缓存中的信息找到服务提供者
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性。
搭建单例Eureka Server服务注册中心
父工程
<!--⽗⼯程打包⽅式为pom-->
<packaging>pom</packaging>
<!--spring boot ⽗启动器依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--引入Jaxb,开始
注意:在⽗⼯程的pom⽂件中⼿动引⼊jaxb的jar,因为Jdk9之后默认没有加载该模
块,EurekaServer使⽤到,所以需要⼿动导⼊,否则EurekaServer服务⽆法启动-->
<dependencies>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.11</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.2.10-b140310.1920</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!--引入Jaxb,结束-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
</dependencies>
eureka-server工程pom.xml中引入依赖
<dependencies>
<!--Eureka server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
在yml文件中配置Eureka server服务端口,服务名等信息
#Eureka server服务端口
server:
port: 9200
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
instance:
hostname: localhost
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
register-with-eureka: false # 自己就是服务不需要注册自己
fetch-registry: false #自己就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
启动类
@SpringBootApplication
//// 声明本项目是一个Eureka服务
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
启动服务 访问http://127.0.0.1:9200
客户端注册eureka
pom
<!--Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置Eureka服务端信息
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone: http://localhost:9200/eureka/
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
启动类
@SpringBootApplication
@EnableDiscoveryClient //@EnableEurekaClient
public class PageApplication {
public static void main(String[] args) {
SpringApplication.run(PageApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
注意:
1)从Spring Cloud Edgware版本开始,@EnableDiscoveryClient 或 @EnableEurekaClient 可省略。只需加 上相关依赖,并进⾏相应配置,即可将 微服务注册到服务发现组件上。
2)@EnableDiscoveryClient和@EnableEurekaClient⼆者的功能是⼀样的。但 是如果选⽤的是eureka服务器,那么就推荐@EnableEurekaClient,如果是其 他的注册中⼼,那么推荐使⽤@EnableDiscoveryClient,考虑到通⽤性,后期 我们可以使⽤@EnableDiscoveryClient
启动类执⾏,在Eureka Server后台界⾯可以看到注册的服务实例
搭建Eureka Server 高可用集群
在互联⽹应⽤中,服务实例很少有单个的。
即使微服务消费者会缓存服务列表,但是如果EurekaServer只有⼀个实例,该实例 挂掉,正好微服务消费者本地缓存列表中的服务实例也不可⽤,那么这个时候整个 系统都受影响。
在生产环境中,我们会配置Eureka Server集群实现高可用。Eureka Server集群之中的节点通过点 对点(P2P)通信的方式共享服务注册表。我们开启两台 Eureka Server 以搭建集群。
win10操作系统下:C:\Windows\System32\drivers\etc\host
127.0.0.1 EurekaServerA
127.0.0.1 EurekaServerB
eureka-server ⼯程中的yml配置⽂件
spring:
application:
name: eureka-server
---
spring:
profiles: EurekaServerA
server:
port: 9200
eureka:
instance:
hostname: eurekaA # 应⽤名称,会在Eureka中作为服务的id标识(serviceId)
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
defaultZone: http://eurekaB:9201/eureka/
register-with-eureka: true # ⾃⼰就是服务不需要注册⾃⼰ 集群需要注册自己
fetch-registry: true #⾃⼰就是服务不需要从Eureka Server获取服务信息,默认为true,置为false 集群需要获取服务信息
---
spring:
profiles: EurekaServerB
server:
port: 9201
eureka:
instance:
hostname: eurekaB # 应⽤名称,会在Eureka中作为服务的id标识(serviceId)
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其它Server的地址
defaultZone: http://eurekaA:9200/eureka/
register-with-eureka: true # ⾃⼰就是服务不需要注册⾃⼰ 集群需要注册自己
fetch-registry: true #⾃⼰就是服务不需要从Eureka Server获取服务信息,默认为true,置为false 集群需要获取服务信息
说明
在⼀个实例中,把另外的实例作为了集群中的镜像节点,那么这个http://eurekaB:9201/eureka URL 中的 eurekaB 就要和其它个profile 中的 eureka.instance.hostname 保持⼀致。
register-with-eureka 和 fetch-registry 在单节点时设置为了 false, 因为 只有⼀台 Eureka Server,并不需要⾃⼰注册⾃⼰,⽽现在有了集群,可以在集 群的其他节点中注册本服务
启动两次该SpringBoot项⽬,分别使⽤两个不同的profiles
访问两个EurekaServer的管理台⻚⾯会发现注册中⼼ eureka-server 已经有两个节点,并且 registered-replicas (相邻集群复制节点)中 已经包含对⽅
客户端
server:
port: 9000 # 后期该微服务多实例,9000(10个以内)
spring:
application:
name: service-product
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/product?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
eureka:
client:
serviceUrl: # eureka server的路径
defaultZone:http://eurekaA:9200/eureka/,http://eurekaB:9201/eureka/ #把 eureka 集群中的所有 url 都填写了进来,也可以只写一台,因为各个 eureka server可以同步注册表
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
服务消费者调用服务提供者
@RestController
@RequestMapping("/page")
public class PageController {
@Autowired
private RestTemplate restTemplate;
//注入服务发现客户端
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/getData/{id}")
public Products findDataById(@PathVariable Integer id){
//1.获得Eureka中注册的service-product实例集合
List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
//2.获得实例集合中的第一个
ServiceInstance instance = instances.get(0);
//3.根据实例信息拼接IP地址
String host = instance.getHost();
int port = instance.getPort();
String url = "http://"+host+":"+port+"/product/query/"+id;
//4.调用
Products products = restTemplate.getForObject(url, Products.class);
System.out.println("service-product获得product对象:"+products);
return products;
}
}
Eureka细节详解
Eureka元数据详解
标准元数据:主机名、IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之 间的调用。
自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这 些元数据可以在远程客户端中访问。
instance:
#使用ip注册,否则会使用主机名注册了(此处考虑到对老版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#自定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@
metadata-map:
ip: 192.168.200.128
port: 10000
user: haha
pwd: 123456
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("show")
public String showMetadata(){
String result = "";
List<ServiceInstance> instances = discoveryClient.getInstances("service-page");
for (ServiceInstance instance:instances) {
//获取服务元数据
Map<String, String> metadata = instance.getMetadata();
Set<Map.Entry<String, String>> entries = metadata.entrySet();
for (Map.Entry<String,String> entry : entries){
String key = entry.getKey();
String value = entry.getValue();
result+="key:"+key+",value:"+value;
}
}
return result;
}
Eureka客户端详解
服务注册详解(服务提供者)
1)当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
2)服务在启动时会向注册中心发起注册请求,携带服务元数据信息
3)Eureka注册中心会把服务的信息保存在Map中。
服务续约详解(服务提供者)
服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期, 然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测
Eureka Client :30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
Eureka Server:90S还没有进行续约,将该微服务实例从服务注册表(Map)剔除 (Client端进行 配置)
Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置)
往往不需要我们调整这两个配置
#向Eureka服务中心集群注册服务
eureka:
instance:
# 租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生心跳,EurekaServer会将服务从列表移除
lease-expiration-duration-in-seconds: 90
获取服务列表(服务注册表)详解(服务消费者)
每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改。往往不需要我们调 整
#向Eureka服务中心集群注册服务
eureka:
client:
# 每隔多久拉取一次服务列表
registry-fetch-interval-seconds: 30
1)服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
2)每隔30秒,会重新获取并更新数据
3)每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
Eureka服务端详解
服务下线:
1)当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
2)服务中心接受到请求后,将该服务置为下线状态
失效剔除:
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检 查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例
自我保护机制:
定期的续约(服务提供者和注册中⼼通信),假如服务提供者和注册中⼼之间的⽹ 络有点问题,不代表服务提供者不可⽤,不代表服务消费者⽆法访问服务提供者
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的 健壮、稳定的运行。
自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么 Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现 以下几种情况:
1.Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
2.Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前 节点依然可用
3.当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半 不可用的情况会导致整个集群不可用而变成瘫痪。
为什么会有自我保护机制?
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳, Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通 信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制,一旦开启了保护机制(默认开启),则服务注册中心维护的服务实例就不是那么准确了, 此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被 及时的剔除(不推荐)。
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
经验:建议生产环境打开自我保护机制