“Eureka”来源于古希腊词汇,意为“发现了”。在软件领域,Eureka是Netflix在线影⽚公司开源 的⼀个服务注册和发现组件,和其他的Netflix公司的服务组件(例如负载均衡,熔断器,⽹关等) ⼀起,被Spring Cloud社区整合为Spring Cloud Netflix模块。
2.1 Eureka简介
和Zookeeper类似,Eureka是⼀个⽤于服务注册和发现的组件,最开始主要应⽤与亚⻢逊公司的云 计算服务平台AWS,Eureka分为Eureka Server和Eureka Client,Eureka Server为Eureka服务注册中⼼,Eureka Client为Eureka客户端。
举个例⼦:Eureka好⽐滴滴⽹约⻋平台,没有滴滴时,⼈们出⻔叫⻋只能叫出租⻋。⼀些私家⻋想做出租却没有资格,被称为⿊⻋。⽽很多⼈想要约⻋,但是⽆奈出租⻋太少,不⽅便。私家⻋很多却不敢拦,⽽且满⼤街的⻋,谁知道哪个才是愿意载⼈的。⼀个想要,⼀个愿意给,就是缺少引⼦,缺乏管理啊。
此时滴滴这样的⽹约⻋平台出现了,所有想载客的私家⻋全部到滴滴注册,记录你的⻋型(服务类型),身份信息(联系⽅式)。这样提供服务的私家⻋,在滴滴那⾥都能找到,⼀⽬了然。此时要叫⻋的⼈,只需要打开APP,输⼊你的⽬的地,选择⻋型(服务类型),滴滴⾃动安排⼀个符合需求的⻋到你⾯前,为你服务,完美!
Eureka相当于微服务架构中的“滴滴”。负责微服务的注册和发现⼯作,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到Eureka注册中⼼,当服务需要调⽤其它服务时,就从Eureka找到服务的地址,进⾏调⽤。Eureka在Spring Cloud中的作⽤是⽤来作为服务治理实现服务注册和发现。Eureka主要涉及到三⼤⻆⾊:服务提供者、服务消费者、注册中⼼。
服务注册是指,各个微服务在启动时,将⾃⼰的⽹络地址等信息注册到Eureka,服务提供者将⾃⼰的服务信息,如服务名、IP等告知服务注册中⼼。
服务发现是指当⼀个服务消费者需要调⽤另外⼀个服务时,服务消费者从Eureka查询服务提供者的地址,并通过该地址调⽤服务提供者的接⼝。⼀个服务既可以是服务消费者,也可以是服务发现者。
各个微服务与注册中⼼使⽤⼀定机制(例如⼼跳)通信。如果Eureka与某微服务⻓时间⽆法通信,Eureka会将该服务实例从服务注册中⼼中剔除,如果剔除掉这个服务实例过了⼀段时间,此服务恢复⼼跳,那么服务注册中⼼将该实例重新纳⼊到服务列表中,Eureka架构图,如图2-1所示。
注意:Eureka2.x已经停更,解决⽅案推荐使⽤Nacos作为替换⽅案,Nacos在Spring Cloud Alibaba中讲解。
2.2 Eureka⼊⻔
本节介绍Eureka的基本使⽤,创建Eureka Server,让后将上⾯⽀付微服务,和订单微服务注册到Eureka Server中。Eureka基本机构主要包括以下3个⻆⾊。
- Eureka Server:服务注册中⼼,提供服务注册和发现功能。
- Provider Service:服务提供者,案例中就是⽀付微服务。
- Consumer Service:服务消费者,案例中就是订单微服务。
2.2.1 EurekaServer
1.选择依赖
选择下⾯依赖,如图2-2所示。
- Spring Boot 2.4.8
- Spring Boot DevTools
- Lombok
- Eureka Server
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
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lxs.demo</groupId>
<artifactId>04_cloud_eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>04_cloud_eureka</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<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>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.启动器
注:@EnableEurekaServer,声明当前应⽤为Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3.配置⽂件
server:
port: 9004
spring:
application:
name: eureka-server
eureka:
client:
service-url:
# eureka 服务地址,如果是集群的话;需要指定其它集群eureka地址
defaultZone: http://127.0.0.1:9004/eureka
# 不注册⾃⼰
register-with-eureka: false
# 不拉取服务
fetch-registry: false
4.启动并测试
启动应⽤访问http://localhost:9004/
2.2.2 服务提供者
改造⽀付服务作为服务提供者,提供⽀付服务,注册到Eureka Server。
1.添加依赖
在⽀付微服务⼯程pom.xml中添加如下依赖。
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</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>
2.配置⽂件
application.yml配置如下。
server:
port: 9001
spring:
application:
name: cloud-payment-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9004/eureka
fetch-registry: true
register-with-eureka: true
注意:
-
这⾥我们添加了spring.application.name属性来指定应⽤名称,将来会作为服务的id使⽤。
-
不⽤指定register-with-eureka和fetch-registry,因为默认是true
3.启动器
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
4.启动并测试
2.2.3 服务消费者
改造订单微服务,订单服务调⽤⽀付服务,订单微服务作为服务消费者,当然订单服务既可以作为消费 者,调⽤⽀付服务,也可以作为被其他服务调⽤,作为服务提供者,所以订单服务也可以注册到Eureka Server
-
添加依赖
在⽀付微服务⼯程pom.xml中添加如下依赖。
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2020.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</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>
2.配置⽂件
application.yml配置如下。
server:
port: 9002
spring:
application:
name: cloud-order-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9004/eureka
注意:
-
这⾥我们添加了spring.application.name属性来指定应⽤名称,将来会作为服务的id使⽤。
-
不⽤指定register-with-eureka和fetch-registry,因为默认是true
3.启动器
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
4.OrderController
修改OrderController,代码如下。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/payment/{id}")
public ResponseEntity<Payment> getPaymentById(@PathVariable("id") Integer id) {
String url = "http://localhost:9001/payment/" + id;
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("cloud-payment-service");
ServiceInstance serviceInstance = serviceInstances.get(0);
url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/payment/" + id;
Payment payment = restTemplate.getForObject(url, Payment.class);
return ResponseEntity.ok(payment);
}
}
5.启动并测试
2.3 源码解析
2.3.1 Eureka的⼀些概念
1.Register — 服务注册
当Eureka Client向Eureka Server注册时,Eureka Client提供⾃身的元数据,⽐如IP地址、端⼝、 运⾏状况指标的URL,主⻚地址等信息。
2.Renew — 服务续约
Eureka Client在默认情况下会每隔30秒发送⼀次⼼跳来进⾏服务续约,通过服务续约来告知Eureka Server该Eureka Client依然可⽤,正常情况下,如果Eureka Server在90秒内没有收到Eureka Client的⼼跳,Eureka Server会将Eureka Client实例从注册列表中删除,注意:官⽹建议不要更爱服务续约的间隔时间。
3.Fetch Registries — 获取服务注册列表信息
Eureka Client从Eureka Server获取服务注册表信息,并将其缓存到本地。Eureka Client 会使⽤服务注册列表信息查找其他服务的信息,从⽽进⾏远程调⽤,改注册列表信息定时(每隔30秒)更新⼀次,每次返回的注册列表信息可能与Eureka Client的缓存信息不同,Erueka Client会重新获取整个注册表信息。Eureka Server缓存了所有的服务注册表信息,并且进⾏了压缩。Eureka Client和Eureka
Server可以使⽤json和xml的数据格式进⾏通信,默认,Eureka Client使⽤JSON格式⽅式来获取服务器注册列表信息。
4.Cancel — 服务下线
Eureka Client在程序关闭时可以向Eureka Server发送下线请求,发送请求后,该客户端的实例信息将从Eureka Server的服务注册列表信息中删除。改下线请求不会⾃动完成,需要在程序关闭时调⽤以下代码
DiscoveryManager.getInstance().shutdownComponent();
5.Eviction — 服务
在默认情况下,Eureka Client连续90秒没有想Eureka Server发送服务续约(⼼跳)时,Eureka Server会将该服务实例从服务列表中删除。即服务剔除。
2.3.2 Register 服务注册
1.Eureka Client源码
Eureka Client向Eureka Server提交⾃⼰的服务信息,包括IP、端⼝、ServiceId等信息。如果Eureka Client没有配置ServiceId,则默认为配置⽂件中的配置的服务名,即${spring.application.name}的值。
(1)DiscoveryClient初始化⽅法initScheduledTasks⽅法
该⽅法主要开启了获取服务注册列表的信息,如果需要向Eureka Server注册,则开启注册,同时开启了定时任务向Eureka Server服务续约,代码如下。
/**
\* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
...//省略了任务调度获取注册列表的代码。
}
if (clientConfig.shouldRegisterWithEureka()) {
...
// Heartbeat timer
heartbeatTask = new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
);
scheduler.schedule(
heartbeatTask,
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),2); // burstSize
...
}
}
(2) instanceInfoReplicator类
initScheduledTasks⽅法中,定时任务调⽤instanceInfoReplicator类,instanceInfoReplicator类继承Runable接⼝,run⽅法代码如下。
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info
replicator", t);
} finally {
Future next = scheduler.schedule(this,
replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
(3)DiscoveryClient的register⽅法
在com.netflix.discovery包下的DiscoveryClient类中有⼀个register()⽅法,该⽅法通过Http请求向 Eureka Server注册,代码如下所示。
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...",
appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse =
eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}",
appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}",
appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}
2.Eureka Server端源码
(1)ApplicationResource类
ApplicationResource类的addInstance⽅法,接收Eureka Client客户端注册请求,完成注册,代 码如下。
/**
* Registers information about a particular instance for an
* {@link com.netflix.discovery.shared.Application}.
* @param info
* {@link InstanceInfo} information of the instance.
* @param isReplication
* a header parameter containing information whether this is
* replicated from other nodes.
*/
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
...
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
(2) PeerAwareInstanceRegistryImpl类
上⾯addInstance⽅法调⽤PeerAwareInstanceRegistryImpl类的register⽅法进⾏注册,代码如 下。
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null &&
info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
//⾼可⽤,多节点同步数据
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
2.3.3 Renew 服务续约
服务续约和服务注册⾮常类似,通过前⽂中的分析可以知道,服务注册在Eureka Client程序启动后开启,并且同时开启服务续约定时任务。
1.Eureka Client端
在DiscoveryClient类下⼜renew()⽅法,完成续约,代码如下。
/**
* Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(),
instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}",
appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}",
appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!",
appPathIdentifier, e);
return false;
}
}
2.Eureka Server端
在com.netflix.eureka.InstanceResource类下,接⼝⽅法renewLease(),它是⼀个RESTful API接⼝。 完成服务器断续,代码如下。
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
...
boolean isSuccess = registry.renew(app.getName(), id,
isFromReplicaNode);
...
}
此外,服务续约的两个参数是可以配置的,即Eureka Client发送续约⼼跳间隔时间参数,和Eureka Server多⻓时间内没有收到⼼跳将实例剔除的时间参数,默认情况下这两个参数分别是30秒和90秒
eureka:
instance:
# ⼼跳间隔时间
lease-renewal-interval-in-seconds: 30
# 没收到⼼跳多⻓时间剔除
lease-expiration-duration-in-seconds: 90
2.4 Eureka的⾃我保护
当有⼀个新的Eureka Server出现时,他尝试从相邻的Peer节点获取所有服务实例注册信息。如果 从相邻的Peer节点获取信息时出现了故障,Eureka Server会尝试其他的Peer节点。如果Eureka Server 能够成功获取所有的服务实例信息。则根据配置信息设置服务续约的阈值。在任何时间,如果Eureka Server接收到的服务续约低于为该值配置的百分⽐(默认为15分钟内低于85%),则服务器开启⾃我保 护模式,即不再剔除注册列表的信息。
这样做的好处在于,如果Eureka Server⾃身的⽹络问题⽽导致Eureka Client⽆法续约,Eureka Client的注册列表信息不再被删除,也就是Eureka Client还可以被其他服务消费。
在默认情况下,Eureka Server的⾃我保护模式是开启的,⽣产环境下这很有效,保证了⼤多数 服务依然可⽤,但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭⾃我保护模式。代 码如下
eureka:
server:
enable-self-preservation: false # 关闭⾃我保护模式(缺省为打开)
eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
2.5 Eureka Server集群
Eureka Server不但需要接收服务的⼼跳,⽤来检测服务是否可⽤,⽽且每个服务会定期会去 Eureka申请服务列表的信息,当服务实例很多时,Eureka中的负载就会很⼤,所以必须实现Eureka服 务注册中⼼的⾼可⽤,⼀般的做法是将Eureka Server集群化。
1.配置⽂件
更改Eureka Server的配置⽂件 application.yml,在改配置⽂件中,采⽤多profile格式,代码如下。
---
spring:
config:
activate:
on-profile: peer1
server:
port: 9003
eureka:
instance:
hostname: peer1
client:
service-url:
defaultZone: http://peer2:9004/eureka,http://peer3:9005/eureka
---
spring:
config:
activate:
on-profile: peer2
server:
port: 9004
eureka:
instance:
hostname: peer2
client:
service-url:
defaultZone: http://peer1:9003/eureka,http://peer3:9005/eureka
---
spring:
config:
activate:
on-profile: peer3
server:
port: 9005
eureka:
instance:
hostname: peer3
client:
service-url:
defaultZone: http://peer1:9003/eureka,http://peer2:9004/eureka
注:
-
在yaml单⼀配置⽂件中,可⽤连续三个连字号(—)区分多个⽂件。
-
Spring Boot2.4.x使⽤spring.config.activate.on-profile代替原来的spring.profiles
2.域名解析
因为本地搭建Eureka Server集群,所以需要修改本地的host⽂件,c:\Windows\System32\drivers\etc\hosts,代码如下。
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3
3.启动并测试
通过mvn package编译后,使⽤java -jar⽅式启动,并通过–spring.profiles.active指定配置⽂ 件,本案例中需要启动2个Eureka Server实例,他们的配置⽂件分别是peer1和peer2,命令如下
java -jar 04_cloud_eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar 04_cloud_eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
java -jar 04_cloud_eureka-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer3
启动⽀付服务,⽀付微服务仅向9004的Eureka Server注册,代码如下。
eureka:
client:
service-url:
defaultZone:
http://peer1:9003/eureka,http://peer2:9004/eureka,http://peer3:9005/eureka
此时,⽀付服务并没有向9003注册,访问Eureka Server的节点的peer1管控台界⾯,可⻅ 9004的注册列表信息已经,同步到了9003节点。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/13440.html