SpringCloud系列之Ribbon

作者 胡萝虎 日期 2018-05-11
SpringCloud系列之Ribbon

在以往的文章中我们已经介绍了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工程下,结构如下:

image-20180511211933987

根目录下的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>
<!--Lombok-->
<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

image-20180511211438605

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} #默认就是spring.application.name
    profile: ${spring.profiles.active:default} # 默认就是spring.profiles.active
    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/ #eureka server地址
  • application-local2.yml

    server:
    port: 9002 #服务端口
    eureka:
    instance:
    prefer-ip-address: true
    client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
    defaultZone: http://localhost:8761/eureka/ #eureka server地址

创建用户查询服务

  • 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

image-20180511214254881

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} #默认就是spring.application.name
    profile: ${spring.profiles.active:default} # 默认就是spring.profiles.active
    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/ #eureka server地址

创建测试代码

  • 配置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 //启动负载均衡(Ribbon实现)
    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) {
    //provider-user为用户服务的服务主机名称,通过该名称从eureka获取服务列表
    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);
    }
    }

测试

  1. 启动Eureka Server(local profile)
  2. 启动Config Server(不需要区分profile)
  3. 启动provider-user(local profile)
  4. 启动provider-user(local2 profile,模拟多节点)
  5. 启动consumer-movie(local profile)
  • 启动应用列表

image-20180511220649099

  • 服务注册情况

image-20180511221749856

  • 测试结果

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

image-20180511220803523

consumer-movie通过RestTemplate的负载均衡,调用了provide-user的一个节点,并获取数据。

Robbin的简单使用到此介绍完了。

你可能会喜欢

“扫一扫接着看”