在以往的文章中我们已经介绍了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的简单使用到此介绍完了。
“扫一扫接着看”
为正常使用评论功能,请开启浏览器的JavaScript