Spring Cloud(二):服务治理( Eureka)&客户端负载均衡(Ribbon)
服务治理是微服务架构中最为核心和基础的模块,主要作用是实现各个微服务实例的自动化注册和发现。Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,基于Netflix EureKa做了二次封装,主要负责微服务架构中的服务治理功能。
Eureka的服务发现包含两大组件,服务端发现组件(Eureka Server)和客户端发现组件(Eureka Client)。服务端发现组件也被称为服务注册中心,主要提供了服务的注册功能,客户端发现组件主要用于处理服务的注册与发现。按照角色分类,在Eureka中,服务发现机制包含3个角色:服务注册中心,服务提供者以及服务消费者。
一、使用Eureka注册服务
1. 创建maven父工程
创建一个maven工程作为父工程,命名为 springcloud-demo-parent ,并在工程的pom.xml中添加Spring Cloud的版本依赖信息:
<?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.fix</groupId> <artifactId>springcloud-demo-parent</artifactId> <version>1.0-SNAPSHOT</version> <!--父依赖--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> </parent> <!--编码以及Java版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <!--Spring Boot包含的Maven插件,可以将项目打包成可以执行的Jar文件--> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2. 搭建服务注册中心
创建Maven子模块 springcloud-demo-server 作为服务注册中心(服务端工程),在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"> <parent> <artifactId>springcloud-demo-parent</artifactId> <groupId>com.fix</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-demo-server</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> </project>
创建配置文件application.yml,在配置文件中增加端口号等配置信息,内容如下:
server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
以上配置表示eureka注册中心的端口号为8761,所有服务的实例都要向此端口注册。 eureka.instance.hostname 表示该eureka注册中心的实例名称为localhost, register-with-eureka:false 表示该Spring Boot应用为注册中心,不需要注册中心注册自己。 fetch-registry: false 的配置是因为注册中心的职责是维护服务实例,并不需要去检索服务。而 defaultZone 的地址是注册中心的地址。
接着,创建项目启动类,并添加 @EnableEurekaServer 注解,用于声明该标注类是一个Erueka Server:
package com.fix.springcloud.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaServerMain { public static void main(String [] args){ SpringApplication.run(EurekaServerMain.class,args); } }
启动 springcloud-demo-server 模块,在浏览器访问:http://localhost:8761/ 可以看到Eureka的信息面板:
此时“Instances currently registered with Eureka”中显示当前已经注册到Eureka的服务实例为空。
3.搭建客户端工程
在父工程 springcloud-demo-parent 中,创建Maven子模块 springcloud-demo-user 作为客户端工程,在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">
<parent>
<artifactId>springcloud-demo-parent</artifactId>
<groupId>com.fix</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-demo-user</artifactId>
<dependencies>
<!--Finchley,Greenwich版本的spring cloud,创建客户端工程的时候需要引入该依赖-->
<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>
</dependencies>
</project>
创建application.yml配置文件,在配置文件中配置Eureka服务实例的端口号,服务端地址等信息,如下:
server:
port: 8000
eureka:
instance:
prefer-ip-address: true #是否显示主机的Ip
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #指定eureka服务端地址
spring:
application:
name: springcloud-demo-user
创建客户端引导类 EurekaUserMain ,并添加 @EnableEurekaClient 注解用于声明标注类是一个Eureka客户端组件。具体内容如下:
@SpringBootApplication @EnableEurekaClient public class EurekaUserMain { public static void main(String[] args) { SpringApplication.run(EurekaUserMain.class, args); } }
启动 springcloud-demo-server 模块之后,启动 springcloud-demo-user 模块,再次访问http://localhost:8761/ ,可以看到在“Instances currently registered with Eureka”中出现了刚才创建的 SPRINGCLOUD-DEMO-USER 实例:
同时,可以看到该页面有红色的字体提示“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 Server的自我保护机制,该机制会使注册中心维护的实例不是很准确。在本地开发时候,可以在服务注册中心中配置 eureka.server.enable-self-preservation=false 参数来关闭保护机制。
二、使用Eureka实现服务间的调用
在上一步中,已经将用户服务 springcloud-demo-user 注册到了服务注册中心,接下来将创建一个订单服务,并实现用户服务和订单服务之间的调用。
1. 搭建订单服务工程
在父工程 springcloud-demo-parent 中,创建子模块 springcloud-demo-order ,pom.xml文件和 springcloud-demo-user 的类似,具体内容如下:
<?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"> <parent> <artifactId>springcloud-demo-parent</artifactId> <groupId>com.fix</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>springcloud-demo-order</artifactId> <dependencies> <!--Finchley,Greenwich版本的spring cloud,创建客户端工程的时候需要引入该依赖--> <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> </dependencies> </project>
创建配置文件 application.yml ,配置Eureka服务实例的端口号,服务注册中心地址等信息,具体内容如下:
server: port: 7900 eureka: instance: prefer-ip-address: true #是否显示主机的Ip client: service-url: defaultZone: http://localhost:8761/eureka/ #指定eureka服务端地址 spring: application: name: springcloud-demo-order #指定应用名称
创建客户端引导类 EurekaOrderMain :
@SpringBootApplication @EnableEurekaClient public class EurekaOrderMain { public static void main(String[] args) { SpringApplication.run(EurekaOrderMain.class, args); } }
创建订单实体类:
package com.fix.springcloud.demo.pojo; public class Order { private String id; private Double price; private String receiverName; private String receiverAddress; private String receiverPhone; public String getId() { return id; } public void setId(String id) { this.id = id; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getReceiverName() { return receiverName; } public void setReceiverName(String receiverName) { this.receiverName = receiverName; } public String getReceiverAddress() { return receiverAddress; } public void setReceiverAddress(String receiverAddress) { this.receiverAddress = receiverAddress; } public String getReceiverPhone() { return receiverPhone; } public void setReceiverPhone(String receiverPhone) { this.receiverPhone = receiverPhone; } @Override public String toString() { return "Order{" + "id='" + id + '\'' + ", price=" + price + ", receiverName='" + receiverName + '\'' + ", receiverAddress='" + receiverAddress + '\'' + ", receiverPhone='" + receiverPhone + '\'' + '}'; } }
创建订单Controller,内容如下:
package com.fix.springcloud.demo.controller; import com.fix.springcloud.eureka.pojo.Order; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { /** * 通过订单id查询订单信息 * @param id 订单id * @return 订单详情 */ @GetMapping("/order/{id}") public String findOrderById(@PathVariable String id){ Order order =new Order(); order.setId("123"); order.setPrice(200.50); order.setReceiverName("张三"); order.setReceiverAddress("陕西西安"); order.setReceiverPhone("123456799"); return order.toString(); } }
2. 编写用户服务功能
在 springcloud-demo-user 模块的引导类中,创建 RestTemplate 的Spring实例,代码如下:
@SpringBootApplication @EnableEurekaClient public class EurekaUserMain { public static void main(String[] args) { SpringApplication.run(EurekaUserMain.class, args); } @Bean public RestTemplate initRestTemplate(){ return new RestTemplate(); } }
创建用户Controller,调用订单Controller接口,查询订单信息,具体代码如下:
package com.fix.springcloud.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/findOrderByUser/{id}") public String findOrderByUser(@PathVariable String id) { int orderId = 123; return this.restTemplate.getForObject("http://localhost:7900/order/" + orderId, String.class); } }
3.启动各Eureka实例进行测试
依次启动服务注册中心, springcloud-demo-order 以及 springcloud-demo-user 实例,访问注册中心管理界面 http://localhost:8761/ 确认两个服务实例已经注册成功:
访问 springcloud-demo-user 实例的接口接口 http://localhost:8000/findOrderByUser/123,可以看到有数据正常返回:
表示使用Eureka进行服务间接口调用成功。接下来简单介绍一下Spring Cloud Ribbon进行客户端负载均衡。
三、使用Ribbon进行客户端负载均衡
Ribbon是Netfix发布的开源项目,主要功能是提供客户端的负载均衡算法。在Eureka的自动配置依赖模块 spring-cloud-starter-netflix-eureka-server 和 spring-cloud-starter-netflix-eureka-client 已经集成了Ribbon,可以直接使用Ribbon来实现客户端的负载均衡。接下来介绍以下Ribbon的使用
1. 添加 @LoadBalanced 注解。
在 springcloud-demo-user 实例的引导类 initRestTemplate() 方法上添加 @LoadBalanced 注解:
@SpringBootApplication @EnableEurekaClient public class EurekaUserMain { public static void main(String[] args) { SpringApplication.run(EurekaUserMain.class, args); } @Bean @LoadBalanced public RestTemplate initRestTemplate(){ return new RestTemplate(); } }
当使用 @LoadBalanced 注解之后, RestTemplate 就具有了负载均衡的能力。
2. 修该接口调用url
修改 UserController.findOrderByUser() 方法中调用 springcloud-demo-order 实例的接口url中的“主机地址+端口号”为服务提供者(springcloud-demo-order)的实例名称,如下:
@RestController public class UserController { @Autowired private RestTemplate restTemplate; @GetMapping("/findOrderByUser/{id}") public String findOrderByUser(@PathVariable String id) { int orderId = 123; return this.restTemplate.getForObject("http://SPRINGCLOUD-DEMO-ORDER/order/" + orderId, String.class); } }
3.调用接口验证
首先在 springcloud-demo-order 服务实例的 OrderController.findOrderById() 方法入口添加日志打印,依次启动注册中心、 springcloud-demo-order 实例以及 springcloud-demo-user 实例,然后修改 springcloud-demo-order 实例的端口号之后,在新的IDEA窗口中再次启动一个 springcloud-demo-order 实例的进程,此时登录注册中心管理界面可以看到 springcloud-demo-order 实例有两个可用节点,一个节点的端口是7900,另外一个是7901.
此时多次调用接口 http://localhost:8000/findOrderByUser/123 观察两个IDEA窗口日志打印,发现两个窗口都有日志打印,说明客户端敷在均衡生效:
源码地址:https://github.com/francis785/springclouddemo.git