Spring Cloud-Eureka 注册中心

Spring Cloud-Eureka 注册中心

第一节 简介

  Eureka是Spring Cloud Netflix微服务套件的一部分,基于Netflix Eureka做了二次封装,主要负责实现微服务架构中的服务治理功能。Eureka是一个基于REST的服务,并且提供了基于Java的客户端组件,可以非常方便的将服务注册到Eureka中进行统一管理。

  服务治理:服务治理是微服务架构中不可缺少的一部分,如阿里的Dubbo框架也是针对服务治理的,而服务治理必须有一个注册中心,除了Eureka还可以使用Consul、Etcd、Zookeeper等来作为注册中心。Dubbo中也有几种注册中心,如基于Zookeeper或基于Redis等,Dubbo主要使用ZooKeeper,而Spring Cloud首选Eureka。

  注册中心无非就是管理所有服务的信息和状态。比如我们可以把12306当作一个注册中心,顾客就相当于调用的客户端,当需要坐车时就登陆12306查询余票,邮票就购买,然后获取火车的车次等信息,最后出发。程序也一样,当需要调用某一个服务时,会先去Eureka中取拉取服务表,查看要调用的服务在不在其中,有的话就拿到服务地址、端口等信息,然后调用。

  使用注册中心的好处是,我们不需要关注提供方,只需关注注册中心即可,就像乘客不必关心有多少火车,只需去12306上看有没有票即可。

为什么说Eureka比Zookeeper更适合作为注册中心?
  因为Eureka是基于AP原则构建的,而ZooKeeper是基于CP原则构建的。Dubbo中大部分都是基于Zookeeper作为注册中心,而Spring Cloud则首选Eureka。

  在分布式领域有个著名的CAP定理:即C为数据一致性,A为服务可用性,P为服务对网络分区故障的容错性。这三个特性在任何分布式系统中都不能同时满足,最多同时满足两个。

  ZooKeeper有一个Leader,在Leader无法使用时通过Paxos(ZAB)算法选举出一个新的Leader。这个Leader的任务就是保证写数据的时候只向这个Leader写入,Leader会同步信息到其他节点,所以通过这个操作可以保证数据的一致性。


第二节 实战

2.1 搭建Eureka Server

  新建一个Maven项目,在pom中配置Eureka依赖。

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
<!-- Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

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

<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

  在启动类中使用注解@EnableEurekaServer,表示开启Eureka Server。

1
2
3
4
5
6
7
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

  在配置文件中添加如下配置。

1
2
3
4
5
6
spring.application.name=eureka-server
server.port=8761
# 此应用为注册中心,所以不需要再注册自己
eureka.client.register-with-eureka=false
# 注册中心职责即维护服务实例,不需要再检索服务
eureka.client.fetch-registry=false

  启动并访问http://localhost:8761/

  可以看到Eureka提供的控制台。

Eureka控制台

2.2 创建服务提供者

  接下来实现一个服务提供者eureka-client-user-service,将其注册到Eureka中,并提供一个接口给其他服务调用。

  新建一个Maven项目,在pom中配置相关依赖。

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
<!-- Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

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

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

<!-- Spring Cloud -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

  在启动类中使用注解@EnableDiscoveryClient,表示当前服务是Eureka的一个客户端。

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientUserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientUserServiceApplication.class, args);
}
}

  在配置文件中添加如下配置。

1
2
3
4
5
6
7
8
9
server.port=8081
spring.application.name=eureka-client-user-service

eureka.client.service-url.defaultZone=http://localhost:8761/eureka

# 采用IP注册
eureka.instance.prefer-ip-address=true
# 采用实例ID格式
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}

  启动服务,控制台会输出注册信息的日志:-redistration status等,回到Eureka控制台并刷新页面,会发现出现了新注册的服务信息。

Eureka控制台

  在客户端新建一个接口服务,测试是否可以访问成功。

1
2
3
4
5
6
7
@RestController
public class HelloController {
@RequestMapping("/user/hello")
public String hello(){
return "hello";
}
}

  重启服务,并访问http://localhost:8081/user/hello

hello

2.3 创建服务消费者

  接下来实现一个服务消费者eureka-client-article-service,消费刚刚创建的hello接口。

  依赖和启动类与上一个service相同,配置信息略有不同。

1
2
server.port=8082
spring.application.name=eureka-client-article-service

  Spring提供了用于访问Rest服务的客户端:RestTemplate。RestTemplate提供了一系列便捷访问远程HTTP服务的方法,可以大大提高客户端的编写效率。

