前言
服务注册与发现是微服务中最为基础的环节,而 Eureka 就是一个可以帮助你实现服务注册与发现的选择之一。如果你对 Eureka 和服务发现了解甚少,那么该篇博客将会帮助到你。文中通过具体操作带你了解如下内容:
- 什么是服务注册与发现
- 什么是 Eureka
- SpringCloud Eureka 单台环境搭建
- SpringCloud Eureka 高可用环境搭建
- SpringCloud Eureka + RestTemplate + Ribbion 的使用
- SpringCloud Feign + SpringCloud Eureka 的使用
阅读本文需要你熟悉SpringBoot项目的基本使用即可,还有一点需要注意的是在操作过程中尽量和我本地环境一致,因为环境不一致可能会带来一些问题。我本地环境如下:
SpringBoot Version: 2.1.0.RELEASE
SpringCloud Version: Greenwich.RELEASE
Apache Maven Version: 3.6.0
Java Version: 1.8.0_144
IDEA:Spring Tools Suite (STS)
服务注册与发现介绍
上面讲到服务发现是微服务中最为基础的环节,什么是服务发现呢 ?我们可以从单体架构说起,单体架构各个服务都在一起,是不需要被发现的。但是在微服务的架构中会出现很多的服务。服务与服务之间怎么获取调用地址URL 呢 ?例如 一个商城网站有商品服务和订单服务。查询订单服务时需要从商品服务中获取商品的信息。
如果有1个订单服务和1 个商品服务,订单服务服务在配置文件中将商品服务的 URL 配置在配置文件中即可。如果出现 2个商品服务,你可能会想到将2个商品服务URL 配置到订单服务中。如果出现上千个服务呢?你可能发现这种方式不是一个好的方案。同时还有一个问题就是:商品服务宕机后。如何将宕机的服务从配置文件中剔除。
我们可以将商品服务的 URL 地址信息都注册到统一的服务上,在调用时拉取所有商品服务的 URL 信息。订单服务可以自定义负载均衡策略来选取一个 URL 地址进行调用,如果注册中心发现某个商品服务已经宕机,可以将其从注册中心剔除。而今天的主角 Eureka 就是通过这种机制来完成服务发现的。
接下来就正式开始今天的主角 Eureka 的详细介绍!
Eureka 是什么 ?
在介绍操作前,首先来了解一下 Eureka 是什么?Eureka 是 Netflix 公司开源的一个基于 REST 服务的服务发现框架,主要用于AWS云,用于定位服务,以实现中间层服务器的负载平衡和故障转移。SpringCloud 对 Netflix 众多的开源框架都封装到其子项目spring-cloud-netflix 中,而 Eureka 就是其中的一个。SpringCloud 可以通过 Eureka 来完成微服务的服务注册与发现,当然你也可以使用其他的服务发现框架在 SpringCloud 中。
具体使用和介绍可以访问 Eureka 的 GitHub wiki 进行查看
https://github.com/Netflix/eureka/wiki/
https://github.com/Netflix/eureka/wiki/Eureka-at-a-glance
闲话少说,开始我们的 Eureka 环境搭建!
SpringCloud Eureka 单台环境搭建
Eureka Server 端搭建
按下图所示打开SpringBoot Starter构建的窗口 File--New--Spring Starter Project
勾选 Eureka Server
如果不勾选 Eureka Server 也可以在SpringBoot项目中引入 spring-cloud-starter-netflix-eureka-server 依赖即可。代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
在 SpringBoot Application上声明 @EnableEurekaServer
在 application.properties 添加配置信息
server.port=8761
spring.application.name=EUREKASERVER
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone= http://localhost:8761/eureka/
eureka.server.enable-self-preservation=false
server.port: 配置服务端的端口号。
spring.application.name: 配置服务端应用名称.。
eureka.client.register-with-eureka: 是否将自己注册到服务端,Eureka 服务端需要配置成false 默认是 true
eureka.client.fetch-registry:是否从其他Eureka服务端获取注册信息,默认为true。如过是单台服务器可以设置为false。
eureka.server.enable-self-preservation:开启自我保护模式,默认为 true 表示为开启。开发中为了方便可以将其设置为false。
eureka.client.service-url.defaultZone:设置 Eureka 服务端的地址,如果是单体服务就配置该 Eureka 单台服务的 ip+端口号+/eureka。需要注意的是 /eureka 一定要加上。
自我保护模式介绍:
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制。
自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
1、Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
2、Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
3、当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
自我保护介绍内容摘抄自 —— Java技术栈的博客: Spring Cloud Eureka 自我保护机制 https://segmentfault.com/a/1190000015349644
如果 eureka.client.register-with-eureka=true 或者不配置该信息会,Eureka 服务端本身也注册到服务端如下图所示:
Eureka Client 端搭建
首先在 SpringBoot Starter 构建的窗口 勾选 Eureka Discovery Client 和 Spring Web Starter 如下图所示:
需要注意的是如果没有勾选 Spring Web Starter 客户端注册是注册不到服务端的。
如果不勾选,也可以直接在SpringBoot项目中引入下面2个依赖来完成。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
然后在 SpringBoot Application 上声明 @EnableDiscoveryClient 如下图所示:
最后在 application.properties 配置如下信息:
server.port=8760
spring.application.name=EUREKACLIENT
eureka.client.service-url.defaultZone= http://localhost:8762/eureka/
客户端的 eureka.client.service-url.defaultZone 配置和Eureka Server(Eureka注册中心)配置一样。
访问 Eureka Server(Eureka注册中心)端如下图所示中Instances currently registered With Eureka中查看注册的客户端。
如果你按照上述方式搭建并未成功,可以请参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为: spring-cloud-eureka-single-service(Eureka Server)和 spring-cloud-eureka-single-service-client( Eureka Client)进行对比查看是否配置有误。
spring-cloud-get-started 项目地址:https://github.com/zhuoqianmingyue/spring-cloud-get-started
搭建高可用的 Eureka 服务端
在生产环境中为保证 Eureka 服务的高可用,我们需要搭建多台 Eureka Server(Eureka注册中心)。Eureka Server(Eureka注册中心)搭建集群还是比较简单的,只需要让各个服务端互相注册即可。官网搭建集群框架图如下:
我来举个例子,假如你有2台 Eureka Server 服务,名称分别为 Eureka1、Eureka2。
Eureka1的配置: 需要将 eureka.client.service-url.defaultZone 配置成 Eureka2 的服务地址。配置代码如下:
eureka.client.service-url.defaultZone=http://eureka2:8762/eureka/
Eureka2 的配置 需要将 eureka.client.service-url.defaultZone 配置成 Eureka1 的服务地址。配置代码如下:
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/
假如你有3台 Eureka 服务名称分别为 Eureka1、Eureka2、Eureka3。
Eureka1的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka2 和 Eureka3的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka2:8762/eureka/,http://eureka3:8763/eureka/
Eureka2的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka1 和 Eureka3的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/,http://eureka3:8763/eureka/
Eureka3的配置: 需要将 eureka.client.service-url.defaultZone 配置上 Eureka1 和 Eureka2的服务地址并用逗号分隔。
eureka.client.service-url.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
需要注意的是如果配置了 eureka.client.fetch-registry 需要将其改为 true,如果没有配置可以忽略。eureka.client.fetch-registry 表示可以从其他 Eureka Server 获取注册信息。
还有一点需要注意的是,eureka.client.serviceUrl.defaultZone 配置不能使用 localhost。
如果你使用的是Windows 系统,并且只有一台机器的情况下,需要在 HOSTS 中配置三个域名信息。
我本地代码服务端和客户端搭建具体配置:
客户端和服务端引入依赖和添加@EnableXXX的注解比较简单,我这里就不在详细说明了。可以参考 SpringCloud Eureka 单台环境搭建 中的介绍。
主要需要注意的是 application.properties 中的配置,以下是我本地服务端和客户端配置详细信息:
Eureka服务1的配置
server.port=8761
spring.application.name=MULTISERVICEEUREKASERVER1
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER2:8762/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/
eureka.server.enable-self-preservation=falses
Eureka服务2的配置
server.port=8762
spring.application.name=MULTISERVICEEUREKASERVER2
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/
eureka.server.enable-self-preservation=false
Eureka服务3的配置
server.port=8763
spring.application.name=MULTISERVICEEUREKASERVER3
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=true
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER2:8762/eureka/
eureka.server.enable-self-preservation=false
客户端的配置
server.port=8760
spring.application.name=MULTISERVICEEUREKACLIENT
eureka.client.service-url.defaultZone=http://MULTISERVICEEUREKASERVER1:8761/eureka/,http://MULTISERVICEEUREKASERVER2:8762/eureka/,http://MULTISERVICEEUREKASERVER3:8763/eureka/
Eureka服务1 控制台界面信息:
Eureka服务2 控制台界面信息:
Eureka服务3 控制台界面信息:
如果你按照上述方式搭建并未成功,可以参考我在 GitHub 项目 spring-cloud-get-started 仓库中模块名为:
spring-cloud-eureka-multi-service-server1(MULTISERVICEEUREKASERVER1)
spring-cloud-eureka-multi-service-server2(MULTISERVICEEUREKASERVER2)
spring-cloud-eureka-multi-service-server3(MULTISERVICEEUREKASERVER3)
spring-cloud-eureka-multi-service-client(MULTISERVICEEUREKACLIENT)
进行对比查看是否配置有误。
spring-cloud-get-started 项目地址:https://github.com/zhuoqianmingyue/spring-cloud-get-started
SpringCloud Eureka + RestTemplate + Ribbion 使用
上面我们介绍如何搭建单机版和高可用版的 Eureka 环境,接下来我们来介绍如果通过 RestTemplate + Ribbion 从 Eureka Server 调用我们的 Eureka 客户端服务的 API 接口。
调用的流程是:消费端从 Eureka Server 获取我们要调用具体服务生产者(Eureka 客户端)的所有 URL 地址信息,然后通过 Ribbion 的负载策略选取一个服务地址。最后通过RestTemplate 进行调用具体服务 API 接口。
我通过一台 Eureka Server( Eureka注册中心服务)端 和 2个商品服务(Eureka客户端),外加一个服务消费者订单服务(Eureka客户端)。通过订单服务从 Eureka 注册中心服务获取2个商品服务(Eureka客户端)地址URL并通过 Ribbion 负载策略选取其中一个商品服务URL 进行服务的调用。
** Eureka Server 注册中心服务的搭建:**
添加 spring-cloud-starter-netflix-eureka-server 依赖,然后再 SpringBoot Application 上声明 @EnableEurekaServer,最后在 application.properties 中添加相关配置信息。
spring-cloud-starter-netflix-eureka-server 依赖配置如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.properties 具体配置信息如下:
server.port=8761
spring.application.name=EUREKASERVER
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.server.enable-self-preservation=false
** 2个 商品服务 (Eureka 客户端)搭建:**
添加 spring-cloud-starter-netflix-eureka-client 依赖,然后再 SpringBoot Application 上声明 @EnableDiscoveryClient ,最后在 application.properties 中添加相关配置信息。
分别在商品服务1 和商品服务2 中定义一样的 RestFul 接口,并通过返回不同的商品名称以示区分。
spring-cloud-starter-netflix-eureka-client 依赖配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
商品服务1 配置信息:
server.port=8760
spring.application.name=PRODUCTSERVER
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
商品服务1 API 接口代码如下:
@RestController
public class ProductController {
@GetMapping("/product/{id}")
public Product productInfo(@PathVariable(name="id")Long productId) {
Product product = new Product();
product.setId(productId);
product.setName("product 1:"+"苹果");
product.setPrice(12d);
return product;
}
}
商品服务 2 配置信息:
server.port=8770
spring.application.name=PRODUCTSERVER
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
商品服务 2 API 接口代码如下:
@RestController
public class ProductController {
@GetMapping("/product/{id}")
public Product productInfo(@PathVariable(name="id")Long productId) {
Product product = new Product();
product.setId(productId);
product.setName("product 2:"+"苹果");
product.setPrice(12d);
return product;
}
}
商品类的代码如下
public class Product {
private Long id;
private String name;
private Double price;
//省略 getter 和 setter
}
订单服务(Eureka客户端)搭建:
添加 spring-cloud-starter-netflix-eureka-client 依赖,然后再 SpringBoot Application 上声明 @EnableDiscoveryClient ,最后在 application.properties 中添加相关配置信息。
server.port=8762
spring.application.name=ORDERSERVICE
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
商品服务的调用需要使用到 RestTemplate,所以需要配置 RestTemplate 到Spring的上下文中。具体代码如下:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate buildRestTemplate() {
return new RestTemplate();
}
}
通过 LoadBalancerClient 的 choose 方法来获取 ServiceInstance,ServiceInstance 就是2个 商品服务(PRODUCTSERVER)其中一个服务信息,LoadBalancerClient 是 SpringCloud 获取服务的抽象。具体实现是通过 spring-cloud-netflix-ribbon 项目中 RibbonLoadBalancerClient 来实现的。通过 ServiceInstance 获取商品服务(PRODUCTSERVER)的服务地址 URL。然后通过 RestTemplate 调用 API 接口。具体代码如下:
@RestController
public class ProductController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/product/{id}")
public String productInfo(@PathVariable(name="id")Long productId) {
ServiceInstance serviceInstance = loadBalancerClient.choose("PRODUCTSERVER");
URI uri = serviceInstance.getUri();
String url = uri.toString()+"/product/"+productId;
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
String message = forEntity.getBody();
return message;
}
}
我们也可以通过使用 @LoadBalanced 注解来完成上述操作,使用@LoadBalanced注解的方式相对上面使用 LoadBalancerClient 代码简洁了不少。具体操作如下:
在 RestTemplateConfig 构建方法上添加 @LoadBalanced 注解。具体代码如下:
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate buildRestTemplate() {
return new RestTemplate();
}
}
在ProductController 中无需在注入 LoadBalancerClient,直接使用 RestTemplate 调用即可。具体代码如下:
@RestController
public class ProductController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/product/{id}")
public String productInfo(@PathVariable(name="id")Long productId) {
String url = "http://PRODUCTSERVER/product/"+productId;
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
String message = forEntity.getBody();
return message;
}
}
测试:
整个测试过程需要启动 一台 注册中心服务、2台商品服务、1台订单服务,个人建议注册中心服务和商品服务通过java -jar的方式启动,而订单服务通过 IDEA 来启动。当然你可以可以都在IDEA 中进行启动所有的服务。
启动 Eureka 注册中心服务:
进入 Eureka 注册中心服务目录执行打包命令 mvn clean package -Dmaven.test.skip=true。会生成一个可执行的 jar 包如下图所示:
然后进入 target目录 执行 java -jar jar包的名称,如下图所示:
如下图所示:Eureka 注册服务启动成功!
启动商品服务:
进入商品服务项目的目录,打包并且通过 java -jar 启动商品服务1项目。具体操作如下图所示:
商品 2服务操作和上述一致,这里就不再进行演示
最后访问服务注册服务,查看商品1和商品2服务是否注册到注册中心服务中,如下图所示表示注册成功!
启动订单服务:
通过下图的方式启动订单服务:
访问 http://localhost:8762/product/1 从订单服务中获取商品服务的信息,如下图所示它会以轮询的方式调用商品服务1 和商品服务2。
如果你按照上述方式搭建并未成功,可以参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为:
spring-cloud-eureka-ribbion-service ( Eureka Server 服务注册中心 EUREKASERVER )
spring-cloud-eureka-ribbion-product (商品服务1)
spring-cloud-eureka-ribbion-product2(商品服务2)
spring-cloud-eureka-ribbion-service-order-consume(订单服务)
spring-cloud-eureka-ribbion-service-order-consume-annotation(订单服务注解版)
进行对比查看是否配置有误。
spring-cloud-get-started 项目地址:https://github.com/zhuoqianmingyue/spring-cloud-get-started
SpringCloud Feign + SpringCloud Eureka 使用
调用 Eureka 客户端服务还可以通过 SpringCloud Feign 来进行操作。Feign的底层还是使用Ribbion。
接下来我们开始 SpringCloud Feign 是用操作流程介绍,看到这里你有没有发SpringCloud 使用的套路。这个套路拢共分三步:
1.引入组件 starter依赖。
2.Application 上添加@EnableXXX注解。
3.appcation.properties上进行添加相关配置信息。
SpringCloud Feign也绕不开这个套路,第一步及先在订单服务添加 Feign starter。具体依赖代码如下:
html <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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-openfeign</artifactId> </dependency>
第二步是在 SpringBoot Application上声明 @EnableFeignClients 如下图所示:
第三步是在application.properties中配置 Eureka客户端的配置,具体配置信息如下:
html server.port=8762 spring.application.name=ORDERSERVICE eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
第四步是在 RestTemplateConfig 构建方法上声明 @LoadBalanced
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate buildRestTemplate() {
return new RestTemplate();
}
}
第五步是创建调用接口,并在接口上声明 @FeignClient(name="PRODUCTSERVER") 其中name的值是调用服务的名称。在声明调用的方法 @RequestMapping()中配置 URL 和商品服务 API 接口一样即可。
@FeignClient(name="PRODUCTSERVER")
public interface ProductClient {
@GetMapping("/product/{id}")
public Product productInfo(@PathVariable(name="id")Long productId);
}
第六步开始编写调用的测试类,在ProductController 中引入 PersonClient 然后调用ProductClient 调用方法即可。
@RestController
public class ProductController {
@Autowired
private ProductClient personClient;
@RequestMapping("/product/{id}")
public Product productInfo(@PathVariable(name="id")Long productId) {
Product product = personClient.productInfo(productId);
return product;
}
}
测试:
Feign 使用 和 Ribbion 使用是一样的服务注册中心和商品服务,启动方式请参考上面Feign 使用操作介绍。
访问 http://localhost:8762/product/1 调用订单服务,和使用Ribbion方式效果一致。具体访问结果如下:
SpringCloud Feign + SpringCloud Eureka 使用 和 SpringCloud Eureka + RestTemplate + Ribbion 使用 共用 Eureka 服务端和服务生产者的项目,如果搭建过程中遇到问题可以参考我在GitHub 项目 spring-cloud-get-started 仓库中模块名为: spring-cloud-eureka-feign-service-order-consume 进行对比即可。
spring-cloud-get-started 项目地址:https://github.com/zhuoqianmingyue/spring-cloud-get-started
小结
SpringCloud Eureka 搭建和使用只要你掌握了搭建三步法:1.引入组件 starter依赖、2.Application 上添加@EnableXXX注解、3.appcation.properties上进行添加相关配置信息。关于具体的配置可以结合 Spring 官网文档进行查阅。
参考文献
https://spring.io/guides/gs/service-registration-and-discovery/
https://cloud.spring.io/spring-cloud-netflix/reference/html/
https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-ribbon.html
http://seymours.cn/articles/2018/09/19/1537338458105.html