Eureka解决了什么问题

这个问题本质是问服务注册中心的价值。很多年前,那个时候微服务还没有兴起,大家用Nginx+Tomcat就搞定了,加机器加Tomcat时候,直接在线改Nginx配置就搞定,这叫手动服务注册。但是在现今的微服务架构中,服务可能动不动就几十个,上百个,算上冗余就几百个,而且微服务的发布很频繁,不断有新的服务加入进来;在应对大流量场景时,还需要扩容缩容。这如果还依靠Nginx那种原始的手工配置方式,是不可行的,成本太高了。所以,就出现了服务注册发现,即服务实例自己会在启动后注册到服务注册中心,这叫服务注册,然后每个服务实例又从服务注册中心拉取服务注册表,动态的感知到服务的上下线,这叫服务发现。所以,服务注册中心这种技术是在微服务架构中自然而然就过渡过来的,并不突兀。eureka作为Netflix全家桶中的服务注册中心实现,其意义也就不言自明了。

快速体验Eureka

1、构建一个Eureka Server

创建一个maven工程,加入如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.cloud-version>Hoxton.RELEASE</spring.cloud-version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</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>

创建一个application.yml文件,配置如下内容:

1
2
3
4
5
6
7
server:
port: 8761

eureka:
client:
register-with-eureka: false #1
fetch-registry: false #2

#1 不要将此服务注册到eureka。因为当前服务已经是eureka server了

#2 不要拉取服务注册表。

创建一个启动类:

1
2
3
4
5
6
7
8
9
10
@Slf4j
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {

public static void main(String[] args) {
SpringApplication.run(EurekaServer.class, args);
log.info("eureka server 启动成功");
}
}

启动后,见到如下输出即为成功:

eureka server启动成功

2、开发一个服务提供者

1
2
3
4
5
6
7
8
@RestController
public class ServiceAController {

@GetMapping("/sayHello/{name}")
public String sayHello(@PathVariable("name") String name) {
return "hello," + name;
}
}
1
2
3
4
5
6
7
8
9
@Slf4j
@EnableEurekaClient
@SpringBootApplication
public class ServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceAApplication.class, args);
log.info("ServiceA 启动成功");
}
}

创建一个application.yml文件,配置如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8080

spring:
application:
name: serviceA

# eureka client config
eureka:
instance:
hostname: loacalhost
client:
service-url:
defaultZone: http://localhost:8761/eureka

服务A启动成功

3、开发一个服务调用者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@Configuration
public class ServiceBController {

@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}

@Resource
private RestTemplate restTemplate;

@GetMapping("/greeting/{name}")
public String greeting(@PathVariable("name") String name) {
return restTemplate.getForObject("http://serviceA/sayHello/" + name, String.class);
}
}
1
2
3
4
5
6
7
8
9
10
@Slf4j
@EnableEurekaClient
@SpringBootApplication
public class ServiceBApplication {

public static void main(String[] args) {
SpringApplication.run(ServiceBApplication.class, args);
log.info("ServiceB 启动成功");
}
}

创建一个application.yml文件,配置如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 9000

spring:
application:
name: serviceB

# eureka client config
eureka:
instance:
hostname: loacalhost
client:
service-url:
defaultZone: http://localhost:8761/eureka

启动服务

serviceB启动成功

4、进行实验

浏览器访问localhost:8761打开Eureka注册服务控制台,可发现ServiceA和B都注册到Eureka Server上了

eureka控制台

在浏览器访问http://localhost:9000/greeting/jack,就可以看到结果了

结果

5、画图讲解Hello Word原理

  • 服务A和B的启动类上都有@EnableEurekaClient注解,此注解会将服务变为Eureka客户端应用,将自己注册到Eureka Server上
  • 服务B和服务A都会从Eureka Server拉取服务注册表
  • 服务B发起调用服务A的请求时,Ribbon根据拉取到本地的服务注册表,将服务名替换为hostname+port并发起服务调用

eureka helloword原理

Eureka集群实战

1、eureka注册中心集群

eureka如此重要的服务注册中心,肯定不可能是单点的。下面我们来构建一个eureka server集群。

分别使用如下配置各启动一个eureka server,组成一个集群。

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8762

eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://peer2:8761/eureka
instance:
hostname: peer2
1
2
3
4
5
6
7
8
9
10
11
server:
port: 8761

eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://peer2:8762/eureka
instance:
hostname: peer1

小技巧:可以同一个工程,分别启动一次即可。

