Ribbon
Ribbon
是 Netflix
开源的基于 HTTP
和 TCP
的客户端负载均衡器框架,目前也已被 Spring Cloud
团队集成在 spring-cloud-netflix
子项目下,主要用于客户端软负载功能,内部已实现了 随机
、轮训
、权重
、减压(选取压力最小的)
等常见的负载算法,同时也提供了 ILoadBalance
与 IRule
两个接口方便我们自己编写适合自己的负载算法
- 负载均衡
- 容错
- 多协议(HTTP,TCP,UDP)支持异步和反应模型
- 缓存和批处理
交互图
Try
要尝试 Spring Cloud Ribbon
首要的就是准备一个服务注册中心,还不太清楚的可以在回头看看上一章 认识Eureka
,这里就不做过多赘述了,准备 eureka-server(回顾上一章)
、product-server
、order-server
三个项目,后面的两个可以理解为上一章的 eureka-client
Eureka Server
详情参考上一章,或从文末的 GITHUB 链接获取对应篇幅的完整代码
Product Server
一个普通的 Eureka Client
即可,详情参考上一章,或从文末的 GITHUB 链接获取对应篇幅的完整代码
Order Server
一个普通的 Eureka Client
细心的小伙伴会发现,这和一个普通的 Eureka Client
也没啥区别啊,没任何额外依赖的,那是因为在 spring-cloud-starter-netflix-eureka-client
中已经帮我们依赖过 spring-cloud-starter-netflix-ribbon
了。假如使用 consul
、zookeeper
、etcd
等容器为服务发现为者时,就必须依赖 spring-cloud-starter-netflix-ribbon
包
1 2 3 4 5 6 7 8 9 10 | <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> |
在 src/main/resources
目录下创建一个 bootstrap.yml
的文件,写上 eureka 相关配置信息
1 2 3 4 5 6 7 8 9 10 11 12 | server: port: 7072 spring: application: name: order-server eureka: instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}} client: service-url: defaultZone: http://localhost:7071/eureka/ |
各位小伙伴对 Spring Boot
中的 RestTemplate
应该都不陌生,它是由 Spring Boot
提供而不是 Spring Cloud
,无负载功能,为了方便开发者,Spring Cloud
团队提供了一个 @LoadBalanced
注解(默认采用轮训算法)
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 | package com.battcn;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 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;
/** * @author Levin */ @EnableDiscoveryClient @SpringBootApplication public class OrderApplication {
@Configuration class MyConfiguration { @LoadBalanced @Bean RestTemplate restTemplate() { return new RestTemplate(); } }
public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } |
客户端(order-server:7702
)从 Eureka Server
同步了 product-server:7703
和 product-server:7704
这个时候它是如何知晓注册表中的信息呢?上一章中遗留了一个知识点就是 DiscoveryClient
,通过它就可以获得注册表中客户端的信息了,下列代码块演示了 DiscoveryClient
的简单用法,更多 API 可以自行尝试
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 | package com.battcn.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;
import java.util.List;
/** * @author Levin * @since 2018/9/28 0028 */ @RestController @RequestMapping("/orders") public class OrderController {
private final RestTemplate restTemplate; private final DiscoveryClient discoveryClient;
@Autowired public OrderController(RestTemplate restTemplate, DiscoveryClient discoveryClient) { this.restTemplate = restTemplate; this.discoveryClient = discoveryClient; }
@GetMapping public String query() { final List<String> services = discoveryClient.getServices(); for (String service : services) { List<ServiceInstance> list = discoveryClient.getInstances(service); for (ServiceInstance instance : list) { System.out.println(instance.getUri() + "/" + service + " - " + instance.getServiceId()); } } return restTemplate.getForObject("http://PRODUCT-SERVER/products", String.class); } } |
有的小伙伴对上面的内容会存在一些疑问,为什么没有写 IP:PORT 了,而是写了一串字符,它是怎么做到的?
1 2 3 4 5 6 7 8 9 10 11 12 13 | 用通俗的概念来说,它就是编码与解码操作,还记得 Eureka Server UI 中 Application 吗? 编码:根据 spring.application.name 设置 serviceId Server ID:PRODUCT-SERVER Client A :http://localhost:7073/products Client B :http://localhost:7074/products
解码:通过 serviceId 找到对应的客户端, 然后根据客户端配置的负载算法从对应集合中 找出符合当前算法条件的结果,最后拼接出相应的 http 地址即可
解码前:http://PRODUCT-SERVER/products 那么将 http://PRODUCT-SERVER 替换成 instance.getUri() 内容是不是就出来了 http://localhost:7073/products 和 http://localhost:7074/products |
效果图
自定义 IRule
假如我们不想使用轮训了,换换口味改成随机算法,又或者想自己写一套适合自己的负载算法,可以用下面这种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.battcn.config;
import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
/** * @author Levin * @since 2018/9/28 0028 */ @Configuration public class RibbonRuleConfiguration {
@Bean public IRule ribbonRule() { return new RandomRule(); } }
@RibbonClient(name = "ribbonRule",configuration = RibbonRuleConfiguration.class) public class OrderController {
} |