首页 > 代码库 > 第三章 服务治理:Spring Cloud Eureka
第三章 服务治理:Spring Cloud Eureka
服务治理:
服务注册:
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。比如:有两个提供服务A的进程分别运行于192.168.0.100:8000 和192.168.0.101:8000 位置上,还有三个提供服务B的进程分别运行于192.168.0.100:9000、192.168.0.101:9000、192.168.0.102:9000位置上。当这些进程都启动,并向注册中心注册自己的服务之后,注册中心就会维护类似下面的一个服务清单。另外,注册中心还需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,达到排除故障服务的效果。
服务发现:
Netflix Eureka
搭建服务注册中心
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-server</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
通过@EnableEurekaServer 注解启动一个服务注册中心提供给其他应用进行对话
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }
server.port=8082 eureka.instance.hostname=localhost # 向注册中心注册服务 eureka.client.register-with-eureka=false # 检索服务 eureka.client.fetch-registry=false eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
注册服务提供者
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>eureka-client</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>eureka-client</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR2</spring-cloud.version> </properties> <dependencies> <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.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
接着,新建RESTful API,通过注入DiscoveryClient对象,在日志中打印出服务的相关内容。
package com.example.demo.web; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @author lxx * @version V1.0.0 * @date 2017-8-9 */ @RestController public class HelloController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "/index") public String index(){ ServiceInstance instance = client.getLocalServiceInstance(); logger.info("/hello:host:"+instance.getHost()+" port:"+instance.getPort() +" service_id:"+instance.getServiceId()); return "hello world!"; } }
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class EurekaClientApplication { public static void main(String[] args) { SpringApplication.run(EurekaClientApplication.class, args); } }
最后修改application.properties文件,通过spring.application.name属性为服务命名,再通过eureka.client.service-url.defaultZone 属性来指定服务注册中心的地址,地址和注册中心设置的地址一致:
server.port=2222 spring.application.name=hello-service eureka.client.service-url.defaultZone=http://localhost:8082/eureka/
2017-08-09 17:17:27.635 INFO 8716 --- [ main] c.example.demo.EurekaClientApplication : Started EurekaClientApplication in 9.844 seconds (JVM running for 10.772) 2017-08-09 17:17:27.797 INFO 8716 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_HELLO-SERVICE/chanpin-PC:hello-service:2222 - registration status: 204
2017-08-09 17:17:27.786 INFO 10396 --- [nio-8082-exec-1] c.n.e.registry.AbstractInstanceRegistry : Registered instance HELLO-SERVICE/chanpin-PC:hello-service:2222 with status UP (replication=false) 2017-08-09 17:17:47.792 INFO 10396 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Running the evict task with compensationTime 0ms
高可用注册中心
- 创建 application-peer1.properties,作为peer1 服务中心的配置,并将serviceUrl指向peer2:
spring.application.name=eureka-server server.port=1111 eureka.instance.hostname=peer1 eureka.client.service-url.defaultZone=http://peer2:1112/eureka/
- 创建 application-peer2.properties,作为peer2 服务中心的配置,并将serviceUrl指向peer1:
spring.application.name=eureka-server server.port=1112 eureka.instance.hostname=peer2 eureka.client.service-url.defaultZone=http://peer1:1111/eureka/
- 在C:\Windows\System32\drivers\etc\hosts 文件中添加对peer1 和 peer2 中的转换,让上面配置的host形式的serviceURL能在本地正确访问到;
127.0.0.1 peer1 127.0.0.1 peer2
- 通过spring.profiles.active 属性来分别启动peer1 和 peer2(打开两个terminal进行启动,在一个terminal中先启动的peer1 会报错,但不影响,是因为它所注册的服务peer2 还未启动,在另外个terminal中把peer2 启动即可,不用启动主类) :
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2
- 在设置了多节点的服务注册中心之后,服务提供方还需要做一些简单的配置才能将服务注册到Eureka Server 集群中。以hello-service为例,修改配置文件如下:
server.port=2222 spring.application.name=hello-service eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
如果不想使用主机名来定义注册中心的地址,也可以使用IP地址的形式,但是需要在配置文件中增加配置参数 eureka.instance.prefer-ip-address=true,该值默认为false。
服务发现与消费
- 准备工作:启动之前实现的服务注册中心eureka-server以及hello-service服务,为了实验Ribbon的客户端负载均衡功能,我们通过java -jar 命令行的方式来启动两个端口不同的hello-service,具体如下:
- 修改配置文件:
server.port=2222 spring.application.name=hello-service eureka.client.service-url.defaultZone=http://localhost:8082/eureka/
- 再将hello-service应用打包:mvn clean package
- 通过下列命令启动应用程序:
java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=8011
java -jar eureka-client-0.0.1-SNAPSHOT.jar --server.port=8012
- 成功启动两个服务后,可以在注册中心看到名为HELLO-SERVICE的服务中出现两个实例单元:
- 创建一个Spring boot项目来实现服务消费者,取名为ribbon-consumer,并在pom.xml中引入如下的依赖内容。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Dalston.SR2</spring-cloud.version> </properties> <dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
- 在主类中通过@EnableDiscoveryClient注解让该应用注册为Eureka客户端应用,以获取服务发现的能力,同时,在该主类中创建RestTemplate的Spring Bean实例,并通过@LoadBalanced 注解开启客户端负载均衡。
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class DemoApplication { @Bean @LoadBalanced RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
- 创建ConsumerController类并实现/ribbon-consumer接口。在该接口中,通过上面创建的RestTemplate 来实现对HELLO-SERVICE 服务提供的 /hello 接口进行调用。此处的访问地址是服务名 HELLO-SERVICE ,而不是一个具体的地址,在服务治理框架中,这是一个重要特性。
package com.example.demo.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; /** * @author lxx * @version V1.0.0 * @date 2017-8-9 */ @RestController public class ConsumerController { @Autowired RestTemplate restTemplate; @RequestMapping(value = "ribbon-consumer", method = RequestMethod.GET) public String helloConsumer(){ return restTemplate.getForEntity("http://HELLO-SERVICE/index", String.class).getBody(); } }
- 在application.properties中配置Eureka服务注册中心的位置,需要与之前的HELLO-SERVICE一样,同时设置该消费者的端口为3333,不与之前启动的应用端口冲突即可。
server.port=3333 spring.application.name=ribbon-consumer eureka.client.service-url.defaultZone=http://localhost:8082/eureka/
- 启动ribbon-consumer应用后,可以在Eureka信息面板中看到,除了HELLO-SERVICE外,还多了实现的RIBBON-CONSUMER服务。
- 通过向 http://localhost:3333/ribbon-consumer 发起访问, 成功返回字符串 “hello world”。在消费者控制台中打印出服务列表情况。
- 多发送几次请求,可以在服务提供方hello-service的控制台中看到一些打印信息,可以看出两个控制台基本是交替访问,实现了客户端的负载均衡。
Eureka详解
基础架构(核心三要素)
- 服务注册中心:Eureka提供的服务端,提供服务注册与发现的功能,即之前的eureka-server。
- 服务提供者:提供服务的应用,可以是spring boot应用,也可以是其他技术平台且遵循Eureka通信机制的应用。它将自己提供的服务注册到Eureka,以供其他应用发现。即之前的HELLO-SERVICE.
- 服务消费者:消费者从服务注册中心获取服务列表,从而使消费者可以知道去何处调用其所需要的服务,在上一节中使用了Ribbon来实现服务消费,后续还会介绍使用Feign的消费方式
服务治理机制
第三章 服务治理:Spring Cloud Eureka