如果是IDEA的话,可能不让你同时启动两个,按照如下配置一下即可,在Allow parallel run前打上勾即可

idea启动多个实例

随意访问一个eureka控制台,可发现两个节点已经组成集群。DS Replicas指的是当前eureka server的副本

eureka集群

小提示:假如访问peer2节点,会发现没有服务实例。不用着急,下面我们改造后就OK了。

2、将服务改造为注册到eureka server集群

分别将服务A和B的配置改为如下这样,变更部分为最后一行

1
2
3
4
5
6
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka

改完重启后访问eureka控制台页面即可看到都有服务实例了。

服务健康自检机制

默认情况下,你的所有服务,比如服务A和服务B,都会自动给eureka注册中心同步心跳,续约。如果eureka server一段时间内没有感知到某个服务的心跳,就会把那个服务下线。

1、基于spring-boot-actuator的服务健康检查

服务加入如下依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

重启后访问http://localhost:port/actuator/health可以看到返回的服务状态,默认是UP

2、spring-cloud-eureka整合spring-boot-actuator实现自定义服务健康检查

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 自定义健康检查器
*/
@Component
public class ServiceAHealthIndicator implements HealthIndicator {

@Override
public Health health() {
// 此处可以加上一些失败条件判断,符合这些条件就返回Status.DOWN即可
// 主要是自己依赖的一些基础设施,看他们是否挂了或者自己连不上他们了,来决定自己是否健康
return new Health.Builder(Status.UP).build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* eureka整合actuator实现自定义健康检查器
*/
@Component
public class ServiceAHealthCheckHandler implements HealthCheckHandler {

@Autowired
private ServiceAHealthIndicator indicator;

@Override
public InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus) {
Status status = indicator.health().getStatus();
// 可以根据status来决定返回什么实例状态
return InstanceInfo.InstanceStatus.UP;
}
}

eureka client会定时调用getStatus方法来判断服务实例的状态,如果状态变化了,就会通知eureka server。当服务实例挂掉了,那么eureka server就会感知到,然后下线这个服务实例。

3、生产经验分享

一般情况下很少使用这第二种方式自定义健康状态检查器,原因如下:

  • 对于eureka
    eureka默认是client通过心跳与eureka server保持心跳通信,如果心跳不及时或者没有心跳了,那么就说明那个服务挂了,然后eureka server就会摘除这个服务实例,这个机制足够用了
  • 对于服务
    在大规模部署中,每个服务都很复杂,不可能去一个个搞一堆健康检查的。大部分情况下,我们就是会对外暴露一个/health接口,然后专门外部定时调用各个服务的/health接口来判断当前这个服务状态就足够了。

Eureka常见配置

心跳检测

eureka客户端,默认每隔30秒发送一次心跳到eureka server(以后简称注册中心)。

1
2
3
4
5
6
eureka:
instance:
# 配置eureka client与注册中心心跳间隔时间,默认是30秒。在eureka中,心跳被称为续约(renew)
lease-renewal-interval-in-seconds:
# 配置如果多少秒内没有收到一个服务实例的心跳,就摘除(expiration 失效)这个服务,默认是90秒。
lease-expiration-duration-in-seconds:

一般情况下,这俩参数建议不要修改

注册表抓取

默认情况下,客户端每隔30秒去注册中心抓取最新的注册表,然后缓存到本地。通过下面参数可修改时间间隔

1
2
3
4
eureka:
client:
# 客户端每隔多少秒去注册中心抓取最新注册表,默认30秒
registry-fetch-interval-seconds:

自定义元数据

通过下面的metadata-map定义服务的元数据,反正就是你自己需要的一些东西,不过一般挺少使用的

1
2
3
eureka:
instance:
metadata-map:

自我保护模式

如果在eureka控制台看到下面这样的东西:

eureka自我保护机制

这就是eureka进入了自我保护模式。如果客户端的心跳失败超过了一定的比例,或者说在一定时间内(默认15分钟)接收到的服务续约低于85%,那么就会认为是自己网络故障了,这个时候会导致人家client无法发送心跳,如果不加以控制,eureka服务实例摘除机制会将实例一个个的下掉。为了避免这种情况的发生,eureka这个时候会一种保护模式,保护服务注册表,不会立即把失效的服务实例摘除。在测试的时候一般会关闭这个自我保护模式:

1
2
3
eureka:
server:
enable-self-preservation: false

总结

本篇博客,我们已经初步体会如何使用eureka,剖析了eureka的常见配置,后续我们马上进入源码剖析环节。

Comments