欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

自定义ILoadBalancer

时间:2023-08-03
一、需求背景

业务中遇到这样一个场景,部分应用在项目环境中都会使用到网关,为了做到环境隔离,都会吧网关应用拉到对应的项目环境中,如何做到不拉取网关应用,直接调用公共环境的网关应用也能转发到正确的项目环境中呢?不通的环境都对应着不同的域名

生产环境(real)预发环境(pre)公共环境(stable)项目环境(dev)本地环境(local)

项目环境域名类似这样的格式http://dev-xxx.com,而我就是获取url上的dev-xxx标识,进行路由,而Ribbon当中也有一个Zone的概念,也就是区域,不同的项目环境就是不同的分区。

二、前置知识

Ribbon包含以下组件:

1.IRule2.IPing3.ServerList4.ServerListFilter5.ServerListUpdater6.IClientConfig7.ILoadBalancer

因本文整合了Eureka,组件的默认实现类会有些许不同

组件接口组件默认实现类整合Eureka实现类IClientConfigDefaultClientConfigImplDefaultClientConfigImplIRuleZoneAvoidanceRuleZoneAvoidanceRuleIPingDummyPingNIWSDiscoveryPingServerListConfigurationbasedServerListDiscoveryEnabledNIWSServerListServerListFilterZonePreferenceServerListFilterZonePreferenceServerListFilterILoadBalancerZoneAwareLoadBalancerZoneAwareLoadBalancerServerListUpdaterPollingServerListUpdaterPollingServerListUpdater

这里我踩坑了许久,没注意项目中引入了 spring-cloud-starter-netflix-eureka-client,我在ServerList获取服务列表的时候一直获取不了,后面才发现用的 ConfigurationbasedServerList这个实现类,而这个实现类是需要将服务写在配置文件里面。所以如果你没有使用到Eureka:

一种方式是吧这个依赖排除

另外一种方式,增加以下配置

ribbon: eureka: enabled: false

我们其中需要对ILoadBalancer进行改造,那它是干什么的呢?

Ribbon作为一个客户端负载均衡器,它最核心的资源便是一堆Server们,也叫服务列表。对于这些服务列表的获取、更新、维护、探活、选择等等都是由上面的组件构成,而如何把这些组件组合在一起有条不紊的工作,便是ILoadBalancer的作用。

