微服务流量分发核心:Spring Cloud 负载均衡解析

目录

理解负载均衡

负载均衡的实现方式

服务端负载均衡

客户端负载均衡

Spring Cloud LoadBalancer快速上手

常见的负载均衡策略

自定义负载均衡策略

LoadBalancer 原理

理解负载均衡

在 Spring Cloud 微服务架构中,负载均衡(Load Balance)是实现服务高可用、提高系统吞吐量的核心机制之一。它通过将请求合理分发到多个服务实例,避免单个实例过载,同时实现故障自动隔离,是服务调用链路中的关键环节。

在微服务中,一个服务通常会部署多个实例(如产品信息服务可能有product-service:9090product-service:9091product-service:9092等)。负载均衡的核心目标是:

  1. 请求分发:将服务消费者的请求均匀分配到多个服务提供者实例,避免单点压力过大。

  2. 故障隔离:自动排除不可用的实例(如宕机、健康检查失败),确保请求只发送到可用实例。

  3. 弹性伸缩支持:当服务实例扩缩容时,能自动感知并调整分发策略,无需人工干预。

负载均衡的实现方式

负载均衡分为服务端负载均衡客户端负载均衡

服务端负载均衡

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

客户端负载均衡

把负载均衡的功能以库的方式集成到客户端,而不再是由⼀台指定的负载均衡设备集中提供。

比如Spring Cloud的Ribbon,请求发送到客户端,客户端从注册中心(比如Eureka)获取服务列表,在发送请求前通过负载均衡算法选择⼀个服务器,然后进行访问。

Ribbon是Spring Cloud早期的默认实现,由于不维护了,所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方维护)

Spring Cloud LoadBalancer快速上手

Spring Cloud LoadBalancer 并不是一个独立服务,而是一个 客户端负载均衡库

调用流程大致是:

  1. 获取服务名(例如调用 http://inventory-service/api/stock/1,这里的 inventory-service 就是服务名)。

  2. 去服务注册中心查找(Nacos、Eureka、Consul…)获取该服务的所有实例地址(IP:Port)。

  3. 负载均衡策略选择:比如轮询、随机、权重优先、本地优先。

  4. 发起请求到选定的实例

使用方式

RestTemplate 加上 @LoadBalanced 注解:

@Configuration
public class RestTemplateConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

使用时直接写服务名:

@Autowired
private RestTemplate restTemplate;
​
public OrderDO selectOrderById(Integer id) {
    OrderDO orderDO = orderMapper.selectOrderById(id);
    
    //这里的 product-service 不再是域名,而是注册中心里的 服务名。
    String url = "http://product-service/product/"+orderDO.getProductId();
    
    //远程调用获取数据
    ProductDO productDO = restTemplate.getForObject(url, ProductDO.class);
    
    orderDO.setProductDO(productDO);
    return orderDO;
}

启动服务进行测试

前置环境配置可参考Spring Cloud——服务注册与服务发现原理与实现-CSDN博客

测试负载均衡

连续多次发起请求: http://127.0.0.1:8080/order/1

观察product-service的日志, 会发现请求被分配到这3个实例上了

常见的负载均衡策略

Spring Cloud LoadBalancer 默认策略是 RoundRobin(轮询)。 常见策略包括:

  • RoundRobin(轮询):依次选择服务实例,分配均匀。

  • Random(随机):随机选一个实例,适合流量比较小的场景。

  • Weighted(权重):根据权重选择(Nacos 支持,可以按版本或机房区分)。

  • ZonePreference(区域优先):优先选择同机房的实例,跨机房兜底。

可以通过自定义配置扩展策略。

自定义负载均衡策略

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

注意: 该类需要满足:

  1. 不用 @Configuration 注释

  2. 在组件扫描范围内

@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)
@Configuration
public class BeanConfig {
    @LoadBalanced
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

在 RestTemplate 配置类上方, 使用 @LoadBalancerClient 或 @LoadBalancerClients 注解, 可以对不同的服务提供方配置不同的客户端负载均衡算法策略,这样就把默认的 轮询策略替换成了 随机策略

@LoadBalancerClient 注解说明

  1. name: 该负载均衡策略对哪个服务生效(服务提供方)。

  2. configuration : 该负载均衡策略用哪个负载均衡策略实现。

LoadBalancer 原理

LoadBalancer 的实现,主要是 LoadBalancerInterceptor ,这个类会对 RestTemplate 的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

我们来看看源码实现:

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
    // ...
    @Override
    public ClientHttpResponse intercept(final HttpRequest request,
                                        final byte[] body,
                                        final ClientHttpRequestExecution execution) throws IOException {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
​
        Assert.state(serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
​
        return (ClientHttpResponse) this.loadBalancer.execute(
                serviceName,
                this.requestFactory.createRequest(request, body, execution)
        );
    }
}

可以看到这里的intercept方法, 拦截了用户的HttpRequest请求,然后做了几件事:

  1. request.getURI() 从请求中获取uri,也就是 http://product-service/product/1001

  2. originalUri.getHost() 从uri中获取路径的主机名,也就是服务id,product-service

  3. loadBalancer.execute 根据服务id,进行负载均衡,并处理请求。

public class BlockingLoadBalancerClient implements LoadBalancerClient {
​
    @Override
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        String hint = this.getHint(serviceId);
​
        LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest =
                new LoadBalancerRequestAdapter<>(request, this.buildRequestContext(request, hint));
​
        Set<LoadBalancerLifecycle> supportedLifecycleProcessors =
                this.getSupportedLifecycleProcessors(serviceId);
​
        supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
​
        // 根据 serviceId 和负载均衡策略选择处理的服务
        ServiceInstance serviceInstance = this.choose(serviceId, lbRequest);
​
        if (serviceInstance == null) {
            supportedLifecycleProcessors.forEach(lifecycle -> {
                lifecycle.onComplete(new CompletionContext(Status.DISCARD, lbRequest, new EmptyResponse()));
            });
            throw new IllegalStateException("No instances available for " + serviceId);
        } else {
            return this.execute(serviceId, serviceInstance, lbRequest);
        }
    }
​
    /**
     * 根据 serviceId 和负载均衡策略选择一个服务实例
     */
    @Override
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
        // 获取负载均衡器
        ReactiveLoadBalancer<ServiceInstance> loadBalancer =
                this.loadBalancerClientFactory.getInstance(serviceId);
​
        if (loadBalancer == null) {
            return null;
        } else {
            // 根据负载均衡算法,在列表中选择一个服务实例
            Response<ServiceInstance> loadBalancerResponse =
                    (Response<ServiceInstance>) Mono.from(loadBalancer.choose(request)).block();
​
            return loadBalancerResponse == null ? null : loadBalancerResponse.getServer();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值