服务注册中⼼本质上是为了解耦服务提供者和服务消费者。
对于任何⼀个微服务,原则上都应存在或者⽀持多个提供者(一个服务多实例)
服务注册中⼼管就是用来理微服务提供者的注册与发现。
分布式微服务架构中,服务注册中⼼⽤于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的⽅式获取服务提供者的地址信息,⽽不再需要通过硬编码⽅式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,⽽不需要知道服务具体存在于什么位置,这就是透明化路由。
另外,注册中⼼也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;
Zookeeper
Zookeeper它是⼀个分布式服务框架,是Apache Hadoop 的⼀个⼦项⽬,它主要是⽤来解决分布式应 ⽤中经常遇到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应⽤配置项的管理等。
简单来说zookeeper本质=存储+监听通知。
znode
Zookeeper ⽤来做服务注册中⼼,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调⽤⽅只要使⽤ Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,⾮常⽅便。
另外, Zookeeper 可⽤性也Ok,因为只要半数以上的选举节点存活,整个集群就是可⽤的。集群最小节点数3个,半数以上3个只能挂一个,4个也只能挂一个服务。
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 |
P:分区容错性(一定要满足的)
C:数据一致性
A:高可用
CAP不可能同时满⾜三个,要么是AP,要么是CP
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接受到注册事件会在集群和分区中进⾏数据同步, ApplicationClient作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进⾏服务调⽤。
4)微服务启动后,会周期性地向Eureka Server发送⼼跳(默认周期为30秒)以续约⾃⼰的信息
5) Eureka Server在⼀定时间内没有接收到某个微服务节点的⼼跳, EurekaServer将会注销该微服务节点(默认90秒)
6)每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的⽅式完成服务注册列表的同步
7) Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使⽤缓存中的信息找到服务提供者
Eureka通过⼼跳检测、健康检查和客户端缓存等机制,提⾼系统的灵活性、可
伸缩性和可⽤性。
1)单实例Eureka Server—>访问管理界⾯—>Eureka Server集群
2)服务提供者(简历微服务注册到集群)
3)服务消费者(⾃动投递微服务注册到集群/从Eureka Server集群获取服务信息)
4)完成调⽤
基于Maven构建SpringBoot工程,在SpringBoot工程之上搭建EurekaServer服务
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
引入eureka-server
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
dependencies>
需要⼿动引⼊jaxb的jar,因为Jdk9之后默认没有加载该模块, EurekaServer使⽤到,所以需要⼿动导⼊,否则EurekaServer服务⽆法启动
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-coreartifactId>
<version>2.2.11version>
dependency>
<dependency>
<groupId>javax.xml.bindgroupId>
<artifactId>jaxb-apiartifactId>
dependency>
<dependency>
<groupId>com.sun.xml.bindgroupId>
<artifactId>jaxb-implartifactId>
<version>2.2.11version>
dependency>
<dependency>
<groupId>org.glassfish.jaxbgroupId>
<artifactId>jaxb-runtimeartifactId>
<version>2.2.10-b140310.1920version>
dependency>
<dependency>
<groupId>javax.activationgroupId>
<artifactId>activationartifactId>
<version>1.1.1version>
dependency>
配置application.xml
#Eureka server服务端⼝
server:
port: 8761
spring:
application:
name: spring-cloud-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
在SpringBoot启动类使⽤@EnableEurekaServer声明当前项⽬为EurekaServer服务
@SpringBootApplication
// 声明当前项目为Eureka项目
@EnableEurekaServer
public class EurekaServerApp8761 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApp8761.class,args);
}
}
启动项目,访问http://127.0.0.1:8761,看到下面界面(Eureka注册中心后台),表明布置成功。
接下来我们开启两台EurekaServer 以搭建集群
1. 修改本机host属性
由于是在个⼈计算机中进⾏测试很难模拟多主机的情况, Eureka配置server集群时需要执⾏host地址。 所以需要修改个⼈电脑中host地址
127.0.0.1 LagouCloudEurekaServerA
127.0.0.1 LagouCloudEurekaServerB
2. 创建一个新的springBoot项目,复制之前的代码
3. 修改两个工程的yml文件
A
#Eureka server服务端⼝
server:
port: 8761
spring:
application:
name: spring-cloud-eureka-server # 应⽤名称,会在Eureka中作为服务的
#id标识(serviceId)
eureka:
instance:
hostname: MyfCloudEurekaServerA
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其
# 它Server的地址
defaultZone: http://MyfCloudEurekaServerB:8762/eureka/
register-with-eureka: true # ⾃⼰就是服务不需要注册⾃⼰
fetch-registry: true #⾃⼰就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
B
#Eureka server服务端⼝
server:
port: 8762
spring:
application:
name: spring-cloud-eureka-server # 应⽤名称,会在Eureka中作为服务的
#id标识(serviceId)
eureka:
instance:
hostname: MyfCloudEurekaServerB
client:
service-url: # 客户端与EurekaServer交互的地址,如果是集群,也需要写其
# 它Server的地址
defaultZone: http://MyfCloudEurekaServerA:8761/eureka/
register-with-eureka: true # ⾃⼰就是服务不需要注册⾃⼰
fetch-registry: true #⾃⼰就是服务不需要从Eureka Server获取服务信息,默认为true,置为false
4. 启动项目就可以看到后台管理界面了
http://myfcloudeurekaservera:8761/ http://myfcloudeurekaserverb:8762/
首先你应该有一个能够对外提供http服务的普通SpringBoot项目。
引入spring-cloud-commons依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-commonsartifactId>
dependency>
引入eureka client的相关坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
配置application.yml⽂件
在application.yml 中添加Eureka Server⾼可⽤集群的地址及相关配置
eureka:
client:
service-url:
defaultZone: http://MyfCloudEurekaServerA:8761/eureka/,http://MyfCloudEurekaServerB:8762/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@
经验:⾃定义实例显示格式,加上版本号,便于多版本管理
启动类添加注解
@EnableEurekaClient
从Spring Cloud Edgware版本开始, @EnableDiscoveryClient 或@EnableEurekaClient 可省略。只需加 上相关依赖,并进⾏相应配置,即可将微服务注册到服务发现组件上。
@EnableDiscoveryClient和@EnableEurekaClient⼆者的功能是⼀样的。但是如果选⽤的是eureka服务器,那么就推荐@EnableEurekaClient,如果是其他的注册中⼼,那么推荐使⽤@EnableDiscoveryClient,考虑到通⽤性,后期我们可以使⽤@EnableDiscoveryClient
启动成功既可以在注册中心找到注册的服务实例,其他服务参照即可。
添加eureka client的相关坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-commonsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eurekaclientartifactId>
dependency>
配置application.yml文件
server:
port: 8090 # 后期该微服务多实例,端⼝从8090递增(10个以内)
eureka:
client:
service-url:
defaultZone: http://MyfCloudEurekaServerA:8761/eureka/,http://MyfCloudEurekaServerB:8762/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@
spring:
application:
name: myf-service-autodeliver
在启动类添加注解@EnableDiscoveryClient,开启服务发现
@SpringBootApplication
@EnableDiscoveryClient
public class AutodeliverApplication {
public static void main(String[] args) {
SpringApplication.run(AutodeliverApplication.class,args);
}
/**
* 注⼊RestTemplate
* @return
*/
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
服务消费者调⽤服务提供者(通过Eureka)
@RestController
@RequestMapping("/autodeliver")
public class AutodeliverController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/checkState/{userId}")
public Integer findResumeOpenState(@PathVariable Long userId){
// 1. 获取Eureka中注册的user-service实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("MYF-SERVICE-RESUME");
// 2. 获取实例(此处我们不考虑负载均衡,就拿第一个)
ServiceInstance serviceInstance = instances.get(0);
// 3. 根据实例的信息拼接请求地址
int port = serviceInstance.getPort();
String host = serviceInstance.getHost();
String url = "http://"+host+":"+port+"/resume/openstate/";
System.out.println(url);
Integer forObject = restTemplate.getForObject(url + userId, Integer.class);
System.out.println("======>>>调⽤简历微服务,获取到⽤户" +
userId + "的默认简历当前状态为: " + forObject);
return forObject;
}
}
Eureka的元数据有两种:标准元数据和⾃定义元数据
标准元数据: 主机名、 IP地址、端⼝号等信息,这些信息都会被发布在服务注册表中,⽤于服务之间的调⽤。
⾃定义元数据: 可以使⽤eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这 些元数据可以在远程客户端中访问。
示例:
instance:
prefer-ip-address: true
metadata-map:
# ⾃定义元数据(kv⾃定义)
cluster: cl1
region: rn1
元数据会存放在ServiceInstance中metadata这个map成员变量中
服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等⼯作
服务注册详解
服务续约详解
服务每隔30秒会向注册中心续约(心跳)一次,如果没有续约,租期在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为心跳检测。
配置,往往不需要修改
#向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
服务下线
失效剔除
EurekaServer会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进⾏检查,如果发现实例在在⼀定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到⼼跳,则会注销此实例。
自我保护
If any time, the renewals falls below the percent configured for that value (below 85% within 15 mins), the server stops expiring instances to protect the current instance registry information.
定期的续约(服务提供者和注册中心通信),假如服务提供者和注册中心之间的网络有点问题,不代表服务提供者不可用,不代表消费者无法访问服务提供者,如果15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka久认为客户端与注册中心出现了网络故障,EurekaServer自动进入自我保护机制。
为什么会有自我保护机制?
默认情况下,如果EurekaServer在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,EurekaServer将会移除该实例。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
当处于⾃我保护模式时
eureka:
server:
enable-self-preservation: false # 关闭⾃我保护模式(缺省为打开)
建议⽣产环境打开⾃我保护机制
注:大佬建议看官网,立个flag我会去的