微服务:深入理解负载均衡

前言

        如果想要实现子服务之间的远程调用,需要通过RestTemplate,这是一个比较原始的方式,因为存在硬编码的特点:

        一旦服务者地址变化,就需要手动修改代码;

        一旦是多个服务(商品服务)提供者,无法实现负载均衡功能;

        一旦服务变多,人工维护调用关系困难

那么应该怎么解决呢, 这时候就需要通过注册中心动态的实现服务治理

但是如果想要发挥微服务动态扩缩容、集群部署的能力,就必须引入负载均衡

如下是RestTemplate实现的“硬编码”代码:

import com.example.service.OrderService;
import org.example.entity.Orders;
import org.example.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;

    @RequestMapping("/list")
    public Object getList(){
        List<Orders> list = orderService.list();
        return list;
    }

    @RequestMapping("/insertOrder/{pid}")
    public Object findProduct(@PathVariable("pid") Integer pid){
        // 模仿实现订单的添加
        // 创建订单对象
        Orders order = new Orders();
        // 设置用户信息
        order.setUid(1);
        order.setUsername("admin");
        // 设置商品信息
        // 使用RestTemplate来实现调用商品微服务的接口
        Product product = restTemplate.getForObject("http://localhost:8081/product/findProduct/" + pid, Product.class);
        order.setPid(product.getId());
        order.setPname(product.getName());
        order.setPprice(product.getPrice());
        order.setNumber(1);
        boolean i = orderService.save(order);
        return i;
    }
}

一、微服务为什么需要负载均衡?

微服务架构最大优势之一:支持动态扩缩容

  • 扩容:电商大促、流量高峰期,单个商品服务实例无法承载海量请求,启动多个相同商品服务实例组成集群,分担流量;
  • 缩容:流量低谷期,关闭多余实例,节约服务器资源。

集群部署之后,引出核心问题:

服务消费者(订单服务)发起请求时,众多服务提供者实例,到底选择哪一台处理请求?

负载均衡,就是用来解决请求分发的核心方案。

二、什么是负载均衡

1. 定义

        通俗理解:负载均衡就是把用户请求、任务流量,分摊到多个服务器实例上执行,避免单一节点压力过大,提升系统吞吐量与可用性。

2. 两大分类

根据负载均衡逻辑运行的位置,分为 服务端负载均衡客户端负载均衡

① 服务端负载均衡(集中式负载均衡)

代表组件:Nginx、Gateway 工作流程:

  1. 客户端只知道负载均衡中间件地址,请求统一发送到 Nginx;
  2. Nginx 保存后端所有服务实例清单;
  3. Nginx 按照负载均衡算法挑选一台后端服务器,转发请求;
  4. 服务处理完成,响应经由 Nginx 返回客户端。

特点:

  • 负载均衡逻辑运行在独立中间件
  • 客户端不需要感知后端集群节点;
  • 存在一次转发,有轻微网络开销;常用于网关层、反向代理。

② 客户端负载均衡(进程内负载均衡,微服务主流方案)

代表组件:Ribbon、SpringCloud LoadBalancer 工作流程:

  1. 服务消费者(订单服务)启动后,从注册中心 (Nacos) 拉取目标服务(商品服务)全部实例列表,缓存到本地内存;
  2. 负载均衡逻辑运行在消费者本地进程内部
  3. 发起远程调用前,本地通过均衡算法选出一个服务实例;
  4. 消费者直接和选中的服务实例建立连接发起请求。

特点:

✅ 微服务之间远程调用首选方案;

✅ 不需要额外中间件转发,性能更好;

✅ 消费者本地缓存实例清单,实时接收注册中心推送的节点上下线消息;

SpringCloud Alibaba 微服务之间调用,使用的就是客户端负载均衡

3.两种负载均衡对比:

类型运行位置典型框架适用场景
服务端负载均衡独立中间件(Nginx)Nginx、Spring Cloud Gateway前端请求接入、网关入口
客户端负载均衡服务调用方内部Ribbon、LoadBalancer微服务与微服务之间内部调用

三、自定义实现负载均衡