1
2
3
4
5
6
7
@Configuration
public class BeanConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

  创建接口并调用hello接口。

1
2
3
4
5
6
7
8
9
10
@RestController
public class ArticleController {
@Autowired
private RestTemplate restTemplate;

@RequestMapping("/article/callHello")
public String callHello(){
return restTemplate.getForObject("http://localhost:8081/user/hello",String.class);
}
}

  启动服务,并访问/article/callHello查看是否有返回hello。

  上述调用接口是直接通过服务接口的地址来调用的,既然使用了注册中心,消费者就不在需要知道由哪些服务提供接口,我们可以改写代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Configuration
public class BeanConfig {
/**
* LoadBalanced会自动构造LoadBalancerClient接口的实现类到Spring容器
* @return
*/
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

//通过Eureka来消费接口
@RequestMapping("/article/callHello2")
public String callHello2(){
return restTemplate.getForObject("http://eureka-client-user-service/user/hello",String.class);
}

  Eureka不在使用固定地址,而是服务注册到Eureka的名称,对应服务的spring.application.name,即eureka-client-user-service。

callHello2

  访问成功,不过这一改动会使上一个callHello失效。

2.4 开启Eureka认证

  Eureka自带的Web管理页面可以帮助我们查询注册的实例信息,但如果在实际使用中,注册中心地址有公网IP的话,必然能直接访问到,这样是不安全的,所以我们要通过加权限认证来保证安全性

  添加security依赖。

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

  配置文件中添加认证信息。

1
2
3
4
# Security 用户名
spring.security.user.name=linyishui
# Security 密码
spring.security.user.password=123456

  增加配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭csrf
http.csrf().disable();
//支持httpBasic
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}

  重启注册中心,并访问http://localhost:8761/

  此时浏览器会提示输入账户密码。

登录Eureka

  登录成功。

登录Eureka成功

  开启认证后,客户端注册的配置也要添加认证的用户名和密码。

  此时客户端心跳检测不断抛出异常。

抛出异常

  注册中心也无法监测到客户端。

无法监测到客户端

1
2
# 添加Security
eureka.client.service-url.defaultZone=http://linyishui:123456@localhost:8761/eureka

  重启客户端,恢复正常。

恢复正常


第三节 高可用搭建

3.1 高可用原理

  高可用集群(High Availability Cluster,简称HACluster),集群(cluster)就是一组计算机,它们作为一个整体向用户提供一组网络资源。这些单个的计算机系统就是集群的节点(node)。

  高可用集群的出现是为了使集群的整体服务尽可能可用,从而减少由计算机硬件和软件易错性所带来的损失。如果某个节点失效,它的备援节点将在几秒钟的时间内接管它的职责。因此,对于用户而言,集群永远不会停机。

  高可用集群软件的主要作用就是实现故障检查和业务切换的自动化。只有两个节点的高可用集群又称为双机热备,即使用两台服务器互相备份。当一台服务器出现故障时,可由另一台服务器承担服务任务,从而在不需要人工干预的情况下,自动保证系统能持续对外提供服务。双机热备只是高可用集群的一种,高可用集群系统更可以支持两个以上的节点,提供比双机热备更多、更高级的功能,更能满足用户不断出现的需求变化。

3.2 Eureka高可用搭建

  我们已搭建的注册中心只适合于本地开发使用,在生产环境中必须搭建一个集群来保证高可用。Eureka搭建集群的方法很简单:每一台Eureka只需要在配置中指定另外多个Eureka的地址即可。

  假设我们有master和slaveone两台机器,我们要做的就是把master注册到slaveone上,以及把slaveone注册到master上。

  新建项目eureka-server-cluster,搭建步骤和前面相同,增加两个属性文件:application-master.properties和application-slaveone.properties

1
2
3
4
5
6
# application-master.properties中添加
server.port=8761
eureka.client.service-url.defaultZone=http://linyishui:123456@localhost:8762/eureka
# application-slaveone.properties中添加
server.port=8762
eureka.client.service-url.defaultZone=http://linyishui:123456@localhost:8761/eureka

  然后在application.properties中添加以下内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring.application.name=eureka-server-cluster

# 此应用为注册中心,所以不需要再注册自己
eureka.client.register-with-eureka=false
# 注册中心职责即维护服务实例,不需要再检索服务
eureka.client.fetch-registry=false

