Spring Cloud之负载均衡之LoadBalance

目录

负载均衡

问题

步骤

现象

什么是负载均衡?

负载均衡的一些实现

服务端负载均衡

客户端负载均衡

[使用Spring Cloud LoadBalance实现负载均衡](#使用Spring Cloud LoadBalance实现负载均衡)

负载均衡策略

[​编辑 ​编辑LoadBalancer原理](#编辑 编辑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代码如下:

java 复制代码
package order.service;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    //计数器
    private AtomicInteger count = new AtomicInteger(1);

    private List<ServiceInstance> instances;

    @PostConstruct
    public void init(){
        //从Eureka中获取服务列表
        instances = discoveryClient.getInstances("product-service");
    }

    public OrderInfo selectOrderById(Integer orderId){
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        //计算轮流的实例idnex
        int index= count.getAndIncrement() % instances.size();
        //获取实例
        String uri = instances.get(index).getUri().toString();
        //拼接url
        String url = uri+"/product/"+orderInfo.getProductId();
        log.info("远程调用url:{}", url);
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }

}

重启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 注解就可以
java 复制代码
package order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

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

2.修改后的order-service中的OrderService代码如下:

修改IP为服务端名称。

java 复制代码
package order.service;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import order.mapper.OrderMapper;
import order.model.OrderInfo;
import order.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;


@Slf4j
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;


    public OrderInfo selectOrderById(Integer orderId){
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        String url = "http://product-service/product/"+orderInfo.getProductId();
        log.info("远程调用url:{}", url);
        ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}

此时再次多次访问"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

java 复制代码
package order.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

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);
    }
}

注意: 该类需要满⾜:

  1. 不⽤ @Configuration 注释
  2. 在组件扫描范围内
  1. 使⽤ @LoadBalancerClient 或者 @LoadBalancerClients 注解

在 RestTemplate 配置类上⽅, 使⽤ @LoadBalancerClient 或 @LoadBalancerClients 注解, 可以对不同的服务提供⽅配置不同的客⼾端负载均衡算法策略.

由于我们只有⼀个客户端服务提供者, 所以使⽤@LoadBalancerClient。

java 复制代码
package order.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;


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

@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 &

远程调用访问

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

相关推荐
言慢行善1 天前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星1 天前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟1 天前
操作系统之虚拟内存
java·服务器·网络
Tong Z1 天前
常见的限流算法和实现原理
java·开发语言
凭君语未可1 天前
Java 中的实现类是什么
java·开发语言
He少年1 天前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新1 天前
myeclipse的pojie
java·ide·myeclipse
迷藏4941 天前
**eBPF实战进阶:从零构建网络流量监控与过滤系统**在现代云原生架构中,**网络可观测性**和**安全隔离**已成为
java·网络·python·云原生·架构
迷藏4941 天前
**发散创新:基于Solid协议的Web3.0去中心化身份认证系统实战解析**在Web3.
java·python·web3·去中心化·区块链
qq_433502181 天前
Codex cli 飞书文档创建进阶实用命令 + Skill 创建&使用 小白完整教程
java·前端·飞书