原文:Spring Cloud 服务治理:Spring Cloud Eureka (一)
Spring Cloud Eureka
Spring Cloud Eureka 是Spring Cloud Netflix微服务套件中的一部分,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。Spring Cloud 通过为Eureka增加了Spring Boot风格的自动化配置,我们只需要通过简单引入依赖和注解配置就能让Spring Boot构建的微服务应用轻松地与Eureka服务治理体系进行整合。
这篇文章将从以下几部分来介绍Eureka:
- 服务治理
- Eureka介绍
- 构建服务注册中心
- 服务注册
- 服务发现
服务治理
在开始之前还是想再对服务治理最一些说明。服务治理可以说是微服务中最为核心和基础的模块,他主要用来实现各个微服务实例的自动化注册于发现。
为什么这么需要服务治理模块呢?
在早期开始构建微服务架构时,我们的微服务系统可能并不多,这时候我们只需要通过一些静态配置来完成服务的调用。比如 有两个服务A、B,其中A服务需要调用服务B来完成一个业务操作时候。为了实现服务B的高可用,不论采用服务端负载均衡还是客户端复杂均衡,都需要手工维护服务B的具体实例清单。但是随着业务的发展,系统功能越来越来维护(通过手工静态配置的方式)。并且随着业务的飞速发现,我们的集群规模、服务的位置、服务的命名等都有可能发生变化,如果还是通过手工维护的方式很容易出错或者命名冲突等问题,同时对于静态文件的维护也必将提高人力成本。 画了个示意图如下:
image.png可以看到早期的微服务架构需要在服务A维护服务B的服务列表,包含服务B的集群、服务等信息,相对来说还比较简单、明了。而当业务发展很快一段时间之后,这个列表信息可能想下面这样子:
image.png可以看到需要维护的服务列表越来越多、越来越复杂,然后这些服务信息也会随着业务的增长不断变化,最终导致的结构就是人工维护已经不现实了,所以就有了服务治理的出现。
服务治理的核心:围绕服务注册和服务发现机制来完成对微服务应用实例的自动化管理。
服务注册
通常都会构建一个注册中心,每个注册单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
比如我们有两个提供服务A的进程分别运行与192.168.0.10:8080与192.168.0.11:8081.另外有三个提供服务B的进程运行与192.168.0.20:8080、192.168.0.20:8081与192.168.0.20:8082位置上。当这些进程均启动并想注册中心注册自己的服务之后,注册中心就会维护一个类似下面的清单:
image.png服务发现
由于在服务治理框架下运作,服务间的调用不在通过指定具体的服务实例地址来实现,而是通过向服务名发起请求调用实现。所以,服务调用方在调用服务提供接口时,是不知道具体的服务实例位置。因此调用方需要向注册中心咨询,并获取所有实例的清单,以实现对具体服务实例的访问。
举个例子,比如服务C需要调用服务A,首先服务C需要向注册中心发起服务A实例清单的拉取请求,会拿到一个服务A的可用位置:192.168.0.10:8080与192.168.0.11:8081,便从该清单中通过某种轮训策略取出一个位置来进行服务调用,这个策略就是大名鼎鼎的复杂均衡策略,后面在介绍。
Netflix Eureka
上面服务治理中我们提供一个很重要的组件 —— 服务注册与发现。Spring Cloud Eureka正式充当这个注册中心的角色,Spring Cloud Eureka使用Netflix Eureka来实现服务注册和发现,它即包含的服务端组件,也包含了客户端组件,并且服务端与客户端均采用Java编写, 所以Eureka主要使用与通过Java实现的分布式系统,或是与JVM兼容语言构建的系统。 但是,Eureka服务端的服务治理机制提供了完备的RESTful API,所以它也支持将非Java语言构建的微服务应用纳入Eureka的服务治理体系中来。
Eureka服务端,我们也成为服务的注册中心。它同其他服务注册中心一样,支持高可用配置。
Eureka客户端,主要处理服务的注册于发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序中运行,Eureka客户端向注册中心注册自身提供服务并周期性地发送心跳来更新它的服务状态。同时,它也能从服务端查询当前注册的服务信息并把他们缓存到本地并周期性地刷新服务状态。
开始搭建服务注册中心
首先,创建一个基础的Spring Boot工程,命名为eureka-server
image.png在pom.xml中引入必要的依赖内容,代码如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第二步,通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话,同时指定一些配置:
启动注册中心
// 启动一个服务注册中心
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
在默认配置下,该服务注册中心也会将自己作为客户端来尝试注册自己,所以我们需要禁用它的客户端注册行为,主需要在application.properties配置中增加如下配置:
# 注册中心端口设置8888,可自己指定其他端口
server.port=8888
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
配置解释:
- eureka.client.register-with-eureka:由于该应用为注册中心,所以设置为false,代表不向注册中心注册自己。
- eureka.client.fetch-registry:由于注册中心的职责就是维护服务实例,斌不需要去检索服务,所以也设置为false。
在完成上面的配置后,启动应用并访问 http://localhost:8888/ , 可以看到如下图所示的Eureka信息面板,其中Instance currently registered with Eureka栏是空的,说明该注册中心还没有注册任何服务:
image.png注册服务提供者
在完成了服务注册中心的搭建之后,接下来我们尝试搭建一个既有的Spring Boot应用加入eureka-server的服务治理体系中去。
搭建方式跟之前的没什么大的区别,首先需要pom.xml配置eureka客户端依赖,并增加服务注册配置:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第二步:application.properties增加服务注册配置:
# eureka client1 server port
server.port=8001
# set spring application name
spring.application.name=eureka-client-1
# server registry config
eureka.client.serviceUrl.defaultZone=http://localhost:8888/eureka/
第三步:使用@EnableEurekaClient注解,激活Eureka中的DiscoveryClient
// 激活Eureka中的DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class EurekaClient1Application {
public static void main(String[] args) {
SpringApplication.run(EurekaClient1Application.class, args);
}
}
第四步:编写一个/hello请求处理接口,并注入DiscoveryClient对象:
@RestController
public class HelloController {
@Autowired
private DiscoveryClient client;
@RequestMapping("/hello")
public String hello(){
ServiceInstance instance = client.getLocalServiceInstance();
System.out.println("/hello, host:"+instance.getHost()+", service_id:"+instance.getServiceId()+);
return "Hello !!!";
}
}
最后我们分别启动注册中心eureka-server和eureka-client两个服务(注意先启动服务端,在启动Client,不然Client注册服务时会找不到服务端)。报错了:
java.lang.NoSuchMethodError: org.springframework.boot.builder.SpringApplicationBuilder.<init>([Ljava/lang/Object;)V
这是因为我的SpringBoot版本与SpringCloud不兼容,这里修改SpringBoot版本解决问题。
修改前:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
修改后:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
服务启动时com.netflix.discovery.DiscoveryClient对象打印了该服务的注册信息,表示服务注册成功。
image.png此时我们到服务注册中心的控制台 可视化 界面可以看到有服务的注册信息:
image.png然后我们再访问http://localhost:8001/hello接口,直接向服务端发起请求,在控制台中可以看到如下输出:
/hello, host:192.168.43.73, service_id:eureka-client-1
这些输出内容就是之前我在HwlloController中注入的DiscoveryClient接口对象,从服务注册中西获取的服务相关信息。
服务高可用注册中心
上面示例中的服务注册是在单机模式下,生产环境我们一定是要保证注册中心的高可用,需要集群部署。那eureka是如何保证高可用的呢?
Eureka Server的设计一开始就考虑了高可用问题,在Eureka的服务治理设计中,所有节点即时服务提供方、也会服务消费方,服务注册中心也不例外。上面在单节点配置中,我设置了一下俩个参数来禁止服务注册中心注册自己。
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Eureka Server的高可用实际就是将自己也作为服务注册到其他服务中心,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。
下面我再来尝试搭建高可用服务注册中心的集群。可以在上面单点的注册中心基础之上进行扩展,构建一个双节点的注册中心集群。
第一步:
1.创建application-peer1.properties,作为peer1服务中心的配置,serveiceUrl执行peer2:
server.port=8888
spring.application.name=eureka-server-1
eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:9999/eureka/
- 创建创建application-peer2.properties,作为peer1服务中心的配置,serveiceUrl执行peer1:
server.port=9999
spring.application.name=eureka-server-2
eureka.instance.hostname=peer2
eureka.client.serviceUrl.defaultZone=http://peer1:8888/eureka/
第二步:
本地hosts添加对peer1和peer2的转换:
127.0.0.1 peer1
127.0.0.1 peer2
第三步:
使用java -jar 同时利用spring.profiles.active属性来分别启动peer1和peer2:
java -jar eureka.jar --spring.profiles.active=peer1
java -jar eureka.jar --spring.profiles.active=peer2
服务都启动后在访问http://localhost:8888/ 或 http://localhost:9999/ 会发现每一个服务注册中心都同时注册了两个服务:
image.png以此方式来保证eureka服务注册中心的高可用。
稍等,还有个问题,eureka在集群部署的情况下,服务提供方服务注册方式需要做一些简单的配置才能将服务注册到Eureka Server集群中。我还是以上面那个提供/hello接口eureka-client为例子。修改application-properties:
# set spring application name
spring.application.name=eureka-client-1
# server registry config, 如果有多个逗号分割即可
eureka.client.serviceUrl.defaultZone=http://peer1:8888/eureka/,http://peer2:9999/eureka/
然后我们在启动eureka-client观察两个服务注册中心看看有什么变化:
image.png可以看到两个注册中心同时注册了自己,另一个注册中心以及eureka-server。。因此此时加入peer1挂调了,peer2依然可以保证服务的可用性。
服务的注册于发现
创建服务Provider
通过上面的示例,我做了服务注册中心的单点搭建、高可用搭建及实现了服务的注册。现在看一下具体在项目使用中如何实现服务的注册和发现。
首先,我还是以上面同上面创建eureka-client方式及注册方式,我创建一个eureka-provider服务,作为服务提供方。首先通过java -jar 结合 server.port的方式我启动四个节点的服务提供方,并注册到peer1、peer2注册中心上。
java -jar eureka-provider.jar --server.port=8001
java -jar eureka-provider.jar --server.port=8002
java -jar eureka-provider.jar --server.port=8003
java -jar eureka-provider.jar --server.port=8004
启动成功后查看注册中心,会发现服务提供者eureka-provider-1注册了四个实例单元:
image.png创建服务Consumer
第一步还是创建一个springBoot工程,pom.xml添加:
dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
第二步,ConsumerApplication通过@EnableEurekaClient让该应用注册为Eureka客户单应用,以获得服务发现的能力。同时,在该主类中创建RestTemplate的Spring Bean实例,并通过@LoadBalanced注解开启客户端负载均衡:
// 激活Eureka中的DiscoveryClient
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
第二步:创建一个ConsumerController类,实现一个/ribbon-consumer接口,在该接口中,通过在上面创建的RestTemplate来实现对EUREKA-PROVIDER-1服务 /hello 接口的调用。需要注意的是这里访问的地址是服务名EUREKA-PROVIDER-1,而不是一个具体的地址,在服务治理框架中,这是一个非常重要的特性:
@RestController
public class ConsumerController {
@Autowired
RestTemplate template;
@GetMapping("/ribbon-consumer")
public String helloConsumer(){
return template.getForEntity("http://EUREKA-PROVIDER-1/hello", String.class).getBody();
}
}
第三步:在application.properties中配置eureka服务注册中心的位置,需要与之前EUREKA-PROVIDER-1的配置一直,不然发现不了该服务:
server.port=9000
spring.application.name=eureka-consumer-1
eureka.client.serviceUrl.defaultZone=http://peer1:8888/eureka/,http://peer2:9999/eureka/
最后一步启动Consumer服务:
image.png访问http://localhost:9000/ribbon-consumer发起Get请求,成功返回了“Hello !!!”信息。此时我们在consumer的服务控制台可以看到如下信息,Ribbon输出了当前客户端维护的EUREKA-PROVIDER-1服务列表情况,Ribbo就是按照这个信息进行轮训访问,以实现基于客户端的负载均衡。另外还输出其他一些有用的信息,如对各个实例的请求总数、第一次连接信息、上一次拦截信息、总的请求失败数量等:
2019-10-26 17:41:28.286 INFO 64779 --- [nio-9000-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: EUREKA-PROVIDER-1.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-26 17:41:28.339 INFO 64779 --- [nio-9000-exec-1] c.n.u.concurrent.ShutdownEnabledTimer : Shutdown hook installed for: NFLoadBalancer-PingTimer-EUREKA-PROVIDER-1
2019-10-26 17:41:28.424 INFO 64779 --- [nio-9000-exec-1] c.netflix.loadbalancer.BaseLoadBalancer : Client:EUREKA-PROVIDER-1 instantiated a LoadBalancer:DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-PROVIDER-1,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2019-10-26 17:41:28.432 INFO 64779 --- [nio-9000-exec-1] c.n.l.DynamicServerListLoadBalancer : Using serverListUpdater PollingServerListUpdater
2019-10-26 17:41:28.496 INFO 64779 --- [nio-9000-exec-1] c.netflix.config.ChainedDynamicProperty : Flipping property: EUREKA-PROVIDER-1.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2019-10-26 17:41:28.501 INFO 64779 --- [nio-9000-exec-1] c.n.l.DynamicServerListLoadBalancer : DynamicServerListLoadBalancer for client EUREKA-PROVIDER-1 initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=EUREKA-PROVIDER-1,current list of Servers=[qiwangs-air:8001, qiwangs-air:8002, qiwangs-air:8004, qiwangs-air:8003],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone; Instance count:4; Active connections count: 0; Circuit breaker tripped count: 0; Active connections per server: 0.0;]
},Server stats: [[Server:qiwangs-air:8003; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
, [Server:qiwangs-air:8004; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
, [Server:qiwangs-air:8001; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
, [Server:qiwangs-air:8002; Zone:defaultZone; Total Requests:0; Successive connection failure:0; Total blackout seconds:0; Last connection made:Thu Jan 01 08:00:00 CST 1970; First connection made: Thu Jan 01 08:00:00 CST 1970; Active Connections:0; total failure count in last (1000) msecs:0; average resp time:0.0; 90 percentile resp time:0.0; 95 percentile resp time:0.0; min resp time:0.0; max resp time:0.0; stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@13b808a3
同时,多请求几次之后会发现provider的4个实例控制台会交替打印/hello, host:qiwangs-air, service_id:eureka-provider-1的信息
负载均衡总结
本片文章花了很长的篇幅记录了自己的学习内容,包括什么是服务治理、Eureka,以及如何构建服务注册中心,包括服务注册中心的高可用构建,以及最终实现了一个服务注册和发现的例子。
原文:Spring Cloud 服务治理:Spring Cloud Eureka (一)
个人博客网站:RelaxHeart网/Tec博客
网友评论