[rabbitmq-discuss] building chat

Alexis Richardson alexis.richardson at gmail.com
Mon Aug 10 09:26:58 BST 2009


Ben,

I've had a few more thoughts about your questions and wanted to
summarise them here:

My understanding is that you are curious about RabbitMQ and AMQP, so
you are interested in how chat would be implemented using them.
That's a great idea.

I think the best approach is if you hack up a quick prototype.  Doing
this will quickly show you what the main moving parts are, and how to
fit them together.  Because AMQP is designed for general purpose
messaging including queueing and pubsub, as opposed to 'single use
special cases' such as IM chat or MUC chat, it is abstract and
flexible.  This makes it a great tool for building systems but working
out how to build them in the best way, may not be immediately obvious.
 So getting your hands dirty with some immediate coding may be the
best way to make things more concrete.

You asked about using XMPP with RabbitMQ.  I would not recommend using
this initially.  It will not show you anything useful about how AMQP
works.  The implementation enables RabbitMQ AMQP messages to be
exposed through an XMPP IM interface, so that (eg) people can chat
with RabbitMQ, or with each other through RabbitMQ.  This means you
can use RabbitMQ as a 'drop in' pubsub server behind an XMPP IM
facade.  This is very useful.  However note that RabbitMQ's XMPP
adaptor is for vanilla IM only and does not implement XEP-0045 or
XEP-0060 or PEP.

Implicit in your question is that you'd be interested in whether AMQP
and RabbitMQ would be a good tool for implementing a chat system.  In
my view, the answer to that is yes: you should be able to build your
own very powerful chat system using RabbitMQ AMQP.  Moreover, RabbitMQ
is built with erlang which is also used in ejbabberd XMPP, in
Facebook's chat app, and afaik for parts of 37signals Campfire.

Basically, to build a chat system using RabbitMQ, including presence
updates, friend requests, etc, you need to think of every action in
your system as being some sort of message.  Then ask what is the best
routing model so that the right messages get delivered to the right
queues and on to the right consumers.

To illustrate my point about AMQP's flexibility, consider the following:

On the one hand, you may see IM as an abstraction of a network
connection (eg TCP) between two users via a chat server.  In this
view, the job of an IM client is to send messages that one user types,
over some network connection, to the other user, and vice versa.  If
the network connection goes down then the user may still see their
messages appearing on their own client GUI, but they may not know
which messages (if any) have been delivered to the chat server or
indeed to the other user.

So to implement this you could create a client that wrote all of its
users messages to the GUI screen, and concurrently attempted to send
them over the wire.  In an AMQP based implementation, these messages
would be AMQP messages sent to eg a direct exchange on a RabbitMQ
broker acting as the chat server.  The messages might have the routing
key "chat_ID_from_user_ID".  Then, the second user would have a queue,
which was bound to the exchange, with just that key.

In other words the second user's queue would be used ONLY to buffer
messages sent in chat chat_ID and from the first user to the second.
The second user would be the sole consumer from that queue and it
would contain only those messages from the first user in that chat,
that had been processed by the broker.  Then, to get messages back
from the second user to the first, the same exchange would be used,
but the second user would have their OWN queue and this would be bound
to the exchange with a different key, e.g.
"chat_ID_from_seconduser_ID".

In the above implementation:

* One direct exchange is used for everything
* There are two queues per chat
* Each queue contains only messages from one user to another
* The messages on any given queue may be a subset of what those
messages' producer created in his or her user GUI, because eg failure
might have prevented the arrival or processing of a message
* We have not mentioned lifecycle, but it is implicit that the chat
queues are bound to the session lifecycle, possibly with some margin
to allow for temporary disconnection and reconnection, handled by the
session (just like Google Talk seems to do it)

....

Now consider an alternatives implementation.

You may see IM as an interaction between two users on a shared piece
of data held eg. on a chat server.  In this view, the job of an IM
client is to send messages that one user types, over some network
connection, to the master of the shared data, and communicate updates
on that shared data to anyone who needs to see them.  If the network
connection goes down then, in this case, the user cannot complete the
operation of updating the shared data because it is remote, so that
*nothing* appears on their GUI screen except an error message.

So to implement this you could create a client that wrote all of its
users messages straight to AMQP messages sent to eg a direct exchange
on a RabbitMQ broker acting as the chat server.  The messages might
have the routing key "chat_ID".  BOTH chat users would each have a
queue, which was bound to the exchange, with that key.

Each user's queue would be used to buffer ALL messages in chat
chat_ID, ie. messages from both users would be in each queue.  The
users would not see the messages until they had been consumed by the
clients, which would publish all messages to the appropriate GUI
screen.

In the above implementation:

