All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.proofpoint.http.client.balancing.HttpServiceBalancerImpl Maven / Gradle / Ivy

There is a newer version: 3.23
Show newest version
/*
 * Copyright 2013 Proofpoint, 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.proofpoint.http.client.balancing;

import com.google.common.annotations.Beta;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableSet;
import com.proofpoint.http.client.balancing.HttpServiceBalancerStats.Status;
import com.proofpoint.stats.MaxGauge;
import org.weakref.jmx.Nested;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

public class HttpServiceBalancerImpl
        implements HttpServiceBalancer
{
    private final AtomicReference> httpUris = new AtomicReference<>((Set) ImmutableSet.of());
    private final Map concurrentAttempts = new HashMap<>();
    private final String description;
    private final HttpServiceBalancerStats httpServiceBalancerStats;
    private final Ticker ticker;
    private final MaxGauge concurrency = new MaxGauge();

    public HttpServiceBalancerImpl(String description, HttpServiceBalancerStats httpServiceBalancerStats)
    {
        this(description, httpServiceBalancerStats, Ticker.systemTicker());
    }

    HttpServiceBalancerImpl(String description, HttpServiceBalancerStats httpServiceBalancerStats, Ticker ticker)
    {
        this.description = checkNotNull(description, "description is null");
        this.httpServiceBalancerStats = checkNotNull(httpServiceBalancerStats, "httpServiceBalancerStats is null");
        this.ticker = checkNotNull(ticker, "ticker is null");
    }

    @Override
    public HttpServiceAttempt createAttempt()
    {
        return new HttpServiceAttemptImpl(ImmutableSet.of());
    }

    @Beta
    public void updateHttpUris(Set newHttpUris)
    {
        httpUris.set(ImmutableSet.copyOf(newHttpUris));
    }

    private class HttpServiceAttemptImpl
            implements HttpServiceAttempt
    {
        private final Set attempted;
        private final URI uri;
        private final long startTick;
        private boolean inProgress = true;

        HttpServiceAttemptImpl(Set attempted)
        {
            ArrayList httpUris = new ArrayList<>(HttpServiceBalancerImpl.this.httpUris.get());
            httpUris.removeAll(attempted);

            if (httpUris.isEmpty()) {
                httpUris = new ArrayList<>(HttpServiceBalancerImpl.this.httpUris.get());
                attempted = ImmutableSet.of();

                if (httpUris.isEmpty()) {
                    throw new ServiceUnavailableException(description);
                }
            }

            int leastConcurrent = Integer.MAX_VALUE;
            ArrayList leastUris = new ArrayList<>();
            synchronized (concurrentAttempts) {
                for (URI uri : httpUris) {
                    int uriConcurrent = firstNonNull(concurrentAttempts.get(uri), 0);
                    if (uriConcurrent < leastConcurrent) {
                        leastConcurrent = uriConcurrent;
                        leastUris = new ArrayList<>(ImmutableSet.of(uri));
                    }
                    else if (uriConcurrent == leastConcurrent) {
                        leastUris.add(uri);
                    }
                }

                uri = leastUris.get(ThreadLocalRandom.current().nextInt(0, leastUris.size()));
                concurrentAttempts.put(uri, leastConcurrent + 1);
                if (leastConcurrent == concurrency.get()) {
                    concurrency.update(leastConcurrent + 1);
                }
            }

            this.attempted = ImmutableSet.copyOf(attempted);
            startTick = ticker.read();
        }

        @Override
        public URI getUri()
        {
            return uri;
        }

        @Override
        public void markGood()
        {
            decrementConcurrency();
            httpServiceBalancerStats.requestTime(uri, Status.SUCCESS).add(ticker.read() - startTick, TimeUnit.NANOSECONDS);
        }

        @Override
        public void markBad(String failureCategory)
        {
            decrementConcurrency();
            httpServiceBalancerStats.requestTime(uri, Status.FAILURE).add(ticker.read() - startTick, TimeUnit.NANOSECONDS);
            httpServiceBalancerStats.failure(uri, failureCategory).add(1);
        }

        @Override
        public void markBad(String failureCategory, String handlerCategory)
        {
            decrementConcurrency();
            httpServiceBalancerStats.requestTime(uri, Status.FAILURE).add(ticker.read() - startTick, TimeUnit.NANOSECONDS);
            httpServiceBalancerStats.failure(uri, failureCategory, handlerCategory).add(1);
        }

        private void decrementConcurrency()
        {
            checkState(inProgress, "is in progress");
            inProgress = false;
            synchronized (concurrentAttempts) {
                Integer uriConcurrent = concurrentAttempts.get(uri);
                if (uriConcurrent == null || uriConcurrent <= 1) {
                    concurrentAttempts.remove(uri);
                    if (concurrentAttempts.isEmpty()) {
                        concurrency.update(0);
                    }
                }
                else {
                    concurrentAttempts.put(uri, uriConcurrent - 1);
                    if (concurrency.get() == uriConcurrent) {
                        for (Integer concurrent : concurrentAttempts.values()) {
                            if (uriConcurrent.equals(concurrent)) {
                                ++uriConcurrent;
                                break;
                            }
                        }
                        concurrency.update(uriConcurrent - 1);
                    }
                }
            }
        }

        @Override
        public HttpServiceAttempt next()
        {
            checkState(!inProgress, "is not still in progress");
            Set newAttempted = ImmutableSet.builder()
                    .add(uri)
                    .addAll(attempted)
                    .build();
            return new HttpServiceAttemptImpl(newAttempted);
        }
    }

    @Nested
    public MaxGauge getConcurrency()
    {
        return concurrency;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy