一、背景介绍
在之前的文章中,我们介绍了 Spring Cloud 相关的技术体系,相信大家对微服务已经有了初步的认识。如果做过微服务架构相关的项目,会发现服务注册中心、服务负载均衡和服务远程调用这三个组件,可以说是核心中的核心,整个微服务之间的互调工作都必须通过这几个组件来完成。
其中服务注册中心,主要专注于对整个微服务系统的服务注册、发现、负载、降级、以及对服务健康状态的监控和管理功能等。
今天通过这篇文章,我们一起来了解一下 Spring Cloud 技术体系中最核心的组件之一 Eureka。
Eureka 是 Netflix 开源的一款提供服务注册和发现的框架,它允许服务实例进行自我注册和发现,从而实现服务的负载均衡和故障转移。
在 Spring Cloud 的服务治理下,开发者只需通过简单的注解配置,即可快速完成具有高可用的服务注册中心。
尽管 Netflix 之后对 Eureka 不再维护了,但是它基本思想和原理对于后来的服务注册中心发展有着深远的影响,例如阿里开源的 Nacos,能隐约看到其身影。
二、架构演变介绍
没有服务注册中心之前,当一个项目调用另一个项目接口时,通常调用流程类似于如下图。
图片
当有了服务注册中心之后,任何一个项目都不能直接去调用,都需要通过服务中心来完成,调用流程会变成如下图。
图片
可以看到,中间插入“服务中心”之后,虽然多了一个模块,流程变得有些复杂,但是整个调用流程变得更加灵活了。尤其是在集群环境下,“服务中心”的优势非常明显。
在单体架构环境下,项目 A 远程调用项目 B 的接口,通常的做法是将接口地址 (例如http://192.168.1.1:8080/a) 写在配置文件中,当项目 B 换了服务器或者端口发生了变化,就需要手动同步更新配置文件,十分麻烦。更重要的是,对于要求高可用的项目来说,通常都是集群部署,项目 B 会部署在多台服务器上,需要人工进行配置的工作量巨大。
在微服务架构环境下,项目 A 和项目 B 在服务启动后,会主动将服务实例里面的接口地址、主机 IP 和端口等信息推送到“服务中心”,这个过程我们称之为服务注册;当项目 A 准备向项目 B 发起远程调用时,会先从“服务中心”拉取相关注册表信息,这个过程可以称之为服务发现;如果找到有效的目标地址,最后再发起远程调用。
整个过程,项目 A 无需关心项目 B 所在的主机 IP 和端口信息,也不需要写死在配置文件中,当项目 B 部署在多台服务器上,项目 A 在发起调用之前,只需要抽取其中一个有效的服务发起远程调用即可,无需人工干预,极大的减轻了运维的工作量。
可以看得出,“服务中心”的引入,相比单体架构而已,虽然交互变得复杂了一点,但是带来的好处也是明显的,开发者只需通过较少的运维工作,就可以实现服务高可用的效果。
下面我们一起来看看,如何利用 Eureka 搭建一套高可用的服务注册中心。
三、方案实践
在 Spring Cloud 生态中,Eureka 采用了 C-S 的架构设计,由 Eureka server 和 Eureka client 两个组件组成。Eureka server,通常用来做服务中心,提供服务的注册与发现功能;Eureka client,用于与服务中心进行交互,同时作为轮询负载均衡器,并提供服务的故障切换支持。
它们之间的关系吗,可以用如下图来描述。
图片
在应用启动后,Eureka client 每隔一个时间段会向 Eureka server 发送心跳(默认 30s),如果 Eureka server 在多个心跳周期没有接受到某个节点的心跳,Eureka server 认为这个节点已经挂掉,会将其从服务注册表中将这个服务节点移除掉。
从上图可以看到,在 Eureka 架构中,有 3 个重要的角色,分别是:
- Eureka Server:服务注册中心,负责提供服务注册和发现功能
- Service Provider:服务提供方,会将自身服务注册到 Eureka,以便服务消费方能够找到
- Service Consumer:服务消费方,会从 Eureka 服务中心获取服务注册列表,以便能够消费服务
下面我们通过一个简单的项目案例,来体验一下这三个角色的关系。
3.1、创建服务注册中心
首先,创建一个 Spring Boot 工程,命名为eureka-server,并在pom.xml中引入相关的依赖内容,示例如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
接着,创建一个服务启动类并添加@EnableEurekaServer注解,表示当前是一个 Eureka 服务注册中心。
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
默认情况下,服务注册中心也会将自己作为客户端来注册它自己,因此我们需要禁用它的客户端注册行为。
只需要在application.properties配置文件中,添加如下信息即可。
spring.application.name=eureka-server
server.port=8001
# 表示当前 eureka 实例主机名称,不配置的话默认为当前电脑名称
eureka.instance.hostname=localhost
# 表示是否将自己注册到Eureka Server,默认为true
eureka.client.register-with-eureka=false
# 表示是否从Eureka Server获取注册信息,默认为true
eureka.client.fetch-registry=false
启动服务之后,访问http://localhost:8001/,可以看到下面的页面,没有发现任何服务,这是因为还没有客户端注册。
图片
3.2、创建服务提供方
与上文类似,首先创建一个 Spring Boot 工程,命名为eureka-provider,并在pom.xml中引入相关的依赖内容,示例如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
接着,创建一个服务启动类并添加@EnableDiscoveryClient注解,表示当前是一个 Eureka 客户端服务。
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
然后,创建一个 web 接口,以便等会发起 RPC 调用测试,可以通过DiscoveryClient接口从服务中心查询所有注册的服务实例。
@RestController
publicclass HelloController {
@Autowired
private DiscoveryClient discoveryClient;
/**
* 从服务中心查询注册的服务
* @return
*/
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
@GetMapping("/hello")
public String index() {
System.out.println("收到客户端发起的rpc请求!");
return"hello,我是服务提供方";
}
}
最后,还需要在application.properties配置文件中,添加服务注册中心地址,示例如下:
spring.application.name=eureka-provider
server.port=9001
# 设置与Eureka Server交互的地址,多个地址可使用【,】分隔
eureka.client.serviceUrl.defaultZnotallow=http://localhost:8001/eureka/
启动服务之后,再次访问http://localhost:8001/,可以看到下面的页面,eureka-provider成功注册到服务中心。
图片
其次,也可以直接访问http://localhost:9001/dc,查询当前服务中心注册的服务,可以得到类似于如下内容。
Services: [eureka-provider]
3.3、创建服务消费方
同理,创建一个 Spring Boot 工程,命名为eureka-consumer。由于服务提供方和服务消费方,都属于 Eureka 客户端服务,其pom.xml所需要的依赖内容和服务启动配置完全一致,在此就不重复粘贴了。
接着,编写一个配置类,因为需要发起远程调用,我们可以利用RestTemplate工具发起 HTTP 请求。
@Configuration
public class WebConfig {
/**
* 初始化一个 RestTemplate 工具
* @return
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在 Spring Cloud Commons 中提供了大量的与服务治理相关的抽象接口,例如上文介绍的DiscoveryClient,还有下文要使用的LoadBalancerClient。
LoadBalancerClient是一个负载均衡客户端接口,我们可以利用它从服务注册中心获取有效的服务实例,然后发起远程调用,示例如下:
@RestController
publicclass HelloController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
/**
* 从服务中心查询注册的服务
* @return
*/
@GetMapping("/dc")
public String dc() {
String services = "Services: " + discoveryClient.getServices();
System.out.println(services);
return services;
}
/**
* 发起远程调用测试
* @return
*/
@GetMapping("/rpc")
public String rpc() {
// 从服务提供方中选择一个有效的服务实例
ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-provider");
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello";
System.out.println("远程调用地址:" + url);
// 通过 http 方式发起远程调用
String result = restTemplate.getForObject(url, String.class);
return"发起远程调用,收到返回的信息:" + result;
}
}
最后,在application.properties配置文件中添加相关的配置信息。
spring.application.name=eureka-consumer
server.port=9002
eureka.client.serviceUrl.defaultZnotallow=http://localhost:8001/eureka/
将服务注册中心、服务提供方、服务消费方依次启动起来,然后访问http://localhost:9002/rpc,可以得到类似于如下内容。
发起远程调用,收到返回的信息:hello,我是服务提供方
可以清晰的看到,服务消费方成功的远程调用了服务提供方的接口,并收到返回结果。
四、集群配置
服务注册中心是一个特别关键的服务,如果是单节点,一旦挂了,整个微服务就无法使用了。对于生产环境,通常都是通过集群方式来完成部署。
实际上,Eureka 可以通过互相注册的方式来实现高可用的部署,因此我们只需要将 Eureke Server 配置其他可用的 serviceUrl 就能实现高可用部署。
对于双节点服务注册中心,可实现的思路如下!
4.1、双节点服务注册中心配置
1)创建application-eureka1.properties配置文件,作为eureka1服务中心的配置,并将serviceUrl指向eureka2,内容如下:
spring.application.name=eureka-server
server.port=8001
eureka.instance.hostname=eureka1
eureka.client.serviceUrl.defaultZnotallow=http://eureka2:8002/eureka/
2)创建application-eureka2.properties配置文件,作为eureka2服务中心的配置,并将serviceUrl指向eureka1,内容如下:
spring.application.name=eureka-server
server.port=8002
eureka.instance.hostname=eureka2
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/
3)在本地 hosts 文件做了一个假域名映射,用来配置区分不同的 Eureka Server 地址。
127.0.0.1 eureka1
127.0.0.1 eureka2
4)将服务注册到 eureka 集群
spring.application.name=eureka-consumer
server.port=9002
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka2:8002/eureka/
5)将服务进行打包,然后启动
#打包
mvn clean package
# 分别以 eureka1 和 eureka2 配置信息启动 eureka
java -jar eureka-server.jar --spring.profiles.active=eureka1
java -jar eureka-server.jar --spring.profiles.active=eureka2
依次启动服务后,访问http://localhost:8001/,可以得到类似于如下内容。
图片
其中eureka2表示备用节点,将其中一个节点停掉,服务依然可以正常运行。
4.2、eureka 集群配置
在生产环境,通常会配置三台及以上的服务注册中心,以此来保证服务的稳定性,配置原理也类似,将当前服务注册中心分别指向其它的服务注册中心。
配置文件案例如下:
# 第一个配置文件
spring.application.name=eureka-server
server.port=8001
eureka.instance.hostname=eureka1
eureka.client.serviceUrl.defaultZnotallow=http://eureka2:8002/eureka/,http://eureka3:8003/eureka/
# 第二个配置文件
spring.application.name=eureka-server
server.port=8002
eureka.instance.hostname=eureka2
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka3:8003/eureka/
# 第三个配置文件
spring.application.name=eureka-server
server.port=8003
eureka.instance.hostname=eureka3
eureka.client.serviceUrl.defaultZnotallow=http://eureka1:8001/eureka/,http://eureka2:8002/eureka/
五、小结
最后总结一下,Eureka 是 Spring Cloud 体系中最重要的组件之一,主要用于服务的注册和发现管理。
虽然之后 Netflix 对其停止维护了,以至于 Spring Cloud 官方不建议大家在新的项目中优先使用,但是 Eureka 作为初代的服务注册中心,但是其基本思想和原理对于后来的服务注册中心发展有着深远的影响。
对于了解和学习 Spring Cloud 技术体系,Eureka 依然是必不可少的一站。
六、参考
1.http://www.ityouknow.com/springcloud/2017/05/10/springcloud-eureka.html