netflix 下的eureka作为当前最为流行的三大服务注册与发现组件之一,与zookeeper、consol齐名。本文开始学习spring-cloud框架下的eureka使用。
所有应用作为Eureka Client和Eureka Server交互,服务提供者启动时向Eureka
Server注册自己的IP、端口、提供服务等信息,并定时续约更新自己的状态。
服务消费者通过Eureka Server发现得到所需服务的提供者地址信息,然后向服务提供者发起远程调用。
为了保证Eureka注册中心的高可用,可以集群部署,其中一个节点信息有更新时通知其他Server节点,不同节点的Eureka通过Replicate进行数据同步。
在Eureka响应的过程中,有三个角色,分别是Eureka、服务提供者、服务消费者;
在服务启动后,服务提供者向Eureka注册自己的信息,如调用地址、提供服务信息等;
Eureka为服务注册中心,向外暴露自己的地址,负责管理、记录服务提供者的信息,同时将符合要求的服务提供者地址列表返回服务消费者;
服务消费者向Eureka订阅服务,表达自己的需求,然后得到服务提供者消息,远程调用即可;
Eureka包含两个组件:Eureka Server和Eureka Client,作用如下:
由于在超时(默认90s)未接受到服务提供者的心跳续约信息后,Eureka Server就注销该实例,但往往由于微服务之间的跨进程调用,受网络通信情况影响较大,比如在微服务状态正常,但网络分区故障时,注销服务实例会让大部分微服务不可用,为避免这样的情况,引进了Eureka的自我保护机制。自我保护机制的原理是,当某个Eureka Server节点在短时间内丢失了过多的客户端节点时,该节点就进入自我保护机制,不再注销任何微服务实例,当网络故障恢复正常后,该节点自动退出自我保护机制。
自我保护机制根据自我保护阈值来区分是Eureka Server还是Eureka Client出了问题导致服务未能按时续约。
一个服务按流程依次为服务注册,然后周期性地续约服务,若服务正常停止,则在停止服务之前向注册中心发送注销请求,若为非正常停止,则不会发送,该服务由Eureka Server主动剔除。
在服务提供者启动时用来注册服务,以及提供者状态发生变化时更新服务状态。
server接受服务注册后,首先保存注册信息;然后更新自我保护阈值,供服务剔除机制使用;最后同步服务信息,将此事件同步至其他Eureka Server节点。
由服务提供者定期调用,向server发送心跳信息续约服务。
在注册中心接受到续约请求后,更新服务对象的最近续约时间,然后同步服务信息,将此事件同步至其他Eureka Server节点。
剔除服务之前会首先判断服务是否过期,是否过期的条件之一就是最近续约时间和当前时间差值是否大于阈值。
由服务提供者shut down时调用,用来注销自己的服务。
类似注册机制,在注册中心接受到cancel请求后,首先删除服务信息,然后更新(剔除服务)阈值,最后同步该事件到其他Eureka Server节点。
服务剔除包括三个步骤,首先判断是否满足服务剔除的条件,然后找出过期的服务,最后执行服务的剔除。
判断是否满足服务剔除的条件——主要看Eureka是否开启自我保护机制
关闭自我保护机制的情况下,直接进入下一步;
开启自我保护机制的情况下,只有实际续约数大于自我保护阈值(说明大部分服务可用,判断Client出了问题,反之判断为Server出了问题),才会进入服务剔除流程。
自我保护阈值=服务总数*(60s / 客户端续约间隔)*自我保护阈值因子。
找出过期的服务
遍历所有服务,判断最近续约时间距离当前时间差值大于设定的阈值,就标记为过期,并将所有过期的服务保存到集合中。
剔除服务
遍历所有过期服务,通过洗牌算法确保每次都公平地选择出要剔除的服务,最后进行剔除。
由服务消费者调用,发现注册中心同名服务列表。
通常服务提供者地址信息列表会在本地缓存一份,默认每30s更新一次缓存信息。
用来实现Server之间的服务信息同步,服务提供者仅需在一个server注册就可在server集群中更新信息。
接受注册请求的节点将请求再次转发到其他的server节点,调用接口和参数等完全相同,除了标记isReplicate=true,避免重复的同步Replicate。
信息同步为异步方式完成,所有不保证节点信息的强一致性,只能保证最终一致。
新的server节点加入,启动时将自己作为消费者,拉取所有已注册信息,然后将每个服务注册到自身节点,标记isReplicate=true,完成初始化。
删除src文件夹,作为项目运行环境。然后新建module名为sever-1;
<?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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hjl</groupId>
<artifactId>eureka-server-test</artifactId>
<version>1.0-SNAPSHOT</version>
<!--配置组件版本-->
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.0</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!--Spring Cloud 统一版本管理-->
<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>
</project>
package com.hjl.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 11:05
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
在启动类上添加@EnableEurekaServer注解,标注为Eureka Server节点。
#服务端口号
server.port=1001
#应用名称
spring.application.name=eureka-server
#实例名称
eureka.instance.appname=localhost
#client.register-with-eureka为是否将应用注册到eureka,默认为true,单点server节点指定为false
eureka.client.register-with-eureka=false
#client.fetch-registry为是否从注册中心拉取已注册信息,默认为true,单点server节点指定为false
eureka.client.fetch-registry=false
#client.service-url.defaultZone指定注册中心地址
eureka.client.service-url.defaultZone=http://localhost:${server.port}/eureka
本地测试配置网址:http://localhost:1001/
当前显示还无注册实例。
学习中遇到的问题:
1、com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server错误
解决:该问题是因为没有对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>eureka</artifactId>
<groupId>com.hjl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-provider</artifactId>
<!--引入依赖-->
<dependencies>
<!--spring-web相关依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入netflix-eureka依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--监控依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--测试相关依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package com.hjl.eureka_provider.controller;
import com.hjl.eureka_provider.service.ProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 12:48
*/
@RestController
public class ProviderController {
@Autowired
ProviderService providerService;
@RequestMapping("/hello")
public String visit(@RequestParam("name") String name){
return providerService.sayHello(name);
}
}
package com.hjl.eureka_provider.service;
import org.springframework.stereotype.Service;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 12:49
*/
@Service
public class ProviderService {
public String sayHello(String name){
return "您好," + name;
}
}
package com.hjl.eureka_provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 12:46
*/
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaProviderApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class,args);
}
}
#端口号
server.port=1002
#应用名称
spring.application.name=eureka-provider
#实例ID
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
#client.service-url.defaultZone指定注册中心地址
eureka.client.service-url.defaultZone=http://localhost:1001/eureka
#client端,开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled=true
#设置租期更新时间,默认30秒
eureka.instance.lease-renewal-interval-seconds=30
#设置租期到期时间,默认90秒
eureka.instance.lease-expiration-duration-in-seconds=90
发现eureka-provider服务已经服务注册中心注册。
学习过程遇到的错误问题:
1、报错解决:Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}
报错原因:
程序中指定的 http://localhost:8088/eureka 注册中心地址无效,而用了springcloud默认的注册中心地址–http://localhost:8761/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>eureka</artifactId>
<groupId>com.hjl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>introduce</artifactId>
<!--引入依赖-->
<dependencies>
<!--spring-web相关依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入netflix-eureka依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入netflix-ribbon依赖包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--监控依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--测试相关依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
此处引入了ribbon依赖包,提供负载均衡
package com.hjl.introduce.controller;
import com.hjl.introduce.service.IntroduceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 12:48
*/
@RestController("/provider")
public class IntroduceController {
@Autowired
IntroduceService providerService;
@RequestMapping("/intro")
public String visit(@RequestParam("name") String name){
return providerService.sayHello(name);
}
}
package com.hjl.introduce.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 12:49
*/
@Service
public class IntroduceService {
@Autowired
private RestTemplate restTemplate;
public String sayHello(String name){
String helloUrl = "http://eureka-provider/hello?name=" + name;
String res = restTemplate.getForObject(helloUrl,String.class);
return res;
}
}
package com.hjl.introduce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* TODO:
*
* @Version 1.0
* @Author HJL
* @Date 2021/12/27 19:04
*/
@EnableEurekaClient
@SpringBootApplication
public class EurekaIntroduceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaIntroduceApplication.class,args);
}
@Bean
@LoadBalanced
@Autowired
RestTemplate restTemplate(){
return new RestTemplate();
}
}
同时实现RestTemplate模板bean
#端口号
server.port=2001
#应用名称
spring.application.name=eureka-introduce
#实例ID
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
#client.service-url.defaultZone指定注册中心地址
eureka.client.service-url.defaultZone=http://localhost:1001/eureka
#client端,开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled=true
#设置租期更新时间,默认30秒
eureka.instance.lease-renewal-interval-seconds=30
#设置租期到期时间,默认90秒
eureka.instance.lease-expiration-duration-in-seconds=90
在网页访问http://localhost:2001/intro?name=hjl
表示已经成功远程调用eureka-provider服务。
eureka服务注册与发现保证AP,即高可用、高容错的能力,保证即使在多个服务节点挂掉的情况下依然能正常使用服务。