限流- Resilience4j-AtomicRateLimiter

/*
 *
 *  Copyright 2016 Robert Winkler and Bohdan Storozhuk
 *
 *  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 io.github.resilience4j.ratelimiter.internal;

import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.event.RateLimiterOnDrainedEvent;
import io.github.resilience4j.ratelimiter.event.RateLimiterOnFailureEvent;
import io.github.resilience4j.ratelimiter.event.RateLimiterOnSuccessEvent;
import io.vavr.collection.HashMap;
import io.vavr.collection.Map;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;

import static java.lang.Long.min;
import static java.lang.System.nanoTime;
import static java.lang.Thread.currentThread;
import static java.util.concurrent.locks.LockSupport.parkNanos;

/**
 * {@link AtomicRateLimiter} splits all nanoseconds from the start of epoch into cycles.
 * 

Each cycle has duration of {@link RateLimiterConfig#getLimitRefreshPeriod} in nanoseconds. *

By contract on start of each cycle {@link AtomicRateLimiter} should * set {@link State#activePermissions} to {@link RateLimiterConfig#getLimitForPeriod}. For the {@link * AtomicRateLimiter} callers it is really looks so, but under the hood there is some optimisations * that will skip this refresh if {@link AtomicRateLimiter} is not used actively. *

All {@link AtomicRateLimiter} updates are atomic and state is encapsulated in {@link * AtomicReference} to {@link AtomicRateLimiter.State} */ public class AtomicRateLimiter implements RateLimiter { private final long nanoTimeStart; private final String name; private final AtomicInteger waitingThreads; private final AtomicReference state; private final Map tags; private final RateLimiterEventProcessor eventProcessor; public AtomicRateLimiter(String name, RateLimiterConfig rateLimiterConfig) { this(name, rateLimiterConfig, HashMap.empty()); } public AtomicRateLimiter(String name, RateLimiterConfig rateLimiterConfig, Map tags) { this.name = name; this.tags = tags; this.nanoTimeStart = nanoTime(); waitingThreads = new AtomicInteger(0); state = new AtomicReference<>(new State( rateLimiterConfig, 0, rateLimiterConfig.getLimitForPeriod(), 0 )); eventProcessor = new RateLimiterEventProcessor(); } /** * {@inheritDoc} */ @Override public void changeTimeoutDuration(final Duration timeoutDuration) { RateLimiterConfig newConfig = RateLimiterConfig.from(state.get().config) .timeoutDuration(timeoutDuration) .build(); state.updateAndGet(currentState -> new State( newConfig, currentState.activeCycle, currentState.activePermissions, currentState.nanosToWait )); } /** * {@inheritDoc} */ @Override public void changeLimitForPeriod(final int limitForPeriod) { RateLimiterConfig newConfig = RateLimiterConfig.from(state.get().config) .limitForPeriod(limitForPeriod) .build(); state.updateAndGet(currentState -> new State( newConfig, currentState.activeCycle, currentState.activePermissions, currentState.nanosToWait )); } /** * Calculates time elapsed from the class loading. */ private long currentNanoTime() { return nanoTime() - nanoTimeStart; } long getNanoTimeStart() { return this.nanoTimeStart; } /** * {@inheritDoc} */ @Override public boolean acquirePermission(final int permits) { long timeoutInNanos = state.get().config.getTimeoutDuration().toNanos(); State modifiedState = updateStateWithBackOff(permits, timeoutInNanos); boolean result = waitForPermissionIfNecessary(timeoutInNanos, modifiedState.nanosToWait); publishRateLimiterAcquisitionEvent(result, permits); return result; } /** * {@inheritDoc} */ @Override public long reservePermission(final int permits) { long timeoutInNanos = state.get().config.getTimeoutDuration().toNanos(); State modifiedState = updateStateWithBackOff(permits, timeoutInNanos); boolean canAcquireImmediately = modifiedState.nanosToWait <= 0; if (canAcquireImmediately) { publishRateLimiterAcquisitionEvent(true, permits); return 0; } boolean canAcquireInTime = timeoutInNanos >= modifiedState.nanosToWait; if (canAcquireInTime) { publishRateLimiterAcquisitionEvent(true, permits); return modifiedState.nanosToWait; } publishRateLimiterAcquisitionEvent(false, permits); return -1; } @Override public void drainPermissions() { AtomicRateLimiter.State prev; AtomicRateLimiter.State next; do { prev = state.get(); next = calculateNextState(prev.activePermissions, 0, prev); } while (!compareAndSet(prev, next)); if (eventProcessor.hasConsumers()) { eventProcessor.consumeEvent(new RateLimiterOnDrainedEvent(name, Math.min(prev.activePermissions, 0))); } } /** * Atomically updates the current {@link State} with the results of applying the {@link * AtomicRateLimiter#calculateNextState}, returning the updated {@link State}. It differs from * {@link AtomicReference#updateAndGet(UnaryOperator)} by constant back off. It means that after * one try to {@link AtomicReference#compareAndSet(Object, Object)} this method will wait for a * while before try one more time. This technique was originally described in this * paper * and showed great results with {@link AtomicRateLimiter} in benchmark tests. * * @param timeoutInNanos a side-effect-free function * @return the updated value */ private State updateStateWithBackOff(final int permits, final long timeoutInNanos) { AtomicRateLimiter.State prev; AtomicRateLimiter.State next; do { prev = state.get(); next = calculateNextState(permits, timeoutInNanos, prev); } while (!compareAndSet(prev, next)); return next; } /** * Atomically sets the value to the given updated value if the current value {@code ==} the * expected value. It differs from {@link AtomicReference#updateAndGet(UnaryOperator)} by * constant back off. It means that after one try to {@link AtomicReference#compareAndSet(Object, * Object)} this method will wait for a while before try one more time. This technique was * originally described in this * paper * and showed great results with {@link AtomicRateLimiter} in benchmark tests. * * @param current the expected value * @param next the new value * @return {@code true} if successful. False return indicates that the actual value was not * equal to the expected value. */ private boolean compareAndSet(final State current, final State next) { if (state.compareAndSet(current, next)) { return true; } parkNanos(1); // back-off return false; } /** * A side-effect-free function that can calculate next {@link State} from current. It determines * time duration that you should wait for the given number of permits and reserves it for you, * if you'll be able to wait long enough. * * @param permits number of permits * @param timeoutInNanos max time that caller can wait for permission in nanoseconds * @param activeState current state of {@link AtomicRateLimiter} * @return next {@link State} */ private State calculateNextState(final int permits, final long timeoutInNanos, final State activeState) { long cyclePeriodInNanos = activeState.config.getLimitRefreshPeriod().toNanos(); int permissionsPerCycle = activeState.config.getLimitForPeriod(); long currentNanos = currentNanoTime(); long currentCycle = currentNanos / cyclePeriodInNanos; long nextCycle = activeState.activeCycle; int nextPermissions = activeState.activePermissions; if (nextCycle != currentCycle) { long elapsedCycles = currentCycle - nextCycle; long accumulatedPermissions = elapsedCycles * permissionsPerCycle; nextCycle = currentCycle; nextPermissions = (int) min(nextPermissions + accumulatedPermissions, permissionsPerCycle); } long nextNanosToWait = nanosToWaitForPermission( permits, cyclePeriodInNanos, permissionsPerCycle, nextPermissions, currentNanos, currentCycle ); State nextState = reservePermissions(activeState.config, permits, timeoutInNanos, nextCycle, nextPermissions, nextNanosToWait); return nextState; } /** * Calculates time to wait for the required permits of permissions to get accumulated * * @param permits permits of required permissions * @param cyclePeriodInNanos current configuration values * @param permissionsPerCycle current configuration values * @param availablePermissions currently available permissions, can be negative if some * permissions have been reserved * @param currentNanos current time in nanoseconds * @param currentCycle current {@link AtomicRateLimiter} cycle @return nanoseconds to * wait for the next permission */ private long nanosToWaitForPermission(final int permits, final long cyclePeriodInNanos, final int permissionsPerCycle, final int availablePermissions, final long currentNanos, final long currentCycle) { if (availablePermissions >= permits) { return 0L; } long nextCycleTimeInNanos = (currentCycle + 1) * cyclePeriodInNanos; long nanosToNextCycle = nextCycleTimeInNanos - currentNanos; int permissionsAtTheStartOfNextCycle = availablePermissions + permissionsPerCycle; int fullCyclesToWait = divCeil(-(permissionsAtTheStartOfNextCycle - permits), permissionsPerCycle); return (fullCyclesToWait * cyclePeriodInNanos) + nanosToNextCycle; } /** * Divide two integers and round result to the bigger near mathematical integer. * * @param x - should be > 0 * @param y - should be > 0 */ private static int divCeil(int x, int y) { return (x + y - 1) / y; } /** * Determines whether caller can acquire permission before timeout or not and then creates * corresponding {@link State}. Reserves permissions only if caller can successfully wait for * permission. * * @param config * @param permits permits of permissions * @param timeoutInNanos max time that caller can wait for permission in nanoseconds * @param cycle cycle for new {@link State} * @param permissions permissions for new {@link State} * @param nanosToWait nanoseconds to wait for the next permission * @return new {@link State} with possibly reserved permissions and time to wait */ private State reservePermissions(final RateLimiterConfig config, final int permits, final long timeoutInNanos, final long cycle, final int permissions, final long nanosToWait) { boolean canAcquireInTime = timeoutInNanos >= nanosToWait; int permissionsWithReservation = permissions; if (canAcquireInTime) { permissionsWithReservation -= permits; } return new State(config, cycle, permissionsWithReservation, nanosToWait); } /** * If nanosToWait is bigger than 0 it tries to park {@link Thread} for nanosToWait but not * longer then timeoutInNanos. * * @param timeoutInNanos max time that caller can wait * @param nanosToWait nanoseconds caller need to wait * @return true if caller was able to wait for nanosToWait without {@link Thread#interrupt} and * not exceed timeout */ private boolean waitForPermissionIfNecessary(final long timeoutInNanos, final long nanosToWait) { boolean canAcquireImmediately = nanosToWait <= 0; boolean canAcquireInTime = timeoutInNanos >= nanosToWait; if (canAcquireImmediately) { return true; } if (canAcquireInTime) { return waitForPermission(nanosToWait); } waitForPermission(timeoutInNanos); return false; } /** * Parks {@link Thread} for nanosToWait. *

If the current thread is {@linkplain Thread#interrupted} * while waiting for a permit then it won't throw {@linkplain InterruptedException}, but its * interrupt status will be set. * * @param nanosToWait nanoseconds caller need to wait * @return true if caller was not {@link Thread#interrupted} while waiting */ private boolean waitForPermission(final long nanosToWait) { waitingThreads.incrementAndGet(); long deadline = currentNanoTime() + nanosToWait; boolean wasInterrupted = false; while (currentNanoTime() < deadline && !wasInterrupted) { long sleepBlockDuration = deadline - currentNanoTime(); parkNanos(sleepBlockDuration); wasInterrupted = Thread.interrupted(); } waitingThreads.decrementAndGet(); if (wasInterrupted) { currentThread().interrupt(); } return !wasInterrupted; } /** * {@inheritDoc} */ @Override public String getName() { return name; } /** * {@inheritDoc} */ @Override public RateLimiterConfig getRateLimiterConfig() { return state.get().config; } /** * {@inheritDoc} */ @Override public Map getTags() { return tags; } /** * {@inheritDoc} */ @Override public Metrics getMetrics() { return new AtomicRateLimiterMetrics(); } @Override public EventPublisher getEventPublisher() { return eventProcessor; } @Override public String toString() { return "AtomicRateLimiter{" + "name='" + name + '\'' + ", rateLimiterConfig=" + state.get().config + '}'; } /** * Get the enhanced Metrics with some implementation specific details. * * @return the detailed metrics */ public AtomicRateLimiterMetrics getDetailedMetrics() { return new AtomicRateLimiterMetrics(); } private void publishRateLimiterAcquisitionEvent(boolean permissionAcquired, int permits) { if (!eventProcessor.hasConsumers()) { return; } if (permissionAcquired) { eventProcessor.consumeEvent(new RateLimiterOnSuccessEvent(name, permits)); return; } eventProcessor.consumeEvent(new RateLimiterOnFailureEvent(name, permits)); } /** *

{@link AtomicRateLimiter.State} represents immutable state of {@link AtomicRateLimiter} * where: *

    *
  • activeCycle - {@link AtomicRateLimiter} cycle number that was used * by the last {@link AtomicRateLimiter#acquirePermission()} call.
  • *

    *

  • activePermissions - count of available permissions after * the last {@link AtomicRateLimiter#acquirePermission()} call. * Can be negative if some permissions where reserved.
  • *

    *

  • nanosToWait - count of nanoseconds to wait for permission for * the last {@link AtomicRateLimiter#acquirePermission()} call.
  • *
*/ private static class State { private final RateLimiterConfig config; private final long activeCycle; private final int activePermissions; private final long nanosToWait; private State(RateLimiterConfig config, final long activeCycle, final int activePermissions, final long nanosToWait) { this.config = config; this.activeCycle = activeCycle; this.activePermissions = activePermissions; this.nanosToWait = nanosToWait; } } /** * Enhanced {@link Metrics} with some implementation specific details */ public class AtomicRateLimiterMetrics implements Metrics { private AtomicRateLimiterMetrics() { } /** * {@inheritDoc} */ @Override public int getNumberOfWaitingThreads() { return waitingThreads.get(); } /** * {@inheritDoc} */ @Override public int getAvailablePermissions() { State currentState = state.get(); State estimatedState = calculateNextState(1, -1, currentState); return estimatedState.activePermissions; } /** * @return estimated time duration in nanos to wait for the next permission */ public long getNanosToWait() { State currentState = state.get(); State estimatedState = calculateNextState(1, -1, currentState); return estimatedState.nanosToWait; } /** * @return estimated current cycle */ public long getCycle() { State currentState = state.get(); State estimatedState = calculateNextState(1, -1, currentState); return estimatedState.activeCycle; } } }

你可能感兴趣的:(java限流)