什么是Spring Cloud Ribbon

一个基于HTTP和TCP的客户端负载均衡工具

简单示例

1、集成ribbon

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>

2、消费方

消费方调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@RestController
public class DemoController {
/**
* 主要用来调用REST服务,本身并不具备调用分布式服务的能力,但通过LoadBalanced注解开启客户端负债均衡
* @return
*/
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}

@RequestMapping(value = "/router", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public String router() {
RestTemplate restTpl = getRestTemplate();
// 根据应用名称调用服务
String json = restTpl.getForObject("http://eureka-provider/person/1", String.class);
return json;
}
}

消费方配置文件

1
2
3
4
5
6
7
8
9
10
11
server:
port: 9050
spring:
application:
name: eureka-consumer
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://localhost:9010/eureka/,http://localhost:9020/eureka/

3、服务方

服务提供方法, 这里就是简单模拟了一下根据personId获取人员信息,并返回对应服务的端口

1
2
3
4
5
6
7
@RequestMapping(value = "/person/{personId}", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public Person findPerson(@PathVariable("personId") Integer personId, HttpServletRequest request) {
Person person = new Person(personId, "songsy", 18);
person.setName(person.getName() + "端口:" + IpConfigurationUtils.getPort());
return person;
}

配置文件

1
2
3
4
5
6
7
8
9
spring:
application:
name: eureka-provider
eureka:
instance:
hostname: localhost
client:
service-url:
defaultZone: http://localhost:9010/eureka/,http://localhost:9020/eureka/

3.1 服务方1 以9011端口启动
1
2
3
public static void main(String[] args) {
new SpringApplicationBuilder(Slave1ProviderApplication.class).properties("server.port=9011").run(args);
}
3.2 服务方2 以9023端口启动
1
2
3
public static void main(String[] args) {
new SpringApplicationBuilder(Slave1ProviderApplication.class).properties("server.port=9023").run(args);
}

4、测试结果

连续访问 http://localhost:9050/router 会得到不同的结果, 可以看到已经实现了负载均衡

1
2
3
{"id":1,"name":"songsy端口:9023","age":18}

{"id":1,"name":"songsy端口:9011","age":18}

二:RestTemplate 使用

GET 请求

提供 getForObject()、 getForEntity()

POST 请求

提供 postForObject()、 postForObject()、postForLocation()

PUT 请求

提供 put(), put() 函数没有返回内容

DELETE 请求

提供 delete()

三:源码分析

Ribbon实现客户端负债均衡是通过@LoadBalanced注解来开启的

  • @LoadBalanced 注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 使用 LoadBalancerClient 该类来配置
    * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
    * @author Spencer Gibb
    */
    @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Qualifier
    public @interface LoadBalanced {
    }
  • LoadBalancerClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Represents a client side load balancer
* @author Spencer Gibb
*/
public interface LoadBalancerClient extends ServiceInstanceChooser {

/**
* 根据挑选出来服务实例执行请求
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

/**
* 根据服务实例执行请求
* execute request using a ServiceInstance from the LoadBalancer for the specified
* service
* @param serviceId the service id to look up the LoadBalancer
* @param serviceInstance the service to execute the request to
* @param request allows implementations to execute pre and post actions such as
* incrementing metrics
* @return the result of the LoadBalancerRequest callback on the selected
* ServiceInstance
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

/**
* 将 http://myservice/path/to/service 构建一个真实的host:port形式的url
* Create a proper URI with a real host and port for systems to utilize.
* Some systems use a URI with the logical serivce name as the host,
* such as http://myservice/path/to/service. This will replace the
* service name with the host:port from the ServiceInstance.
* @param instance
* @param original a URI with the host as a logical service name
* @return a reconstructed URI
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
  • ServiceInstanceChooser.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface ServiceInstanceChooser {

    /**
    * 根据传入的服务实例名serviceId,从负债均衡中挑选一个对应服务的实例
    * Choose a ServiceInstance from the LoadBalancer for the specified service
    * @param serviceId the service id to look up the LoadBalancer
    * @return a ServiceInstance that matches the serviceId
    */
    ServiceInstance choose(String serviceId);
    }

主要负载均衡策略

1、简单轮询负载均衡(RoundRobin)

以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。

2、随机负载均衡 (Random)

随机选择状态为UP的Server

3、加权响应时间负载均衡 (WeightedResponseTime)

根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。

4、区域感知轮询负载均衡(ZoneAvoidanceRule)

复合判断server所在区域的性能和server的可用性选择server