redis / node-redis

Redis Node.js client
https://redis.js.org/
MIT License
16.89k stars 1.88k forks source link

Redis create cluster issue #1782

Open ellik95 opened 2 years ago

ellik95 commented 2 years ago

Hello, I have a Redis MemoryDB cluster with 2 nodes in AWS and I'm trying to connect to it through createCluster, but the connection isn't established. Here is how I try to connect:

import { createCluster } from 'redis';
(async () => {
    const REDIS_USERNAME = 'test-username';
    const REDIS_PASSWORD = 'test-pass';
    const cluster = createCluster({
        rootNodes: [
            {
                url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
                username: REDIS_USERNAME,
                password: REDIS_PASSWORD,
            },
            {
                url: `rediss://redis-region-0001-002.amazonaws.com:6379`,
                username: REDIS_USERNAME,
                password: REDIS_PASSWORD,
            },
        ]
    });
    cluster.on('error', (err) => console.log('Redis Cluster Error', err));
    console.log('before connection');
    await cluster.connect();
    console.log('connected to cluster...');
    await cluster.set('key', 'value');
    const value = await cluster.get('key');
    console.log(value);
    await cluster.disconnect();
})();

The log connected to cluster... is never showed up. If I add in the configuration of createCluster to the defaults option the master node endpoint like this:

defaults: {
            url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }

The connection is established and everything works fine! Why do I need the defaults option? Shouldn't it work only with rootNodes? Thank you!

Environment:

leibale commented 2 years ago

As described in the clustering guide:

node-specific properties should be listed in rootNodes, the rest in defaults

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});
ellik95 commented 2 years ago

I had tried this also and the same thing happened! Could you please reopen the issue?

leibale commented 2 years ago

@ellik95 It didn't work even when the username & password are in the defaults object? can you please share your code?

ellik95 commented 2 years ago

Yes, with this code:

import { createCluster } from 'redis';
(async () => {
    const REDIS_USERNAME = 'test-username';
    const REDIS_PASSWORD = 'test-pass';
    const cluster = createCluster({
        rootNodes: [
            {
                url: `rediss://redis-region-0001-001.amazonaws.com:6379`
            },
            {
                url: `rediss://redis-region-0001-002.amazonaws.com:6379`
            },
        ],
       defaults: {
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }
    });
    cluster.on('error', (err) => console.log('Redis Cluster Error', err));
    console.log('before connection');
    await cluster.connect();
    console.log('connected to cluster...');
    await cluster.set('key', 'value');
    const value = await cluster.get('key');
    console.log(value);
    await cluster.disconnect();
})();

it is not connected. Only if I add the url of the master node to the defaults options works. Like this:

defaults: {
            url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }

And I was wondering why is this needed.

leibale commented 2 years ago
createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    tls: true,
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});
ellik95 commented 2 years ago

It's not working either and tls option does not exist in type Partial<RedisClusterClientOptions>.

leibale commented 2 years ago

@ellik95

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    socket: {
      tls: true
    },
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});
ellik95 commented 2 years ago

Unfortunately it is not working either. Only with the below code is:

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});
ellik95 commented 2 years ago

Also when I connect I sometimes get the error ReplyError: MOVED 12539 rediss://redis-region-0001-002.amazonaws.com:6379 and I cannot get the value from the key.

lohanbodevan commented 2 years ago

Same issue as @ellik95. I'm trying to connect with a Redis cluster running on Kubernetes and the connection is always ReplyError: MOVED xxxx In my case I have Load Balancer to connect with, so I'm using createClient rather than createCluster.

Connection through redis-client command line works fine, though. Is there any specific configuration we have to do with createClient when using Load Balanced redis cluster?

acb commented 2 years ago

I'm having the same issue with elasticache and the createCluster function, it just hangs indefinitely when you call cluster.connect() unless you provide a url entry in the defaults object.

However, when you do that it seems like it only connects to that node. If you try to access a key that gets sorted onto one of the other nodes you get the MOVED error.

@leibale I see that you mentioned this issue on https://github.com/redis/node-redis/pull/1827 but I'm not sure how that would apply to solve this issue. My server has access to the elasticache nodes just fine, it can connect to any of them individually with createClient but createCluster isn't able to connect unless you provide that default url.

leibale commented 2 years ago

@acb this line should fix it

acb commented 2 years ago

@leibale oh, awesome! Do you have any idea when that might be pushed out in a release?

leibale commented 2 years ago

@acb Sunday/Monday :)

acb commented 2 years ago

@leibale I just tried release 4.0.4 and unfortunately I'm still having the same problem. With the code

const client = createCluster({
    rootNodes: [
        {
            url: "rediss://redis-1-url:6379",
        },
        {
            url: "rediss://redis-2-url:6379",
        },
        {
            url: "rediss://redis-3-url:6379",
        },
    ],
    defaults: {
        password: "password123"
    }
});
client.on("error", (err: any) => {
    console.log("REDIS ERROR");
    console.log(err);
});

