分布式微服务学习总结——Eureka详解
前言
最近刚看完springcloud、dubbo的学习视频,但感觉不是那么扎实,所以打算写一个系列的博客来巩固自身所学。
当然有些内容是参考了别的博客,毕竟我也是初探分布式微服务的,并不是所谓的大神,只是一个新手在初探分布式微服务后写下的一些自己的理解和总结。
我们之前说了分布式微服务概述,聊了聊分布式微服务这种架构思想。
那么今天我们聊一聊Netflix全家桶“五大神兽”之一——Eureka。
一、Eureka是什么?
Eureka是Netflix系列最重要的组件之一!
Eureka有什么作用?
我们先思考一下要达到分布式微服务架构,需要解决哪些问题。
要做到分布式微服务,就必然要分布式部署,那么服务之间如何通讯?如此多的服务又如何管理呢?
…
Eureka就是来解决服务之间通讯和管理服务的问题的。
它是通过一种现在普遍流行的机制——服务注册与发现来实现的。
二、Eureka原理与机制
1.服务注册与发现的三大核心要素
Eureka如何实现的呢?
简单来说它是通过服务注册与发现的机制。从名字就可以看出它大概的运行原理。
它采用类似CS这种设计方式,分有Eureka Server(服务注册中心),Service Provider(服务提供者),Service Consumer(服务消费者),这三个角色共同组成了Eureka服务治理基础架构的三大核心要素。
Eureka Server(服务注册中心)
Eureka Server是Eureka提供的服务端,提供服务注册与发现的功能。此服务可以为集群,(集群就是两个或以上),形成高可用的eureka注册中心。
Service Provider(服务提供者)
Service Provider是提供服务的应用,可以是Spring
Boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现。
Service Consumer(服务消费者)
Service Consumer是服务的消费者,消费者从服务注册中心获取服务列表,通过客户端负载均衡某种算***询服务列表,然后调用其所需要的服务,也即调用对应的服务提供者。
Service Consumer是服务的消费者,消费者从服务注册中心获取服务列表,通过客户端负载均衡某种算***询服务列表,然后调用其所需要的服务,即调用对应的服务提供者。 上述就是Eureka服务治理基础架构的三大核心要素。但是上述的三个核心要素遵守的都是逻辑角色的原则,意思是上述三个核心要素在实际运行中,可能是两个,甚至有可能是同一个实例。Eureka客户端通常包括Service Provider(服务提供者)与Service Consumer(服务消费者),那么在实际的运行中,这三个角色又是怎样交互的呢?
2.Eureka机制
在实际的运行中,Service Provider会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线) 等操作,Eureka Server之间会做注册服务的同步,从而保证状态一致,Service Consumer会向Eureka Server获取注册服务列表,并消费服务。每个角色都有着自己独特的运行方式,来完成整个服务治理,下面,我们就通过探索者的角度一步步详细的梳理一下整个服务治理机制。
服务注册
在微服务启动时,首先,服务提供者需要将自己的服务注册到服务注册中心,服务提供者在启动的时候会发送REST请求将自己注册到服务注册中心上,并带上一些元信息。
服务注册中心接收到REST请求,会将元信息存储在一个双层Map中,第一层key是服务名,第二层key是具体服务的实例名。注意:在服务注册时,需要确认一下eureka.client.register-with-eureka=true是否正确,如果为false是禁止向服务注册中心注册的。
服务同步
当服务成功的注册到了注册中心之后,由于注册中心可能是高可用的集群,那么我们的服务可能只注册到了一个集群中的一个注册中心上,被一个注册中心所维护,而不被另外一个注册中心所维护,那么这个时候,我们就需要将这个注册中心的信息同步给集群中其他的注册中心,这就叫服务同步。那么他是如何实现的呢?
由于在集群中,一个注册中心互为其他注册中心的服务,当服务提供者请求到一个服务注册中心后,它会将请求转发到其他服务注册中心,实现注册中心之间的服务同步。
通过服务同步,服务提供者的服务信息可以通过集群中的任何一个服务注册中心获取。
服务续约
在注册完成后。服务提供者会维护一个心跳告诉注册中心服务,心跳间隔大约是30S,防止注册中心剔除服务, 正常情况下,如果Eureka Server在90秒没有收到Eureka客户的续约,它会将实例从其注册表中删除。这个过程称为服务续约。
服务获取
当一切的注册相关工作完成后,我们自然要获取服务清单,那么如何获取服务呢?
启动服务消费者后,消费者会发送一个REST请求给服务注册中心,来获取上面注册的服务清单。
而服务注册中心会维护一份只读清单返回给消费者客户端,该缓存清单30s更新一次。
服务调用
消费者获取服务清单后,可以通过服务名获取到具体服务实例与实例的元数据信息。这个时候,我们可以通过Ribbon调用我们的目标服务,默认采用轮询的方式,从而实现负载均衡。
服务下线
当我们需要对服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给服务端。注册中心接收到请求后,将该服务状态置为DOWN,并把下线时间传播出去。
失效剔除
有的时候,我们的服务意外挂掉了,那么Eureka如何识别出我们异常的服务,并将其剔除呢?
服务注册中心启动时会创建定时任务,默认60s一次,将当前清单中超时(90s)没有续约的服务剔除。
自我保护(好死不如赖活着)
当失效剔除机制引入的时候,就会有一个问题,如果一个地区网络特别不稳定,那么服务可能不会续约,但我们还需要这个服务存在。这个时候,我们怎么解决呢?
还好,Eureka拥有自我保护机制,可以很好的解决这个问题。Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果低于,就会将当前实例注册信息保护起来,同时提示一个警告,一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据。也就是不会注销任何微服务。
但是保护机制也有可能会出现问题,导致服务实例不能够被正确剔除。比如在保护期间,实例出现问题,那么客户端很容易拿到实际已经不存在的服务实例,会出现调用失败。
可以通过设置配置application核心配置文件的eureka.server.enable-self-preservation=false/true来控制是否开启保护机制。
3.Eureka设计思想(不同于Zookeeper的地方)
这里值得一提的是zookeeper和Eureka的设计思路的区别。
要说这两者设计思想的区别,不得不讲一下分布式里著名的CAP原则。
CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件,
因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足 。
因此在进行分布式架构设计时,必须做出取舍。当前一般是通过分布式缓存中各节点的最终一致性来提高系统的性能,通过使用多节点之间的数据异步复制技术来实现集群化的数据一致性。
Zookeeper就是遵循CP原则,而Eureka就是典型的遵循AP原则。
Zookeeper在保证分区容忍性的基础上,保证了数据在同一时间的一致性,也就是说它获取的数据一定是实时的。
但是Zookeeper会出现这么一种情况,当master发生网络故障时,Zookeeper会进行leader选举,但是这个阶段,所有服务是终止,也就是说在这个时间里用户无法访问任何服务的。
这和我们Java里面内存回收时的“世界停止”类似,不过与其不同的是Zookeeper这个选举过程居然要30-120s!这对于我们(尤其是那种大型应用)来说简直无法忍受。
我们可以忍受自己获取的数据是几十秒前的,但是我们无法忍受服务终止了这么久,Eureka就是看准了这一点,他舍弃了一致性,保证了可用性,这也是它被许多公司采用的原因。
当然,以上都是些原理,代码实现远比这复杂的多,如果想了解更深层次建议还是去读源码(当然我也没读过QAQ)
三、使用步骤
1.创建一个空的maven项目
①导入依赖,添加总项目的依赖管理
注:以下有些东西比如moudle什么都是后面加的,因为我是拿一个写好的练手项目来做演示的
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.1.RELEASE</version>
</parent>
<groupId>com.dreamchaser</groupId>
<artifactId>springcloud_study</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-api</module>
<module>springcloud-consumer-tag</module>
<module>springcloud-euraka</module>
<module>springcloud-provider-tag-8001</module>
<module>springcloud-provider-blog-8002</module>
<module>springcloud-provider-type-8003</module>
<module>springcloud-provider-tag-8004</module>
<module>springcloud-provider-tag-hystrix-8005</module>
<module>springcloud-zuul</module>
<module>springcloud-config-server-3344</module>
<module>springcloud-config-client-3345</module>
</modules>
<packaging>pom</packaging>
<properties>
<junit.version>4.12</junit.version>
</properties>
<!--依赖管理不会给你真正给你导入-->
<dependencyManagement>
<dependencies>
<!-- springCloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<!--它只使用在<dependencyManagement>中,表示从其它的pom中导入dependency的配置,例如 (B项目导入A项目中的包配置),这样才会继承-->
<!--dependencyManagement只会影响现有依赖的配置,但不会引入依赖,即子model不会继承parent中dependencyManagement 所有预定义的depandency,只引入需要的依赖即可,简单说就是“按需引入依赖”或者“按需继承”;因此,在parent中严禁直接使用 depandencys预定义依赖,坏处是子model会自动继承depandencys中所有预定义依赖;-->
<!--这样子模块才会继承-->
<scope>import</scope>
</dependency>
<!--SpringBoot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!--springboot启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.创建新模块作为Eureka Server(服务注册中心)
①添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_study</artifactId>
<groupId>com.dreamchaser</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-euraka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
②编辑核心配置文件application.yml
#用户级别的配置
server:
port: 7001
#Euraka配置
eureka:
instance:
#Eureka服务端的实例名字
hostname: localhost
client:
#表示是否向Eureka注册中心注册自己
register-with-eureka: false
#fetch-registry如果为false则表示自己为注册中心
fetch-registry: false
#监控页面
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
③在核心启动类上开启配置
package com.dreamchaser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
//服务端的启动类,可以接受别人注册进来
@EnableEurekaServer
public class Application_eureka {
public static void main(String[] args) {
SpringApplication.run(Application_eureka.class, args);
}
}
3.创建新模块作为Service Provider(服务提供者)
①导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_study</artifactId>
<groupId>com.dreamchaser</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-blog-8002</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
②编辑核心配置文件application.yml
server:
port: 8002
eureka:
client:
service-url:
#有集群就把所有uereka地址都写出来,以逗号区别
defaultZone: http://localhost:7001/eureka/
instance:
#用于描述实例名称
instance-id: springcloud-provider-blog-8002
#spring配置
spring:
application:
name: springcloud-provider-blog
③在核心启动类上开启配置
package com.dreamchaser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class Application_blog_8002 {
public static void main(String[] args) {
SpringApplication.run(Application_blog_8002.class, args);
}
}
4.创建新模块作为Service Consumer(服务消费者)
①导入依赖
注:理论上来说还应该有个api模块,用作其他模块的公共部分,我这里就导入自己的api模块,但是为了演示Eureka用法,我就不在此列出了
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud_study</artifactId>
<groupId>com.dreamchaser</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-tag</artifactId>
<dependencies>
<!--消费端做负载均衡用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--dashboard流监控-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.dreamchaser</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
</dependencies>
</project>
②编辑核心配置文件application.yml
server:
port: 80
eureka:
client:
service-url:
#有集群就把所有uereka地址都写出来,以逗号区别
defaultZone: http://localhost:7001/eureka/
instance:
#用于描述实例名称
instance-id: springcloud-provider-tag-8001
spring:
application:
name: eureka-consumer
③在核心启动类上开启配置
注:在这里Eureka真正用到的注解就只有@EnableEurekaClient,其他注解是其他核心组件的注解,我也会在以后的博客中介绍
package com.dreamchaser.springcloud;
import com.dreamchaser.myrule.RibbonRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
//Ribbon和Eureka整合后,客户端可以直接调用,不用关心IP地址和端口号
@SpringBootApplication
@EnableEurekaClient
//开启流监控
@EnableHystrixDashboard
@EnableCircuitBreaker
//在微服务启动的时候就加载我们自定义的Ribbon类
@RibbonClient(name ="SPRINGCLOUD-PROVIDER-TAG",configuration = RibbonRule.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
四、运行Eureka
1.运行Eureka Server
2.运行Eureka provider
其会自动注册进Eureka Server
3.打开http://localhost:7001/(Eureka Server服务地址)
它会出现这么一个界面
这里我们可以看到有个叫做SPRINGCLOUD-PROVIDER-TAG的服务以及注册进来。
注:这个服务的名字取决于,你在application.yml配置的spring.application.name
4.运行Eureka Consumer
这时候我们可以发现Eureka中多了一个叫做EUREKA-CONSUMER的服务
注:由于演示要拿具体业务加调用代码,并且涉及到后面组件的使用,所以这里就不加展示了,我将在后面介绍Ribbon的时候再来展示调用过程。
注:Eureka Server也可以作集群,这里我只用了一台,不过可以加一个Server,调用端再把所有集群地址写上,用逗号隔开,和单个非常类似只是改动了一点地方,这里就不加演示了,具体的可以去搜相关博客了解详情。当然,如果评论提出要求的话,我也可以把这部分加上。
总结
Eureka是Netflix系列下的最主要的核心组件之一,用来解决服务间通讯和服务管理的,专业点说就是用来服务注册和发现,遵循AP原则。
而springcloud框架下的组件凭借着SpringBoot的优势,功能开启也很简单,只需简单的三步——导入依赖、编辑配置、核心类上添加相关注解开***能。
如果随着我的学习发现自己所写的有所纰漏或者错误,我会第一时间来修正博客,当然如果有什么错误,欢迎大家评论区评论指正,不胜感激。
愿我们都能以梦为马,不负人生韶华!
以此为记,与君共勉!
参考文章:https://zhuanlan.zhihu.com/p/98572822