Ribbon负载均衡
一、简介
1:什么是负载均衡
负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡(Load Balance)其意思就是分摊到多个操作单元上进行执行,例如Web服务器、FTP服务器、企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。以上内容来自于百度百科
个人理解而言,负载均衡,是弥补了单体架构或者单个服务的负载能力不足而导致的整体性能瓶颈,因此,可以通过将一个服务部署多台服务器,然后将一台机器原来的压力分摊到多个执行单元上,这样就提升了原有架构的性能瓶颈。可以理解为 “将负载均衡到多个服务中”。常见的负载均衡有两种策略:
- Nginx独立进程做负载均衡,通过负载均衡策略,将请求转发到不同的执行单元上
- 客户端负载均衡策略,通过在客户端保存服务列表信息,然后自己调用负载均衡策略,分摊调用不同的执行单元。
2:什么是Ribbon
Ribbon是Netflix公司开源的一个负载均衡的组件,它属于上述负载均衡方式中的第二种。将负载均衡的逻辑封装在了客户端中,并且运行在客户端。Ribbon经过了非常严格的测试,可以更好的控制Http和Tcp客户端的负载均衡行为。
Ribbon的负载均衡有两种方式
- 和RestTemplate结合
- 和Feign结合
Ribbon的核心子模块
- ribbon-loadbalancer:可以独立使用或者和其他模块一起使用的负载均衡API
- ribbon-eureka:结合Eureka作客户端的API
- ribbon-core:Ribbon的核心API
3:什么是RestTemplate
RestTemplate是SpringResource中一个访问第三方RESTful API接口的网络通讯框架。其主要方法都与REST的HTTP协议的方法紧密关联。
RestTemplate支持常见的HTTP请求方式,例如GET、POST、PUT、DELETE等,所以RestTemplate很容易构建RESTful API
调用方式例如
restTemplate.getForObject("http://common-service/hello", String.class)
其核心API如下
https://docs.spring.io/spring/docs/3.0.x/javadoc-api/org/springframework/web/client/RestTemplate.html

二、开始使用Ribbon
Ⅰ.代码编写
- ribbon-test父项目
<groupId>com.calvin.ribbon</groupId>
 <artifactId>ribbon-test</artifactId>
 <packaging>pom</packaging>
 <version>1.0-SNAPSHOT</version>
 <modules>
     <module>common-service</module>
     <module>eureka-server</module>
     <module>ribbon-service</module>
 </modules>
 <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>1.5.3.RELEASE</version>
 </parent>
 <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>Dalston.SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
- eureka-server
<parent>
    <groupId>com.calvin.ribbon</groupId>
    <artifactId>ribbon-test</artifactId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<properties>
    <java.version>1.8</java.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>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
application.yml
server:
  port: 8080
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone:
        http://${eureka.instance.hostname}:${server.port}/eureka
@EnableEurekaServer
@SpringBootApplication
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class);
    }
}
- commons-service
<parent>
    <artifactId>ribbon-test</artifactId>
    <groupId>com.calvin.ribbon</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common-service</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>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
spring:
  application:
    name: common-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka/
此处没有指定server.port,是因为我们在启动此服务的是时候做点手脚,需要以不同端口在本地启动,
即配置两个Name不同的启动类,给分别指定不同的Environment variables,内容为server.port=8083, server.port=8084
具体教程是在Idea下的EditConfiguration中做如下操作:

- CommonServiceApplication.java
@SpringBootApplication
@EnableEurekaClient
public class CommonServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CommonServiceApplication.class);
    }
}
@RestController
public class HelloController {
    @Value("${server.port}")
    private String port;
    /**
     * 接口测试
     * @return
     */
    @GetMapping("/hello")
    public String sayHello(){
        return "port : " + port ;
    }
}
- ribbon-service
<parent>
    <artifactId>ribbon-test</artifactId>
    <groupId>com.calvin.ribbon</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ribbon-service</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-ribbon</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
server:
  port: 8082
spring:
  application:
    name: ribbon-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka/
- RibbonClientApplication.java
@EnableEurekaClient
@SpringBootApplication
public class RibbonServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonServerApplication.class);
    }
    /**
     * 配置LoadBalance和RestTemplate
     * @return RestTemplate
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
接下来就是调用的关键代码
/**
 * <p> 
 *	CommonService服务远程调用类
 * </p>
 * @author Calvin
 * @date 2019/10/09
 * @since 1.0
 */
@Service
public class RemoteCommonService {
    /**
     * 注入RestTemplate
     */
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 远程调用common-service/hello
     * @return
     */
    public String sayHi(){
        return restTemplate.getForObject("http://common-service/hello", String.class);
    }
}
@RestController
public class SayHiController {
    @Autowired
    private RemoteCommonService remoteCommonService;
    @GetMapping("hi")
    public String sayHi(){
        return remoteCommonService.sayHi() + ", this is ribbon service";
    }
}
Ⅱ.配置启动
最终配置四个启动类,如图所示:

启动顺序:
- step1. EurekaSeverApplicaton
- step2. CommonServiceApplication
- step3. CommonServiceApplication2
- step4. RibbonServerApplication
Ⅲ.结果验证
Eureka管理界面 http://localhost:8080/

调用 http://localhost:8082/hi

刷新页面

三、核心剖析——LoadBalancerClient
负载均衡的核心类是LoadBalanceClient,此类可以获取负载均衡的服务提供者的实例信息,当然这些实例信息是通过EurekaClient获取的,并且缓存了一份服务实例信息。
@Autowired
    private LoadBalancerClient loanBalanceClient;
    /**
     * 远程调用common-service/hello
     * @return
     */
    public String sayHi(){
        ServiceInstance serviceInstance = loanBalanceClient.choose("common-service");
        logger.info("current service info is {} : {}", serviceInstance.getHost(), serviceInstance.getPort());
        return restTemplate.getForObject("http://common-service/hello", String.class);
    }
此时不断刷接口,同样可以看到LoadBalancerClient在不断的改变请求的实例信息。
四、小结
- 认识了关于Ribbon以及常用负载均衡的概念
- 动手实践了一下Ribbon进行负载均衡的调用
- 认识Ribbon的一个核心类
五、总结
- 本文首次实践Ribbon做负载均衡,缺乏原理剖析
- 文章中代码相对简单,但是基本可以表达Ribbon所做的事情
- 需要在以后,对整个Ribbon的源码进行解析(算是插眼)
- 关于RestTemplate还有很多用法,后续可以专门出文章进行深入理解