【自研网关系列】过滤器链 -- 实现负载均衡过滤器

🌈Yu-Gateway:基于 Netty 构建的自研 API 网关,采用 Java 原生实现,整合 Nacos 作为注册配置中心。其设计目标是为微服务架构提供高性能、可扩展的统一入口和基础设施,承载请求路由、安全控制、流量治理等核心网关职能。

🌈项目代码地址:GitHub - YYYUUU42/YuGateway-master

如果该项目对你有帮助,可以在 github 上点个 ⭐ 喔 🥰🥰

🌈自研网关系列:可以点开专栏,参看完整的文档

目录

1、负载均衡的定义

2、设计实现

随机

轮询

加权轮询


1、负载均衡的定义

负载均衡(Load Balancing)是一种计算机网络和服务器架构的技术,旨在分配网络请求、数据流或负载到多个服务器或计算资源,以确保高可用性、提高性能和避免任何单一服务器或资源的过载。负载均衡在分布式系统和网络应用中起着重要作用,它可以帮助应对流量波动和提供冗余性,从而提高系统的可靠性和性能。

而对于负载均衡的实现,我们有如下几种方式:

  • 负载均衡(Load Balancing)作为一种关键的计算机网络与服务器架构技术,旨在将网络请求、数据流或工作负载合理地分散至多个服务器或计算资源,旨在确保系统的高可用性、提升整体性能并防止任何单一服务器或资源因过载而导致的服务中断。在分布式系统及网络应用领域,负载均衡发挥着不可或缺的作用,它能够有效应对流量波动,提供冗余保护,从而显著增强系统的可靠性和响应速度。

针对负载均衡的实现手段,我们可以将其划分为以下三种主要类型:

  1. DNS负载均衡(地理级别)
    • 原理:DNS负载均衡利用DNS服务器将域名解析请求映射至一组不同的IP地址,每个IP对应一个负载均衡器或服务器。当DNS服务器返回解析结果(IP地址列表)给客户端时,客户端会随机选择其中一个IP发起请求。
    • 优点:实现简单,无需额外购置硬件或软件负载均衡器,易于部署和扩展。成本相对较低。
    • 缺点:DNS负载均衡缺乏智能流量调度能力,不能实时调整负载分配策略,也无法主动检测和应对后端服务器故障。此外,由于客户端可能会缓存DNS记录,可能导致流量分布不均。最重要的是,DNS负载均衡无法判断后端服务的实际状态,存在请求被导向已宕机服务器的风险。
  1. 硬件负载均衡(集群级别)
    • 原理:硬件负载均衡采用专用的硬件设备来执行流量分发任务,这些设备专为处理高并发连接和请求而设计,具备出色的性能优势。
    • 优点:具备极高的性能和稳定性,特别适用于大规模、高负载的场景。此类设备通常支持丰富的负载均衡算法和精细的流量管理功能,且具有较高的可用性和可靠性。
    • 缺点:购置成本较高,需要投资专用硬件设备。配置与管理可能相对复杂,需要具备一定的专业技术知识。此外,硬件设备的升级和扩展灵活性相对较弱。
  1. 软件负载均衡(机器级别)
    • 原理:软件负载均衡通过在标准服务器上部署负载均衡软件(如Nginx、HAProxy、LVS等开源或商业产品)来实现流量分配。
    • 优点:成本效益显著,可充分利用现有服务器资源,部署灵活,管理便捷。软件负载均衡器通常提供多种负载均衡算法和高级配置选项,以适应不同业务需求。
    • 缺点:性能表现可能受限于承载其运行的服务器硬件,对于极高流量场景,可能需要增加服务器数量以满足性能需求。尽管软件负载均衡器的可靠性已大幅提升,但与专用硬件相比,其可用性和故障恢复能力仍可能存在一定差距。

在实际生产环境中,这三种负载均衡策略并非孤立使用,而是通常结合在一起,形成多层次的负载均衡体系:

  • DNS负载均衡负责在地理层面分散流量,可根据用户地理位置、网络状况等因素将请求定向到最近或最优的数据中心或服务器集群。
  • 硬件负载均衡在集群内部承担核心的流量分发任务,利用其强大的处理能力和丰富的功能,确保集群内服务器间的负载均衡和高效运作。
  • 软件负载均衡则在单台或多台服务器内部实现更细粒度的负载均衡,例如,对于同一台服务器上运行的多个应用实例或容器,软件负载均衡器可以精确地控制流量在它们之间的分配。

综上所述,选择和组合运用恰当的负载均衡策略,能有效提升系统的可扩展性、可用性和响应速度,为业务的稳定运行和持续发展提供有力支撑。

2、设计实现

首先依旧是创建我们的顶层接口,这个接口用于帮助我们获取根据负载均衡策略选择到的后端服务实例

/**
 * @author yu
 * @description 负载均衡策略接口
 */
public interface LoadBalanceRule {
	/**
	 * 通过上下文参数获取服务实例
	 */
	ServiceInstance choose(GatewayContext ctx, boolean gray);

