| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 618a4de commit b66250e
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -38,7 +38,7 @@ | |||
| 38 | 38 | * looks for an "rpc_behavior" field in its configuration and includes the value in the | |
| 39 | 39 | * "rpc-behavior" metadata entry that is sent to the server. This will cause the test server to | |
| 40 | 40 | * behave in a predefined way. Endpoint picking logic is delegated to the | |
| 41 | - * {@link RoundRobinLoadBalancer}. | ||
| 41 | + * io.grpc.util.RoundRobinLoadBalancer. | ||
| 42 | 42 | * | |
| 43 | 43 | * <p>Initial use case is to prove that a custom load balancer can be configured by the control | |
| 44 | 44 | * plane via xDS. An interop test will configure this LB and then verify it has been correctly | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,7 +44,7 @@ final class AdaptiveThrottler implements Throttler { | |||
| 44 | 44 | ||
| 45 | 45 | private static final int DEFAULT_HISTORY_SECONDS = 30; | |
| 46 | 46 | private static final int DEFAULT_REQUEST_PADDING = 8; | |
| 47 | - private static final float DEFAULT_RATIO_FOR_ACCEPT = 1.2f; | ||
| 47 | + private static final float DEFAULT_RATIO_FOR_ACCEPT = 2.0f; | ||
| 48 | 48 | ||
| 49 | 49 | /** | |
| 50 | 50 | * The duration of history of calls used by Adaptive Throttler. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -81,12 +81,17 @@ final class CachingRlsLbClient { | |||
| 81 | 81 | REQUEST_CONVERTER = new RlsProtoConverters.RouteLookupRequestConverter().reverse(); | |
| 82 | 82 | private static final Converter<RouteLookupResponse, io.grpc.lookup.v1.RouteLookupResponse> | |
| 83 | 83 | RESPONSE_CONVERTER = new RouteLookupResponseConverter().reverse(); | |
| 84 | + public static final long MIN_EVICTION_TIME_DELTA_NANOS = TimeUnit.SECONDS.toNanos(5); | ||
| 85 | + public static final int BYTES_PER_CHAR = 2; | ||
| 86 | + public static final int STRING_OVERHEAD_BYTES = 38; | ||
| 87 | + /** Minimum bytes for a Java Object. */ | ||
| 88 | + public static final int OBJ_OVERHEAD_B = 16; | ||
| 84 | 89 | ||
| 85 | 90 | // All cache status changes (pending, backoff, success) must be under this lock | |
| 86 | 91 | private final Object lock = new Object(); | |
| 87 | 92 | // LRU cache based on access order (BACKOFF and actual data will be here) | |
| 88 | 93 | @GuardedBy("lock") | |
| 89 | - private final LinkedHashLruCache<RouteLookupRequest, CacheEntry> linkedHashLruCache; | ||
| 94 | + private final RlsAsyncLruCache linkedHashLruCache; | ||
| 90 | 95 | // any RPC on the fly will cached in this map | |
| 91 | 96 | @GuardedBy("lock") | |
| 92 | 97 | private final Map<RouteLookupRequest, PendingCacheEntry> pendingCallCache = new HashMap<>(); | |
@@ -287,12 +292,12 @@ private CachedRouteLookupResponse handleNewRequest(RouteLookupRequest request) { | |||
| 287 | 292 | try { | |
| 288 | 293 | RouteLookupResponse response = asyncCall.get(); | |
| 289 | 294 | DataCacheEntry dataEntry = new DataCacheEntry(request, response); | |
| 290 | - linkedHashLruCache.cache(request, dataEntry); | ||
| 295 | + linkedHashLruCache.cacheAndClean(request, dataEntry); | ||
| 291 | 296 | return CachedRouteLookupResponse.dataEntry(dataEntry); | |
| 292 | 297 | } catch (Exception e) { | |
| 293 | 298 | BackoffCacheEntry backoffEntry = | |
| 294 | 299 | new BackoffCacheEntry(request, Status.fromThrowable(e), backoffProvider.get()); | |
| 295 | - linkedHashLruCache.cache(request, backoffEntry); | ||
| 300 | + linkedHashLruCache.cacheAndClean(request, backoffEntry); | ||
| 296 | 301 | return CachedRouteLookupResponse.backoffEntry(backoffEntry); | |
| 297 | 302 | } | |
| 298 | 303 | } | |
@@ -336,6 +341,10 @@ public void run() { | |||
| 336 | 341 | } | |
| 337 | 342 | }); | |
| 338 | 343 | } | |
| 344 | + | ||
| 345 | + void triggerPendingRpcProcessing() { | ||
| 346 | + super.updateBalancingState(state, picker); | ||
| 347 | + } | ||
| 339 | 348 | } | |
| 340 | 349 | ||
| 341 | 350 | /** | |
@@ -488,14 +497,15 @@ private void transitionToDataEntry(RouteLookupResponse routeLookupResponse) { | |||
| 488 | 497 | ChannelLogLevel.DEBUG, | |
| 489 | 498 | "Transition to data cache: routeLookupResponse={0}", | |
| 490 | 499 | routeLookupResponse); | |
| 491 | - linkedHashLruCache.cache(request, new DataCacheEntry(request, routeLookupResponse)); | ||
| 500 | + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, routeLookupResponse)); | ||
| 492 | 501 | } | |
| 493 | 502 | } | |
| 494 | 503 | ||
| 495 | 504 | private void transitionToBackOff(Status status) { | |
| 496 | 505 | synchronized (lock) { | |
| 497 | 506 | logger.log(ChannelLogLevel.DEBUG, "Transition to back off: status={0}", status); | |
| 498 | - linkedHashLruCache.cache(request, new BackoffCacheEntry(request, status, backoffPolicy)); | ||
| 507 | + linkedHashLruCache.cacheAndClean(request, | ||
| 508 | + new BackoffCacheEntry(request, status, backoffPolicy)); | ||
| 499 | 509 | } | |
| 500 | 510 | } | |
| 501 | 511 | ||
@@ -525,11 +535,20 @@ final boolean isExpired() { | |||
| 525 | 535 | abstract boolean isExpired(long now); | |
| 526 | 536 | ||
| 527 | 537 | abstract void cleanup(); | |
| 538 | + | ||
| 539 | + protected long getMinEvictionTime() { | ||
| 540 | + return 0L; | ||
| 541 | + } | ||
| 542 | + | ||
| 543 | + protected void triggerPendingRpcProcessing() { | ||
| 544 | + helper.triggerPendingRpcProcessing(); | ||
| 545 | + } | ||
| 528 | 546 | } | |
| 529 | 547 | ||
| 530 | 548 | /** Implementation of {@link CacheEntry} contains valid data. */ | |
| 531 | 549 | final class DataCacheEntry extends CacheEntry { | |
| 532 | 550 | private final RouteLookupResponse response; | |
| 551 | + private final long minEvictionTime; | ||
| 533 | 552 | private final long expireTime; | |
| 534 | 553 | private final long staleTime; | |
| 535 | 554 | private final List<ChildPolicyWrapper> childPolicyWrappers; | |
@@ -543,6 +562,7 @@ final class DataCacheEntry extends CacheEntry { | |||
| 543 | 562 | refCountedChildPolicyWrapperFactory | |
| 544 | 563 | .createOrGet(response.targets()); | |
| 545 | 564 | long now = ticker.read(); | |
| 565 | + minEvictionTime = now + MIN_EVICTION_TIME_DELTA_NANOS; | ||
| 546 | 566 | expireTime = now + maxAgeNanos; | |
| 547 | 567 | staleTime = now + staleAgeNanos; | |
| 548 | 568 | } | |
@@ -574,13 +594,13 @@ void maybeRefresh() { | |||
| 574 | 594 | // async call returned finished future is most likely throttled | |
| 575 | 595 | try { | |
| 576 | 596 | RouteLookupResponse response = asyncCall.get(); | |
| 577 | - linkedHashLruCache.cache(request, new DataCacheEntry(request, response)); | ||
| 597 | + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, response)); | ||
| 578 | 598 | } catch (InterruptedException e) { | |
| 579 | 599 | Thread.currentThread().interrupt(); | |
| 580 | 600 | } catch (Exception e) { | |
| 581 | 601 | BackoffCacheEntry backoffEntry = | |
| 582 | 602 | new BackoffCacheEntry(request, Status.fromThrowable(e), backoffProvider.get()); | |
| 583 | - linkedHashLruCache.cache(request, backoffEntry); | ||
| 603 | + linkedHashLruCache.cacheAndClean(request, backoffEntry); | ||
| 584 | 604 | } | |
| 585 | 605 | } | |
| 586 | 606 | } | |
@@ -611,11 +631,19 @@ String getHeaderData() { | |||
| 611 | 631 | return response.getHeaderData(); | |
| 612 | 632 | } | |
| 613 | 633 | ||
| 634 | + // Assume UTF-16 (2 bytes) and overhead of a String object is 38 bytes | ||
| 635 | + int calcStringSize(String target) { | ||
| 636 | + return target.length() * BYTES_PER_CHAR + STRING_OVERHEAD_BYTES; | ||
| 637 | + } | ||
| 638 | + | ||
| 614 | 639 | @Override | |
| 615 | 640 | int getSizeBytes() { | |
| 616 | - // size of strings and java object overhead, actual memory usage is more than this. | ||
| 617 | - return | ||
| 618 | - (response.targets().get(0).length() + response.getHeaderData().length()) * 2 + 38 * 2; | ||
| 641 | + int targetSize = 0; | ||
| 642 | + for (String target : response.targets()) { | ||
| 643 | + targetSize += calcStringSize(target); | ||
| 644 | + } | ||
| 645 | + return targetSize + calcStringSize(response.getHeaderData()) + OBJ_OVERHEAD_B // response size | ||
| 646 | + + Long.SIZE * 2 + OBJ_OVERHEAD_B; // Other fields | ||
| 619 | 647 | } | |
| 620 | 648 | ||
| 621 | 649 | @Override | |
@@ -627,6 +655,11 @@ boolean isStaled(long now) { | |||
| 627 | 655 | return staleTime - now <= 0; | |
| 628 | 656 | } | |
| 629 | 657 | ||
| 658 | + @Override | ||
| 659 | + protected long getMinEvictionTime() { | ||
| 660 | + return minEvictionTime; | ||
| 661 | + } | ||
| 662 | + | ||
| 630 | 663 | @Override | |
| 631 | 664 | void cleanup() { | |
| 632 | 665 | synchronized (lock) { | |
@@ -700,11 +733,11 @@ private void transitionToPending() { | |||
| 700 | 733 | } else { | |
| 701 | 734 | try { | |
| 702 | 735 | RouteLookupResponse response = call.get(); | |
| 703 | - linkedHashLruCache.cache(request, new DataCacheEntry(request, response)); | ||
| 736 | + linkedHashLruCache.cacheAndClean(request, new DataCacheEntry(request, response)); | ||
| 704 | 737 | } catch (InterruptedException e) { | |
| 705 | 738 | Thread.currentThread().interrupt(); | |
| 706 | 739 | } catch (Exception e) { | |
| 707 | - linkedHashLruCache.cache( | ||
| 740 | + linkedHashLruCache.cacheAndClean( | ||
| 708 | 741 | request, | |
| 709 | 742 | new BackoffCacheEntry(request, Status.fromThrowable(e), backoffPolicy)); | |
| 710 | 743 | } | |
@@ -718,7 +751,7 @@ Status getStatus() { | |||
| 718 | 751 | ||
| 719 | 752 | @Override | |
| 720 | 753 | int getSizeBytes() { | |
| 721 | - return 0; | ||
| 754 | + return OBJ_OVERHEAD_B * 3 + Long.SIZE + 8; // 3 java objects, 1 long and a boolean | ||
| 722 | 755 | } | |
| 723 | 756 | ||
| 724 | 757 | @Override | |
@@ -876,8 +909,22 @@ protected int estimateSizeOf(RouteLookupRequest key, CacheEntry value) { | |||
| 876 | 909 | @Override | |
| 877 | 910 | protected boolean shouldInvalidateEldestEntry( | |
| 878 | 911 | RouteLookupRequest eldestKey, CacheEntry eldestValue) { | |
| 912 | + if (eldestValue.getMinEvictionTime() > now()) { | ||
| 913 | + return false; | ||
| 914 | + } | ||
| 915 | + | ||
| 879 | 916 | // eldest entry should be evicted if size limit exceeded | |
| 880 | - return true; | ||
| 917 | + return this.estimatedSizeBytes() > this.estimatedMaxSizeBytes(); | ||
| 918 | + } | ||
| 919 | + | ||
| 920 | + public CacheEntry cacheAndClean(RouteLookupRequest key, CacheEntry value) { | ||
| 921 | + CacheEntry newEntry = cache(key, value); | ||
| 922 | + | ||
| 923 | + // force cleanup if new entry pushed cache over max size (in bytes) | ||
| 924 | + if (fitToLimit()) { | ||
| 925 | + value.triggerPendingRpcProcessing(); | ||
| 926 | + } | ||
| 927 | + return newEntry; | ||
| 881 | 928 | } | |
| 882 | 929 | } | |
| 883 | 930 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -118,6 +118,10 @@ protected int estimateSizeOf(K key, V value) { | |||
| 118 | 118 | return 1; | |
| 119 | 119 | } | |
| 120 | 120 | ||
| 121 | + protected long estimatedMaxSizeBytes() { | ||
| 122 | + return estimatedMaxSizeBytes; | ||
| 123 | + } | ||
| 124 | + | ||
| 121 | 125 | /** Updates size for given key if entry exists. It is useful if the cache value is mutated. */ | |
| 122 | 126 | public void updateEntrySize(K key) { | |
| 123 | 127 | synchronized (lock) { | |
@@ -233,30 +237,50 @@ public final List<V> values() { | |||
| 233 | 237 | } | |
| 234 | 238 | } | |
| 235 | 239 | ||
| 240 | + protected long now() { | ||
| 241 | + return ticker.read(); | ||
| 242 | + } | ||
| 243 | + | ||
| 236 | 244 | /** | |
| 237 | - * Resizes cache. If new size is smaller than current estimated size, it will free up space by | ||
| 245 | + * Cleans up cache if needed to fit into max size bytes by | ||
| 238 | 246 | * removing expired entries and removing oldest entries by LRU order. | |
| 247 | + * Returns TRUE if any unexpired entries were removed | ||
| 239 | 248 | */ | |
| 240 | - public final void resize(int newSizeBytes) { | ||
| 241 | - long now = ticker.read(); | ||
| 249 | + protected final boolean fitToLimit() { | ||
| 250 | + boolean removedAnyUnexpired = false; | ||
| 242 | 251 | synchronized (lock) { | |
| 243 | - this.estimatedMaxSizeBytes = newSizeBytes; | ||
| 244 | - if (estimatedSizeBytes.get() <= newSizeBytes) { | ||
| 252 | + if (estimatedSizeBytes.get() <= estimatedMaxSizeBytes) { | ||
| 245 | 253 | // new size is larger no need to do cleanup | |
| 246 | - return; | ||
| 254 | + return false; | ||
| 247 | 255 | } | |
| 248 | 256 | // cleanup expired entries | |
| 249 | - cleanupExpiredEntries(now); | ||
| 257 | + cleanupExpiredEntries(now()); | ||
| 250 | 258 | ||
| 251 | 259 | // cleanup eldest entry until new size limit | |
| 252 | 260 | Iterator<Map.Entry<K, SizedValue>> lruIter = delegate.entrySet().iterator(); | |
| 253 | 261 | while (lruIter.hasNext() && estimatedMaxSizeBytes < this.estimatedSizeBytes.get()) { | |
| 254 | 262 | Map.Entry<K, SizedValue> entry = lruIter.next(); | |
| 263 | + if (!shouldInvalidateEldestEntry(entry.getKey(), entry.getValue().value)) { | ||
| 264 | + break; // Violates some constraint like minimum age so stop our cleanup | ||
| 265 | + } | ||
| 255 | 266 | lruIter.remove(); | |
| 256 | 267 | // eviction listener will update the estimatedSizeBytes | |
| 257 | 268 | evictionListener.onEviction(entry.getKey(), entry.getValue(), EvictionType.SIZE); | |
| 269 | + removedAnyUnexpired = true; | ||
| 258 | 270 | } | |
| 259 | 271 | } | |
| 272 | + return removedAnyUnexpired; | ||
| 273 | + } | ||
| 274 | + | ||
| 275 | + /** | ||
| 276 | + * Resizes cache. If new size is smaller than current estimated size, it will free up space by | ||
| 277 | + * removing expired entries and removing oldest entries by LRU order. | ||
| 278 | + */ | ||
| 279 | + public final void resize(long newSizeBytes) { | ||
| 280 | + synchronized (lock) { | ||
| 281 | + this.estimatedMaxSizeBytes = newSizeBytes; | ||
| 282 | + fitToLimit(); | ||
| 283 | + } | ||
| 260 | 284 | } | |
| 261 | 285 | ||
| 262 | 286 | @Override | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,13 +112,28 @@ protected RouteLookupConfig doForward(Map<String, ?> json) { | |||
| 112 | 112 | ImmutableList<GrpcKeyBuilder> grpcKeybuilders = | |
| 113 | 113 | GrpcKeyBuilderConverter.covertAll( | |
| 114 | 114 | checkNotNull(JsonUtil.getListOfObjects(json, "grpcKeybuilders"), "grpcKeybuilders")); | |
| 115 | + | ||
| 116 | + // Validate grpc_keybuilders | ||
| 115 | 117 | checkArgument(!grpcKeybuilders.isEmpty(), "must have at least one GrpcKeyBuilder"); | |
| 116 | 118 | Set<Name> names = new HashSet<>(); | |
| 117 | 119 | for (GrpcKeyBuilder keyBuilder : grpcKeybuilders) { | |
| 118 | 120 | for (Name name : keyBuilder.names()) { | |
| 119 | 121 | checkArgument(names.add(name), "duplicate names in grpc_keybuilders: " + name); | |
| 120 | 122 | } | |
| 123 | + | ||
| 124 | + Set<String> keys = new HashSet<>(); | ||
| 125 | + for (NameMatcher header : keyBuilder.headers()) { | ||
| 126 | + checkKeys(keys, header.key(), "header"); | ||
| 127 | + } | ||
| 128 | + for (String key : keyBuilder.constantKeys().keySet()) { | ||
| 129 | + checkKeys(keys, key, "constant"); | ||
| 130 | + } | ||
| 131 | + String extraKeyStr = keyToString(keyBuilder.extraKeys()); | ||
| 132 | + checkArgument(keys.add(extraKeyStr), | ||
| 133 | + "duplicate extra key in grpc_keybuilders: " + extraKeyStr); | ||
| 121 | 134 | } | |
| 135 | + | ||
| 136 | + // Validate lookup_service | ||
| 122 | 137 | String lookupService = JsonUtil.getString(json, "lookupService"); | |
| 123 | 138 | checkArgument(!Strings.isNullOrEmpty(lookupService), "lookupService must not be empty"); | |
| 124 | 139 | try { | |
@@ -157,6 +172,11 @@ protected RouteLookupConfig doForward(Map<String, ?> json) { | |||
| 157 | 172 | .build(); | |
| 158 | 173 | } | |
| 159 | 174 | ||
| 175 | + private static String keyToString(ExtraKeys extraKeys) { | ||
| 176 | + return String.format("host: %s, service: %s, method: %s", | ||
| 177 | + extraKeys.host(), extraKeys.service(), extraKeys.method()); | ||
| 178 | + } | ||
| 179 | + | ||
| 160 | 180 | private static <T> T orDefault(@Nullable T value, T defaultValue) { | |
| 161 | 181 | if (value == null) { | |
| 162 | 182 | return checkNotNull(defaultValue, "defaultValue"); | |
@@ -170,6 +190,12 @@ protected Map<String, Object> doBackward(RouteLookupConfig routeLookupConfig) { | |||
| 170 | 190 | } | |
| 171 | 191 | } | |
| 172 | 192 | ||
| 193 | + private static void checkKeys(Set<String> keys, String key, String keyType) { | ||
| 194 | + checkArgument(key != null, "unset " + keyType + " key"); | ||
| 195 | + checkArgument(!key.isEmpty(), "Empty string for " + keyType + " key"); | ||
| 196 | + checkArgument(keys.add(key), "duplicate " + keyType + " key in grpc_keybuilders: " + key); | ||
| 197 | + } | ||
| 198 | + | ||
| 173 | 199 | private static final class GrpcKeyBuilderConverter { | |
| 174 | 200 | public static ImmutableList<GrpcKeyBuilder> covertAll(List<Map<String, ?>> keyBuilders) { | |
| 175 | 201 | ImmutableList.Builder<GrpcKeyBuilder> keyBuilderList = ImmutableList.builder(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,8 +52,8 @@ private static Map<String, GrpcKeyBuilder> createKeyBuilderTable( | |||
| 52 | 52 | Map<String, GrpcKeyBuilder> table = new HashMap<>(); | |
| 53 | 53 | for (GrpcKeyBuilder grpcKeyBuilder : config.grpcKeybuilders()) { | |
| 54 | 54 | for (Name name : grpcKeyBuilder.names()) { | |
| 55 | - boolean hasMethod = name.method() == null || name.method().isEmpty(); | ||
| 56 | - String method = hasMethod ? "*" : name.method(); | ||
| 55 | + boolean noMethod = name.method() == null || name.method().isEmpty(); | ||
| 56 | + String method = noMethod ? "*" : name.method(); | ||
| 57 | 57 | String path = "/" + name.service() + "/" + method; | |
| 58 | 58 | table.put(path, grpcKeyBuilder); | |
| 59 | 59 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments