负载均衡(Load Balancing)是一种将网络流量或计算任务分配到多个服务器或资源上的技术,目的是优化资源使用、最大化吞吐量、减少响应时间、提高系统的可靠性和可用性。通过平衡工作负载,负载均衡可以避免单个服务器或资源过载,从而提高整个系统的性能和稳定性。
1问题描述
我们的订单服务每次被调用的时候,都会通过Eureka的服务发现去获取到商品服务的实例列表。如果仅仅单纯获取某一个商品服务,那么流量就一直压在一台服务器之上。如果⼀个服务对应多个实例呢? 流量是否可以合理的分配到多个实例呢?
2场景复现
为了更好的演示效果,我们可以多启动几个product-service的实例。【不修改代码,修改修改配置,调整端口号并启动服务】 项目名名称和端口号可以自己设定,两者最好有对应关系,方便观察。
apply之后,现在IDEA的Service窗⼝就会多出来⼀个启动配置, 右键启动服务就可以,同样的操作, 再启动1个实例, 共启动3个服务。启动后观察Eureka,可以看到product-service下有三个实例:
现在我们来访问订单服务,然后订单服务远程调用商品服务。
结果发现,都是请求多次访问, 都是同⼀台机器.
这肯定不是我们想要的结果, 我们启动多个实例, 是希望可以分担其他机器的负荷, 那么如何实现呢?
3 解决方案
先来看看现在的带代码,为什么会出现这样的现象,也就是我们再去获取服务的时候,使用的方式是不对的,这样每次获取到的服务大概率是相同的。
好的,那么我就来来写代码。修改远程调用的逻辑
java
package com.guan.order.service;
import com.guan.order.mapper.OrderMapper;
import com.guan.order.model.OrderInfo;
import com.guan.product.model.ProductInfo;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
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;
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static List<ServiceInstance> instances;
@PostConstruct
public void init() {
//根据应⽤名称获取服务列表
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo selectOrderByID(Integer orderID) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderID);
// 从Eureka获取服务列表
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
//有多个服务,根据轮询获取
int index = atomicInteger.getAndIncrement() % instances.size();
String uri = instances.get(index).getUri().toString();
String url = uri + "/product/" + orderInfo.getProductId();
log.info("远程调用url:{}", url);
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
现在在来观察服务。
通过⽇志可以看到, 请求被均衡的分配在了不同的实例上, 这就是负载均衡
4 什么是负载均衡
负载均衡(Load Balance,简称 LB) , 是⾼并发, ⾼可⽤系统必不可少的关键组件.
当服务流量增⼤时, 通常会采⽤增加机器的⽅式进⾏扩容, 负载均衡就是⽤来在多个机器或者其他资源中, 按照⼀定的规则合理分配负载
⼀个团队最开始只有⼀个⼈, 后来随着⼯作量的增加, 公司⼜招聘了⼏个⼈. 负载均衡就是: 如何把⼯作量均衡的分配到这⼏个⼈⾝上, 以提⾼整个团队的效率
5 负载均衡的一些实现
上面的例子中,我们只是简单的对实例进行了轮询,但真实的业务场景会更加复杂,比如根据机器的配置进行负载分配,配置高的分配的流量高,配置低的分配流量低等.也就是"能者多劳"。
服务多机部署时,开发人员都需要考虑负载均衡的实现,所以也出现了一些负载均衡器,来帮助我们实现负载均衡.
负载均衡分为服务端负载均衡和客户端负载均衡
服务端负载均衡
在服务端进行负载均衡的算法分配.
比较有名的服务端负载均衡器是Nginx.请求先到达Nginx负载均衡器,然后通过负载均衡算法,在多个服务器之间选择一个进行访问.
客户端负载均衡
在客户端进行负载均衡的算法分配.
把负载均衡的功能以库的方式集成到客户端,而不再是由一台指定的负载均衡设备集中提供,比如Spring Cloud的Ribbon,请求发送到客户端,客户端从注册中心(比如Eureka)获取服务列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问.Ribbon是Spring Cloud早期的默认实现,由于不维护了,所以最新版本的Spring Cloud负载均衡集成的是Spring Cloud LoadBalancer(Spring Cloud官方维护)
客⼾端负载均衡和服务端负载均衡最⼤的区别在于服务清单所存储的位置