在以往的文章中我们已经介绍了Spring Cloud框架中的Eureka(服务注册/发现)、Config(中心化配置管理)的简单使用,接下来将要介绍如何创建服务提供者、服务消费者,以及如何实现服务调用。在Spring Cloud中,服务间的RPC是通过Http实现的,服务提供者开放Restful接口,消费者可以使用RestTemplate或其他客户端调用接口。
在微服务系统中,服务启动后会注册到注册/发现服务器上,同时也会从注册/发现服务器上获取所需的服务列表。同一个服务可能会存在多个甚至数以十计百计个节点,这时候消费者如果还是通过简单的Http客户端消费服务,显然难以实现有效的负载均衡。那么Spring Cloud时如何处理服务调用的负载均衡的呢?
Ribbon
Ribbon时Netflix开发的负载均衡实现,默认支持轮询、随机等算法,使用者也可以自定义实现自己的算法。在Spring Cloud中可以和Eureka配合使用,从Eureka Server获取服务列表后,完成请求的负载均衡。
实战
创建主项目
为了演示方便,笔者将Eureka Server、Config Server、服务提供者和服务消费者都创建在一个大的Maven工程下,结构如下:

根目录下的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.huluohu.cloud</groupId> <artifactId>huluohu-spring-cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>eureka-server</module> <module>config-server-git</module> <module>provider-user</module> <module>consumer-movie</module> </modules> <packaging>pom</packaging>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.10.RELEASE</version> </parent>
<properties> <java.version>1.8</java.version> <lombok.version>1.16.20</lombok.version> <spring-boot.version>1.5.10.RELEASE</spring-boot.version> <spring-cloud.version>Edgware.SR3</spring-cloud.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </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>Edgware.SR3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <target>${java.version}</target> <source>${java.version}</source> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </pluginManagement> </build> </project>
|
创建服务提供者
这里以用户服务为例provider-user

pom.xml
<parent> <artifactId>huluohu-spring-cloud-demo</artifactId> <groupId>com.huluohu.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>provider-user</artifactId>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <finalName>${project.name}</finalName> </configuration> </plugin> </plugins> </build>
|
创建启动类
package com.huluohu.cloud.user;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication @EnableDiscoveryClient public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class,args); } }
|
创建配置
bootstrap.yml
spring: application: name: provider-user rabbitmq: host: rabbitmq.huluohu.com port: 32602 username: guest password: guest cloud: config: uri: http://localhost:8888 fail-fast: true name: ${spring.application.name} profile: ${spring.profiles.active:default} label: master retry: max-attempts: 6 max-interval: 2000
|
application.yml
management: security: enabled: false
|
application-local.yml
server: port: 9001 eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8761/eureka/
|
application-local2.yml
server: port: 9002 #服务端口 eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http:
|
创建用户查询服务
UserDTO
package com.huluohu.cloud.user.presentation.mode;
import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor;
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class UserDTO { private Integer id; private String username; private String name; private Integer age; private Long balance; }
|
UserController
package com.huluohu.cloud.user.presentation.web;
import com.huluohu.cloud.user.presentation.mode.UserDTO; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController public class UserController { private Random random = new Random();
@GetMapping("/{id}") public UserDTO findById(@PathVariable Integer id) { return UserDTO.builder() .id(id) .username("root_" + id) .name("huluohu_" + id) .age(random.nextInt(30) % (30 - 15 + 1) + 15) .balance(random.nextLong()) .build(); } }
|
创建服务消费者
这里以电影服务为例consumer-movie

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>huluohu-spring-cloud-demo</artifactId> <groupId>com.huluohu.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion>
<artifactId>consumer-movie</artifactId>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <finalName>${project.name}</finalName> </configuration> </plugin> </plugins> </build> </project>
|
由于spring-cloud-starter-eureka
已经包含了spring-cloud-starter-ribbon的依赖,这里就不用单独添加了。
创建启动类
package com.huluohu.cloud.movie;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication @EnableDiscoveryClient public class MovieApplication { public static void main(String[] args) { SpringApplication.run(MovieApplication.class,args); } }
|
创建配置
bootstrap.yml
spring: application: name: consumer-movie rabbitmq: host: rabbitmq.huluohu.com port: 32602 username: guest password: guest cloud: config: uri: http://localhost:8888 fail-fast: true name: ${spring.application.name} profile: ${spring.profiles.active:default} label: master retry: max-attempts: 6 max-interval: 2000
|
application.yml
management: security: enabled: false
|
application-local.yml
server: port: 9009 eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8761/eureka/
|
创建测试代码
配置RestTemplate并启用Ribbon
package com.huluohu.cloud.movie;
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate;
@Configuration public class ApplicationConfiguration {
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
|
创建服务Facade
package com.huluohu.cloud.movie.application.service;
import com.huluohu.cloud.movie.application.model.User; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@Service public class UserService { @Resource private RestTemplate restTemplate;
public User findById(Integer id) { return restTemplate.getForObject("http://provider-user/" + id, User.class); } }
|
创建测试服务
package com.huluohu.cloud.movie.presentation.web;
import com.huluohu.cloud.movie.application.model.User; import com.huluohu.cloud.movie.application.service.UserService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController @RequestMapping("/test") public class TestController { @Resource private UserService userService;
@GetMapping("/user/{id}") public Object testFindUser(@PathVariable Integer id){ final User user = userService.findById(id); return ResponseEntity.ok(user); } }
|
测试
- 启动Eureka Server(local profile)
- 启动Config Server(不需要区分profile)
- 启动provider-user(local profile)
- 启动provider-user(local2 profile,模拟多节点)
- 启动consumer-movie(local profile)


在浏览器中访问http://localhost:9009/test/user/1
,访问consumer-movie中的测试接口,结果如下

consumer-movie通过RestTemplate的负载均衡,调用了provide-user的一个节点,并获取数据。
Robbin的简单使用到此介绍完了。

“扫一扫接着看”