console.log('connecting to redis');
client.connect().then(() => {
    console.log('connected to redis');
});

I see 'connecting to redis' in my logs but it seems to hang and never actually connects. If I update my defaults to be

    defaults: {
        url: "rediss://redis-1-url:6379",
        password: "password123"
    }

Then in my logs I get 'connecting to redis' followed immediately by 'connected to redis', but the connection is only to the one redis server that's in the default. If I try to sample random keys 2/3rds of them return with something like MOVED 11388 redis-2-url.

leibale commented 2 years ago

@acb try this

const client = createCluster({
    rootNodes: [
        {
            url: "rediss://redis-1-url:6379",
        },
        {
            url: "rediss://redis-2-url:6379",
        },
        {
            url: "rediss://redis-3-url:6379",
        },
    ],
    defaults: {
        password: "password123",
        socket: { tls: true }
    }
});
acb commented 2 years ago

@leibale you're a wizard! That did it, I'm getting successful cluster connections now. I thought that having rediss as the protocol would handle the tls-ing but I guess cluster mode handles things a little differently.

Thanks so much for the help!

killdash9 commented 2 years ago

For me, I found both the :6379 and the extra s in rediss:// to be unnecessary. This worked:

const client = createCluster({
    rootNodes: [
        {
            url: "redis://redis-1-url",
        },
        {
            url: "redis://redis-2-url",
        },
        {
            url: "redis://redis-3-url",
        },
    ],
    defaults: {
        password: "password123",
        socket: { tls: true }
    }
});
nixRajan commented 2 years ago

I am still getting the Error MOVED 12218 ip:6379. Following is the code

import {createClient} from "redis"; // redis: ^4.0.1
const client = createClient({url: "redis://xyz.abc.clustercfg.use2.cache.amazonaws.com:6379"});
await client.connect();
console.log("client connected");
console.log(await client.ping());

I do get the output : "client connected" and "PONG". But when i do await client.get() or await client.set() i get the MOVED error.

nixRajan commented 2 years ago

This is to way to connect for above issue.

    const redis = require('redis');

    const client = redis.createCluster({
      rootNodes: [
        {
          url: `redis://${ConfigurationEndpoint}:${port}`,
        },
      ],
      useReplicas: true,
     });
nibblesnbits commented 1 year ago

Has this ever been addressed? I can't go to production with MemoryDB if this isn't fixed, and the approaches noted here result in an infinite loop of trying connection to localhost.

leibale commented 1 year ago

@nibblesnbits none of these worked for you? https://github.com/redis/node-redis/issues/1782#issuecomment-1048241231 https://github.com/redis/node-redis/issues/1782#issuecomment-1003704869

wanna share some code?

nibblesnbits commented 1 year ago

Well it looks like lack of sleep and attention to detail killed me. I missed that the call was createCluster, not createClient. 🤦‍♂️

But now I'm curious; is it more correct to use the cluster(s)' hostname, or the individual nodes? Seems logical that the latter is more correct.

timvi402 commented 7 months ago

For anyone trying to connect to a MemoryDb cluster using TLS, this has proven to be the solution:

    const redisClient = redis.createCluster({
        rootNodes: [{
            url: `redis://${redisHost}:6379`
        }],
        useReplicas: true,
        defaults: {
            socket: { tls: true }
        }
    });

Well it looks like lack of sleep and attention to detail killed me. I missed that the call was createCluster, not createClient. 🤦‍♂️

But now I'm curious; is it more correct to use the cluster(s)' hostname, or the individual nodes? Seems logical that the latter is more correct.

AFAIK you want to use the cluster hostname(cluster endpoint), especially for MemoryDb where you may add or delete nodes dynamically.

cb-rnag commented 6 months ago

For me, I found both the :6379 and the extra s in rediss:// to be unnecessary. This worked [...]

@killdash9 correct. Not sure what the implications are, but redis:// instead of rediss:// appears to work. Port seems unnecessary too.

With those changes in mind, my code now becomes:

import { createCluster } from "redis";

const socket = {
    tls: true,
    rejectUnauthorized: false, // Disable certificate validation
};

// Assuming you're using a Redis client that supports cluster mode and TLS
const redisClient = createCluster({
    rootNodes: [
        {
            url: "redis://<shard #1, node #1 endpoint>",
        },
        {
            url: "redis://<shard #2, node #1 endpoint>",
        },
        {
            url: "redis://<shard #3, node #1 endpoint>",
        },
    ],
    defaults: {
        password: "TODO",
        socket,
    },
});

// Handle errors
redisClient.on("error", (err) => {
    console.error("Redis Cluster Error", err);
});

// Connect to the cluster
await redisClient.connect().catch(console.error);

Example of <shard #2, node #1 endpoint>

example-0002-001.example.abc123.use1.cache.amazonaws.com