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

org.graylog2.lookup.adapters.dnslookup.DnsResolverPool Maven / Gradle / Ivy

There is a newer version: 6.0.1
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.lookup.adapters.dnslookup;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.resolver.dns.DnsNameResolver;
import io.netty.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * Manages a pool of Netty {@link DnsNameResolverFactory} objects.
 *
 * 
* Since the source port for DNS resolution requests is fixed for the duration of the resolver lifecycle, * the pooling capability of this class allows the source address to by varied for each request by choosing * a random resolver from the pool for each request. * *
* The resolvers in the pool are periodically refreshed to cycle in new source ports for subsequent requests. * *
* The pool size and refresh interval are configurable globally for all DNS Lookup adapters with the following * Graylog server configuration properties (the defaults are indicated below as well). *
*
 * dns_lookup_adapter_resolver_pool_size = 10
 * dns_lookup_adapter_resolver_pool_refresh_interval = 300s
 * 
* *
* Callers can use the {@link #takeLease()} method to acquire a lease for a resolver. * The {@link ResolverLease#release()} method must be called to release a resolver lease after use. * These operations are thread-safe. */ public class DnsResolverPool { private static final Logger LOG = LoggerFactory.getLogger(DnsResolverPool.class); private final long poolSize; private final long poolRefreshSeconds; private final ScheduledExecutorService executorService; private final NioEventLoopGroup eventLoopGroup; private final DnsNameResolverFactory resolverFactory; // Pool is accessed by resolution requests and by refresh tasks. private final List resolverPool; protected DnsResolverPool(String dnsServerIps, long queryTimeout, long poolSize, long poolRefreshSeconds) { this.poolSize = poolSize; this.poolRefreshSeconds = poolRefreshSeconds; this.executorService = Executors.newSingleThreadScheduledExecutor( new ThreadFactoryBuilder().setNameFormat("dns-lookup-refresh-task-%d").build()); // A synchronized list is used to ensure thread safety for list mutations and accesses. this.resolverPool = Collections.synchronizedList(new ArrayList<>()); // Use a single Netty EventLoopGroup (1 thread by default) for all resolvers in the pool. // We are expecting DNS resolution requests for a specific pooler to be executed sequentially // (one after the other) amongst the pool of resolvers. So, a single thread should be sufficient. this.eventLoopGroup = new NioEventLoopGroup(); this.resolverFactory = new DnsNameResolverFactory(eventLoopGroup, dnsServerIps, queryTimeout); } protected void initialize() { for (int i = 0; i < poolSize; i++) { resolverPool.add(new ResolverLease(resolverFactory.create())); } executorService.scheduleAtFixedRate(new ResolverRefreshTask(), poolRefreshSeconds, poolRefreshSeconds, TimeUnit.SECONDS); } protected ResolverLease takeLease() { if (resolverPool.size() == 0) { throw new RuntimeException("Resolver pool is empty. Cannot return lease."); } final ResolverLease lease = resolverPool.get(randomResolverIndex()); lease.take(); return lease; } protected void returnLease(ResolverLease lease) { lease.release(); } public void stop() { LOG.debug("Attempting to stop pool."); executorService.shutdown(); if (resolverPool == null) { LOG.error("Resolver pool has not been initialized."); return; } synchronized (resolverPool) { final Iterator iterator = resolverPool.iterator(); while (iterator.hasNext()) { ResolverLease lease = iterator.next(); LOG.debug("Attempting to stop resolver [{}].", lease.getId()); if (lease.isLeased()) { LOG.warn("Attempting to stop a leased resolver..."); } lease.take(); lease.getResolver().close(); iterator.remove(); LOG.debug("Successfully stopped resolver [{}].", lease.getId()); } } // Shutdown event loop (required by Netty). final Future shutdownFuture = eventLoopGroup.shutdownGracefully(); shutdownFuture.addListener(future -> LOG.debug("Finished shutting down pool.")); LOG.debug("Resolver pool shutdown complete."); } protected boolean isStopped() { return (eventLoopGroup == null || eventLoopGroup.isShutdown()) && executorService.isShutdown(); } /** * Allows for a random resolver to be returned for each request. */ protected int randomResolverIndex() { // Use ThreadLocalRandom to ensure that different random numbers are returned when queried from different // threads referencing the same instance. return ThreadLocalRandom.current().nextInt(resolverPool.size()); } private class ResolverRefreshTask implements Runnable { @Override public void run() { LOG.debug("Starting resolver refresh."); LOG.debug("Existing IDs: [{}]", resolverPool.stream().map(ResolverLease::getId).collect(Collectors.joining(", "))); synchronized (resolverPool) { final ListIterator iterator = resolverPool.listIterator(); while (iterator.hasNext()) { ResolverLease lease = iterator.next(); if (!lease.getHasBeenLeased()) { LOG.debug("Resolver [{}] has not been leased yet. Skipping refresh.", lease.getId()); continue; } if (!lease.isLeased()) { lease.getResolver().close(); iterator.remove(); iterator.add(new ResolverLease(resolverFactory.create())); } else { LOG.warn("Lease for resolver [{}] is in-use. Skipping refresh. This will be attempted again in [{}] seconds. " + "If this happens frequently for high message rates, consider increasing the [dns_lookup_adapter_resolver_pool_size = 10] " + "server configuration property to allow more DNS resolvers.", lease.getId(), poolRefreshSeconds); } } } LOG.debug("Resolver IDs refreshed: [{}]", resolverPool.stream().map(ResolverLease::getId).collect(Collectors.joining(", "))); LOG.debug("Finished resolver refresh."); } } protected int poolSize() { return resolverPool != null ? resolverPool.size() : 0; } protected static class ResolverLease { private final String id; private final DnsNameResolver resolver; private AtomicInteger leaseCount; private AtomicBoolean hasBeenLeased; private ResolverLease(DnsNameResolver resolver) { this.id = UUID.randomUUID().toString(); this.resolver = resolver; this.leaseCount = new AtomicInteger(0); this.hasBeenLeased = new AtomicBoolean(); } private void take() { this.leaseCount.incrementAndGet(); this.hasBeenLeased.set(true); } private void release() { this.leaseCount.decrementAndGet(); } protected String getId() { return id; } private boolean isLeased() { return leaseCount.get() > 0; } private boolean getHasBeenLeased() { return hasBeenLeased.get(); } protected DnsNameResolver getResolver() { return resolver; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final ResolverLease that = (ResolverLease) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return Objects.hash(id); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy