Spring Cloud之负载均衡之LoadBalance

打印 上一主题 下一主题

主题 987|帖子 987|积分 2961


目录

负载均衡
问题
步调
征象 
什么是负载均衡?
负载均衡的一些实现
服务端负载均衡
客户端负载均衡
利用Spring Cloud LoadBalance实现负载均衡
负载均衡策略
​编辑 ​编辑LoadBalancer原理
服务部署
准备环境和数据
服务构建打包
启动服务
上传Jar包到云服务器
启动服务
长途调用访问 


负载均衡

问题


上面是我们之前的代码,是根据应用名称获取了服务实例列表,并从列表中选择了一个服务实例。
那如果一个服务对应多个实例呢?流量是否可以公道的分配到多个实例呢?
我们再启动两个product-service示例。
步调

打开View->Tool Windows->Services

选中ProductServiceApplication,然后右键,选择Copy Configuration

然后改名,并点击Modify options

然后点击Add VM options

然后添加-Dserver.port=9091,然后Apply,OK

然后再重复上述步调,再添加一个服务实例。

征象 

启动上述所有实例后,可以在Eureka网站页面看到:

此时多次访问"http://127.0.0.1:8080/order/1",然后查看IDEA上的日志,可以看到,我们刚刚的多次访问,都访问到了同一台机器上,即第一个注册到Eureka的服务实例端标语为9092的机器。



这肯定不是我们想要的效果,我们启动多个服务实例,是希望可以分担其它机器的负荷,那么怎样实现呢?
我们可以修改一下之前的order-service中的OrderService代码,把只请求服务列表第一台机器修改为轮询请求服务列表中的机器。
修改后的order-service中的OrderService代码如下:
  1. package order.service;
  2. import jakarta.annotation.PostConstruct;
  3. import lombok.extern.slf4j.Slf4j;
  4. import order.mapper.OrderMapper;
  5. import order.model.OrderInfo;
  6. import order.model.ProductInfo;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.cloud.client.ServiceInstance;
  9. import org.springframework.cloud.client.discovery.DiscoveryClient;
  10. import org.springframework.stereotype.Service;
  11. import org.springframework.web.client.RestTemplate;
  12. import java.util.List;
  13. import java.util.concurrent.atomic.AtomicInteger;
  14. @Slf4j
  15. @Service
  16. public class OrderService {
  17.     @Autowired
  18.     private OrderMapper orderMapper;
  19.     @Autowired
  20.     private RestTemplate restTemplate;
  21.     @Autowired
  22.     private DiscoveryClient discoveryClient;
  23.     //计数器
  24.     private AtomicInteger count = new AtomicInteger(1);
  25.     private List<ServiceInstance> instances;
  26.     @PostConstruct
  27.     public void init(){
  28.         //从Eureka中获取服务列表
  29.         instances = discoveryClient.getInstances("product-service");
  30.     }
  31.     public OrderInfo selectOrderById(Integer orderId){
  32.         OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
  33.         //计算轮流的实例idnex
  34.         int index= count.getAndIncrement() % instances.size();
  35.         //获取实例
  36.         String uri = instances.get(index).getUri().toString();
  37.         //拼接url
  38.         String url = uri+"/product/"+orderInfo.getProductId();
  39.         log.info("远程调用url:{}", url);
  40.         ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
  41.         orderInfo.setProductInfo(productInfo);
  42.         return orderInfo;
  43.     }
  44. }
复制代码
重启order-service,再次多次访问"127.0.0.1:8080/order/1",可以看到每个服务实例都有被请求到:



通过⽇志可以看到, 请求被均衡的分配在了差异的实例上, 这就是负载均衡.
什么是负载均衡?

负载均衡(Load Balance,简称 LB) , 是⾼并发, ⾼可⽤体系必不可少的关键组件.
当服务流量增⼤时, 通常会采⽤增长机器的⽅式进⾏扩容, 负载均衡就是⽤来在多个机器或者其他资源中, 按照⼀定的规则公道分配负载.
负载均衡的一些实现

