spring cloud eureka 服务分组隔离
- spring cloud eureka 作为注册中心,feign 服务之间调用,原生不支持服务的隔离,
比如以下场景: 服务A 调用服务 B(b1,b2),某些情况下只想让A请求到b1,实现服务之间的分组隔离
或者 共用注册中心,配置中心,公共的服务模块,开发人员本机调试的时候不用启动大量的服务,导致开发机运行缓慢
实现思路
- 其实要实现很简单,参考MQ消息分组消费的原理,只需要在服务注册的时候,将每个服务实例分组标示好,然后自定义实现负载均衡的策略,根据服务消费者的分组名找到对应分组的服务提供者,选择性的请求就可以
负载均衡
- 客户端负载均衡,客户端负载均衡策略,由客户端实现,客户端自己决定需要调用那一个服务 ribbon就是属于客户端负载模式
- 服务端负载模式,服务端收到请求,由服务端决定调用那一个服务,spring cloud getway就是属于服务端负载均衡模式,另外 还有典型的ngxin
Ribbon 负载均衡规则策略
- IRule 接口
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
- AbstractLoadBalancerRule 抽象实现
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb;
@Override
public void setLoadBalancer(ILoadBalancer lb){
this.lb = lb;
}
/**
*ILoadBalancer 维护了所有服务实例的相关信息, com.netflix.loadbalancer.ILoadBalancer#getAllServers
*/
@Override
public ILoadBalancer getLoadBalancer(){
return lb;
}
}
- RandomRule 随机选择一个
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
// 随机选择一个
int index = rand.nextInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
- RoundRobinRule 照线性轮询
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
// 照线性轮询
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
- RetryRule 重试机制的选择器
public Server choose(ILoadBalancer lb, Object key) {
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;
Server answer = null;
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
InterruptTask task = new InterruptTask(deadline
- System.currentTimeMillis());
while (!Thread.interrupted()) {
answer = subRule.choose(key);
if (((answer == null) || (!answer.isAlive()))
&& (System.currentTimeMillis() < deadline)) {
/* pause and retry hoping it's transient */
Thread.yield();
} else {
break;
}
}
task.cancel();
}
if ((answer == null) || (!answer.isAlive())) {
return null;
} else {
return answer;
}
}
- WeightedResponseTimeRule 根据权重计算
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
// get hold of the current reference in case it is changed from the other thread
List<Double> currentWeights = accumulatedWeights;
if (Thread.interrupted()) {
return null;
}
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
int serverIndex = 0;
// last one in the list is the sum of all weights
double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);
// No server has been hit yet and total weight is not initialized
// fallback to use round robin
if (maxTotalWeight < 0.001d) {
server = super.choose(getLoadBalancer(), key);
if(server == null) {
return server;
}
} else {
// generate a random weight between 0 (inclusive) to maxTotalWeight (exclusive)
double randomWeight = random.nextDouble() * maxTotalWeight;
// pick the server index based on the randomIndex
int n = 0;
for (Double d : currentWeights) {
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);
}
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Next.
server = null;
}
return server;
}
- ZoneAvoidanceRule 复合判断server所在区域的性能和server的可用性选择服务器 (默认规则)
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
- BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择-一个并发 量最小的服务
public Server choose(Object key) {
if (loadBalancerStats == null) {
return super.choose(key);
}
List<Server> serverList = getLoadBalancer().getAllServers();
int minimalConcurrentConnections = Integer.MAX_VALUE;
long currentTime = System.currentTimeMillis();
Server chosen = null;
for (Server server: serverList) {
ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
if (!serverStats.isCircuitBreakerTripped(currentTime)) {
int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
if (concurrentConnections < minimalConcurrentConnections) {
minimalConcurrentConnections = concurrentConnections;
chosen = server;
}
}
}
if (chosen == null) {
return super.choose(key);
} else {
return chosen;
}
}
自定义规则 ZoneAvoidanceRuleSupport
- 自定义实现 IRule , 这里我们继承 ZoneAvoidanceRule,
public class ZoneAvoidanceRuleSupport extends ZoneAvoidanceRule {
@Autowired
private Environment env;
@Override
public Server choose(Object key) {
//获取服务调用者的groupName
String appGroup = env.getProperty("eureka.instance.app-group-name");
ILoadBalancer lb = getLoadBalancer();
//获取所有的服务实例
List<Server> allServers = lb.getAllServers();
// 根据分组groupName 过滤,找到对应分组的服务
List<Server> collect = allServers.stream().filter(server -> {
DiscoveryEnabledServer discoveryEnabledServer=(DiscoveryEnabledServer)server;
String serverGroup = discoveryEnabledServer.getInstanceInfo().getAppGroupName();
if (StrUtil.isBlank(appGroup)){
return StrUtil.isBlank(serverGroup);
}else {
return appGroup.equals(serverGroup);
}
}).collect(Collectors.toList());
// 调用父类的实现,找到一个可用的服务
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(collect, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
配置分组名
- 服务提供者和服务消费者同时配置相同分组
- 服务消费者也可以在启动参数中传递分组名
eureka:
client:
serviceUrl:
defaultZone: http://172.16.1.248:11001/eureka/
# 配置实例的分组名
instance:
app-group-name: LUJIA
指定 ribbon 负载规则
- 服务消费者指定
queenOA-service:
ribbon:
NFLoadBalancerRuleClassName: com.queen.oa.api.feign.youpin.ZoneAvoidanceRuleSupport
ok,启动服务,就可以根据不同的分组之间的隔离调用。