Skip to main content

Node address mapping

Redis Cluster and Sentinel advertise addresses that may be unreachable from outside Docker or Kubernetes. Use RespireOptions.NodeAddressMap to connect through published ports:

await using var client = await RespireClient.ConnectAsync(new RespireOptions
{
UseCluster = true,
Endpoints = [new("localhost", 7001)],
NodeAddressMap = new Dictionary<RespireEndpoint, RespireEndpoint>
{
["10.0.0.5:6379"] = "localhost:7001",
["10.0.0.6:6379"] = "localhost:7002",
["10.0.0.7:6379"] = "localhost:7003",
},
});

Mapping happens once per physical connection attempt, including reconnects. It covers Cluster topology, MOVED and ASK redirects, Sentinel primary, replica and peer discovery, explicit standalone replicas, subscriptions, blocking connections, and MOVING maintenance handoffs. Configured seeds also pass through the map. Unlisted endpoints connect unchanged. Unix sockets bypass mapping.

Respire keeps the original endpoint for routing, diagnostics, and credential providers. Connection hooks receive the mapped socket destination before credentials or transport creation. Map keys ignore hostname case and match ports exactly; include the hostnames or IP addresses your servers actually advertise. IPv6 strings use brackets, for example [fd00::5]:6379. Entries must contain valid TCP endpoints. The client copies the map when connecting, so later dictionary changes do not affect it.

Docker Compose​

Publish a different host port for each node in your Compose cluster:

services:
redis-1:
image: redis:7-alpine
ports: ["7001:6379"]
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-announce-hostname redis-1 --cluster-preferred-endpoint-type hostname
redis-2:
image: redis:7-alpine
ports: ["7002:6379"]
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-announce-hostname redis-2 --cluster-preferred-endpoint-type hostname
redis-3:
image: redis:7-alpine
ports: ["7003:6379"]
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf --cluster-announce-hostname redis-3 --cluster-preferred-endpoint-type hostname

After creating the cluster and assigning slots, map redis-1:6379 to localhost:7001, redis-2:6379 to localhost:7002, and redis-3:6379 to localhost:7003.

For Sentinel, configure SentinelPrimaryName and use Sentinel addresses as seeds. Map the primary and replica addresses returned by Sentinel to their published data ports. Map internal Sentinel peer addresses to their published Sentinel ports too.

Kubernetes​

Give each Redis node its own reachable destination. For local development, separate port forwards can expose StatefulSet pods:

kubectl port-forward pod/redis-0 7001:6379
kubectl port-forward pod/redis-1 7002:6379
kubectl port-forward pod/redis-2 7003:6379

Map each advertised pod hostname, for example redis-0.redis-headless.default.svc.cluster.local:6379, to its corresponding local port. A single service that randomly chooses nodes cannot preserve Cluster node identity. For production, use stable per-node routes reachable from the client.

Dynamic resolution​

Use NodeAddressResolver instead of NodeAddressMap for dynamic routes. The callback must be thread-safe, return a valid TCP endpoint, and return the input for unchanged endpoints. Callback failures fail that connection attempt. Resolution is synchronous; avoid blocking network calls inside the callback.

var options = new RespireOptions
{
Endpoints = [new("redis-0.internal", 6379)],
NodeAddressResolver = endpoint => endpoint.Host switch
{
"redis-0.internal" => new("localhost", 7001),
"redis-1.internal" => new("localhost", 7002),
_ => endpoint,
},
};

Do not configure both forms. Mapping is not recursive: the result is the socket destination, even if it also appears as another map key.

TLS hostname​

By default, TLS SNI and certificate hostname validation use the original hostname. Mapping redis.internal:6380 to localhost:7001 still validates a certificate for redis.internal. Set UseMappedHostForTls = true to use localhost instead. An explicit TlsOptions.TargetHost (or SentinelTlsOptions.TargetHost for Sentinel) takes precedence in either mode. Certificate validation retains the usual platform defaults.

Connection strings​

Both connection string forms accept nodeAddressMap. Separate entries with semicolons and separate each source from its target with =:

localhost:7001,cluster=true,nodeAddressMap=redis-1:6379=localhost:7001;redis-2:6379=localhost:7002

In a URI, percent-encode the map value:

redis://localhost:7001?cluster=true&nodeAddressMap=redis-1%3A6379%3Dlocalhost%3A7001%3Bredis-2%3A6379%3Dlocalhost%3A7002

Both forms also accept useMappedHostForTls=true. A callback requires RespireOptions.

Configuration registration​

AddRespire(IConfigurationSection) accepts the same semicolon-separated map as a scalar NodeAddressMap setting. NodeAddressResolver is configured in code only:

{
"Redis": {
"Endpoints": ["localhost:7001"],
"NodeAddressMap": "redis-1:6379=localhost:7001;redis-2:6379=localhost:7002",
"UseMappedHostForTls": false
}
}