# Security 用户名
spring.security.user.name=linyishui
# Security 密码
spring.security.user.password=123456

#环境配置
#部署时通过java -jar xxx.jar --spring.profiles.active=dev来指定所用配置
spring.profiles.active=master

  这样在A机器中通过默认master启动,在B机器上添加–spring.profiles.active=slaveone启动。这样无论哪台机器上出问题,都可以继续使用存活的注册中心。

  然后在客户端中指定多个注册中心。

1
2
# 多个节点
eureka.client.service-url.defaultZone=http://linyishui:123456@localhost:8761/eureka,http://linyishui:123456@localhost:8762/eureka

第四节 常用配置

4.1 关闭自我保护

  保护模式主要用于在一组客户端和Eureka Server之间存在网络分区场景时使用。一旦进入保护模式,Eureka Server会尝试保护其服务的注册表中的信息,不再删除其中数据。当网络故障恢复后,此Eureka Server节点会自动退出保护模式。可以通过以下配置关闭自我保护模式。

1
2
# 关闭自我保护模式
eureka.server.enableSelfPreservation=false

4.2 自定义Eureka的InstanceID

  客户端注册时,服务的默认InstanceID格式如下,即“主机名:服务名:服务端口”。

1
${spring.cloud.client.hostname}:${spring.application.name}:${spring.cloud.client.instance_id:${server.port}}

  有时我们会想把IP显示在InstanceID中,可以改为如下格式。

1
2
3
4
eureka.instance.instance-id=${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}

# 使跳转链接也显示IP
eureka.instance.prefer-ip-address=true

4.3 自定义实例跳转链接

  我们实现了IP进行注册,跳转时默认地址为:IP+PORT/info,我们也可以自定义这个跳转的地址。

1
2
# 自定义实例跳转链接
eureka.instance.status-page-url=http:/www.baidu.com

4.4 快速移除已经失效的服务信息

  实际开发中,我们可能会不停的重启服务,由于Eureka的保护机制,节点下线后其服务信息还会一直存在于Eureka中。我们可以通过配置来让移除的速度加快,当然这只推荐在开发环境下使用,生产环境不建议这样配置。

1
2
3
4
# 关闭自我保护模式
eureka.server.enableSelfPreservation=false
# 修改移除服务信息频率,默认60000毫秒
eureka.server.eviction-interval-timer-in-ms=5000

  在客户端中添加如下配置。

1
2
3
4
5
6
# 开启健康检查
eureka.client.healthcheck.enabled=true
# Eureka Client发送心跳频率给Server的频率,默认 30 秒
eureka.instance.lease-renewal-interval-in-seconds=5
# Eureka Server至上一次收到client的心跳后,等待下一次心跳的超时时间,在这个时间内若没有收到下一次心跳,则移除该Instance,默认 90 秒
eureka.instance.lease-expiration-duration-in-seconds=5

  开启健康检查需要引入actuator依赖。

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

第五节 扩展

5.1 Eureka REST API

  Eureka作为注册中心其本质是存储了每个客户端的注册信息,Ribbon在转发的时候会获取注册中心的服务列表,然后根据对应的路由规则来选择一个服务给Feign进行调用。

  Eureka提供了REST API来让我们调用去获取如服务注册信息等常用数据,在我们不选用Spring Cloud技术栈仍要使用Eureka时会比较有用。

  举个例子如对Nginx动态进行upstream的配置:在架构变成微服务之后,微服务是没有依赖的,可以独立部署,端口也可以随机分配,反正会注册到注册中心,调用方也无需关心提供方的IP和Port。但API网关的部署可以这样吗?API网关大部分会用Nginx作为负载,所以Nginx必须知道API网关又哪几个节点,这样网关服务就不能随便启动了,而是需要固定。当然网关不会经常变动,也不会经常发布,唯一不好的地方是不能自动扩容。

  其实利用Eureka提供的API我们可以获取某个服务的实例信息,也就是说我们可以根据Eureka中的数据动态配置Nginx的upstream。这样就可以实现网关的自动部署和扩容了,当然也有很多方案:Lua脚本或自己实现Sheel脚本等。

  Eureka的相关接口信息可以查看官方文档:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

  获取某个服务的注册信息,可以直接GET请求:http://localhost:8761/eureka/apps/eureka-client-user-service。

  浏览器中返回数据的默认格式是XML,如果想要获取Json格式的数据,可以尝试使用如Postman等接口测试工具,在请求头添加以下两行即可。

