blob: cc552c21ef8336275e7af0a95ebea4961537df13 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicecomb.loadbalance;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.apache.servicecomb.foundation.common.utils.SPIServiceUtils;
import org.apache.servicecomb.registry.api.registry.MicroserviceInstance;
import org.apache.servicecomb.registry.consumer.MicroserviceInstancePing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalListener;
import com.netflix.config.DynamicPropertyFactory;
/**
* Add special stats that com.netflix.loadbalancer.LoadBalancerStats not provided
*/
public class ServiceCombLoadBalancerStats {
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceCombLoadBalancerStats.class);
private final Map<ServiceCombServer, ServiceCombServerStats> pingView = new ConcurrentHashMap<>();
private int serverExpireInSeconds = DynamicPropertyFactory.getInstance()
.getIntProperty(Configuration.SERVER_EXPIRED_IN_SECONDS, 300).get();
private long timerIntervalInMillis = DynamicPropertyFactory.getInstance()
.getLongProperty(Configuration.TIMER_INTERVAL_IN_MILLIS, 10000).get();
private LoadingCache<ServiceCombServer, ServiceCombServerStats> serverStatsCache;
private final Map<String, ServiceCombServer> serviceCombServers = new ConcurrentHashMap<>();
public static ServiceCombLoadBalancerStats INSTANCE;
private Timer timer;
static {
INSTANCE = new ServiceCombLoadBalancerStats();
INSTANCE.init();
}
/**
* Should be singleton, use it only for testing
*/
ServiceCombLoadBalancerStats() {
}
public void markIsolated(ServiceCombServer server, boolean isolated) {
try {
serverStatsCache.get(server).markIsolated(isolated);
} catch (ExecutionException e) {
LOGGER.error("Not expected to happen, maybe a bug.", e);
}
}
public void markSuccess(ServiceCombServer server) {
try {
serverStatsCache.get(server).markSuccess();
} catch (ExecutionException e) {
LOGGER.error("Not expected to happen, maybe a bug.", e);
}
}
public void markFailure(ServiceCombServer server) {
try {
serverStatsCache.get(server).markFailure();
} catch (ExecutionException e) {
LOGGER.error("Not expected to happen, maybe a bug.", e);
}
}
public ServiceCombServerStats getServiceCombServerStats(ServiceCombServer server) {
try {
return serverStatsCache.get(server);
} catch (ExecutionException e) {
LOGGER.error("Not expected to happen, maybe a bug.", e);
return null;
}
}
public ServiceCombServer getServiceCombServer(MicroserviceInstance instance) {
return serviceCombServers.get(instance.getInstanceId());
}
@VisibleForTesting
void setServerExpireInSeconds(int sec) {
this.serverExpireInSeconds = sec;
}
@VisibleForTesting
void setTimerIntervalInMillis(int millis) {
this.timerIntervalInMillis = millis;
}
@VisibleForTesting
Map<ServiceCombServer, ServiceCombServerStats> getPingView() {
return this.pingView;
}
@VisibleForTesting
public void init() {
// for testing
if (timer != null) {
timer.cancel();
}
if (serverStatsCache != null) {
serverStatsCache.cleanUp();
}
pingView.clear();
serverStatsCache =
CacheBuilder.newBuilder()
.expireAfterAccess(serverExpireInSeconds, TimeUnit.SECONDS)
.removalListener(
(RemovalListener<ServiceCombServer, ServiceCombServerStats>) notification -> {
ServiceCombServer server = notification.getKey();
LOGGER.info("stats of instance {} removed, host is {}",
server.getInstance().getInstanceId(), server.getHost());
pingView.remove(notification.getKey());
serviceCombServers.remove(notification.getKey());
})
.build(
new CacheLoader<ServiceCombServer, ServiceCombServerStats>() {
public ServiceCombServerStats load(ServiceCombServer server) {
ServiceCombServerStats stats = new ServiceCombServerStats(server.getMicroserviceName());
pingView.put(server, stats);
serviceCombServers.put(server.getInstance().getInstanceId(), server);
return stats;
}
});
timer = new Timer("LoadBalancerStatsTimer", true);
timer.schedule(new TimerTask() {
private final MicroserviceInstancePing ping = SPIServiceUtils.getPriorityHighestService(MicroserviceInstancePing.class);
@Override
public void run() {
try {
Map<ServiceCombServer, ServiceCombServerStats> allServers = pingView;
allServers.forEach((server, stats) -> {
if ((System.currentTimeMillis() - stats.getLastVisitTime() > timerIntervalInMillis) && !ping
.ping(server.getInstance())) {
LOGGER.info("ping mark server {} failure.", server.getInstance().getInstanceId());
stats.markFailure();
}
});
serverStatsCache.cleanUp();
} catch (Throwable e) {
LOGGER.warn("LoadBalancerStatsTimer error.", e);
}
}
}, timerIntervalInMillis, timerIntervalInMillis);
}
}