【微服务】(4) 负载均衡

一、问题

当远程调用的服务有多个实例时,instances.get(0) 每次获取的都是服务列表中的第一个实例,可能会导致每次都是对同一个实例发起请求,让该实例压力过大,而其它的实例却没有得到利用。

在服务流量增大时,为了分担压力,会增加机器进行扩容。负载均衡 组件的作用:按照策略合理分配流量到每个实例,应用于高并发、高可用的系统中。

二、实现轮询策略的负载均衡

product-server 创建多个实例

再创建 2 个实例,9091、9092:

启动:

order-server 轮询 向不同实例发起请求,实现简单的负载均衡

java 复制代码
    // 原子计数器,保证线程安全
    private AtomicInteger count = new AtomicInteger(0);

    private List<ServiceInstance> instances;
    private int size;


    @PostConstruct
    public void init() {
        //从Eureka中获取服务列表,指定要查询的服务名
        // 在应用启动、类加载时,初始化服务列表,避免在请求时再次获取导致每次拿到的服务列表顺序不同
        instances = discoveryClient.getInstances("product-service");
        size = instances.size();
    }


    @Override
    public OrderInfo getOrder(String orderId) {
        // 打印当前线程名称
        log.info("当前处理线程:{}", Thread.currentThread().getName());

        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);

        // 多个商品服务,请求计数器 % size 获得当前请求的服务实例在列表中的 index,实现轮询策略的负载均衡
        String uri = instances.get(count.getAndIncrement() % size).getUri().toString();
        // 构造访问商品服务的 url
        String url = uri+"/product/"+orderInfo.getProductId();
        log.info("访问商品服务的 url: " + url);

        // 使用 restTemplate 访问商品服务
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        // 把商品详情设置到 orderInfo 中
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
  • 为什么使用原则类计数器:spring boot 集成的 tomcat 中使用了线程池来处理并发的 http 请求,为了避免线程不安全问题(不是原子的加法操作,会导致最终计数结果不符合预期),使用原子类定义计数器。
  • 为什么在类加载时就初始化服务里表:每次获取的服务列表是不一样的,我们希望在整个启动的应用中,服务列表不变。
  • 该案例存在 bug:服务列表无法实时更新,感知实例的上线、下线。

执行结果:

三、负载均衡的实现

  • 服务端 负载均衡:通过部署的负载均衡器(如 Nginx),来选择服务器。
  • 客户端 负载均衡:在客户端从注册中心获取服务列表 ,通过公共类库提供的负载均衡策略的实现,来选择服务器。
  • 常见的客户端负载均衡库:Ribbon 因 Netflix 不再维护,Spring Cloud 官方弃用。现在使用 Spring Cloud 官方维护的 Spring Cloud LoadBalancer。

四、Spring Cloud LoadBalance

1、快速上手

1、给 RestTemplate Bean 添加:@LoadBalanced

java 复制代码
@Configuration
public class BeanConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

2、改 IP:端口号 为服务名:

java 复制代码
    @Override
    public OrderInfo getOrder(String orderId) {
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);

        // 构造访问商品服务的 url
        String url = "http://product-service/product/"+orderInfo.getProductId();

        // 使用 restTemplate 访问商品服务
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        // 把商品详情设置到 orderInfo 中
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

3、启动 注册中心、订单服务、多个商品服务实例,测试:实现了负载均衡,每个实例收到的请求差不错。

2、负载均衡策略

Spring Cloud LoadBalancer 实现了两种负载均衡策略:

  • 轮询(Round Robin,默认):循环依次选择实例。
  • 随机(Random):随机选择一个实例。
  • 也可以自定义策略:

自定义一个随即策略 bean,参考官方文档:Spring Cloud LoadBalancer :: Spring Cloud Commons

java 复制代码
public class CustomLoadBalancerConfiguration {

	@Bean
	ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
			LoadBalancerClientFactory loadBalancerClientFactory) {
		String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
		return new RandomLoadBalancer(loadBalancerClientFactory
				.getLazyProvider(name, ServiceInstanceListSupplier.class),
				name);
	}
}

注意:使用 @LoadBalancerClient 或者 @LoadBalancerClients 给指定服务配置自定义负载均衡类,要么自定义类不加 @Configuration 注解,要么在 Spring 扫描之外(目的:期望实现对局部服务生效,而不是全局服务,不按提示做就会产生冲突)。

在远程调用模板上,加上 @LoadBalancerClient 注解,指定对 product-server 服务生效,应用自定义负载均衡策略:

java 复制代码
@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

重新启动 order-service,测试:每个实例收到的请求数量是随机的。

3、LoadBalancer 原理

LoadBalancerInterceptor 类会对 RestTemplate 远程调用的请求进行拦截:

跟踪 execute:

跟踪 choose:

跟踪 choose:

跟踪随机:

跟踪轮询:

相关推荐
短视频矩阵源码定制3 小时前
矩阵系统哪个好?2025年全方位选型指南与品牌深度解析
java·人工智能·矩阵·架构·aigc
xyhshen4 小时前
记录一次K8S跨命名空间访问 xxx.xxx.svc.cluster.local 类似内部服务不通的问题
云原生·容器·kubernetes
海鸥814 小时前
在k8s中部署seaweedfs,上传文件到seaweedfs方法
云原生·容器·kubernetes
DARLING Zero two♡4 小时前
云原生基石的试金石:基于 openEuler 部署 Docker 与 Nginx 的全景实录
nginx·docker·云原生
阿里云云原生4 小时前
云效「AI 智能评审」,先锋体验官招募活动正式启动,赢取极客专属好礼!
云原生
小猪咪piggy4 小时前
【微服务】(3) 服务注册与发现
微服务·云原生·架构
刺客_Andy5 小时前
React 第五十二节 Router中 useResolvedPath使用详解和注意事项示例
前端·react.js·架构
推理幻觉6 小时前
IDE/编码代理架构与 Cursor 相关研究(汇总)
ide·人工智能·架构·agent
容器魔方6 小时前
KCD 杭州站 x OpenInfra Days China首次联手!华为云云原生团队与您共探Karmada多模板工作负载多集
云原生·容器·云计算