/** * Copyright 2016 Yahoo 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.yahoo.pulsar.broker.loadbalance.impl; import static com.google.common.base.Preconditions.checkArgument; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.yahoo.pulsar.broker.PulsarService; import com.yahoo.pulsar.broker.admin.AdminResource; import com.yahoo.pulsar.broker.loadbalance.BrokerHostUsage; import com.yahoo.pulsar.common.naming.NamespaceName; import com.yahoo.pulsar.common.naming.ServiceUnitId; import com.yahoo.pulsar.common.policies.data.loadbalancer.SystemResourceUsage; /** * This class contains code which in shared between the two load manager implementations. */ public class LoadManagerShared { public static final Logger log = LoggerFactory.getLogger(LoadManagerShared.class); // Value of prefix "mibi" (e.g., number of bytes in a mibibyte). public static final int MIBI = 1024 * 1024; // Cache for primary brokers according to policies. private static final Set<String> primariesCache = new HashSet<>(); // Cache for shard brokers according to policies. private static final Set<String> sharedCache = new HashSet<>(); // Don't allow construction: static method namespace only. private LoadManagerShared() { } // Determines the brokers available for the given service unit according to the given policies. // The brokers are put into brokerCandidateCache. public static synchronized void applyPolicies(final ServiceUnitId serviceUnit, final SimpleResourceAllocationPolicies policies, final Set<String> brokerCandidateCache, final Set<String> availableBrokers) { primariesCache.clear(); sharedCache.clear(); NamespaceName namespace = serviceUnit.getNamespaceObject(); boolean isIsolationPoliciesPresent = policies.IsIsolationPoliciesPresent(namespace); if (isIsolationPoliciesPresent) { log.debug("Isolation Policies Present for namespace - [{}]", namespace.toString()); } for (final String broker : availableBrokers) { final String brokerUrlString = String.format("http://%s", broker); URL brokerUrl; try { brokerUrl = new URL(brokerUrlString); } catch (MalformedURLException e) { log.error("Unable to parse brokerUrl from ResourceUnitId - [{}]", e); continue; } // todo: in future check if the resource unit has resources to take // the namespace if (isIsolationPoliciesPresent) { // note: serviceUnitID is namespace name and ResourceID is // brokerName if (policies.isPrimaryBroker(namespace, brokerUrl.getHost())) { primariesCache.add(broker); if (log.isDebugEnabled()) { log.debug("Added Primary Broker - [{}] as possible Candidates for" + " namespace - [{}] with policies", brokerUrl.getHost(), namespace.toString()); } } else if (policies.isSharedBroker(brokerUrl.getHost())) { sharedCache.add(broker); if (log.isDebugEnabled()) { log.debug( "Added Shared Broker - [{}] as possible " + "Candidates for namespace - [{}] with policies", brokerUrl.getHost(), namespace.toString()); } } else { if (log.isDebugEnabled()) { log.debug("Skipping Broker - [{}] not primary broker and not shared" + " for namespace - [{}] ", brokerUrl.getHost(), namespace.toString()); } } } else { if (policies.isSharedBroker(brokerUrl.getHost())) { sharedCache.add(broker); log.debug("Added Shared Broker - [{}] as possible Candidates for namespace - [{}]", brokerUrl.getHost(), namespace.toString()); } } } if (isIsolationPoliciesPresent) { brokerCandidateCache.addAll(primariesCache); if (policies.shouldFailoverToSecondaries(namespace, primariesCache.size())) { log.debug( "Not enough of primaries [{}] available for namespace - [{}], " + "adding shared [{}] as possible candidate owners", primariesCache.size(), namespace.toString(), sharedCache.size()); brokerCandidateCache.addAll(sharedCache); } } else { log.debug( "Policies not present for namespace - [{}] so only " + "considering shared [{}] brokers for possible owner", namespace.toString(), sharedCache.size()); brokerCandidateCache.addAll(sharedCache); } } /** * Using the given bundles, populate the namespace to bundle range map. * * @param bundles * Bundles with which to populate. * @param target * Map to fill. */ public static void fillNamespaceToBundlesMap(final Set<String> bundles, final Map<String, Set<String>> target) { bundles.forEach(bundleName -> { final String namespaceName = getNamespaceNameFromBundleName(bundleName); final String bundleRange = getBundleRangeFromBundleName(bundleName); target.computeIfAbsent(namespaceName, k -> new HashSet<>()).add(bundleRange); }); } // From a full bundle name, extract the bundle range. public static String getBundleRangeFromBundleName(String bundleName) { // the bundle format is property/cluster/namespace/0x00000000_0xFFFFFFFF int pos = bundleName.lastIndexOf("/"); checkArgument(pos != -1); return bundleName.substring(pos + 1, bundleName.length()); } // From a full bundle name, extract the namespace name. public static String getNamespaceNameFromBundleName(String bundleName) { // the bundle format is property/cluster/namespace/0x00000000_0xFFFFFFFF int pos = bundleName.lastIndexOf('/'); checkArgument(pos != -1); return bundleName.substring(0, pos); } // Get the system resource usage for this broker. public static SystemResourceUsage getSystemResourceUsage(final BrokerHostUsage brokerHostUsage) throws IOException { SystemResourceUsage systemResourceUsage = brokerHostUsage.getBrokerHostUsage(); // Override System memory usage and limit with JVM heap usage and limit long maxHeapMemoryInBytes = Runtime.getRuntime().maxMemory(); long memoryUsageInBytes = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); systemResourceUsage.memory.usage = (double) memoryUsageInBytes / MIBI; systemResourceUsage.memory.limit = (double) maxHeapMemoryInBytes / MIBI; // Collect JVM direct memory systemResourceUsage.directMemory.usage = (double) (sun.misc.SharedSecrets.getJavaNioAccess() .getDirectBufferPool().getMemoryUsed() / MIBI); systemResourceUsage.directMemory.limit = (double) (sun.misc.VM.maxDirectMemory() / MIBI); return systemResourceUsage; } /** * If load balancing is enabled, load shedding is enabled by default unless forced off by setting a flag in global * zk /admin/flags/load-shedding-unload-disabled * * @return false by default, unload is allowed in load shedding true if zk flag is set, unload is disabled */ public static boolean isUnloadDisabledInLoadShedding(final PulsarService pulsar) { if (!pulsar.getConfiguration().isLoadBalancerEnabled()) { return true; } boolean unloadDisabledInLoadShedding = false; try { unloadDisabledInLoadShedding = pulsar.getGlobalZkCache() .exists(AdminResource.LOAD_SHEDDING_UNLOAD_DISABLED_FLAG_PATH); } catch (Exception e) { log.warn("Unable to fetch contents of [{}] from global zookeeper", AdminResource.LOAD_SHEDDING_UNLOAD_DISABLED_FLAG_PATH, e); } return unloadDisabledInLoadShedding; } /** * Removes the brokers which have more bundles assigned to them in the same namespace as the incoming bundle than at * least one other available broker from consideration. * * @param assignedBundleName * Name of bundle to be assigned. * @param candidates * Brokers available for placement. * @param brokerToNamespaceToBundleRange * Map from brokers to namespaces to bundle ranges. */ public static void removeMostServicingBrokersForNamespace(final String assignedBundleName, final Set<String> candidates, final Map<String, Map<String, Set<String>>> brokerToNamespaceToBundleRange) { if (candidates.isEmpty()) { return; } final String namespaceName = getNamespaceNameFromBundleName(assignedBundleName); int leastBundles = Integer.MAX_VALUE; for (final String broker : candidates) { if (brokerToNamespaceToBundleRange.containsKey(broker)) { final Set<String> bundleRanges = brokerToNamespaceToBundleRange.get(broker).get(namespaceName); if (bundleRanges == null) { // Assume that when the namespace is absent, there are no bundles for this namespace assigned to // that broker. leastBundles = 0; break; } leastBundles = Math.min(leastBundles, bundleRanges.size()); } else { // Assume non-present brokers have 0 bundles. leastBundles = 0; break; } } if (leastBundles == 0) { // By assumption, the namespace name will not be present if there are no bundles in the namespace // assigned to the broker. candidates.removeIf(broker -> brokerToNamespaceToBundleRange.containsKey(broker) && brokerToNamespaceToBundleRange.get(broker).containsKey(namespaceName)); } else { final int finalLeastBundles = leastBundles; // We may safely assume that each broker has at least one bundle for this namespace. // Note that this case is far less likely since it implies that there are at least as many bundles for this // namespace as brokers. candidates.removeIf(broker -> brokerToNamespaceToBundleRange.get(broker).get(namespaceName) .size() != finalLeastBundles); } } }