I want to built a real time chat system for my project but actually I have some problems with Redis because I want my data stored as better as possible.
My problem:
I'd like to use Socket Io to do real time chatting in a closed group (of two people), but how to store messages?
Redis is a key value store and that means that if i want to store something i need to add an unique key to my data before getting stored.
If the same user posts more than one messages which keys would I use inside redis? I'm thinking about unique ids as unique keys but since I want to be able to fetch this comments when a user log the chat page, but if I do that I need to write another database that relate chat ids to the user that posted that message
Am I forgetting anything? Is there a best method to do this?
Sorry for my bad English.
Redis is more then key-value store.
So you want the following:
- chat messages,
- two-person discussions,
- you did not mention time constraints, so lets assume that you archive messages after a while,
- you also don't say if you want separate "threads" between two people, like forums or continuous messages, like facebook. I'm assuming continuous.
For each user, you have to store messages he sends. Let's say APP_NAMESPACE:MESSAGES:<USER_ID>:<MESSAGE_ID>
. We add userId here so that we can easily retreive all messages sent by a single user.
And, for each two users, you need to track their conversations. As a key, you can simply use their userids APP_NAMESPACE:CONVERSATIONS:<USER1_ID>-<USER2_ID>
. To make sure you always get the same, shared conversation for the two users, you can sort their ids alfabetically, so that users 132 and 145 will both have 132:145 as conversation key
So what to store in "conversations"? Let's use a list: [messageKey, messageKey, messageKey]
.
Ok, but what is now the messageKey? Combo of userId above and a messageId (so we can get the actual message).
So basically, you need two things:
- Store the message and give it an ID
- Store a reference to this message to the relevant conversation.
With node and standard redis/hiredis client this would be somehting like (I'll skip the obvious error etc checks, and I'll write ES6. If you cannot read ES6 yet, just paste it to babel):
// assuming the init connects to redis and exports a redisClient
import redisClient from './redis-init';
import uuid from `node-uuid`;
export function storeMessage(userId, toUserId, message) {
return new Promise(function(resolve, reject) {
// give it an id.
let messageId = uuid.v4(); // gets us a random uid.
let messageKey = `${userId}:${messageId}`;
let key = `MY_APP:MESSAGES:${messageKey}`;
client.hmset(key, [
"message", message,
"timestamp", new Date(),
"toUserId", toUserId
], function(err) {
if (err) { return reject(err); }
// Now we stored the message. But we also want to store a reference to the messageKey
let convoKey = `MY_APP:CONVERSATIONS:${userId}-${toUserId}`;
client.lpush(convoKey, messageKey, function(err) {
if (err) { return reject(err); }
return resolve();
});
});
});
}
// We also need to retreive the messages for the users.
export function getConversation(userId, otherUserId, page = 1, limit = 10) {
return new Promise(function(resolve, reject) {
let [userId1, userId2] = [userId, otherUserId].sort();
let convoKey = `MY_APP:CONVERSATIONS:${userId1}-${userId2}`;
// lets sort out paging stuff.
let start = (page - 1) * limit; // we're zero-based here.
let stop = page * limit - 1;
client.lrange(convoKey, start, stop, function(err, messageKeys) {
if (err) { return reject(err); }
// we have message keys, now get all messages.
let keys = messageKeys.map(key => `MY_APP:MESSAGES:${key}`);
let promises = keys.map(key => getMessage(key));
Promise.all(promises)
.then(function(messages) {
// now we have them. We can sort them too
return resolve(messages.sort((m1, m2) => m1.timestamp - m2.timestamp));
})
.catch(reject);
});
});
}
// we also need the getMessage here as a promise. We could also have used some Promisify implementation but hey.
export function getMessage(key) {
return new Promise(function(resolve, reject) {
client.hgetall(key, function(err, message) {
if (err) { return reject(err); }
resolve(message);
});
});
}
Now that's crude and untested, but that's the gist of how you can do this.
Is redis is a constraint in your project?
you can go through this http://autobahn.ws/python/wamp/programming.html