文章目录
关于服务注册中心
服务注册中心本质上是为了解耦服务提供者和服务消费者。
对于任何⼀个微服务,原则上都应存在或者⽀持多个提供者(⽐如简历微服务部署多个实例),这是由微服务的分布式属性决定的。
更进⼀步,为了⽀持弹性扩缩容特性,⼀个微服务的提供者的数量和分布往往是动态变化的,也是⽆法预先确定的。因此,原本在单体应⽤阶段常⽤的静态LB机制就不再适⽤了,需要引⼊额外的组件来管理微服务提供者的注册与发现,⽽这个组件就是服务注册中心。
服务注册中心一般原理
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的⽅式获取服务提供者的地址信息,而不再需要通过硬编码⽅式得到提供者的地址信息。消费者只需要知道当前系统发布了那些服务,⽽不需要知道服务具体存在于什么位置,这就是透明化路由。
-
服务提供者启动
-
服务提供者将相关服务信息主动注册到注册中⼼
-
服务消费者获取服务注册信息:
pull模式:服务消费者可以主动拉取可⽤的服务提供者清单
push模式:服务消费者订阅服务(当服务提供者有变化时,注册中⼼也会主动推送更新后的服务清单给消费者 -
服务消费者直接调⽤服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除;
主流服务中心对比
-
Zookeeper
Zookeeper它是⼀个分布式服务框架,是Apache Hadoop 的⼀个⼦项⽬,它主要是⽤来解决分布式应 ⽤中经常遇到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应⽤配置项的管理等。简单来说zookeeper本质=存储+监听通知。(znode)
Zookeeper ⽤来做服务注册中⼼,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调⽤⽅只要使⽤ Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,⾮常⽅便。另外,Zookeeper 可⽤性也可以,因为只要半数以上的选举节点存活,整个集群就是可⽤的。 -
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 交互流程及原理
下图是官⽹描述的⼀个架构图
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是⼀个Java客户端,⽤于简化与Eureka Server的交互;Eureka Server提供服务发现的能⼒,各个微服务启动时,会通过Eureka Client向Eureka Server 进⾏注册⾃⼰的信息(例如⽹络信息),Eureka Server会存储该服务的信息;
-
图中us-east-1c、us-east-1d,us-east-1e代表不同的区也就是不同的机房
-
图中每⼀个Eureka Server都是⼀个集群。
-
图中Application Service作为服务提供者向Eureka Server中注册服务,Eureka Server接受到注册事件会在集群和分区中进⾏数据同步,ApplicationClient作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进⾏服务调⽤。
-
微服务启动后,会周期性地向Eureka Server发送⼼跳(默认周期为30秒)以续约⾃⼰的信息
-
Eureka Server在⼀定时间内没有接收到某个微服务节点的⼼跳,EurekaServer将会注销该微服务节点(默认90秒)
-
每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的⽅式完成服务注册列表的同步
-
Eureka Client会缓存Eureka Server中的信息。即使所有的Eureka Server节点都宕掉,服务消费者依然可以使⽤缓存中的信息找到服务提供者
Eureka通过⼼跳检测、健康检查和客户端缓存等机制,提⾼系统的灵活性、可伸缩性和可⽤性。
Eureka应⽤及⾼可⽤集群
-
单实例Eureka Server—>访问管理界⾯—>Eureka Server集群
-
服务提供者(简历微服务注册到集群)
-
服务消费者(⾃动投递微服务注册到集群/从Eureka Server集群获取服务信息)
-
完成调⽤
搭建单例Eureka Server服务注册中心
- 创建父工程,添加pom依赖
<parent>
<artifactId>spring-boot-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.9.RELEASE</version>
</parent>
<dependencies>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--⽇志依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok⼯具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<!-- Actuator可以帮助你监控和管理Spring Boot应⽤-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
注意:在父工程的pom⽂件中⼿动引⼊jaxb的jar,因为Jdk9之后默认没有加载该模块,EurekaServer使⽤到,所以需要⼿动导入,否则EurekaServer服务无法启动
<!--eureka server 需要引入Jaxb,开始-->
<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,结束-->
-
创建子module
cloud-eureka-server-8761
-
改子模块POM
SpringBoot2.X对应的SpringCloud中 -
写配置文件YAML
server:
port: 8761
spring:
application:
name: cloud-eureka-server
eureka:
instance:
hostname: localhost
client:
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
#false表示不向注册中心注册自己
register-with-eureka: false
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 主启动类
@SpringBootApplication
//声明我是服务注册中心
@EnableEurekaServer
public class CloudEurekaServer8761 {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaServer8761.class, args);
}
}
- 测试
完成以上步骤后,运行该Eureka Server主启动类,访问 localhost:8761,就会看到下面的服务注册中心:
搭建Eureka Server HA⾼可⽤集群
在互联⽹应⽤中,服务实例很少有单个的。
即使微服务消费者会缓存服务列表,但是如果EurekaServer只有⼀个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可⽤,那么这个时候整个系统都受影响。
在⽣产环境中,会配置Eureka Server集群实现⾼可⽤。Eureka Server集群之中的节点通过点对点(P2P)通信的⽅式共享服务注册表。开启两台 EurekaServer 以搭建集群。
- 修改本机host属性
由于是在个⼈计算机中进⾏测试很难模拟多主机的情况,Eureka配置server集群时需要执⾏host地址。 所以需要修改个⼈电脑中host地址
127.0.0.1 eureka8761.com
127.0.0.1 eureka8762.com
- 复制一份子工程
cloud-eureka-server-8762
修改pom文件
8761工程pom文件:
server:
port: 8761
spring:
application:
name: cloud-eureka-server
eureka:
instance:
hostname: eureka8761.com
client:
fetch-registry: true
register-with-eureka: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# 互相注册,相互守望
defaultZone: http://eureka8762.com:8761/eureka/
8762工程pom文件:
server:
port: 8762
spring:
application:
name: cloud-eureka-server
eureka:
instance:
hostname: eureka8762.com
client:
fetch-registry: true
register-with-eureka: true
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# 互相注册,相互守望
defaultZone: http://eureka8761.com:8761/eureka/
- 分别启动两个工程,访问两个EurekaServer的管理台⻚⾯http://eureka8761.com:8761/和http://eureka8762.com:8762/会发现注册中心
CLOUD-EUREKA-SERVER
已经有两个节点,并且 registered-replicas (相邻集群复制节点)中已经包含对⽅
微服务提供者—>注册到Eureka Server集群
注册简历微服务(简历服务部署两个实例,分别占⽤8080、8081端⼝)
- ⽗⼯程中引⼊spring-cloud-commons依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
- 准备服务提供者子工程
cloud-service-resume-8081
和cloud-service-resume-8082
并修改pom文件,添加下面的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 分别修改两个提供者的yaml,这里展示8080的,8081只需要改端口即可
server:
port: 8080
spring:
application:
name: lagou-service-resume
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 单机版
#defaultZone: http://localhost:8761/eureka # 入驻的服务注册中心地址
# 集群版
defaultZone: http://eureka8762.com:8762/eureka/,http://eureka8761.com:8761/eureka/ # 入驻的服务注册中心地址
instance:
#使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
- 准备controller
@RestController
@RequestMapping("/resume")
public class ResumeController {
@Autowired
private ResumeService resumeService;
@RequestMapping("/openstate/{userId}")
public Integer findResumeStateById(@PathVariable Long userId){
return 3;
}
}
- 修改启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ResumeApplication8081 {
public static void main(String[] args) {
SpringApplication.run(ResumeApplication8081.class, args);
}
}
注意:
6. 从Spring Cloud Edgware版本开始,@EnableDiscoveryClient
或@EnableEurekaClient
可省略。只需加 上相关依赖,并进⾏相应配置,即可将微服务注册到服务发现组件上。
-
@EnableDiscoveryClient
和@EnableEurekaClient
⼆者的功能是⼀样的。但是如果选⽤的是eureka服务器,那么就推荐@EnableEurekaClient
,如果是其他的注册中⼼,那么推荐使⽤@EnableDiscoveryClient
,考虑到通⽤性,后期我们可以使⽤@EnableDiscoveryClient
-
启动类执⾏,在Eureka Server后台界⾯可以看到注册的服务实例
注意如果像上图中出现版本号无法正常找到的话,参考下面这篇文章
https://blog.csdn.net/adminpwd/article/details/96966359
正常修改之后的效果如下:
微服务消费者—>注册到Eureka Server集群
- 创建消费者模块
cloud-service-autodeliver
, 修改pom文件
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
- 改yaml
server:
port: 8090
spring:
application:
name: cloud-service-autodeliver
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 单机版
#defaultZone: http://localhost:8761/eureka # 入驻的服务注册中心地址
# 集群版
defaultZone: http://eureka8762.com:8762/eureka/,http://eureka8761.com:8761/eureka/ # 入驻的服务注册中心地址
instance:
#使⽤ip注册,否则会使⽤主机名注册了(此处考虑到对⽼版本的兼容,新版本经过实验都是ip)
prefer-ip-address: true
#⾃定义实例显示格式,加上版本号,便于多版本管理,注意是ip-address,早期版本是ipAddress
instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
- 创建配置类
@Configuration
public class EurekaConfigure {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 创建controller
@RestController
@RequestMapping("/autodeliver")
public class AutoDeliverController {
@Autowired
RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/checkState/{userId}")
public Integer findResumeStateById(@PathVariable Long userId){
// 1、从 Eureka Server中获取lagou-service-resume服务的实例信息(使用客户端对象做这件事)
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-service-resume");
// 2、如果有多个实例,选择一个使用(负载均衡的过程)
ServiceInstance instance = instances.get(0);
// 3、从元数据信息获取host port
String host = instance.getHost();
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/resume/openstate/" + userId;
System.out.println("===============>>>从EurekaServer集群获取服务实例拼接的url:" + url);
// httpclient封装好多内容进行远程调用
Integer forObject = restTemplate.getForObject(url, Integer.class);
return forObject;
}
}
- 主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class AutoDeliverApplication {
public static void main(String[] args) {
SpringApplication.run(AutoDeliverApplication.class, args);
}
}
- 测试
Eureka细节详解
Eureka元数据详解
Eureka的元数据有两种:标准元数据和⾃定义元数据。
标准元数据:主机名、IP地址、端⼝号等信息,这些信息都会被发布在服务注册表中,⽤于服务之间的调⽤。
⾃定义元数据:可以使⽤eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式。这 些元数据可以在远程客户端中访问。
类似于
instance:
prefer-ip-address: true
metadata-map:
cluster: cl1
region: rn1
我们可以在程序中可以使⽤DiscoveryClient 获取指定微服务的所有元数据信息
@SpringBootTest(classes = {AutoDeliverApplication.class})
public class AutoDeliverApplicationTest {
@Autowired
private DiscoveryClient discoveryClient;
@Test
public void test() {
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-service-autodeliver");
for (ServiceInstance instance : instances) {
Map<String, String> metadata = instance.getMetadata();
System.out.println(metadata);
}
}
}
Eureka客户端详解
服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等⼯作
服务注册详解(服务提供者)
-
当我们导⼊了eureka-client依赖坐标,配置Eureka服务注册中⼼地址
-
服务在启动时会向注册中⼼发起注册请求,携带服务元数据信息
-
Eureka注册中⼼会把服务的信息保存在Map中。
服务续约详解(服务提供者)
服务每隔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服务列表获取只读备份,缓存到本地
-
每隔30秒,会重新获取并更新数据
-
每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
Eureka服务端详解
服务下线
-
当服务正常关闭操作时,会发送服务下线的REST请求给EurekaServer。
-
服务中⼼接受到请求后,将该服务置为下线状态失效剔除
失效剔除
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进⾏检查,如果发现实例在在⼀定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到⼼跳,则会注销此实例。
⾃我保护
服务提供者 —> 注册中⼼
定期的续约(服务提供者和注册中⼼通信),假如服务提供者和注册中⼼之间的⽹络有点问题,不代表服务提供者不可⽤,不代表服务消费者⽆法访问服务提供者如果在15分钟内超过85%的客户端节点都没有正常的⼼跳,那么Eureka就认为客户端与注册中⼼出现了⽹络故障,Eureka Server⾃动进⼊⾃我保护机制。
为什么会有⾃我保护机制?
默认情况下,如果Eureka Server在⼀定时间内(默认90秒)没有接收到某个微服务实例的⼼跳,Eureka Server将会移除该实例。但是当⽹络分区故障发⽣时,微服务与Eureka Server之间⽆法正常通信,⽽微服务本身是正常运⾏的,此时不应该移除这个微服务,所以引⼊了⾃我保护机制。
服务中⼼⻚⾯会显示如下提示信息
当处于⾃我保护模式时
-
不会剔除任何服务实例(可能是服务提供者和EurekaServer之间⽹络问题),保证了⼤多数服务依然可⽤
-
Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可⽤,当⽹络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
-
在Eureka Server⼯程中通过eureka.server.enable-self-preservation配置可⽤关停⾃我保护,默认值是打开
eureka:
server:
enable-self-preservation: false # 关闭⾃我保护模式(缺省为打开)