write start in 2020-3-15 22:05:48
目录导航
前言
本章主要介绍Eureka,同时对Eureka架构有一部分分析,如果觉得很难吸收可以跳过Eureka架构直接上手代码这样学起来效率会高一点。
本章案例代码(github):https://github.com/zihuaSuperHandsome/springcloud-learning-demo/tree/master/chapter1
服务中心
微服务架构中最核心的部分是服务治理,服务治理最基础的组件是注册中心(服务中心)。随着微服务的发展出现了很多微服务的解决方案,比如Dubbo、SpringCLoud。
服务中心(或注册中心),管理各种服务功能包括服务的注册与发现、熔断、负载、降级等。有了服务中心后服务之间的调用会产生变化。
服务中心调用方式
服务A调用服务B:
有了服务中心后,任何一个服务不允许直接调用,都需要通过服务中心。
服务A调用服务B,服务B被调用后再调用服务C:
加入服务中心后,调用步骤分两步:第一步,服务A首先从服务中心请求服务B,然后服务B再从服务中心请求服务C。
上述只是简单的二三个服务之间的调用,如果多达几十个呢?画一张图描述项目的调用关系全是线条,任何一个其中的项目有所改动就会引起依赖这个项目的项目重启。
而通过服务中心的调用方式,不需要关注调用的具体项目,我只需要告诉服务中心我要调用的是什么,让服务中心给我找,如果服务中心上的注册表有我需要调用的服务,那么就反馈给我,如没有则可以直接熔断降级。
由于各种服务都注册到了服务中心,就有了去做很多高级功能的前置技能点。比如把某个服务做成集群用来负载均衡减轻压力;监控服务器调用的情况来做熔断,移除服务器列表的故障点;监控服务调用时间对不同的服务设置不同的权重等等。
Eureka
关于注册中心的解决方案,dubbo支持了Zookeeper、Redis、Multicast和Simple,官方推荐Zookeeper。SpringCloud支持了Zookeeper、Consul和Eureka,官方推荐Eureka。
两者之所以推荐不同的实现方式,原因在于组件的特点以及适用场景不同,简单的说:
- Zookeeper的设计原则是CP,即强一致性和分区容错性。它保证数据一致性,但舍弃了可用性,如果网络出现问题可能会影响Zookeeper选举导致注册中心不可用。
- Eureka的设计原则是AP,即可用性和分区容错性。它保证了注册中心的可用性,但舍弃了数据一致性,各节点上的数据有不一致的(会最终一致)。
第二篇中已经介绍过Eureka组件和背景,它是服务中心(或注册中心),它能实现对所有的服务集中管理。
官方介绍:
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
Eureka 是一个基于 REST 的服务,主要在 AWS 云中使用, 定位服务来进行中间层服务器的负载均衡和故障转移。
前面介绍过,Pivotal公司封装了Netfilx公司开发的Eureka模块来实现服务注册与发现,采用了C-S架构。
Eureka架构
Eureka-Server作为服务注册功能的服务器,它是服务注册中心,而系统中的其他微服务通过Eureka-Client连接到Eureka-Server,并维持心跳连接。这样开发或运维人员可以通过Eureka-Server后台来监控各个服务的运行情况。
Eureka-Client客户端是一个java客户端,用来简化与服务端的交互和客户端负载均衡,并提供服务的故障切换支持,关系如图:
上图由三个部分组成,Eureka-Server(服务注册中心)、Server- Provider(服务生产者)、Server-Consumer(服务消费者),简单的说:
- Eureka-Server(服务注册中心):提供服务注册与发现
- Server- Provider(服务生产者):将自身注册到Eureka-Server,从而使消费者找到
- Server-Consumer(服务消费者):从Eureka-Server获取服务列表从而找到消费者
Eureka-Server(服务注册中心):
- 启动后,从其他节点(集群)拉取服务注册信息
- 运行过程中,定时运行evict任务,剔除没有按时renew的服务(包括非正常停止和网络故障的服务)。
- 运行过程中,接收到的register、renew、cancel请求都会同步到其他集群中。
Server- Provider(服务生产者):
- 启动后,向注册中心发起"register"请求,注册服务
- 运行时,定时向注册中心发送renew心跳,证明"我还活着"
- 停止服务提供者,向注册中心发起cancel请求,清空当前服务注册信息
Server-Consumer(服务消费者):
- 启动后,从注册中心拉取服务注册信息
- 运行时,定时更新服务注册信息
- 服务消费者发起远程调用:
- 服务消费者会从服务注册信息中选择同机房的服务提供者,发起远程调用。只要有同机房的服务提供者挂了才会选择其他机房的服务提供者。
- 服务消费者因为同机房没有服务提供者,则会按负载均衡算法选择服务提供者发起远程调用。
服务注册机制(Register)
注册中心服务接收到register请求后:
- 保存服务信息,将服务信息保存到register中;
- 更新队列,将此事件添加到更新队列中;
- 清空二级缓存,用于保持数据的一致性;
- 更新阀值,供剔除服务使用;
- 同步服务信息,同步至其他节点;
服务续约机制(Renew)
服务注册后,要定时(默认30秒)向注册中心发送续约请求,告诉注册中心"我还活着"。
注册中心收到续约请求后:
- 更新服务对象的最近续约时间;
- 同步服务信息,将此事件同步至其他的Eureka Server节点。
剔除服务之前会先判断服务是否已过期,判断服务是否已过期的条件之一是续约时间和当前时间的差值是不是大于阀值。
服务注销机制(Cancel)
服务正常停止之前会向注册中心发送注销请求,告诉注册中心"我要下线了"。
注册中心收到"cancel"请求后:
- 删除服务信息,将服务从register中删除;
- 更新队列,将此事件添加到更新队列中供客户端增量同步服务信息使用;
- 清空二级缓存,保持数据一致性;
- 更新阀值;
- 同步服务信息,将此事件发送给其他节点;
服务正常停止才会发送Calcel,如果是非正常停止则不会发送,此服务由Eureka Server主动剔除
服务剔除机制(Eviction)
Eureka Server提供服务剔除机制,用于剔除那些非正常下线的服务。
剔除包含三个步骤:首先判断是否满足服务剔除条件,然后找出过期的服务,最后执行剔除。
判断是否满足服务剔除条件:
- 关闭了自我保护
- 如果开启了自我保护则需要进一步判断是Server的问题还是Client的问题,如果是Client的问题则直接剔除
阀值的计算:
- 自我保护阀值 = 服务总数 × 每分钟续约数 × 自我保护阀值因子
- 每分钟续约数 = (60 / 客户端续约间隔)
自我保护阀值的计算公式:
- 自我保护阀值 = 服务总数 × 每分钟续约数 × 自我保护阀值因子
举个例子,如果有100个服务,续约间隔是30秒,自我保护阀值因子是0.85
自我保护阀值 = 100 × (60 / 30) × 0.85 = 170
假如上一分钟的续约数是180,得出的自我保护阀值170小于180,说明大量服务可用,是服务的问题,进入剔除流程;
假如上一分钟的续约数是160,则服务大量不可用,是注册中心的问题,不执行剔除;
找出过期的服务
遍历所有的服务,判断上次续约时间距离当前时间大于阀值就标记为过期,并将这些过期的服务保存在集合中。
剔除服务
在剔除服务之前先计算剔除的数量,然后遍历过期服务,通过洗牌算法确保每次都公平的选择出要剔除的任务,最后进行剔除。
执行剔除服务后:
- 删除服务信息,从register中删除服务;
- 更新队列;
- 清空二级缓存;
(上边的先消化,后面的几个机制以后再研究了。)
服务获取机制(Fetch Registers)
服务同步机制
简单实现
使用idea进行开发。
SpringCloud已经实现了服务注册中心,我们只需要很简单的几个步骤就可以配置好。
首先创建一个基本的maven项目,它继承于SpringBoot,虽然可以单开一个消费者和生产者的项目,但是如果在一个模块下开发二者,使用maven模块化管理依赖会更好一点。
主项目pom添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<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>
使用dependencyManagement统一管理接下来要登场的服务端和客户端的springcloud依赖。
新建一个server模块,并添加依赖
Idea中点击File
->New
->Module
,名称取为server,parent要选择父项目,然后finish
,在pom.xml中添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
说明一下,添加这个依赖后,server子模块会寻找父模块中的"spring-cloud-starter-netflix-eureka-server",而这个玩意处于之前在父模块"dependencyManagement"中添加的"spring-cloud-dependencies"模块下,有兴趣的可以点进去,然后"ctrl+左键"查看"spring-cloud-netflix-dependencies"就可以查看到"spring-cloud-starter-netflix-eureka-server"这个依赖,这个就是使用dependencyManagement进行统一依赖版本管理的用法了。
添加启动代码
新建一个包,创建一个SpringBoot的标准启动类,在上面添加注解,如下:
@SpringBootApplication
@EnableEurekaServer
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
配置项
对于eureka-server,它默认也是一个客户端,如果我们不设置而是只使用默认配置的话,它会将自己识别成一个客户端并尝试注册自己,所以我们需要区分server与client的行为,在application.properties
中添加配置:
server.port = 8888
eureka.instance.hostname = 127.0.0.1
spring.application.name = eureka-server
eureka.client.register-with-eureka = false
eureka.client.fetch-registry = false
eureka.client.service-url.defaultZone = http://${eureka.instance.hostname}:${server.port}/eureka/
eureka.client.register-with-eureka
:表示是否将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server, 因此设为 false;eureka.client.fetch-registry
:表示是否从Eureka Server获取注册信息,默认为true。如果这是一个单点的 Eureka Server,不需要同步其他节点的数据,可以设为false。eureka.client.serviceUrl.defaultZone
:设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是http://localhost:8761/eureka ;多个地址可使用 , 分隔。
启动工程后访问 http://localhost:8888/eureka
可以看到结果,当然没有任何服务
集群
注册中心这么重要的服务,如果只是单服务遇到故障就是毁灭性。。。在分布式系统中,服务注册中心通常是最重要的基础部分,理所应当实现高可用,使用集群是很方便的解决方案。Eureka通过相互注册的方式实现高可用的部署,所以我们只需要将Eureka-Server配置成其他可用的serviceUrl就能实现高可用
双节点注册中心
尝试简单的双节点注册中心的搭建。
设置host
将地址加入host,具体方法可自行百度。
127.0.0.1 peer1
127.0.0.1 peer2
修改server模块的配置
# 启动地址
server.port = 8888
spring.application.name = eureka-server
eureka.instance.hostname = peer1
# 向peer2注册自己
eureka.client.service-url.defaultZone = http://peer2:8889/eureka/
新建模块reserve-server,与server模块内容相同
# 启动地址
server.port = 8889
spring.application.name = eureka-server
eureka.instance.hostname = peer2
# 向peer1注册自己
eureka.client.service-url.defaultZone = http://peer1:8888/eureka/
启动和参数说明
在两个项目都启动完毕后,访问 http://peer1:8888
,即可看到效果:
说明一下部分参数:
- DS Replicas:当前节点的相邻节点,也就是集群
- Instances currently registered with Eureka:可以看到包含了2个服务中心
- registered-replicas:已完成注册的副本
- unavailable-replicas:副本不可用,不可用的原因下面会提到
- available-replicas:副本可用
副本不可用原因:
如果你的eureka服务并没有显示在available-replicas(副本可用)
中,则可能是如下的几个问题:
- 不能使用localhost作为ip,可以设置hosts文件建立其他映射
spring.application.name
和eureka.instance.appname
必须一致- 相互注册要开启,即:
eureka.client.register-with-eureka = true
、eureka.client.fetch-registry = true
到此双节点的配置已完成。
多节点集群
生产环境中我们可能需要三台或大于三台的注册中心来保证服务的稳定性,配置的原理其实都一样,将注册中心分别指向其他的注册中心。这里不介绍了,因为与上面的双节点区别很小,相信聪明的你一定能举一反三。
参考:
- Finchley版教程-Eureka:https://www.fangzhipeng.com/springcloud/2018/08/01/sc-f1-eureka.html
- 纯洁的微笑-SpringCloud- Eureka篇:http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html
- 微服务注册中心 Eureka 架构深入解读:https://zhuanlan.zhihu.com/p/70006570
write end on 2020-3-16 06:36:51