	/**
	 * 通过服务ID拿到对应的服务实例
	 */
	ServiceInstance chooseByServiceId(String serviceId, boolean gray);
}

随机

其实现方式为根据服务 id,然后保存当前服务 id 对应的所有服务实例,之后就可以从服务实例中随机返回一个

核心代码:

public ServiceInstance chooseByServiceId(String serviceId, boolean gray) {
	// 根据服务ID获取服务实例集合
	Set<ServiceInstance> serviceSets = DynamicConfigManager.getInstance().getServiceInstanceByUniqueId(serviceId, gray);
	if (CollectionUtils.isEmpty(serviceSets)) {
		log.warn("serviceId {} don't match any serviceInstance", serviceId);
		throw new ResponseException(ResponseCode.SERVICE_INVOKER_NOT_FOUND);
	}
	List<ServiceInstance> serviceLists = new ArrayList<>(serviceSets);
	int index = ThreadLocalRandom.current().nextInt(serviceLists.size());
	return serviceLists.get(index);
}

轮询

轮询的负载均衡过滤器的实现方式只不过变为了需要维护一个 AtomicInteger 用于计数

private AtomicInteger position = new AtomicInteger(1);
public ServiceInstance chooseByServiceId(String serviceId, boolean gray) {
    // 根据服务ID获取服务实例集合
    Set<ServiceInstance> serviceSets = DynamicConfigManager.getInstance().getServiceInstanceByUniqueId(serviceId, gray);
    if (CollectionUtils.isEmpty(serviceSets)) {
        log.warn("serviceId {} don't match any serviceInstance", serviceId);
        throw new ResponseException(ResponseCode.SERVICE_INVOKER_NOT_FOUND);
    }
    List<ServiceInstance> serviceLists = new ArrayList<>(serviceSets);
    int pos = Math.abs(position.incrementAndGet());
    return serviceLists.get(pos % serviceLists.size());
}

加权轮询

  1. 首先,计算所有服务实例的总权重。这是通过对服务实例列表中的每个实例调用getWeight方法并将结果相加来完成的。
  2. 初始化一个名为currentWeight的变量,用于在接下来的循环中累加每个服务实例的权重。
  3. 计算当前的位置,这是通过将position的值递增并对总权重取模来完成的。这样可以确保当position的值超过总权重时,它会从头开始。
  4. 遍历服务实例列表。对于列表中的每个服务实例,将其权重加到currentWeight上。
  5. 如果currentWeight大于当前位置,那么就返回当前的服务实例。这意味着我们已经找到了一个权重大于或等于当前位置的服务实例。
  6. 如果遍历完所有的服务实例都没有找到合适的实例(即currentWeight始终小于当前位置),那么就返回服务实例列表中的第一个实例。
private AtomicInteger position = new AtomicInteger(0);
public ServiceInstance chooseByServiceId(String serviceId, boolean gray) {
    // 获取服务实例集合
    Set<ServiceInstance> serviceSets = DynamicConfigManager.getInstance().getServiceInstanceByUniqueId(serviceId, gray);
    // 如果服务实例集合为空,则抛出异常
    if (CollectionUtils.isEmpty(serviceSets)) {
        log.warn("serviceId {} don't match any serviceInstance", serviceId);
        throw new ResponseException(ResponseCode.SERVICE_INVOKER_NOT_FOUND);
    }
    // 将服务实例集合转换为列表
    List<ServiceInstance> serviceLists = new ArrayList<>(serviceSets);
    // 计算总权重
    int totalWeight = serviceLists.stream().mapToInt(ServiceInstance::getWeight).sum();
    int currentWeight = 0;
    // 计算当前位置
    int index = position.getAndIncrement() % totalWeight;
    // 遍历服务实例列表,根据权重和当前位置选择服务实例
    for (ServiceInstance instance : serviceLists) {
        currentWeight += instance.getWeight();
        if (currentWeight > index) {
            return instance;
        }
    }
    // 如果没有找到合适的服务实例,则返回列表中的第一个
    return serviceLists.get(0);
}

最后在过滤器中得到规则选择对应的算法

private LoadBalanceRule getLoadBalanceRuleByStrategy(String strategy, String serviceId) {
	return switch (strategy) {
		case FilterConst.LOAD_BALANCE_STRATEGY_RANDOM -> RandomLoadBalanceRule.getInstance(serviceId);
		case FilterConst.LOAD_BALANCE_STRATEGY_ROUND_ROBIN -> RoundRobinLoadBalanceRule.getInstance(serviceId);
		case FilterConst.LOAD_BALANCE_STRATEGY_WEIGHT_RANDOM -> WeightedRoundRobinLoadBalanceRule.getInstance(serviceId);
		default -> {
			logger.warn("No load balance rule can be loaded for service={}, using default strategy: {}", serviceId, strategy);
			yield RandomLoadBalanceRule.getInstance(serviceId);
		}
	};
}
  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值