* One direct exchange is used for everything
* There are two queues per chat
* Each queue contains only the same messages - these are the messages
that the broker has processed from the users
* Both users' GUIs will show the same messages but not necessarily at
the same time
* Once again the queue and chat are implicitly of session duration

It's worth thinking about things like:

- which implementation is best - and why
- how one might do logging of the chat, in both cases
- how each case might be adapted to do MUC
- what happens if you want to change the lifecycle

Let me know what you think.

alexis























On Sun, Aug 9, 2009 at 10:25 PM, Alexis
Richardson<alexis.richardson at gmail.com> wrote:
> Ben,
>
>
> On Sat, Aug 8, 2009 at 2:28 PM, Ben Browitt<ben.browitt at gmail.com> wrote:
>>>
>>> Do you want to do MUC or IM?
>>
>> I'm not actually building it.
>> I keep hearing about AMQP and RabbitMQ so I read the spec and the server
>> docs.
>> I couldn't find use cases on the web so I thought I'll try to fit the
>> protocol to a fun application.
>
> Cool - understood.
>
>
>
>>> In fact, you can use a single approach to both.
>>>
>>> One approach is to have one queue per consumer 'chat window' and one
>>> exchange per 'chat room'.  The queue is bound to the exchange when the
>>> user is in the chatroom.  Then, IM is a special case of MUC - it is a
>>> chatroom with two participants.
>>>
>>> Another approach would be to have one queue per consumer, and have
>>> that queue bound to the exchange(s) N times, where N is the number of
>>> chatrooms (or IMs) which the consumer is currently in.  In this
>>> approach, you will need to identify which message came from which
>>> room, before you show the messages to the user.  That's easy but it's
>>> extra work and data to carry around.
>>
>> When a client sends a message to another client he usually doesn't create a
>> chatroom for two,
>> he just sends the message and the server routes it. With your suggestion the
>> server will create
>> the room if it doesn't exists when it needs to route a message.
>
> What I am suggesting is that the solution for IM can use queues.  In
> AMQP you can also think of queues as buffers with configurable
> properties including routing rules based on AMQP's "bindings" and QoS
> eg durability.  So, for an IM example you could have each user
> consuming a message stream of 'what the other user said' through that
> user's queue.  So, yes, when the queue doesn't exist, and is needed,
> it should be created.  It can be deleted later, or kept around -
> depending on what you want to achieve.
>
>
>
>
>> When all the users leaves a MUC room if it is not persistent the server
>> destroys it.
>
> You could set up an AMQP MUC system to do that if you liked.  Or, you
> could set it up to make the queues durable and therefore not destroyed
> in this case.
>
>
>
>
>> What will be the case with a chatroom with two participants?
>
> It's up to you.
>
> I would suggest deleting the queues used for the IM once the IM has completed.
>
> Separately, you might want to add the feature "being able to send
> messages when the other user is offline.  For this, you would want the
> queue lifecycle to be independent of the user's connections.
>
>
>
>> Maybe the server will destroy it when both users are offline?
>
> It comes down to how you set up your system.  AMQP is very flexible,
> eg allowing both the cases (a) destroy queues, (b) keep queues.
>
>
>> Is there a way to destroy an exchange if it is not active for several
>> minutes to save memory?
>
> You can destroy exchanges whenever you like.  How you monitor activity
> is up to you.
>
> There isn't a 'built in' functions for 'delete an unused exchange
> after time T'.  Some of the ideas Paul was talking about could be
> helpful here - using presence to manage internal broker lifecycle.
>
> Direct and fanout exchanges are 'lightweight' lookup tables so you
> might want to manage memory by monitoring overall memory use and user
> activity.
>
>
>> Can you use a standard XMPP client with this setup?
>
> You can use an XMPP client if you run RabbitMQ with the XMPP adaptor.
>
>
>
>>> Let me know if you want more detail on either of the above, eg which
>>> exchange type to use.
>>
>> I thought of a non durable exchange. One exchange per chatroom and one queue
>> per user.
>
> That seems like a good approach.  In this case you would use a
> 'fanout' exchange.
>
> Another approach would be to use just one 'direct' exchange and bind
> all user queues to it using the routing keys corresponding to the
> chatrooms the users are in.
>
> If you haven't yet seen it, the 'rabbits and warrens' intro article
> linked to here is good: http://www.rabbitmq.com/how.html
>
>
>
>>> > I read the xmpp gateway
>>> > docs and have some questions.
>>> >
>>> > In a chat we want to be able to get presence from users on our
>>> > friend-list,
>>> > send them messages
>>> > and send subscription request and messages to users not yet on our
>>> > friend-list.
>>>
>>> Do you only want to enable chat when both users are online and have a
>>> 'friend' relationship?
>>>
>>>
>>> > One possibility is that each user will have an exchange and a queue with
>>> > binding to his own exchange.
>>> > When user1 wants to send a message to user2 he send it directly to the
>>> > exchange of user2.
>>> > There is a problem with presence updates. If user1 changes his presence,
>>> > he'll have to sends the update
>>> > to the exchange of all the users on his friend-list. With this design we
>>> > move the logic to the client rather then use the AMQP features.
>>>
>>> One approach to presence is to broadcast changes using the pubsub
>>> system.  Changes could include:
>>>
>>> * User goes online / offline
>>> * User changes their 'status message'
>>
>>
>> All I know is that there are queues exchanges and bindings.
>> What do you mean by the pubsub system?
>
> By 'the pubsub system' I mean the RabbitMQ broker, and how you have
> configured the queues by binding them to the exchange(s).
>
> For example you can do 'pubsub' by binding queues to a direct
> exchange.  Take a look at slides 20-22 from the presentation linked to
> from this page:
> http://skillsmatter.com/podcast/erlang/alexis-richardson-introduction-to-rabbitmq
>  This shows how a queue can use one or more bindings to 'subscribe' to
> what producers ('publishers') are saying.
>
> So there is nothing stopping you using the same mechanism to publish
> presence information.  Suppose that whenever a user's presence
> changes, a message is sent to a direct exchange on the broker with key
> 'username' and content 'presence_update_info'.  Then, anyone who cares
> about that user's presence just has to bind a queue with the key
> 'username'.  I suggest you try and implement this pattern - it should
> be very straightforward.
>
>
>>> > Another option is that when a user logs in he'll create a binding to the
>>> > exchanges of all the users on his friend-list.
>>>
>>> You can do this but it creates quite a lot of churn on the exchange
>>> layer.  Paul's suggestion is one way to prevent that.  Another is to
>>> use exchange-exchange bindings, which we are also looking at.
>>
>> I meant to create bindings to all your online friends.
>
> Yes I understand that.
>
>
>> Can you explain how Paul's suggestion or exchange-exchange bindings solve or
>> improve it?
>
> Mostly they are performance optimisations that I would ignore for now,
> not least because they are under investigation and not implemented yet
> :-)
>
> When you create a binding, this action adds information to the routing
> tables (ie the exchange).  This carries a (very small) computational
> cost that you may not want to pay (if under heavy load, say).
> Additionally if you are running in a multi-machine cluster, RabbitMQ
> will copy the bindings to other machines, so that the routing table
> info is shared across the cluster.  This additional copying carries a
> (higher) computational cost.  To avoid paying this cost, you might
> want to optimise how much binding and unbinding takes place.  Using
> exchange-exchange bindings would mean, for example, that you could
> store N bindings persistently on one exchange, corresponding to a
> user's N friends, and have one binding between exchanges which gets
> destroyed when that user is offline, and recreated whenever they come
> online.  This would save you from having to make N bindings every time
> a user came online.
>
>
>
>
>>> > When user1 want to send a private message or a presence update to user2
>>> > he'll send it to his own exchange and it'll be directed to user2. The
>>> > problem with this design is that users without mutual presence
>>> > subscription
>>> > can't send private message to each other and can't send subscription
>>> > requests.
>>>
>>> You can decouple presence, which is about status updates, from
>>> friendship.  If you like, you could have a set up where one user can
>>> DM another, provided they are mutual friends, regardless of who is
>>> online.  Do you want that?
>>>
>>> Subscription requests could also be implemented using a queue - one
>>> per user.  Whenever a new user wishes to request mutual friendship
>>> with user A, then user A could be notified of this via a queue.
>>
>> In that case will the server send the friendship request directly to the
>> queue?
>
> Yes.
>
>> Do you need another queue per user just for friendship requests or can you
>> use the same queue from above?
>
> It's up to you, but I would have, for each user, a special queue for
> messages from people they are not following yet.
>
> Is this helping?
>
> alexis
>
>
>
>
>
>>> > Can you please share your thoughts on the two options and maybe other
>>> > design
>>> > options?
>>>
>>> I've tried to shed some light on some of the issues above.  Feel free
>>> to elaborate on your needs and ask more questions.
>>>
>>> alexis
>>>
>>>
>>>
>>>
>>>
>>> > Thanks
>>> > _______________________________________________
>>> > rabbitmq-discuss mailing list
>>> > rabbitmq-discuss at lists.rabbitmq.com
>>> > http://lists.rabbitmq.com/cgi-bin/mailman/listinfo/rabbitmq-discuss
>>> >
>>> >
>>
>>
>




More information about the rabbitmq-discuss mailing list