来源:NanCheung`s Blog
链接:https://blog.nancheung.com
/archives/512102
商业转载请联系作者获得授权,非商业转载请注明出处。
在项目的实际开发中,开发人员本地开发时,不希望本地将整个调用链启动,不想启动多个服务,渴望接入测试环境,既能依赖测试环境的上游服务,同时并不希望自己会影响到下游服务。
思路
为了解决整个问题,我们发现开发环境的本地微服务(例如IP地址为172.16.0.8)已经注册到测试环境的服务注册发现中心,那么可以在配置中心维护一个黑/白名单的IP地址过滤(支持全局和局部的过滤)的规则,该本地微服务就不会被其他测试环境的微服务所调用。
那么我们怎么实现呢?我们还得研究Ribbon的负载均衡规则体系。
Ribbon的负载均衡规则
我们都知道SpringCloudGateway中默认使用Ribbon来实现负载均衡。
那么我们看一下Ribbon的负载均衡规则体系:
可以发现,其中有一个抽象类 PredicateBasedRule
,我们看这个类的注释:
A rule which delegates the server filtering logic to an instance of {@link AbstractServerPredicate}.
After filtering, a server is returned from filtered list in a round robin fashion.
大意为它是实现AbstractServerPredicate类的服务器过滤逻辑。究竟请求哪个服务器,需要经过它的过滤。
看看如下代码:
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
/**
* 定义了一个抽象函数来获取AbstractServerPredicate
*/
public abstract AbstractServerPredicate getPredicate();
/**
* 通过调用获取服务器AbstractServerPredicate.chooseRandomlyAfterFiltering(java.util.List, Object)
* 方法来过滤服务。
* 该方法的性能为O(n),其中n是要被过滤服务器的数量。
*/
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
//通过AbstractServerPredicate的chooseRoundRobinAfterFiltering函数来选出具体的服务实例
//AbstractServerPredicate的子类实现的Predicate逻辑来过滤一部分服务实例
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
所以我们只要继承 PredicateBasedRule
类,并重写 choose
就可以了。
实现方案
一、定义一个配置文件,配置白名单(优先名单)
/**
* Ribbon 配置
*
* @author NanCheung
*/
@Getter
@Setter
@RefreshScope
@ConfigurationProperties("ribbon.rule")
public class RibbonRuleProperties {
/**
* 是否开启,默认:true
*/
private boolean enabled = Boolean.TRUE;
/**
* 优先的ip列表,支持通配符,例如:10.20.0.8*、10.20.0.*
* <p>为了解决线上网关的服务池被污染的问题,当匹配不到本地ip时,筛选出此ip列表,一般为各服务器ip</p>
*/
private List<String> priorIpPattern = new ArrayList<>();
}
二、继承 PredicateBasedRule
,实现自定义路由过滤规则
/**
* ribbon路由过滤规则 优先本地主机服务
*
* @author NanCheung
*/
@Slf4j
public class LocalHostAwareRule extends PredicateBasedRule {
@Autowired
private RibbonRuleProperties ribbonRuleProperties;
@Override
public AbstractServerPredicate getPredicate() {
return AbstractServerPredicate.alwaysTrue();
}
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
List<Server> allServers = lb.getAllServers();
// 过滤服务列表
List<Server> serverList = this.filterServers(allServers);
return getPredicate().chooseRoundRobinAfterFiltering(serverList, key).orNull();
}
private List<Server> filterServers(List<Server> serverList) {
List<String> priorIpPattern = ribbonRuleProperties.getPriorIpPattern();
// 1. 查找本机ip,此处我用的是HuTool工具类,可以自己替换成相同功用的方法
HostInfo hostInfo = SystemUtil.getHostInfo();
String hostIp = hostInfo.getAddress();
// 优先的 ip 规则
boolean hasPriorIpPattern = !priorIpPattern.isEmpty();
String[] priorIpPatterns = priorIpPattern.toArray(new String[0]);
List<Server> priorServerList = new ArrayList<>();
for (Server server : serverList) {
String host = server.getHost();
// 2. 优先本地ip的服务 某些服务使用主机名注册为host,所以要同时判断ip和主机名
// 此处我用的是HuTool工具类,可以自己替换成相同功用的方法
if (ObjectUtil.equal(hostIp, host) || ObjectUtil.equal(hostInfo.getName(), host)) {
log.debug("[{}]请求转发至本地路由[{}]", hostInfo, host);
return Collections.singletonList(server);
}
// 3. 优先的 ip 服务
if (hasPriorIpPattern && PatternMatchUtils.simpleMatch(priorIpPatterns, host)) {
priorServerList.add(server);
}
}
// 4. 如果优先的有数据直接返回
if (!priorServerList.isEmpty()) {
return priorServerList;
}
return Collections.unmodifiableList(serverList);
}
}
三、在自动配置器中注册自定义路由规则
/**
* Ribbon路由规则自动配置器
*
* @author NanCheung
*/
@Configuration
@RequiredArgsConstructor
//启用自定义的配置文件
@EnableConfigurationProperties(RibbonRuleProperties.class)
//一定要在ribbon默认的配置器之前加载
@AutoConfigureBefore(RibbonClientConfiguration.class)
//当ribbon.rule.enabled为false时不启用自定义过滤规则
@ConditionalOnProperty(value = "ribbon.rule.enabled", matchIfMissing = true)
public class RibbonRuleAutoConfiguration {
@Bean
@ConditionalOnMissingBean
//这里一定要配置为多例
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PredicateBasedRule localHostAwareRule() {
return new LocalHostAwareRule();
}
}
五、配置服务器白名单(优先)列表
在项目的application配置文件中加入配置:
将除了本地地址外需要优先请求的地址配置进来,注意线上服务器的ip地址是必须配置的,可以使用通配符。
ribbon:
rule:
prior-ip-pattern:
- 10.1.1.*