Round-robin DNS support in .NET HttpClient
Round-robin DNS is a load-balancing technique where the DNS returns multiple IP addresses for a host. The client can choose any of the IP addresses to connect to the server. For example, bing.com
has 2 IPv4 addresses:
In .NET, the HttpClient
connects to the server using the first IP address in the list. If the connection fails, the client will try the next IP address in the list. If the first IP address succeeds, all clients will use the same IP address, so this reduces the benefits of the round-robin DNS.
You can customize the HttpClient
to use a different IP address for each connection. SocketsHttpHandler.ConnectCallback
allows creating the connection manually for the HttpClient
.
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
var indexByHosts = new ConcurrentDictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var sockerHttpHandler = new SocketsHttpHandler()
{
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
PooledConnectionLifetime = TimeSpan.FromMinutes(1),
ConnectCallback = async (context, cancellationToken) =>
{
// Get the list of IP addresses for the host
// note: AddressFamily.Unspecified: IPv4 or IPv6
var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, AddressFamily.Unspecified, cancellationToken);
IPAddress[] addresses;
if (entry.AddressList.Length == 1) // No need to handle round-robin as there is only 1 address
{
addresses = entry.AddressList;
}
else
{
// Compute the first IP address to connect to
var index = indexByHosts.AddOrUpdate(
key: entry.HostName,
addValue: Random.Shared.Next(),
updateValueFactory: (host, existingValue) => existingValue + 1);
index %= entry.AddressList.Length;
if (index == 0)
{
// no need to change the addresses
addresses = entry.AddressList;
}
else
{
// Rotate the list of addresses
addresses = new IPAddress[entry.AddressList.Length];
entry.AddressList.AsSpan(index).CopyTo(addresses);
entry.AddressList.AsSpan(0, index).CopyTo(addresses.AsSpan(index));
}
}
// Connect to the remote host
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp)
{
// Turn off Nagle's algorithm since it degrades performance in most HttpClient scenarios.
NoDelay = true
};
try
{
await socket.ConnectAsync(addresses, context.DnsEndPoint.Port, cancellationToken);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
};
var httpClient = new HttpClient(sockerHttpHandler, disposeHandler: true);
await httpClient.GetStringAsync("https://www.bing.com");
#Additional resources
Do you have a question or a suggestion about this post? Contact me!