A single queue.
curb -cap 10MiB -addr :61613
rdy
- message is queuedwip
- message has been deliveredack
- message successfully processedstateDiagram-v2
[*] --> rdy: SEND
rdy --> wip: MESSAGE
wip --> rdy: NACK/DISCONNECT
wip --> ack: ACK
ack --> [*]
002 ack second
003 rdy third
004 wip forth
005 rdy forth
This file can never exceed the given -cap
number of bytes.
Messages in state ack
are removed as soon as possible.
STOMP
accept-version:1.2
host:whatever
^@
host
is ignored.
CONNECTED
version:1.2
server:curb/idea.1.0 STOMP 1.2 subset
^@
The server only speaks 1.2
as of now.
SUBSCRIBE
id:0
destination:.
ack:client-individual
^@
destination
is ignored, there is only one destination.
Note for implementers:
It might be useful to allow clients to subscribe to the message count
i.e. queue length.
E.g. destination:./count
.
Only ack:client-individual
is supported, for now.
SEND
destination:.
hello or whatever
^@
destination
is ignored, there is only one destination.
MESSAGE
subscription:0
message-id:00001234
ack:00001234
destination:.
hello or whatever
^@
Notable difference to actual STOMP
servers:
MESSAGE
frames will NOT include user defined headers that were present when the message was sent.
They are discarded.
It may or may not contain the content-length
header.
NACK
id:00001234
^@
MESSAGE
subscription:0
message-id:00001234
ack:00001234
destination:.
hello or whatever
^@
Nacked messages are redelivered.
ACK
id:00001234
^@
UNSUBSCRIBE
id:0
^@
UNSUBSCRIBE
is supported to pause receiving while keeping the connection open.
Subscribing consecutively without unsubscribing will cause an error.
DISCONNECT
receipt:77
^@
RECEIPT
receipt-id:77
^@
The receipt
headers and RECEIPT
frames are supported as in STOMP 1.2.
BEGIN
,
COMMIT
,
ABORT
,
are not supported, cause errors.
I am unsure whether they should be supported in the future as their implementation introduces quite a bit of complexity.
Curb does not have many features. It prioritizes simplicity.
Some features could be implemented in other layers.
A Stomp routing layer can be used to add destinations to Curb.
Each destination needs a Curb instance.
architecture-beta
service client(unknown)[STOMP Client]
group arch(server)[Curb Destinations]
service router(internet)[STOMP Router] in arch
service curb1(database)[Curb 1] in arch
service curb2(database)[Curb 2] in arch
service curbN(database)[Curb N] in arch
router:T -- L:curb1
router:R -- L:curb2
router:B -- L:curbN
client:R -- L:router
If the router receives a SEND destination:./1
from a client,
it forwards it to the respective Curb instance.
SUBSCRIBE destination:./1
is forwarded to "Curb 1" as
SUBSCRIBE destination:.
(if the router is not already subscribed).
MESSAGE
frames from instances are forwarded to one of the clients that
subscribed to the destination.
If all subscribers of a destination are busy or unsubscribe, the router unsubscribes from the respective Curb instance.
MESSAGE
frames that cannot be forwarded to clients are nacked.
(If ACK
/NACK
frames would include a destination
header,
routers could be more stateless.
This could also be achieved,
if the router adds destination info to the message id.)
By buffering SEND
frames until a corresponding COMMIT
frame happens
architecture-beta
service client(unknown)[STOMP Client]
group arch(server)[Tx Curb]
service buffer(database)[Curb Tx Buffer] in arch
service curb(database)[Curb 1] in arch
buffer:R -- L:curb
client:R -- L:buffer
Note that this particular pattern is not very compatible with other patterns. E.g. cross instance transactions need more complicated patterns and might not be possible with Curb at all. (And might not be possible in any system, physically?)
Similar to destinations, partitioning one queue into multiple Curb instances can be supported when adding a load balancer instead of a router.
architecture-beta
service client(unknown)[STOMP Client]
group arch(server)[Partitioned Curb]
service balancer(internet)[STOMP Load Balancer] in arch
service curb1(database)[Curb 1] in arch
service curbN(database)[Curb N] in arch
balancer:T -- L:curb1
balancer:R -- L:curbN
client:R -- L:balancer
Yet another similar pattern could be applied for message replication.
Though, how to deal with the situation (that should in theory never occur) when a MESSAGE
frame is only received from one of the instances is unclear.
Maybe another feature in Curb is needed:
Acking/deleting messages that have never been delivered,
so that the replicator need not subscribe to all but one instance,
while still being able to ack their messages.
Depending on why replication is desired, different strategies could be applied.
Or maybe replication should be achieved by using an additional database.
Or by using an underlying replicated file system.
Or maybe Curb is not the right fit, if you need certain types of replication.
All messages are written to the file system. Curb aims to have reasonable, derived properties depending on the properties of the underlying file system.
You can choose file systems for Curb instances depending on your use case.
Creating new queues implies spinning up additional Curb instances. Some operator service can take care of that.
Keep in mind that most storage devices have limited capacity.