public interface ILoadBalancer {// 初始化Server列表。当然后期你可以可以再添加// 在某些情况下,你可能想给出更多的“权重”时 该方法有用public void addServers(List newServers);// 从load balancer里面找到一个Serverpublic Server chooseServer(Object key);// 由负载均衡器的客户端调用,以通知服务器停机否则// LB会认为它还活着,直到下一个Ping周期// 也就说该方法可以手动调用,让Server停机public void markServerDown(Server server);// 该方法已过期,被下面两个方法代替@Deprecatedpublic List getServerList(boolean availableOnly);// 只有服务器是可访问的就返回 public List getReachableServers(); // 所有已知的服务器,包括可访问的和不可访问的。public List getAllServers();}

来看看Ribbon帮我们实现了那些ILoadBalancer

主要讲解一下以下3个Balancer 

(1)baseLoadBalancer

基本实现了ILoadBalancer所使用的接口。

(2)DynamicServerListLoadBalancer

在baseLoadBalancer基础上,进一步集成了ServerListFilter、ServerListUpdater、ServerList三大组件,DynamicServerListLoadBalancer具有如下能力:可定制服务列表(例如可指定从注册中心获取,可从配置文件中获取)、可动态获取服务列表(例如配置文件中配置的服务列表变更可自动获取,注册中心中新增一个服务负载均衡可自动获取),服务过滤能力(可对某些条件的服务器过滤)

(3)ZoneAwareLoadBalancer

在DynamicServerListLoadBalancer基础上,增加了额外的特性。据提供服务的实例所处的区域(Zone)过滤掉那些不是同一个区域的实例,也就是说通过chooseServer()服务一定在同一个区域。

综上核心就是在ZoneAwareLoadBalancer上,而它有个核心方法chooseServer,我们只需要改造一下此方法就可以,先来看看它的源码:

@Override public Server chooseServer(Object key) { // 如果禁用了区域意识。或者只有一个zone,那就遵照父类逻辑 if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { return super.chooseServer(key); } Server server = null; try { LoadBalancerStats lbStats = getLoadBalancerStats();..、 // 核心方法:根据triggeringLoad等阈值计算出可用区~~~~ Set availableZones = ZoneAvoidanceRule.getAvailableZones(zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get()); if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {// 从可用区里随机选择一个区域(zone里面机器越多,被选中概率越大) String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones); if (zone != null) { baseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone); // 按照IRule从该zone内选择一台Server出来 server = zoneLoadBalancer.chooseServer(key); } } } catch (Exception e) { logger.error("Error choosing server using zone aware logic for load balancer={}", name, e); } if (server != null) { return server; } else { // 回退到父类逻辑~~~兜底 return super.chooseServer(key); } }

三、需求实现

(1)实现一个拦截器将Zone信息存入上下文

@Componentpublic class ZoneFilter implements GlobalFilter, Ordered { private static final Pattern ZONE_PATTERN = Pattern.compile("dev-[0-9]+"); @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { String uri = exchange.getRequest().getURI().getHost(); String zoneInUri = matchedZone(uri); zoneInUri = zoneInUri == null ? "stable" : zoneInUri; if (!StringUtils.isEmpty(zoneInUri)) { ZoneUtils.setZoneInfo(zoneInUri); } return chain.filter(exchange); } @Override public int getOrder() { return -1; } private String matchedZone(String zoneUri) { //判断是否是项目环境的Url Matcher matcher = ZONE_PATTERN.matcher(zoneUri); if (matcher.find()) { return matcher.group(); } return null; }}public class ZoneUtils { private static ThreadLocal zoneInfo = new ThreadLocal<>(); public static void setZoneInfo(String zone) { zoneInfo.set(zone); } public static String current() { return zoneInfo.get(); } public static void clear() { zoneInfo.remove(); }}

(2)重写chooseServer方法

public class DianZoneAwareILoadBalance extends ZoneAwareLoadBalancer { public DianZoneAwareILoadBalance(IClientConfig clientConfig, IRule rule, IPing ping, ServerList serverList, ServerListFilter filter, ServerListUpdater serverListUpdater) { super(clientConfig, rule, ping, serverList, filter, serverListUpdater); } @Override public Server chooseServer(Object key) { //如果是线上环境就直接走父逻辑 if (EnvUtils.isReal() || EnvUtils.isPre()) { return super.chooseServer(key); } String zone = ZoneUtils.current(); // 父类baseLoadBalancer定义了upServerList属性,可获取所有可用的服务列表 List upServerList = this.upServerList; for (Server server : upServerList) { if (server.getZone().equals(zone)) { return server; } } return null; }}

(3)定义一个配置类 (注意不要加上@Configuration注解,因为ILoadBalancer加载的方式是饿汉式,当请求第一次来的时候才去初始化它,容器启动的时候并不会去加载,否则你会连容器都起不起来)当然也可以开启懒汉式加载,增加以下配置:

ribbon: eager-load: enabled: true clients: - "服务1" - "服务2"

这样的话,服务1和服务2对应的Ribbon组件会在容器启动的时候去加载(因为Ribbon对每个客户端服务,都会对其生成一套组件,相互隔离)

public class DynamicUrlZoneConfiguration { @Bean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList serverList, ServerListFilter serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { return new DianZoneAwareILoadBalance<>(config, rule, ping, serverList, serverListFilter, serverListUpdater); }}

(4)启动类加上

@RibbonClients(defaultConfiguration = DynamicUrlZoneConfiguration.class)

要注意@RibbonClient和@RibbonClients,前者只针对单个服务应用DynamicUrlZoneConfiguration配置,后者针对所有服务都应用DynamicUrlZoneConfiguration配置,网关场景中,调用下游服务,所以使用@RibbonClients。

其中也是踩了很多坑,也是看了许多源码才一步步解决的

参考文章:

[享学Netflix] 五十六、Ribbon负载均衡器ILoadBalancer(一):baseLoadBalancer - 云+社区 - 腾讯云

通过【application.yml】配置自定义Ribbon客户端时参数【listOfServers】不起作用 - 走看看

SpringCloud Ribbon(一)之自定义负载均衡器ILoadBalancer_茅坤宝骏氹的博客-CSDN博客_自定义负载均衡器

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。