上⾯的例⼦中, 我们只是简单的对实例进⾏了轮询, 但真实的业务场景会更加复杂. ⽐如根据机器的设置进⾏负载分配, 设置⾼的分配的流量⾼, 设置低的分配流量低等.
服务多机部署时, 开发⼈员都需要考虑负载均衡的实现, 所以也出现了⼀些负载均衡器, 来帮助我们实现负载均衡.
负载均衡分为服务端负载均衡和客⼾端负载均衡.
服务端负载均衡

在服务端进⾏负载均衡的算法分配.
⽐较有名的服务端负载均衡器是Nginx. 请求先到达Nginx负载均衡器, 然后通过负载均衡算法, 在多个服务器之间选择⼀个进⾏访问.

客户端负载均衡

在客⼾端进⾏负载均衡的算法分配.
把负载均衡的功能以库的⽅式集成到客⼾端, ⽽不再是由⼀台指定的负载均衡设备集中提供.
⽐如Spring Cloud的Ribbon, 请求发送到客⼾端, 客⼾端从注册中⼼(⽐如Eureka)获取服务列表, 在发送请求前通过负载均衡算法选择⼀个服务器,然后进⾏访问.
Ribbon是Spring Cloud早期的默认实现, 由于不维护了, 所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官⽅维护)。

客⼾端负载均衡和服务端负载均衡最⼤的区别在于服务清单所存储的位置。
Spring Cloud LoadBalance
SpringCloud 从 2020.0.1 版本开始, 移除了Ribbon 组件,使⽤Spring Cloud LoadBalancer 组件来代替 Ribbon 实现客⼾端负载均衡。
利用Spring Cloud LoadBalance实现负载均衡

1. 给 RestTemplate 这个Bean添加 @LoadBalanced 注解就可以
  1. package order.config;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.client.RestTemplate;
  6. @Configuration
  7. public class BeanConfig {
  8.     @Bean
  9.     @LoadBalanced
  10.     public RestTemplate restTemplate(){
  11.         return new RestTemplate();
  12.     }
  13. }
复制代码
2.修改后的order-service中的OrderService代码如下:
修改IP为服务端名称。
  1. package order.service;
  2. import jakarta.annotation.PostConstruct;
  3. import lombok.extern.slf4j.Slf4j;
  4. import order.mapper.OrderMapper;
  5. import order.model.OrderInfo;
  6. import order.model.ProductInfo;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.stereotype.Service;
  9. import org.springframework.web.client.RestTemplate;
  10. @Slf4j
  11. @Service
  12. public class OrderService {
  13.     @Autowired
  14.     private OrderMapper orderMapper;
  15.     @Autowired
  16.     private RestTemplate restTemplate;
  17.     public OrderInfo selectOrderById(Integer orderId){
  18.         OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
  19.         String url = "http://product-service/product/"+orderInfo.getProductId();
  20.         log.info("远程调用url:{}", url);
  21.         ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
  22.         orderInfo.setProductInfo(productInfo);
  23.         return orderInfo;
  24.     }
  25. }
复制代码
此时再次多次访问"127.0.0.1:8080/order/1",可以看到每个服务实例都有被请求到,且比例差不多:



负载均衡策略

负载均衡策略是⼀种头脑, ⽆论是哪种负载均衡器, 它们的负载均衡策略都是相似的. Spring Cloud
LoadBalancer 仅⽀持两种负载均衡策略: 轮询策略 和 随机策略。
   1. 轮询(Round Robin): 轮询策略是指服务器轮流处置惩罚⽤⼾的请求. 这是⼀种实现最简单, 也最常⽤的策略. ⽣活中也有雷同的场景, ⽐如学校轮流值⽇, 或者轮流打扫卫⽣.
2. 随机选择(Random): 随机选择策略是指随机选择⼀个后端服务器来处置惩罚新的请求.
  官方介绍

翻译:
   Spring Cloud提供了本身的客户端负载均衡器抽象和实现。对于负载平衡机制,添加了ReactiveLoadBalancer接口,并为其提供了基于轮转和随机的实现。为了让实例从反应式ServiceInstanceListSupplier中进行选择,利用了该接口。目前,我们支持ServiceInstanceListSupplier的基于服务发现的实现,该实现利用类路径中可用的发现客户端从服务发现中检索可用实例。通过将Spring.Cloud.LoadBalancer.enabled的值设置为false,可以禁用Spring Cloud LoadBalancer。
  1. 界说随机算法对象, 通过 @Bean 将其加载到 Spring 容器中
