负载均衡两种方式:服务端负载均衡,客户端负载均衡。
Ribbon简介
Ribbon是Netflix发布的云中间层服务开源项目,其主要功能是提供客户端侧负载均衡算法。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单来说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出Load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。
下图展示了Eureka使用Ribbon时的大致架构:
Ribbon工作是分为两步:
第一步:优先选择Eureka Server,它优先选择在同一个Zone且负载均衡较少的Server。
第二步:再根据用户指定的策略,在从Server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略,例如轮询round robin、随机Random、根据响应时间加权等。
使用Ribbon来进行负载均衡的时候不需要添加依赖包,因为在添加Eureka依赖的时候就包含了Ribbon的依赖。
1. 在启动类的Rest上添加注解@LoadBalanced,该注解帮我们做负载均衡的工作。
2. 在Controller类将硬编码的IP改替换成注册到EurekaServer上的IP,称为Virtual IP(简称VIP)。
通过代码自定义配置Ribbon
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>
<artifactId>microservice-consumer-movie-ribbon</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.kevin.cloud</groupId>
<artifactId>microservice-spring-cloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/>
</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>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 8010
spring:
application:
name: microservice-consumer-movie-ribbon
eureka:
client:
healthcheck:
enabled: true
service-url:
defaultZone: http://kevin:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
ConsumerMovieRibbonApplication.java
因为是将TestConfiguration.java放在与启动类同一行,所以在启动类需要添加注解
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = ExcludeFromComponentScan.class)})
package com.keivn.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;
/**
*
* @title 服务消费者
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@SpringBootApplication
@EnableEurekaClient //注册到Eureka
@RibbonClient(name = "microservice-provider-user", configuration = TestConfiguration.class) //随机使用服务提供者
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = ExcludeFromComponentScan.class)})
public class ConsumerMovieRibbonApplication {
@Bean
@LoadBalanced //该注解让Restful拥有负载均衡的能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieRibbonApplication.class, args);
System.out.println("服务消费者启动...");
}
}
ExcludeFromComponentScan.java
并在该类添加作为注解
package com.keivn.cloud;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/8
* @version 1.0
*/
public @interface ExcludeFromComponentScan {
}
TestConfiguration.java
package com.keivn.cloud;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/8
* @version 1.0
*/
@Configuration
@ExcludeFromComponentScan
public class TestConfiguration {
//@Autowired
//IClientConfig iClientConfig;
//@Bean
//public IRule ribbonRule(IClientConfig config){
// return new RandomRule();
//}
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
MovieController.java
microservice-provider-user和microservice-provider-user2分别是服务提供者在配置文件配置时的IP名称,在之前的文章就有,这里就不多说了
package com.keivn.cloud.controller;
import com.keivn.cloud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
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;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/movie/{id}")
public User finById(@PathVariable long id ){
//http://localhost:7900/simple/
//VIP virtual IP
return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
}
@GetMapping("/test")
public String test(){
// 指定需要调用的服务
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
System.out.println("111: " + serviceInstance.getServiceId() + ": " +serviceInstance.getHost() + ": " + serviceInstance.getPort());
ServiceInstance serviceInstance2 = this.loadBalancerClient.choose("microservice-provider-user2");
System.out.println("222: "+ serviceInstance2.getServiceId() + ": " +serviceInstance2.getHost() + ": " + serviceInstance2.getPort());
return "1";
}
}
User.java
该实体类在本文章是通用的,所以下面就不再讲述
package com.keivn.cloud.entity;
import java.io.Serializable;
import java.math.BigDecimal;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
public class User implements Serializable {
private Long id;
private String username;
private String name;
private Short age;
private BigDecimal balance;
public User() {
}
public User(Long id, String username, String name, Short age, BigDecimal balance) {
this.id = id;
this.username = username;
this.name = name;
this.balance = balance;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Short getAge() {
return age;
}
public void setAge(Short age) {
this.age = age;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
通过配置文件自定义配置Ribbon
application.yml
server:
port: 8010
spring:
application:
name: microservice-consumer-movie-ribbon
eureka:
client:
healthcheck:
enabled: true
service-url:
defaultZone: http://kevin:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
microservice-provider-user:
ribbon: #配置负载均衡的规则
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#配置负载均衡的规则:响应时间加权的规则
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
ConsumerMovieRibbonApplication.java
package com.keivn.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;
/**
*
* @title 服务消费者
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@SpringBootApplication
@EnableEurekaClient //注册到Eureka
public class ConsumerMovieRibbonApplication {
@Bean
@LoadBalanced //该注解让Restful拥有负载均衡的能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieRibbonApplication.class, args);
System.out.println("服务消费者启动...");
}
}
MovieController.java
package com.keivn.cloud.controller;
import com.keivn.cloud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
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;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/movie/{id}")
public User findById(@PathVariable long id ){
//http://localhost:7900/simple/
//VIP virtual IP
//HAProxy Heartbeat
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
System.out.println("---: " + serviceInstance.getServiceId() + ": " +serviceInstance.getHost() + ": " + serviceInstance.getPort());
return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
}
@GetMapping("/test")
public String test(){
// 指定需要调用的服务
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
System.out.println("111: " + serviceInstance.getServiceId() + ": " +serviceInstance.getHost() + ": " + serviceInstance.getPort());
ServiceInstance serviceInstance2 = this.loadBalancerClient.choose("microservice-provider-user2");
System.out.println("222: "+ serviceInstance2.getServiceId() + ": " +serviceInstance2.getHost() + ": " + serviceInstance2.getPort());
return "1";
}
}
User.java(跟上面的一样)
Ribbon脱离Eureka的使用
Ribbon的依赖包包含在Eureka的依赖包里,所有pom.xml不必变更。
在配置文件中可以配置让Ribbon与Eureka脱离使用
application.yml
ribbon.eureka.enabled: false 使用该配置让Riboon可以跟eureka分离使用
server:
port: 8010
spring:
application:
name: microservice-consumer-movie-ribbon-without-eureka
eureka:
client:
healthcheck:
enabled: true
service-url:
defaultZone: http://kevin:123456@localhost:8761/eureka
instance:
prefer-ip-address: true
ribbon: #使用该配置让Riboon可以跟eureka分离使用
eureka:
enabled: false
microservice-provider-user:
ribbon: #配置Ribbon能访问的为服务节点,多个节点使用逗号隔开
listOfServers: localhost:7900, localhost:7902
#配置负载均衡的规则:响应时间加权的规则
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
ConsumerMovieRibbonApplication.java
package com.keivn.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;
/**
*
* @title 服务消费者
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@SpringBootApplication // 使用SpringBoot开发微服务
//@EnableEurekaClient //注册到Eureka
public class ConsumerMovieRibbonApplication {
@Bean
@LoadBalanced //该注解让Restful拥有负载均衡的能力
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieRibbonApplication.class, args);
System.out.println("服务消费者启动...");
}
}
MovieController.java
package com.keivn.cloud.controller;
import com.keivn.cloud.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
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;
/**
*
* @title
* @description
* @author caonanqing
* @createDate 2018/11/7
* @version 1.0
*/
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@GetMapping(value = "/movie/{id}")
public User findById(@PathVariable long id ){
//http://localhost:7900/simple/
//VIP virtual IP
//HAProxy Heartbeat
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
System.out.println("---: " + serviceInstance.getServiceId() + ": " +serviceInstance.getHost() + ": " + serviceInstance.getPort());
return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
}
@GetMapping("/test")
public String test(){
// 指定需要调用的服务
ServiceInstance serviceInstance = this.loadBalancerClient.choose("microservice-provider-user");
System.out.println("111: " + serviceInstance.getServiceId() + ": " +serviceInstance.getHost() + ": " + serviceInstance.getPort());
ServiceInstance serviceInstance2 = this.loadBalancerClient.choose("microservice-provider-user2");
System.out.println("222: " + serviceInstance2.getServiceId() + ": " +serviceInstance2.getHost() + ": " + serviceInstance2.getPort());
return "1";
}
}
User.java(跟上面一样)
Ribbon负载均衡策略
Ribbon一共提供了7中负载均衡策略。
负载均衡的策略是在配置文件application.yml配置的
(比如:)
microservice-provider-user:
ribbon: #配置负载均衡的规则
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.负载均衡策略名称