/*
* Copyright 2015 Netflix, Inc.
*
* Licensed 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 com.netflix.discovery.shared.transport.decorator;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.EurekaHttpClientFactory;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.discovery.shared.transport.TransportUtils;
import com.netflix.servo.annotations.DataSourceType;
import com.netflix.servo.annotations.Monitor;
import com.netflix.servo.monitor.Monitors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.netflix.discovery.EurekaClientNames.METRIC_TRANSPORT_PREFIX;
/**
* {@link SessionedEurekaHttpClient} enforces full reconnect at a regular interval (a session), preventing
* a client to sticking to a particular Eureka server instance forever. This in turn guarantees even
* load distribution in case of cluster topology change.
*
* @author Tomasz Bak
*/
public class SessionedEurekaHttpClient extends EurekaHttpClientDecorator {
private static final Logger logger = LoggerFactory.getLogger(SessionedEurekaHttpClient.class);
private final Random random = new Random();
private final String name;
private final EurekaHttpClientFactory clientFactory;
private final long sessionDurationMs;
private volatile long currentSessionDurationMs;
private volatile long lastReconnectTimeStamp = -1;
private final AtomicReference<EurekaHttpClient> eurekaHttpClientRef = new AtomicReference<>();
public SessionedEurekaHttpClient(String name, EurekaHttpClientFactory clientFactory, long sessionDurationMs) {
this.name = name;
this.clientFactory = clientFactory;
this.sessionDurationMs = sessionDurationMs;
this.currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs);
Monitors.registerObject(name, this);
}
@Override
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
long now = System.currentTimeMillis();
long delay = now - lastReconnectTimeStamp;
if (delay >= currentSessionDurationMs) {
logger.debug("Ending a session and starting anew");
lastReconnectTimeStamp = now;
currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs);
TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null));
}
EurekaHttpClient eurekaHttpClient = eurekaHttpClientRef.get();
if (eurekaHttpClient == null) {
eurekaHttpClient = TransportUtils.getOrSetAnotherClient(eurekaHttpClientRef, clientFactory.newClient());
}
return requestExecutor.execute(eurekaHttpClient);
}
@Override
public void shutdown() {
if(Monitors.isObjectRegistered(name, this)) {
Monitors.unregisterObject(name, this);
}
TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null));
}
/**
* @return a randomized sessionDuration in ms calculated as +/- an additional amount in [0, sessionDurationMs/2]
*/
protected long randomizeSessionDuration(long sessionDurationMs) {
long delta = (long) (sessionDurationMs * (random.nextDouble() - 0.5));
return sessionDurationMs + delta;
}
@Monitor(name = METRIC_TRANSPORT_PREFIX + "currentSessionDuration",
description = "Duration of the current session", type = DataSourceType.GAUGE)
public long getCurrentSessionDuration() {
return lastReconnectTimeStamp < 0 ? 0 : System.currentTimeMillis() - lastReconnectTimeStamp;
}
}