此处使⽤Spring Cloud LoadBalancer提供的 RandomLoadBalancer
  1. package order.config;
  2. import org.springframework.cloud.client.ServiceInstance;
  3. import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
  4. import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
  5. import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
  6. import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.core.env.Environment;
  9. public class CustomLoadBalancerConfiguration {
  10.     @Bean
  11.     ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
  12.                                                             LoadBalancerClientFactory loadBalancerClientFactory) {
  13.         String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
  14.         return new RandomLoadBalancer(loadBalancerClientFactory
  15.                 .getLazyProvider(name, ServiceInstanceListSupplier.class),
  16.                 name);
  17.     }
  18. }
复制代码
  注意: 该类需要满⾜:
1. 不⽤ @Configuration 注释
2. 在组件扫描范围内 
  2. 使⽤ @LoadBalancerClient 或者 @LoadBalancerClients 注解
   在 RestTemplate 设置类上⽅, 使⽤ @LoadBalancerClient 或 @LoadBalancerClients 注解, 可以对差异的服务提供⽅设置差异的客⼾端负载均衡算法策略.
由于我们只有⼀个客户端服务提供者, 所以使⽤@LoadBalancerClient。
  1. package order.config;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.client.RestTemplate;
  7. @LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
  8. @Configuration
  9. public class BeanConfig {
  10.     @Bean
  11.     @LoadBalanced
  12.     public RestTemplate restTemplate(){
  13.         return new RestTemplate();
  14.     }
  15. }
复制代码
  @LoadBalancerClient 注讲授明
1. name: 该负载均衡策略对哪个服务⽣效(服务提供⽅)
2. configuration : 该负载均衡策略 ⽤哪个负载均衡策略实现. 
  此时再次多次访问"127.0.0.1:8080/order/1",可以看到每个服务实例都有被请求到,且比例随机:

 
LoadBalancer原理


LoadBalancer 的实现, 重要是 LoadBalancerInterceptor , 这个类会对 RestTemplate 的请
求进⾏拦截, 然后从Eureka根据服务id获取服务列表,随后利⽤负载均衡算法得到真实的服务地址信息,更换服务id。
我们来看看源码实现:

可以看到这⾥的intercept⽅法, 拦截了⽤⼾的HttpRequest请求,然后做了⼏件事:
   1. request.getURI() 从请求中获取uri, 也就是 http://product-service
2. service/product/1001 originalUri.getHost() 从uri中获取路径的主机名, 也就是服务id, product-service
3. loadBalancer.execute 根据服务id, 进⾏负载均衡, 并处置惩罚请求 
  根据serviceId,和负载均衡策略, 选择处置惩罚的服务: 
 根据serviceId,和负载均衡策略, 选择处置惩罚的服务:

服务部署

准备环境和数据

安装好JDK17和MySQL,并在MySQL中建表且存放好数据信息。
修改设置文件中的数据库暗码。
服务构建打包

采⽤Maven打包, 需要对3个服务分别打包:
eureka-server, order-service, product-service

启动服务

上传Jar包到云服务器

第一次上传需要安装 lrzsz
Centos:
   yum install lrzsz
  Ubantu:
   apt install lrzsz 
  直接拖动文件到xshell窗口,上传成功。
启动服务

   #后台启动eureka-server, 并设置输出⽇志到logs/eureka.log
nohup java -jar eureka-server.jar >logs/eureka.log &
  
#后台启动order-service, 并设置输出⽇志到logs/order.log
nohup java -jar order-service.jar >logs/order.log &
  
#后台启动product-service, 并设置输出⽇志到logs/order.log
nohup java -jar product-service.jar >logs/product-9090.log &
  再多启动两台product-service实例
   #启动实例, 指定端⼝号为9091
nohup java -jar product-service.jar --server.port=9091 >logs/product-9091.log &
  
#启动实例, 指定端⼝号为9092
nohup java -jar product-service.jar --server.port=9092 >logs/product-9092.log & 
  

长途调用访问 




可以看到,能够正常访问并相应。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表