1.实现思路

  1. 借助DiscoveryClient从 Nacos 注册中心,拉取商品服务所有可用实例集合;
  2. 自定义负载均衡算法(轮询、随机),从实例列表挑选一个节点;
  3. 拼接选中实例的 IP + 端口,构建完整 URL 发起远程调用。

2.代码示例(简易轮询负载均衡)

@RestController
@Slf4j
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @RequestMapping("/insertOrder/{pid}")
    public Object findProduct(@PathVariable("pid") Integer pid){
        // 使用nacos方式来动态的获取要访问的服务器
        List<ServiceInstance> instances = discoveryClient.getInstances("shop- product");
        int index = new Random().nextInt(instances.size());   //自定义负载均衡,生成一个随机数,随机获取服务
        ServiceInstance serviceInstance = instances.get(index);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        //通过restTemplate调用商品微服务
        Product product = restTemplate.
                getForObject("http://" + url + "/product/findProduct/" + pid, Product.class);

        System.out.println(product.toString());
        // 模仿实现订单的添加
        // 创建订单对象
        Orders order = new Orders();
        // 设置用户信息
        order.setUid(1);
        order.setUsername("admin");
        // ?? 设置商品信息 ??
        order.setPid(product.getId());
        order.setPname(product.getName());
        order.setPprice(product.getPrice());
        order.setNumber(1);
        boolean i = orderService.save(order);
        return i;
    }
}

手写方案存在的缺陷:

  1. 代码侵入严重,每个接口都需要重复编写实例获取、算法逻辑;
  2. 需要自己处理服务节点下线、实例列表刷新;
  3. 切换负载均衡算法需要大面积修改代码;
  4. 缺少重试、超时、故障转移机制。

企业开发不会手写负载均衡,该方案仅用于理解原理。 生产环境直接使用成熟组件 Ribbon。

四、基于Ribbon实现负载均衡

1. Ribbon 介绍

Ribbon 是 Netflix 开源的客户端负载均衡组件,是 SpringCloud 初代负载均衡标准实现。

小贴士:Spring Cloud 2020 版本之后官方使用 Spring Cloud LoadBalancer 替代 Ribbon; 但 Spring Cloud Alibaba 2021.0.1.0 体系中,RestTemplate 配合@LoadBalanced底层依旧兼容 Ribbon,也是现阶段学习主流方案。

2. 快速改造实现负载均衡

步骤 1:配置类给 RestTemplate 添加 @LoadBalanced注解
@Configuration
public class RestTemplateConfig {
    // @LoadBalanced:开启Ribbon客户端负载均衡能力
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
步骤 2:修改调用 URL,不再硬编码 IP 端口,使用服务名称调用
// 修改前(硬编码地址,不支持集群)
// String url = "http://localhost:8081/product/findProduct/" + pid;

// 修改后:直接填写Nacos注册的服务名 shop_product
String url = "http://shop_product/product/findProduct/" + pid;
Product product = restTemplate.getForObject(url, Product.class);

3. 运行原理剖析

    1. 订单服务启动,@LoadBalanced对 RestTemplate 进行增强;
    2. 发起请求http://shop_product/xxx时,拦截器识别这是服务名;
    3. 从 Nacos 拉取shop_product全部实例列表;
    4. Ribbon 内置负载均衡算法挑选一个实例;
    5. 将服务名替换成真实 IP:port,发起 http 请求。

    4. Ribbon 内置负载均衡策略

    策略说明
    RoundRobinRule(默认)轮询策略,请求依次分发到每一个实例
    RandomRule随机选择实例
    WeightedResponseTimeRule根据实例响应时间分配权重,响应越快权重越高
    BestAvailableRule选择当前并发请求量最小的实例

    总结

    1. 负载均衡分为服务端负载均衡 (Nginx)客户端负载均衡 (Ribbon),微服务内部调用优先使用客户端负载均衡;
    2. 客户端负载均衡核心:调用方本地缓存服务实例清单,调用前本地完成节点选择;
    3. 手写负载均衡帮助理解原理,正式项目使用 Ribbon;
    4. 使用 Ribbon 核心两点:@LoadBalanced注解 + URL 中使用注册中心服务名称替代硬编码 IP 端口;
    5. 负载均衡配合 Nacos 服务注册发现,彻底解决地址硬编码、服务集群流量分发两大痛点,支撑微服务弹性扩缩容。
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值