《Spring Cloud微服务架构实战》--注册中心--Eureka
《Spring Cloud微服务架构实战》基础篇--Eureka
本书使用的Spring Cloud版本为Dalston.SRl,相关的技术版本如下所示。
> spring-cloud-commons:公共模块,版本为 1.2.2.RELEASE。
> spring-cloud-config:配置管理模块,版本为 1.3.1.RELEASE。
> spring-cloud-netflix: Spring Cloud的核心模块,用于提供服务管理、负载均衡等功 能,版本为 1.3.1.RELEASE。
> spring-cloud-sleuth:服务跟踪模块,版本为 1.2.1.RELEASE。
> spring-cloud-stream:用于构建消息驱动微服务的模块,版本为Chelsea.SR2。
> spring-cloud-bus:消息总线模块,对应版本为1.3.1.RELEASE。
> spring-boot: Spring Cloud 基于 Spring Boot 快速搭建,使用的 Spring Boot 版本为 1.5.3.RELEASEo
3.0 服务与发现中心: Eureka
Spring Cloud 集成了 Netflix OSS 的多个项目,形成了 spring-cloud-netflix 项目。该项目 包含多个子模块,这些子模块对集成的Netflix旗下的框架进行了封装,
本节将讲述其中一 个较为重要的服务管理框架:Eureka
3.1.1 关于 Eureka
Eureka提供基于REST的服务,在集群中主要用于服务管理。Eureka提供了基于Java 语言的客户端组件,客户端组件实现了负载均衡的功能,为业务组件的集群部署创造了条 件。
使用该框架,可以将业务组件注册到Eureka容器中,这些组件可进行集群部署,Eureka 主要维护这些服务的列表并自动检査它们的状态。
3.1.2 Eureka 架构
一个简单的Eureka集群,需要一个Eureka服务器、若干个服务提供者。我们可以将 业务组件注册到Eureka服务器中,其他客户端组件可以向服务器获取服务并且进行远程调 用。图3-1所示为Eureka的架构图。
图3-1中有两个服务器,服务器支持集群部署,每个服务器也可以作为对方服务器的 客户端进行相互注册与复制。图3-1中所示的三个Eureka客户端,两个用于发布服务,另 一个用于调用服务。不管是服务器还是客户端,都可以部署多个实例,如此一来,就很容 易构建高可用的服务集群。
3.1.3服务器端
对于注册到服务器端的服务组件,Eureka服务器并没有提供后台的存储,这些注册的 服务实例被保存在内存的注册中心,它们通过心跳来保持其最新状态,这些操作都可以在 内存中完成。客户端存在着相同的机制,同样在内存中保存了注册表信息,这样的机制提 升了 Eureka组件的性能,每次服务的请求都不必经过服务器端的注册中心。
3.1.4服务提供者
作为Eureka客户端存在的服务提供者,主要进行以下工作:第一,向服务器注册服务; 第二,发送心跳给服务器;第三,向服务器端获取注册列表。当客户端注册到服务器时, 它将会提供一些关于自己的信息给服务器端,例如自己的主机、端口、健康检测连接等。
3.1.5服务调用者
对于发布到Eureka服务器的服务,服务调用者可对其进行服务查找与调用,服务调用 者也是作为客户端存在的,但其职责主要是发现与调用服务。在实际情况中,有可能出现 本身既是服务提供者,又是服务调用者的情况,例如在传统的企业应用三层架构中,服务 层会调用数据访问层的接口进行数据操作,它本身也会提供服务给控制层使用。
本节对Eureka进行了介绍,读者大概了解Eureka架构及各个角色的作用即可;
3.2 第一个Eureka应用
3.2.1构建服务器
先创建一个名称为first-ek-server的Maven项目作为服务器,在pom.xml文件中加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <dependencyManagement> <dependencies> <dependency> <groupld>org.springframework.cloud</groupld> <artifactld>spring-cloud-dependencies</artifactld> <version>Dalston.SRl</version> <type>pom</type> <scope> import </scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupld>org.springframework.cloud</groupld> <artifactld>spring-cloud-starter-eureka-server</artifactld><br> </dependency> </dependencies> |
加入的 spring-cloud-starter-eureka-server 会自动引入 spring-boot-starter-web,因此只需 加入该依赖,我们的项目就具有Web容器的功能了。
接下来,编写一个最简单的启动类, 启动我们的Eureka服务器,启动类如代码清单:
1 2 3 4 5 6 | @SpringBootApplication @EnableEurekaServer public class FirstServer { public static void main(String[] arg§) ( new SpringApplicationBuilder(FirstServer. class ).run(args); }<br>} |
启动类几乎与前面章节中介绍的Spring Boot项目一致,只是加入了@EnableEurekaServer, 声明这是一个Eureka服务器。直接运行FirstServer即可启动Eureka服务器,需要注意的是,
本例中并没有配置服务器端口,因此默认端口为8080,我们将端口配置为8761;
在 src/main/resources目录下创建application.yml配置文件,内容如下:
1 2 | server: port: 8761 |
运行FirstServer的main方法后,可以看到控制台的 输出如下:
1 2 3 4 5 6 | 2017 - 08 - 04 15 : 35 : 58.900 INFO 4028 —— [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8761 <br> 2017 - 08 - 04 15 : 35 : 58.901 INFO 4028 —— [ main] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761 2017 - 08 - 04 15 : 35 : 58.906 INFO 4028 —— [ main] org.crazyit.cloud.FirstServer : Started FirstServer in 12.361 seconds JVM running for 12.891 2017 - 08 - 04 15 : 35 : 59.488 INFO 4028 —— [nio- 8761 -exec-l] <br>o.a.c.c.C.[Tomcat] . [localhost].[/] : Initializing Spring FrameworkServlet * dispatcherServlet * |
成功启动后,打开浏览器,输入http://localhost:8761,可以看到Eureka 服务器控制台,如图3-2所示。
在图3-2的下方,可以看到服务的实例列表,目前我们并没有注册服务,因此列表 为空。
3.2.2服务器注册开关
在启动Eureka服务器时,会在控制台看到以下两个异常信息:
1 | java.net.ConnectException: Connection refused: <br>connect com.netflix.discovery.shared.transport.TransportException: <br>Cannot execute request on any known server |
这是由于在服务器启动时,服务器会把自己当作一个客户端,去注册Eureka服务器, 并且会到Eureka服务器抓取注册信息,它自己本身只是一个服务器,而不是服务的提供者 (客户端),
因此可以修改application.yml文件,修改以下两个配置:
1 2 3 4 | eureka: client: registerWithEureka: false fetchRegistry: false |
以上配置中的eureka.client.registerWithEureka属性,声明是否将自己的信息注册到 Eureka服务器,默认值为true。属性eureka.client.fetchRegistry则表示,是否到Eureka服务 器中抓取注册信息。
将这两个属性设置为false,启动时不会出现异常信息。
3.2.3编写服务提供者
在前面搭建环境章节,我们使用Spring Boot来建立一个简单的Web工程,并且在里 面编写了一个REST服务,本例中的服务提供者,与该案例类似。
建立名称为 first-ek-service-provider的项目,在pom.xml中加入依赖,如下所示:
1 2 3 4 5 6 7 | <dependency> <groupld>org.springframework.cloud</groupld><br> <artifactld>spring-cloud-starter-config</artifactld> </dependency> <dependency> <groupld>org.springframework.cloud</groupld> <artifactld>spring-cloud-starter-eureka</artifactld> </dependency> |
在src/main/resources目录中建立application.yml配置文件,文件内容如代码清单:
1 2 3 4 5 6 7 8 9 | spring: application: name: first-service-provider eureka: instance: hostname: localhost client: serviceUrl: defaultzone: http: //localhost:8761/eureka/ |
以上配置中,将应用名称配置为first-service-provider,该服务将会被注册到端口为8761 的Eureka服务器,也就是本节前面所构建的服务器。
另外,还使用了 eureka.instance.hostname 来配置该服务实例的主机名称。编写一个Controller类,并提供一个最简单的REST服务, 如代码所示:
1 2 3 4 5 6 | @RestController public class FirstController {<br> @RequestMapping (value = "/person/{personld}" , method = RequestMethod.GET,<br> produces = MediaType.APPLICATION_JSON_VALUE) public Person findPerson( @PathVariable ( "personld" ) Integer personld) {<br> Person person = new Person(personld, "Crazyit" , 30 ); return person; } } |
编写启动类:
1 2 3 4 5 6 7 | @SpringBootApplication @EnableEurekaClient public class FirstServiceprovider { public static void main(String[] args) ( new SpringApplicationBuilder(FirstServiceProvider. class ).run(args); } } |
在启动类中,使用@EnableEurekaClient注解,声明该应用是一个Eureka客户端。
配置完成后,运行服务器项目first-ek-server的启动类FirstServer,再运行代码清单3-5中的 FirstServiceProvider,
在浏览器中访问 Eureka: http://localhost:8761/,可以看到服务列表如图3-3所示:
如图3-3所示,可以看到当前注册的服务列表,只有我们编写的first-service-provider。 服务注册成功后,接下来编写服务调用者。
3.2.4编写服务调用者
服务被注册、发布到Eureka服务器后,需要有程序去发现它,并且进行调用。此处所 说的调用者,是指同样注册到Eureka的客户端,来调用其他客户端发布的服务。简单地说, 就是Eureka内部调用。
同一个服务可能会部署多个实例,调用过程可能涉及负载均衡、服务器查找等问题,Netflix的项目已经帮我们解决,并且Spring Cloud已经封装了一次,我 们仅需编写少量代码就可以实现服务调用。
新建名称为first-ek-service-invoker的项目,在pom.xml文件中加入依赖,如代码清单:
1 2 3 4 5 6 7 8 9 10 | <dependency> <groupld>org.springframework.cloud</groupld><br> <artifactld>spring-cloud-starter-config</artifactld> </dependency> <dependency> <groupld>org.springframework.cloud</groupld><br> <artifactld>spring-cloud-starter-eureka</artifactld> </dependency> <dependency> <groupld>org.springframework.cloud</groupld> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> |
建立配置文件application.yml,内容如代码清单:
1 2 3 4 5 6 7 8 9 10 11 | server: port: 9000 spring: application: name: first-service-invoker eureka: instance: hostname: localhost client: serviceUrl: defaultzone: http: //localhost:8761/eureka/ |
在配置文件中,配置了应用名称为first-service-invoker,这个调用者的访问端口为9000, 需要注意的是,这个调用本身也可以对外提供服务。与提供者一样,使用eureka的配置, 将调用者注册到first-ek-server上面。
下面编写一个控制器,让调用者对外提供一个测试的 服务,代码清单3-8为控制器的代码实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @RestController @Configuration public class InvokerController {<br> @Bean @LoadBalanced public RestTemplate getRestTemplate() ( return new RestTemplate(); }<br> @RequestMapping (value = "/router" , method = RequestMethod.GET,<br> produces = MediaType.APPLICATION_JSON_VALUE) <br> public String router() { RestTemplate restTemplate = getRestTemplate(); //根据应用名称调用服务 String json = restTemplate.getForObject( "http://first-service-provider/person/l" , String. class ); <br> return json; } } |
在控制器中,配置了 RestTemplate的Bean, RestTemplate本来是spring-web模块下面 的类,主要用来调用REST服务。本身并不具备调用分布式服务的能力,但是RestTemplate 的Bean被@LoadBalanced注解修饰后,
这个RestTemplate实例就具有访问分布式服务的能 力了。关于该类的一些机制,我们将放到“负载均衡”章节中讲解。
在控制器中,新建了一个router的测试方法,用来对外发布REST服务。该方法只起 路由作用,实际上是使用RestTemplate来调用first-ek-service-provider (服务提供者)的服 务。
需要注意的是,调用服务时,仅仅通过服务名称进行调用。接下来编写启动类,如代 码清单3-9所示
1 2 3 4 5 6 7 | @SpringBootApplication @EnableDiscoveryClient public class Firstlnvoker { public static void main(String[] args) { SpringApplication.run(Firstlnvoker. class , args); } } |
在启动类中,使用了@EnableDiscoveryClient注解来修改启动类,该注解使得服务调用 者有能力去Eureka中发现服务。需要注意的是,@EnableEurekaClient注解巳经包含了 @EnableDiscoveryClient的功能,
也就是说,一个Eureka客户端,本身就具有发现服务的 能力。配置完成后,依次执行以下操作:
- 启动服务器(first-ek-server)。
- 启动服务提供者(first-ek-service-provider)
- 启动服务调用者(first-ek-service-invoker)
使用浏览器访问Eureka,可看到注册的客户信息,如图3-4所示。
全部成功启动后,在浏览器中访问服务调用者发布的router服务:http://localhost:9000/ router,可以看到在浏览器中输出如下:
1 | { "id" : 1 , "name" : "Crazyit" , "age" : 30 } |
根据输出可知,实际上调用了服务提供者的/person/1服务,第一个Eureka应用到此结 束,下面对这个应用程序的结构进行简单描述;
3.2.5程序结构
本案例新建了三个项目,如果读者对程序的结构不太清晰,可以参看图
如图3-5所示,Eureka服务为本例的first-ek-server,服务提供者为first-ek-service- provider,而调用者为first-ek-service-invoker,用户通过浏览器访问调用者的9000端口的 router服务、router服务中查找服务提供者的服务并进行调用。
在本例中,服务调用有点像 路由器的角色。
为了能演示Eureka的高可用特性,下一节将会以本案例为基础,搭建一个复杂一点的 集群
3.3 Eureka集群搭建
在运行第一个Eureka应用时,服务器实例、服务提供者实例都只启动了一个,并没有 体现高可用的特性,本节将对前面的Eureka应用进行改造,使其可以进行集群部署。
3.3.1本例集群结构图
本例将会运行两个服务器实例、两个服务提供者实例,然后服务调用者请求服务,集 群结构如图3-6所示。
第一个Eureka应用,使用的是浏览器访问Eureka的服务调用者,而改造后,为了能 看到负载均衡的效果,会编写一个HttpClient的REST客户端访问服务调用者发布的服务。
由于本书的开发环境只有一台电脑,操作系统为Windows,如果要构建集群,需要修 改hosts文件,为其添加主机名的映射。修改C:\Windows\System32\drivers\etc\hosts文件, 添加以下内容:
3.3.2改造服务器端
新建项目first-cloud-server,使用的Maven配置与3.2节中介绍的服务器一致。由于需 要对同一个应用程序启动两次,因此需要在配置文件中使用profiles (关于profiles己经在 第2章中讲述过)。服务器配置文件请见代码清单
1 2 3 4 5 6 7 8 9 10 11 12 | server: port: 8761 spring: application: name: first-cloud-server profiles: slavel eureka: instance: hostname: slavel client: serviceUrl: defaultzone: http: //slave2:8762/eureka/ |
1 2 3 4 5 6 7 8 9 10 11 12 | server: port: 8762 spring: application: name: first-cloud-server profiles: slave2 eureka: instance: hostname: slave2 client: serviceUrl: defaultzone: http: //slavel:8761/eureka/ |
代码清单3.10中配置了两个profiles,名称分别为slavel和slave2。在slavel中,配置 了应用端口为8761,主机名为slavel。当使用salve 1这个profiles来启动服务器时,将会 向 http://slave2:8762/eureka/^册自己。使用 salve2 来启动服务器,会向 http://slavel:8761/ eureka/注册自己。简单点说,就是两个服务器启动后,它们会互相注册。
修改启动类,让类在启动时读取控制台的输入,决定使用哪个profiles来启动服务器, 请见代码清单
1 2 3 4 5 6 7 | @SpringBootApplication @EnableEurekaServer public class FirstServer { public static void main(String[] args) ( <br> //读取控制台输入,决定使用哪个<br> Profiles Scanner scan = new Scanner(System.in); <br> String profiles = scan.nextLine(); new SpringApplicationBuilder(FirstServer. class ).profiles(profiles).run(args); } } |
在启动类中,先读取控制的输入,再调用profiles方法设置启动的profileso需要注意 的是,第一个启动的服务器会抛出异常,异常原因我们在前面已经经讲述过,抛出的异常不必理会。
3.3.3改造服务提供者
服务提供者也需要启动两个实例,服务提供者的改造与服务端类似,将3.2节中的 first-ek-service-provider复制出来,并改名为first-cknid-provider。
修改配置文件,将服务提 供者注册到两个服务器中,配置文件请见代码清单
1 2 3 4 5 6 7 8 9 | spring: application: name: first-cloud-provider eureka: instance: hostname: localhost client: serviceUrl: defaultzone : http: //localhost:8761/eureka/,http://localhost:8762/eureka/ |
再修改启动类,为了避免端口冲突,启动时读取控制台输出,决定使用哪个端口来启 动,启动类如代码:
1 2 3 4 | //读取控制台输入的端口,避免端口冲突 Scanner scan = new Scanner(System.in); String port = scan.nextLine(); new SpringApplicationBuilder(FirstServiceProvider. class ).properties( "server.port=" + port).run(args); |
启动类中使用了 properties方法来设置启动端口。为了能看到效果,还需要改造控制器, 将服务调用者请求的URL保存起来并返回,修改后的控制器请见代码
1 2 3 4 5 6 7 8 9 | @RestController public class FirstController {<br> @RequestMapping (value = "/person/(personld}" , method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) public Person findPerson ( @Pathvariable ( "personId" ) Integer personld, <br> HttpServletRequest request) { Person person = new Person(personld, "Crazyit" , 30 ); //为了査看结果,将请求的URL设置到Person实例中 person.setMessage(request.getRequestURL().toString()); return person; } } |
控制器的findPerson方法将请求的URL保存到Person实例的message属性中,调用服务后,可以通过message属性来查看请求的URL。
3.3.4 改造服务调用者
将3.2节中的first-ek-service-invoker复制并改名为first-cloud-invoker;本例中的服务调 用者只需启动一个实例,因此修改配置文件即可使用,请见代码:
1 2 3 4 5 6 7 8 9 10 11 12 | server: port: 9000 spring: application: name: first-cloud-invoker eureka: instance: hostname: localhost client: serviceUrl: defaultzone: http: //slavel:8761/eureka/,http://slavel2:8762/eureka/ |
修改的配置将服务调用注册到两个服务器上。
3.3.5 编写REST客户端进行测试
本例使用的是HttpClient, HttpClient是Apache提供的一个HTTP工具包。
新建名称为 first-cloud-rest-client的项目,在pom.xml中加入以下依赖:
1 2 3 4 5 | <dependency> <groupld>org.apache.httpcomponents</groupld> <artifactld>httpclient</artifactld> <version> 4.5 . 2 </version> </dependency> |
新建启动类,在main方法中编写调用REST服务的代码,如代码:
1 2 3 4 5 6 7 8 9 | //创建默认的HttpClient CloseableHttpClient httpclient = HttpClients.createDefault (); //调用6次服务并输出结果 for ( int i = 0 ; i < 6 ; i++) ( //调用GET方法请求服务 HttpGet httpget = new HttpGet( "http://localhost:9000/router" ); //获取响应 HttpResponse response = httpclient.execute(httpget); //根据响应解析出字符串 System.out.printin(EntityUtils.toString(response.getEntity())); |
在main方法中,调用了 6次9000端口的router服务并输出结果。完成编写后,按以下顺序启动各个组件:
① 启动两个服务器端,在控制台分别输入slave1和slave2
② 启动两个服务提供者,在控制台分别输入8081与8082
③ 启动服务调用者。
启动了整个集群后,运行TestHttpClient,可以看到输出如下:
1 2 3 4 5 | { "id" : 1 , "name" : "Crazyit" , "age”:30," message ":" http: //localhost:8081/person/l"} <br>{"id":1,"name":"Crazyit","age":30,"message":"http://localhost:8082/person/1"} { "id" : 1 , "name" : "Crazyit" , "age" : 30 , "message" : "http://localhost:8081/person/l" } ( "id" : 1 , "name" : "Crazyit" , "age" : 30 , "message" : "http://localhost: 8082/person/l" } { "id" : 1 , "name" : "Crazyit" , "age" : 30 , "message" : "http://localhost: 8081/person/l" } ( "id" : 1 , "name" : "Crazyit" , "age" : 30 , "message" : "http://localhost:8082/person/l" } |
根据输出结果可知,8081与8082端口分别被请求了 3次,可见已经达到负载均衡的 目的
3.4 服务实例的健康自检
在默认情况下,Eureka的客户端每隔30秒会发送一次心跳给服务器端,告知它仍然存 活。但是,在实际环境中有可能出现这种情况,客户端表面上可以正常发送心跳,
但实际 上服务是不可用的。
例如一个需要访问数据的服务提供者,表面上可以正常响应,但是数据库已经无法访问;又如,服务提供者需要访问第三方的服务,而这些服务早已失效。
对于这些情况,应当告诉服务器当前客户的状态,调用者或者其他客户端无法获取这些有问题的实例。实现 该功能,可以使用Eureka的健康检查控制器。
3.4.1 程序结构
将3.2节的服务器、服务提供者和服务调用者进行复制,命名如下。
> health-handler-server:本例的 Eureka 服务器。
> health-handler-provider:本例的服务提供者客户端。
> health-handler-invoker:本例的服务调用者客户端。
假设在实际环境中,服务提供者模块需要访问数据库,本例的health-handler-provider 模块,将是进行健康自检的模块。
3.4.2 使用 Spring Boot Actuator
Spring Boot Actuator模块主要用于系统监控,当应用程序整合了 Actuatei•后,它就会 自动提供多个服务端点,这些端点可以让外部看到应用程序的健康情况。
在本例中使用的 是/health端点。修改health-handler-provider的pom.xml文件,加入以下依赖:
1 2 3 4 5 | <dependency> <groupld>org.springframework.boot</groupld> <artifactld>spring-boot-starter-actuator</artifactld> <version> 1.5 . 3 .RELEASE</version> </dependency> |
加入依赖后,先启动health-handler-server,再启动health-handler-providero这两个模块 与3.2节中介绍的基本类似,仅仅修改了部分类名。
启动完成后,在浏览器中访问 http://localhost:8080/health,可以看到输出如下:
1 | { "description" : "Spring Cloud Eureka Discovery Client'*, " status'*: "UP" } |
该REST服务向外展示当前应用的状态为“UP”。
3.4.3实现应用健康自检
如果一个客户端本身没有问题,但是该模块所依赖的服务无法使用,那么对于服务器 以及其他客户端来说,该客户端也是不可用的,最常见的就是访问数据库的模块。我们需 要做两件事:第一,让客户端自己进行检查,是否能连接数据库;第二,将连接数据库的 结果与客户端的状态进行关联,并且将状态告诉服务器。
我们使用Spring Boot Actuator可以直接实现一个自定义的Healthindicator,根据是否能访问数据库,来决定应用自身的健康。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | @Component public class MyHealthlndicator implements Healthindicator { <br> @Override public Health health() {<br> if (Healthcontroller.canVisitDb) { //成功连接数据库,返回UP return new Health.Builder(Status.UP).build(); } else ( //连接数据库失败,返回out of service return new Health.Builder(Status.DOWN).build(); } } } |
为了简单起见,使用Healthcontroller类的canVisitDb变量来模拟是否能连接上数据库, 该变量的相关代码请见代码:
1 2 3 4 5 6 7 8 | @RestController public class Healthcontroller { <br> //标识当前数据库是否可以访问 static Boolean canVisitDb = false ;<br> @RequestMapping (value = "/db/{canVisitDb}" , method = RequestMethod.GET) <br> public String setConnectState ( @Pathvariable ( "canVisitDb" ) Boolean canVisitDb) { <br> this .canVisitDb = canVisitDb; return ”当前数据库是否正常:"+ this .canVisitDb; } } |
控制器中的canVisitDb变量,可以通过/db/false或者/db/true两个地址来修改,配合健 康指示器一起使用。如果该值为true,健康指示器将会返回“UP”状态,反之则返回“DOWN” 状态。
修改完后,启动服务器(health-handler-server)以及服务提供者(health-handler-provider ), 访问http://localhost:8080/health可以看到服务提供者的健康状态,
默认状态为DOWN,因 此控制器中的canVisitDb默认值为false;
如果想让应用的健康状态变为UP,则访问 http://localhost:8080/db/true 即可。
接下来,如果服务提供者想把健康状态告诉服务器,还需要实现“健康检查处理器”。 处理器会将应用的健康状态保存到内存中,状态一旦发生改变,就会重新向服务器进行注 册,
其他的客户端将拿不到这些不可用的实例。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Component public class MyHealthCheckHandler implements HealthCheckHandler { <br> @Autowired private MyHealthlndicator indicator;<br> public Instancestatus getStatus(Instancestatus currentStatus) { Status s = indicator.health().getStatus (); if (s.equals(Status.UP)) ( System.out.printin( "数据库正常连接" ); return Instancestatus.UP; } else { System.out.printin( "数据库无法连接" ); return Instancestatus.DOWN; } } } |
在自定义的健康检查处理器中,注入了前面编写的健康指示器,根据健康指示器的结 果来返回不同的状态。Eureka中会启动一个定时器,定时刷新本地实例的信息,
并且执行 “处理器”中的getStatus方法,再将服务实例的状态“更新”到服务器中。执行以上逻辑 的定时器,默认30秒执行一次,如果想加快看到效果,
可以修改eureka.client.instancelnfo- ReplicationlntervalSeconds配置。
代码清单为服务器提供者的配置文件:
1 2 3 4 5 6 7 8 9 10 | spring: application: name: health-handler-provider eureka: instance: hostname: localhost client: instancelnfoReplicationlntervalSeconds: 10 serviceUrl: defaultzone: http: //localhost:8761/eureka/ |
启动服务器,再启动服务提供者,在浏览器中访问http://localhost:8761,可看到服务提 供者的状态为DOWN,访问http://localhost:8080/db/true,将数据库设置为“可以连接”,
再访问8761端口,可以看到服务提供者状态已经改变为UP。
3.4.4服务查询
在前一节中,通过浏览器访问Eureka界面可査看服务状态的改变。本例通过修改服务 调用者的代码来查看应用健康自检的效果。修改服务调用者(health-handler-invoker模块),
控制器修改后如代码所示:
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 | @RestController @Configuration public class InvokerController { <br> @Autowired private DiscoveryClient discoveryclient;<br> @RequestMapping (value = "/router" , method = RequestMethod.GET) <br> public String router() { <br> //查找服务列表 List<ServiceInstance> ins = getServicelnstances(); //输出服务信息及状态 for (ServiceInstance service : ins) { EurekaServicelnstance esi = (EurekaServiceInstance) service; <br> Instanceinfo info = esi.getInstanceInfo(); System.out.printin(info.getAppName() + "---" + info.getInstanceId()+ "---" + info.getStatus()); } return "" ; }<br> /** * 査询可用服务 */ private List<ServiceInstance> getServicelnstances () ( List<String> ids = discoveryclient.getServices(); <br> List<ServiceInstance> result = new ArrayList<ServiceInstance> (); <br> for (String id : ids) { List<ServiceInstance> ins = discoveryclient.getlnstances(id); <br> result.addAll(ins); } return result; } } |
在客户端中,如果需要查询集群中的服务,可以使用Spring Cloud的discoveryClient 类,或者Eureka的eurekaClient类,Spring Cloud对Eureka进行了封装。
本例中调用了 discoveryClient的方法来查询服务实例(如代码清单3-21中的粗体代码)。在控制器的router 方法中,仅将查询到的服务实例进行输出。
修改完后,依次进行以下操作:
- 运行服务器。
- 服务提供者。
- 服务调用者。
- 在浏览器中输入http://localhost:8080/db/true,将数据库设置为可以连接。
- 在浏览器中输入http://localhost:9000/router,控制台输出如下:
1 2 | •HEALTH-HANDLER-PROVIDER—AY-PC:health-handler-provider—UP •HEALTH-HANDLER-INVOKER—A Y-PC:health-handler-invoker: 9000 —UP |
6. 在浏览器中输入http://localhost:8080/db/false,将数据库设置为不可连接。
7. 在浏览器中输入http://localhost:9000/router,控制台输出如下:
1 | • HEALTH-HANDLER-INVOKER—A Y-PC:health-handler-invoker: 9000 —UP |
根据输出结果可知,将数据库设置为不可连接后,可用的服务只剩下调用者自己,服 务提供者已经不存在于服务列表中。
运行案例需要注意,默认情况下,客户端到服务器端抓取注册表会有一定的时间间隔, 因此在设置数据库是否可以连接后,访问''调用者”查看效果时需要稍等一会儿;
3.5 Eureka的常用配置
本节将讲述部分Eureka的常用配置。
3.5.1心跳检测配置
客户端的实例会向服务器发送周期性的心跳,默认是30秒发送一次,可以通过修改客 户端的 eureka.instance.leaseRenewallntervallnSeconds 属性来改变这个时间。
服务器端接收心跳请求,如果在一定期限内没有接收到服务实例的心跳,那么会将该 实例从注册表中清理掉,其他的客户端将会无法访问这个实例。
这个期限默认值为90秒, 可以通过修改客户端的eureka.instance.leaseExpirationDurationlnSeconds属性来改变这个值。
也就是说,服务器90秒没有收到客户端的心跳,就会将这个实例从列表中清理掉。但需要 注意的是,清理注册表有一个定时器在执行,默认是60秒执行一次,如果将 leaseExpirationDurationlnSeconds设置为小于60秒,
虽然符合删除实例的条件,但是还没 到60秒,这个实例将仍然存在注册表中(因为还没有执行清理)。
我们可以在服务器端配 置eureka.server.eviction-interval-timer-in-ms属性来修改注册表的清理间隔,该属性的单位是毫秒。
需要特别注意,如果开启了自我保护模式,则实例不会被剔除。在测试时,为避免受 自我保护模式的影响,建议先关闭自我保护模式,在服务器中配置:
1 | eureka.server.enable-self- preservation= false 。 |
3.5.2注册表抓取间隔
在默认情况下,客户端每隔30秒去服务器端抓取注册表(可用的服务列表),并且将 服务器端的注册表保存到本地缓存中。
可以通过修改eureka.client.registryFetch- IntervalSeconds配置来改变注册表抓取间隔,但仍然需要考虑性能,改为哪个值比较合适,
需要在性能与实时性方面进行权衡。
3.5.3配置与使用元数据
框架自带的元数据,包括实例id、主机名称、ip地址等,如果需要自定义元数据并提 供给其他客户端使用,可以配置eureka.instance.metadata-map属性来指定。
元数据都会保存 在服务器的注册表中,并且使用简单的方式与客户端进行共享。在正常情况下,自定义元 数据不会改变客户端的行为,除非客户端知道这些元数据的含义,
以下配置片断使用了元数据。
1 2 3 4 5 | eureka: instance: hostname: localhost metadata-map: company-name: crazyit |
配置了一个名为company-name的元数据,值为crazyit,使用元数据的一方,可以调用 discoveryClient的方法获取元数据,如以下代码所示:
1 2 3 4 5 6 7 8 9 10 11 12 | @Autowired private DiscoveryClient discoveryClient;<br> @RequestMapping (value = "/router" , method = RequestMethod.GET) public String router() { //查询服务实例 List<ServiceInstance> ins = discoveryClient.getlnstances ( "heart-beat-client" ); //遍历实例并输岀元数据值 for (Serviceinstance service : ins) { System.out.printIn(service.getMetadata().get( "company-name" )); } return "" ; } |
3.5.4自我保护模式
在开发过程中,经常可以在Eureka的主界面中看到红色字体的提醒,内容如下:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE
NOT BEING EXPIRED JUST TO BE SAFE.
出现该提示意味着Eureka进入了自我保护模式。根据前面章节的介绍可知,客户端会定时发送心跳给服务器端,如果心跳的失败率超过一定比例,服务会将这些实例保护起来,
并不会马上将其从注册表中剔除此时对于另外的客户端来说,有可能会拿到一些无法使 用的实例,这种情况可能会导致灾难的 "蔓延",这些情况可以使用容错机制予以解决,
关于集群的容错机制,将在后面的章节中讲述;
在开发过程中,为服务器配置 eureka.server.enable-self-preservation属性,将值设置为false来关闭自我保护机制。
关闭后 再打开Eureka主界面,可以看到以下提示信息:
THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
自我保护模式己经关闭,在出现网络或者其他问题时,将不会保护过期的实例。
本章讲述了 Eureka框架,读者学习完本章后,可以掌握Eureka的架构、如何使用Eureka 搭建集群等内容,最基本的是,能对Spring Cloud等技术有一个初步认识。
如果想更进一步,可以学习编写服务实例的自检程序。实际环境中的服务不可避免地依赖第三方环境,例如数据库、第三方服务等,因此, 如果掌握了对程序的自检,可以使得我们的应用程序更加健壮。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)