1
2
Content-Type:application/json 
Accept:application/json

  如果Eureka开启了认证,记得添加认证信息,用户密码必须是Base64编码过的Authorization:Basic 用户名:密码。Postman支持Basic认证,将选项从Headers切换到Authorization,选择认证方式为Basic Auth就可以填写用户信息了。填写完直接发起请求。

5.2 元数据使用

  Eureka的元数据有两种类型,分别是框架制订的标准元数据用户自定义元数据。标准元数据指的是:主机名、IP地址、端口号、状态页、健康检查等信息,这些信息会被发布在服务注册表中,用于服务间的调用。自定义元数据可以使用eureka.instance.metadataMap进行配置。

  自定义元数据可以用来做一些扩展信息,比如灰度发布之类的功能,可以用元数据来存储灰度发布的状态数据,Ribbon转发的时候就可以根据服务的元数据来做一些处理。当不需要灰度发布的时候可以调用Eureka提供的REST API将元数据清除掉。

1
eureka.instance.metadataMap.a=b

  此配置定义了一个key为a的配置,value为b。重启服务,然后通过Eureka提供的REST API来查看刚刚配置的元数据是否已存在与Eureka中。

5.3 EurekaClient使用

  我们可以使用EurekaClient来获取一些我们想要的数据,比如上面自定义的元数据可以直接通过EurekaClient来获取,而不用再调用Eureka REST API。

1
2
3
4
5
6
7
@Autowired
private EurekaClient eurekaClient;
@GetMapping("/article/infos")
public Object serviceUrl() {
return eurekaClient.getInstanceByVipAddress("eureka-client-user-service", false);
// return discoveryClient.getInstances("eureka-client-user-service");
}

  除了EurekaClient,也可以使用DiscoveryClient,这个是Spring Cloud重新封装的类。

5.4 健康检查

  默认情况下,Eureka通过心跳和服务端通信来判断客户端是否存活,在某些场景下,比如MongoDB出现了异常,但你的应用进程还是存在的,这就意味着应用会继续通过心跳上报。

  Spring Boot Actuator提供的/actuator/health端点可以展示应用程序的健康信息,当MongoDB出现异常,端点状态变为DOWN。这时我们希望可以将健康信息传递给Eureka服务端,这样Eureka就可以把应用的实例信息下线,隔离正常请求以防止出错。通过以下配置开启健康检查。

1
eureka.client.healthcheck.enable=true

  扩展健康检查端点来模拟异常。

1
2
3
4
5
6
7
@Component
public class CustomHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) {
builder.down().withDetail("status",false);
}
}

  访问/actuator/health发现现在状态是DOWN,但此时Eureka中的状态还是UP。通过配置开启监控检查,再重启后发现已修改为DOWN。

5.5 服务上下线监控

  有时我们想要对服务的上线和下限进行监控,并通过邮件通知。

Eureka提供了事件监听的方式来扩展:

  • EurekaInstanceCanceledEvent:服务下线事件
  • EurekaInstanceRegisteredEvent:服务注册事件
  • EurekaInstanceRenewedEvent:服务续约事件
  • EurekaRegistryAvailableEvent:Eureka 注册中心启动事件
  • EurekaServerStartedEvent:Eureka Server启动事件

  如下代码演示了监控的过程,但未发送邮件。

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
@Component
public class EurekaStateChangeListener {
@EventListener
public void listen(EurekaInstanceCanceledEvent event) {
System.out.println(event.getSeverId() + "\t" + event.getAppName() + " 服务下线 ");
}

@EventListener
public void listen(EurekaInstanceRegisteredEvent event) {
InstanceInfo instanceInfo = event.getInstanceInfo();
System.out.println(instanceInfo.getAppName() + " 进行注册 ");
}

@EventListener
public void listen(EurekaInstanceRenewedEvent event) {
System.out.println(event.getSeverId() + "\t" + event.getAppName() + " 服务进行续约 ");
}

@EventListener
public void listen(EurekaRegistryAvailableEvent event) {
System.out.println(" 注册中心 启动 ");
}

@EventListener
public void listen(EurekaServerStartedEvent event) {
System.out.println(" Eureka Server 启动 ");
}
}

  在Eureka集群的环境下,每个节点都会触发事件,这时需要控制下发送通知的行为,不然每个节点都会发送通知。


参考博客和文章书籍等:

《Spring Cloud微服务-入门、实战和进阶》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容