|
18 | 18 | */ |
19 | 19 | package org.apache.pulsar.broker.loadbalance.extensions.channel; |
20 | 20 |
|
| 21 | +import static java.lang.String.format; |
| 22 | +import static java.util.concurrent.TimeUnit.MILLISECONDS; |
21 | 23 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitState.Assigned; |
22 | 24 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitState.Free; |
23 | 25 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitState.Owned; |
|
35 | 37 | import static org.apache.pulsar.broker.loadbalance.extensions.channel.ServiceUnitStateChannelImpl.MetadataState.Unstable; |
36 | 38 | import static org.apache.pulsar.metadata.api.extended.SessionEvent.SessionLost; |
37 | 39 | import static org.apache.pulsar.metadata.api.extended.SessionEvent.SessionReestablished; |
| 40 | +import com.google.common.annotations.VisibleForTesting; |
38 | 41 | import java.util.ArrayList; |
| 42 | +import java.util.Collections; |
39 | 43 | import java.util.HashMap; |
40 | 44 | import java.util.HashSet; |
41 | 45 | import java.util.List; |
|
48 | 52 | import java.util.concurrent.ScheduledFuture; |
49 | 53 | import java.util.concurrent.TimeUnit; |
50 | 54 | import java.util.concurrent.TimeoutException; |
| 55 | +import java.util.concurrent.atomic.AtomicInteger; |
51 | 56 | import java.util.concurrent.atomic.AtomicLong; |
52 | 57 | import lombok.AllArgsConstructor; |
53 | 58 | import lombok.Getter; |
|
60 | 65 | import org.apache.pulsar.broker.loadbalance.extensions.models.Split; |
61 | 66 | import org.apache.pulsar.broker.loadbalance.extensions.models.Unload; |
62 | 67 | import org.apache.pulsar.broker.loadbalance.impl.LoadManagerShared; |
| 68 | +import org.apache.pulsar.broker.namespace.NamespaceService; |
| 69 | +import org.apache.pulsar.broker.service.BrokerServiceException; |
63 | 70 | import org.apache.pulsar.client.api.MessageId; |
64 | 71 | import org.apache.pulsar.client.api.Producer; |
65 | 72 | import org.apache.pulsar.client.api.PulsarClientException; |
66 | 73 | import org.apache.pulsar.client.api.Schema; |
67 | 74 | import org.apache.pulsar.client.api.TableView; |
68 | 75 | import org.apache.pulsar.common.naming.NamespaceBundle; |
69 | | -import org.apache.pulsar.common.naming.NamespaceBundleSplitAlgorithm; |
| 76 | +import org.apache.pulsar.common.naming.NamespaceBundleFactory; |
| 77 | +import org.apache.pulsar.common.naming.NamespaceBundles; |
70 | 78 | import org.apache.pulsar.common.naming.NamespaceName; |
71 | 79 | import org.apache.pulsar.common.naming.TopicDomain; |
72 | 80 | import org.apache.pulsar.common.naming.TopicName; |
73 | 81 | import org.apache.pulsar.common.stats.Metrics; |
| 82 | +import org.apache.pulsar.common.util.FutureUtil; |
74 | 83 | import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap; |
| 84 | +import org.apache.pulsar.metadata.api.MetadataStoreException; |
75 | 85 | import org.apache.pulsar.metadata.api.NotificationType; |
76 | 86 | import org.apache.pulsar.metadata.api.coordination.LeaderElectionState; |
77 | 87 | import org.apache.pulsar.metadata.api.extended.SessionEvent; |
@@ -523,8 +533,7 @@ private void handleReleaseEvent(String serviceUnit, ServiceUnitStateData data) { |
523 | 533 |
|
524 | 534 | private void handleSplitEvent(String serviceUnit, ServiceUnitStateData data) { |
525 | 535 | if (isTargetBroker(data.broker())) { |
526 | | - splitServiceUnit(serviceUnit) |
527 | | - .thenCompose(__ -> tombstoneAsync(serviceUnit)) |
| 536 | + splitServiceUnit(serviceUnit, data) |
528 | 537 | .whenComplete((__, e) -> log(e, serviceUnit, data, null)); |
529 | 538 | } |
530 | 539 | } |
@@ -625,25 +634,107 @@ private CompletableFuture<Integer> closeServiceUnit(String serviceUnit) { |
625 | 634 | }); |
626 | 635 | } |
627 | 636 |
|
628 | | - private CompletableFuture<Void> splitServiceUnit(String serviceUnit) { |
629 | | - // TODO: after the split we need to write the child ownerships to BSC instead of ZK. |
| 637 | + private CompletableFuture<Void> splitServiceUnit(String serviceUnit, ServiceUnitStateData data) { |
| 638 | + // Write the child ownerships to BSC. |
630 | 639 | long startTime = System.nanoTime(); |
631 | | - return pulsar.getNamespaceService() |
632 | | - .splitAndOwnBundle(getNamespaceBundle(serviceUnit), |
633 | | - false, |
634 | | - NamespaceBundleSplitAlgorithm.of(pulsar.getConfig().getDefaultNamespaceBundleSplitAlgorithm()), |
635 | | - null) |
636 | | - .whenComplete((__, ex) -> { |
637 | | - double splitBundleTime = TimeUnit.NANOSECONDS |
638 | | - .toMillis((System.nanoTime() - startTime)); |
639 | | - if (ex == null) { |
640 | | - log.info("Successfully split {} namespace-bundle in {} ms", |
641 | | - serviceUnit, splitBundleTime); |
642 | | - } else { |
643 | | - log.error("Failed to split {} namespace-bundle in {} ms", |
644 | | - serviceUnit, splitBundleTime, ex); |
645 | | - } |
646 | | - }); |
| 640 | + NamespaceService namespaceService = pulsar.getNamespaceService(); |
| 641 | + NamespaceBundleFactory bundleFactory = namespaceService.getNamespaceBundleFactory(); |
| 642 | + NamespaceBundle bundle = getNamespaceBundle(serviceUnit); |
| 643 | + CompletableFuture<Void> completionFuture = new CompletableFuture<>(); |
| 644 | + final AtomicInteger counter = new AtomicInteger(0); |
| 645 | + this.splitServiceUnitOnceAndRetry(namespaceService, bundleFactory, bundle, serviceUnit, data, |
| 646 | + counter, startTime, completionFuture); |
| 647 | + return completionFuture; |
| 648 | + } |
| 649 | + |
| 650 | + @VisibleForTesting |
| 651 | + protected void splitServiceUnitOnceAndRetry(NamespaceService namespaceService, |
| 652 | + NamespaceBundleFactory bundleFactory, |
| 653 | + NamespaceBundle bundle, |
| 654 | + String serviceUnit, |
| 655 | + ServiceUnitStateData data, |
| 656 | + AtomicInteger counter, |
| 657 | + long startTime, |
| 658 | + CompletableFuture<Void> completionFuture) { |
| 659 | + CompletableFuture<List<NamespaceBundle>> updateFuture = new CompletableFuture<>(); |
| 660 | + |
| 661 | + pulsar.getNamespaceService().getSplitBoundary(bundle, null).thenAccept(splitBundlesPair -> { |
| 662 | + // Split and updateNamespaceBundles. Update may fail because of concurrent write to Zookeeper. |
| 663 | + if (splitBundlesPair == null) { |
| 664 | + String msg = format("Bundle %s not found under namespace", serviceUnit); |
| 665 | + updateFuture.completeExceptionally(new BrokerServiceException.ServiceUnitNotReadyException(msg)); |
| 666 | + return; |
| 667 | + } |
| 668 | + ServiceUnitStateData next = new ServiceUnitStateData(Owned, data.broker()); |
| 669 | + NamespaceBundles targetNsBundle = splitBundlesPair.getLeft(); |
| 670 | + List<NamespaceBundle> splitBundles = Collections.unmodifiableList(splitBundlesPair.getRight()); |
| 671 | + List<NamespaceBundle> successPublishedBundles = |
| 672 | + Collections.synchronizedList(new ArrayList<>(splitBundles.size())); |
| 673 | + List<CompletableFuture<Void>> futures = new ArrayList<>(splitBundles.size()); |
| 674 | + for (NamespaceBundle sBundle : splitBundles) { |
| 675 | + futures.add(pubAsync(sBundle.toString(), next).thenAccept(__ -> successPublishedBundles.add(sBundle))); |
| 676 | + } |
| 677 | + NamespaceName nsname = bundle.getNamespaceObject(); |
| 678 | + FutureUtil.waitForAll(futures) |
| 679 | + .thenCompose(__ -> namespaceService.updateNamespaceBundles(nsname, targetNsBundle)) |
| 680 | + .thenCompose(__ -> namespaceService.updateNamespaceBundlesForPolicies(nsname, targetNsBundle)) |
| 681 | + .thenRun(() -> { |
| 682 | + bundleFactory.invalidateBundleCache(bundle.getNamespaceObject()); |
| 683 | + updateFuture.complete(splitBundles); |
| 684 | + }).exceptionally(e -> { |
| 685 | + // Clean the new bundle when has exception. |
| 686 | + List<CompletableFuture<Void>> futureList = new ArrayList<>(); |
| 687 | + for (NamespaceBundle sBundle : successPublishedBundles) { |
| 688 | + futureList.add(tombstoneAsync(sBundle.toString()).thenAccept(__ -> {})); |
| 689 | + } |
| 690 | + FutureUtil.waitForAll(futureList) |
| 691 | + .whenComplete((__, ex) -> { |
| 692 | + if (ex != null) { |
| 693 | + log.warn("Clean new bundles failed,", ex); |
| 694 | + } |
| 695 | + updateFuture.completeExceptionally(e); |
| 696 | + }); |
| 697 | + return null; |
| 698 | + }); |
| 699 | + }).exceptionally(e -> { |
| 700 | + updateFuture.completeExceptionally(e); |
| 701 | + return null; |
| 702 | + }); |
| 703 | + |
| 704 | + updateFuture.thenAccept(r -> { |
| 705 | + // Free the old bundle |
| 706 | + tombstoneAsync(serviceUnit).thenRun(() -> { |
| 707 | + // Update bundled_topic cache for load-report-generation |
| 708 | + pulsar.getBrokerService().refreshTopicToStatsMaps(bundle); |
| 709 | + // TODO: Update the load data immediately if needed. |
| 710 | + completionFuture.complete(null); |
| 711 | + double splitBundleTime = TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - startTime)); |
| 712 | + log.info("Successfully split {} parent namespace-bundle to {} in {} ms", serviceUnit, r, |
| 713 | + splitBundleTime); |
| 714 | + }).exceptionally(e -> { |
| 715 | + double splitBundleTime = TimeUnit.NANOSECONDS.toMillis((System.nanoTime() - startTime)); |
| 716 | + String msg = format("Failed to free bundle %s in %s ms, under namespace [%s] with error %s", |
| 717 | + bundle.getNamespaceObject().toString(), splitBundleTime, bundle, e.getMessage()); |
| 718 | + completionFuture.completeExceptionally(new BrokerServiceException.ServiceUnitNotReadyException(msg)); |
| 719 | + return null; |
| 720 | + }); |
| 721 | + }).exceptionally(ex -> { |
| 722 | + // Retry several times on BadVersion |
| 723 | + Throwable throwable = FutureUtil.unwrapCompletionException(ex); |
| 724 | + if ((throwable instanceof MetadataStoreException.BadVersionException) |
| 725 | + && (counter.incrementAndGet() < NamespaceService.BUNDLE_SPLIT_RETRY_LIMIT)) { |
| 726 | + pulsar.getExecutor().schedule(() -> splitServiceUnitOnceAndRetry(namespaceService, bundleFactory, |
| 727 | + bundle, serviceUnit, data, counter, startTime, completionFuture), 100, MILLISECONDS); |
| 728 | + } else if (throwable instanceof IllegalArgumentException) { |
| 729 | + completionFuture.completeExceptionally(throwable); |
| 730 | + } else { |
| 731 | + // Retry enough, or meet other exception |
| 732 | + String msg = format("Bundle: %s not success update nsBundles, counter %d, reason %s", |
| 733 | + bundle.toString(), counter.get(), throwable.getMessage()); |
| 734 | + completionFuture.completeExceptionally(new BrokerServiceException.ServiceUnitNotReadyException(msg)); |
| 735 | + } |
| 736 | + return null; |
| 737 | + }); |
647 | 738 | } |
648 | 739 |
|
649 | 740 | public void handleMetadataSessionEvent(SessionEvent e) { |
|
0 commit comments