1. 两种负载均衡

当系统面临大量的用户访问,负载过高的时候,通常会增加服务器数量来进行横向扩展(集群),多个服务器的负载需要均衡,以免出现服务器负载不均衡,部分服务器负载较大,部分服务器负载较小的情况。通过负载均衡,使得集群中服务器的负载保持在稳定高效的状态,从而提高整个系统的处理能力。

软件负载均衡:nginx lvs
硬件负载均衡:F5
我们只关注软件负载均衡,
第一层可以用DNS,配置多个A记录,让DNS做第一层分发。
第二层用比较流行的是反向代理,核心原理:代理根据一定规则,将http请求转发到服务器集群的单一服务器上。

软件负载均衡分为:服务端(集中式),客户端。
服务端负载均衡:在客户端和服务端中间使用代理,nginx。
客户端负载均衡:根据自己的情况做负载。Ribbon就是。
客户端负载均衡和服务端负载均衡最大的区别在于: 服务端地址列表的存储位置,以及负载算法在哪里

1.1 客户端负载均衡

在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端地址列表,这些列表统统都是从服务注册中心获取的;
Ribbon使用的是客户端负载均衡。
而在Spring Cloud中我们如果想要使用客户端负载均衡,方法很简单,使用@LoadBalanced注解即可,这样客户端在发起请求的时候会根据负载均衡策略从服务端列表中选择一个服务端,向该服务端发起网络请求,从而实现负载均衡。

Ribbon官方: https://github.com/Netflix/ribbon

1.2 服务端负载均衡

在服务端负载均衡中,客户端节点只知道单一服务代理的地址,服务代理则知道所有服务端的地址。

上面几种负载均衡,硬件,软件(服务端nginx,客户端ribbon)。目的都是将请求分发到其他功能相同的服务。
如果手动实现,其实也是它的原理,做事的方法。

手写客户端负载均衡

  1. 知道自己的请求目的地(虚拟主机名,默认是spring.application.name
  2. 获取所有服务端地址列表(也就是注册表)。
  3. 选出一个地址,找到虚拟主机名对应的ipport(将虚拟主机名 对应到 ip和port上)。
  4. 发起实际请求(纯HTTP的请求)。

Ribbon是Netflix开发的客户端负载均衡器,为Ribbon配置服务提供者地址列表后,Ribbon就可以基于某种负载均衡策略算法,自动地帮助服务消费者去请求服务提供者。Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等。我们也可以实现自定义负载均衡算法。

Ribbon作为Spring Cloud的负载均衡机制的实现,它实现了:

  1. Ribbon与Eureka配合使用时,Ribbon可自动从Eureka Server获取服务提供者地址列表,并基于负载均衡算法,请求其中一个服务提供者实例。
  2. Ribbon可以单独使用,作为一个独立的负载均衡组件。只是需要我们手动配置 服务地址列表。
  3. RibbonOpenFeignRestTemplate可以进行无缝对接,让二者具有负载均衡的能力。OpenFeign组件默认集成了ribbon。

    2. Ribbon组成

    官网首页:https://github.com/Netflix/ribbon
Ribbon组件 说明
ribbon-core 核心的通用性代码。api一些配置。
ribbon-eureka 基于eureka封装的模块,能快速集成eureka。
ribbon-examples 学习示例。
ribbon-httpclient 基于apache httpClient封装的rest客户端,集成了负载均衡模块,可以直接在项目中使用。
ribbon-loadbalancer 负载均衡模块。
ribbon-transport 基于netty实现多协议的支持。比如http,tcp,udp等。

3. Ribbon自带的负载均衡算法

策略算法 名字 说明
ZoneAvoidanceRule(默认) 区域权衡策略 复合判断Server所在区域的性能和Server的可用性,轮询选择服务器。
其他规则:
BestAvailableRule 最低并发策略 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略。
RoundRobinRule 轮询策略 以简单轮询选择一个服务器。按顺序循环选择一个server。
RandomRule 随机策略 随机选择一个服务器。
AvailabilityFilteringRule 可用过滤策略 会先过滤掉多次访问故障而处于断路器跳闸状态的服务和过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表安装轮询策略进行访问。
WeightedResponseTimeRule 响应时间加权策略 据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用RoundRobinRule(轮询)策略,等统计的信息足够了会自动的切换到WeightedResponseTimeRule。响应时间长,权重低,被选择的概率低。反之,同样道理。此策略综合了各种因素(网络,磁盘,IO等),这些因素直接影响响应时间。
RetryRule 重试策略 先按照RoundRobinRule(轮询)的策略获取服务,如果获取的服务失败则在指定的时间会进行重试,进行获取可用的服务。如多次获取某个服务失败,就不会再次获取该服务。主要是在一个时间段内,如果选择一个服务不成功,就继续找可用的服务,直到超时。

4. 使用LoadBalancerClient

给定业务: 查询用户收藏的订单列表. 次业务需要从用户中心发起, 调用订单中心.

4.1 order_center项目新增接口如下

  1. import org.springframework.web.bind.annotation.GetMapping;
  2. import org.springframework.web.bind.annotation.PathVariable;
  3. import org.springframework.web.bind.annotation.RequestParam;
  4. import org.springframework.web.bind.annotation.RestController;
  5. /**
  6. * @Author:壹心科技BCF项目组 wangfan
  7. * @Date:Created in 2020/10/5 13:17
  8. * @Project:epec
  9. * @Description:TODO
  10. * @Modified By:wangfan
  11. * @Version: V1.0
  12. */
  13. @RestController
  14. @RequestMapping("/order")
  15. public class OrderController {
  16. /**
  17. * 通过用户ID获取收藏的订单列表接口
  18. * @param userId
  19. * @return
  20. */
  21. @GetMapping("/user/collect/list/{userId}")
  22. public String getOrderList(@PathVariable Integer userId){
  23. return "{\"code\":\"0000\",\"msg\":\"success\",\"rersult\":[{\"name\":\"name1\",\"id\":\"100034\"},{\"name\":\"name2\",\"id\":\"100035\"},{\"name\":\"name3\",\"id\":\"100036\"}]}";
  24. }
  25. }

4.2 user_center项目新增接口如下

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.cloud.client.ServiceInstance;
  3. import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.PathVariable;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import org.springframework.web.client.RestTemplate;
  8. /**
  9. * @Author:壹心科技BCF项目组 wangfan
  10. * @Date:Created in 2020/10/5 13:17
  11. * @Project:epec
  12. * @Description:TODO
  13. * @Modified By:wangfan
  14. * @Version: V1.0
  15. */
  16. @RestController
  17. public class CollectController {
  18. //http客户端工具
  19. @Autowired
  20. private RestTemplate template;
  21. //注入负载均衡客户端
  22. @Autowired
  23. private LoadBalancerClient loadBalancerClient;
  24. /**
  25. * 获取用户收藏的订单列表
  26. * @param userId
  27. * @return
  28. */
  29. @GetMapping("/collect/orders/{userId}")
  30. public String getCollectOrders(@PathVariable("userId") Integer userId){
  31. //选择一个ORDER-CENTER实例
  32. ServiceInstance serviceInstance = loadBalancerClient.choose("ORDER-CENTER");
  33. //拼接HTTP URL请求
  34. String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/order/user/collect/list/"+userId;
  35. //发起请求, 接收结果
  36. String result = template.getForObject(url, String.class);
  37. result = "请求"+url+", 返回结果:"+result;
  38. return result;
  39. }
  40. }

4.3 启动项目并测试

user_center项目启动2个端口: 90009001
order_center项目启动2个端口: 1000010001
user_center项目 和 order_center 项目和 Eureka注册中心都启动成功之后. 打开注册中心控制面板:
图片.png
表示注册没问题.
打开浏览器输入: http://192.168.0.100:9000/collect/orders/1
得到结果:

请求http://localhost:10000/order/user/collect/list/1, 返回结果:{“code”:”0000”,”msg”:”success”,”rersult”:[{“name”:”name1”,”id”:”100034”},{“name”:”name2”,”id”:”100035”},{“name”:”name3”,”id”:”100036”}]} 或者 请求http://localhost:10001/order/user/collect/list/1, 返回结果:{“code”:”0000”,”msg”:”success”,”rersult”:[{“name”:”name1”,”id”:”100034”},{“name”:”name2”,”id”:”100035”},{“name”:”name3”,”id”:”100036”}]}

至此LoadBalancerClient成功使用.

5. 切换负载均衡策略

5.1 声明@Bean注解方式切换

  1. import com.netflix.loadbalancer.*;
  2. /**
  3. * 切换客户端负载均衡策略
  4. */
  5. @Bean
  6. public IRule configRule(){
  7. // return new RoundRobinRule(); //轮询策略
  8. // return new RandomRule(); //随机策略
  9. // return new ZoneAvoidanceRule(); //区域权衡策略
  10. // return new BestAvailableRule(); //最低并发策略
  11. // return new AvailabilityFilteringRule();//可用过滤策略
  12. // return new WeightedResponseTimeRule(); //响应时间加权策略
  13. return new RetryRule(); //重试策略
  14. }

5.2 配置方式切换

  1. #切换客户端负载均衡策略
  2. #给所有服务定ribbon策略:
  3. ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
  4. #针对服务定ribbon策略:
  5. provider.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

属性配置方式优先级高于Java代码。

6. 自定义负载均衡策略

  1. /**
  2. * 获取用户收藏的订单列表
  3. * @param userId
  4. * @return
  5. */
  6. @GetMapping("/collect/orders/{userId}")
  7. public String getCollectOrders(@PathVariable("userId") Integer userId){
  8. //选择一个ORDER-CENTER实例
  9. ServiceInstance serviceInstance;
  10. //获取ORDER-CENTER所有实例列表
  11. List<ServiceInstance> instances = discoveryClient.getInstances("ORDER-CENTER");
  12. //自定义轮训算法
  13. AtomicInteger counter = new AtomicInteger();
  14. int i = counter.getAndIncrement();
  15. serviceInstance = instances.get(i % instances.size());
  16. //自定义随机算法
  17. int random = new Random().nextInt(instances.size());
  18. serviceInstance = instances.get(random);
  19. //自定义权重
  20. instances.forEach(instance->{
  21. instance.getMetadata(); //获取自定义权重元信息分析权重来获取权重算法
  22. });
  23. //使用默认方式选择一个ORDER-CENTER实例
  24. serviceInstance = loadBalancerClient.choose("ORDER-CENTER");
  25. //拼接HTTP URL请求
  26. String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/order/user/collect/list/"+userId;
  27. //发起请求, 接收结果
  28. String result = template.getForObject(url, String.class);
  29. result = "请求"+url+", 返回结果:"+result;
  30. return result;
  31. }

7. 单独使用ribbon

ribbon默认和Spring cloud集成, 从服务注册中心获取服务端的地址信息. 如果要单独使用, 需要额外配置关闭它的微服务能力并手动指定服务列表给ribbon.

  1. #ribbon单独使用
  2. ribbon.eureka.enabled=false
  3. ribbon.listOfServers=localhost:10000,localhost:10001

8. Ribbon集成RestTemplate

8.1 @Bean注解方法上加上@Loadbalancer

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. /**
  4. * 因为RestTemplate不需要维护状态或属性等信息, 所以可以做成单例让Spring管理.
  5. * @return
  6. */
  7. @Bean
  8. @LoadBalanced
  9. public RestTemplate getRestTemplate(){
  10. return new RestTemplate();
  11. }

8.3 调用方式发生变化

  1. /**
  2. * 获取用户收藏的订单列表版本v2
  3. * @param userId
  4. * @return
  5. */
  6. @GetMapping("/collect/orders/v2/{userId}")
  7. public String getCollectOrdersV2(@PathVariable("userId") Integer userId){
  8. //注册中心服务名
  9. String orderCenterName = "ORDER-CENTER";
  10. //拼接HTTP URL请求
  11. String url = "http://"+orderCenterName+"/order/user/collect/list/"+userId;
  12. //发起请求, 接收结果
  13. String result = template.getForObject(url, String.class);
  14. result = "请求"+url+", 返回结果:"+result;
  15. return result;
  16. }

8.4 测试

打开浏览器输入: http://localhost:9000/collect/orders/v2/1
响应数据:

请求http://ORDER-CENTER/order/user/collect/list/1, 返回结果:{“code”:”0000”,”msg”:”success”,”rersult”:[{“name”:”name1”,”id”:”100034”},{“name”:”name2”,”id”:”100035”},{“name”:”name3”,”id”:”100036”}]}