When using Masstransit with RabbitMQ I see a disappointing performance. Deliver/get drops to 0.20/sec. I started to investigate this problem with a simple test application. This application runs in parallel a thread sending messages to RabbitMQ using the RabbitMQ client library and a thread using the Masstransit library sending the same message to RabbitMQ. The RabbitMQ client library can send 10 times more message than the Masstransit library.
RabbitMQ is running in a Docker container in a HyperV machine.
The PublishConfirm flag of Masstransit has some effect but not that much.
In order to get a fair comparison a defined the same topology for the RabbitMQ case as is used in the Masstransit case.
Masstransit code:
public MasstransitMessageSender(string user, string password, string rabbitMqHost)
{
this.user = user;
this.password = password;
this.rabbitMqHost = rabbitMqHost;
}
public RabbitMqMessageSender(string user, string password, string rabbitMqHost)
{
var myUri = new Uri(rabbitMqHost);
this.factory = new ConnectionFactory
{
HostName = myUri.Host,
UserName = user,
Password = password,
VirtualHost = myUri.LocalPath,
Port = (myUri.Port > 0 ? myUri.Port : -1),
AutomaticRecoveryEnabled = true
};
}
public Task SendCommands(int numberOfMessages)
{
return Task.Run(() =>
{
var busControl = global::MassTransit.Bus.Factory.CreateUsingRabbitMq(
sbc =>
{
// Host control
var host =
sbc.Host(
new Uri(this.rabbitMqHost),
h =>
{
h.Username(this.user);
h.Password(this.password);
h.Heartbeat(60);
h.PublisherConfirmation = false;
});
});
busControl.StartAsync().Wait();
var task = busControl.GetSendEndpoint(new Uri(this.rabbitMqHost + "/MasstransitService"));
var tasks = new List<Task>();
task.Wait();
var endpoint = task.Result;
for (var i = 0; i < numberOfMessages; i++)
{
tasks.Add(endpoint.Send<IMyMessage>(new
{
Number = i,
Description = "MyMessage"
}));
}
Task.WaitAll(tasks.ToArray());
});
}
RabbitMQ code:
public RabbitMqMessageSender(string user, string password, string rabbitMqHost)
{
var myUri = new Uri(rabbitMqHost);
this.factory = new ConnectionFactory
{
HostName = myUri.Host,
UserName = user,
Password = password,
VirtualHost = myUri.LocalPath,
Port = (myUri.Port > 0 ? myUri.Port : -1),
AutomaticRecoveryEnabled = true
};
}
public Task SendCommands(int numberOfMessages)
{
return Task.Run(() =>
{
using (var connection = this.factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
var messageProperties = channel.CreateBasicProperties();
channel.ExchangeDeclare("PerformanceConsole:ShowRabbitMqMessage", "fanout", true, false, null);
channel.ExchangeDeclare("RabbitMqService", "fanout", true, false, null);
channel.QueueDeclare("RabbitMqService", true, false, false, null);
channel.ExchangeBind("RabbitMqService", "PerformanceConsole:ShowRabbitMqMessage", "", null);
channel.QueueBind("RabbitMqService", "RabbitMqService", "", null);
for (var i = 0; i < numberOfMessages; i++)
{
var bericht = new
{
Volgnummer = 1,
Tekst = "Bericht"
};
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(bericht));
channel.BasicPublish("RabbitMqService", "", messageProperties, body);
}
}
}
});
}
The RabbitMQ dashboard shows the following rates:
Masstransit incoming 664 msg/s
RabbitMQ incoming 6764 msg/s
I expected to incoming rates to be in the same range.
Maybe I made a mistake in the configuration, suggestions are welcome.
Using the MassTransit-Benchmark I get the following performance on my MacBook Pro 2015, using netcoreapp2.2 on OS X.
PhatBoyG-Pro15:MassTransit-Benchmark Chris$ dotnet run -f netcoreapp2.2 -- --clients=50 --count=50000 --prefetch=100
MassTransit Benchmark
Transport: RabbitMQ
Host: localhost
Virtual Host: /
Username: guest
Password: *****
Heartbeat: 0
Publisher Confirmation: False
Running Message Latency Benchmark
Message Count: 50000
Clients: 50
Durable: False
Payload Length: 0
Prefetch Count: 100
Concurrency Limit: 0
Total send duration: 0:00:05.2250045
Send message rate: 9569.37 (msg/s)
Total consume duration: 0:00:07.2385114
Consume message rate: 6907.50 (msg/s)
Concurrent Consumer Count: 8
Avg Ack Time: 4ms
Min Ack Time: 0ms
Max Ack Time: 251ms
Med Ack Time: 4ms
95t Ack Time: 6ms
Avg Consume Time: 1431ms
Min Consume Time: 268ms
Max Consume Time: 2075ms
Med Consume Time: 1639ms
95t Consume Time: 2070ms
You can download the benchmark and run it yourself:
https://github.com/MassTransit/MassTransit-Benchmark
Related
I'm sending a message to "config" topic from my Spring boot backend application
Here's my mqtt setup
final String mqttServerAddress =
String.format("ssl://%s:%s", options.mqttBridgeHostname, options.mqttBridgePort);
// Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
// Google Cloud IoT Core, it must be in the format below.
final String mqttClientId =
String.format(
"projects/%s/locations/%s/registries/%s/devices/%s",
options.projectId, options.cloudRegion, options.registryId, options.deviceId);
MqttConnectOptions connectOptions = new MqttConnectOptions();
// Note that the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
// explictly set this. If you don't set MQTT version, the server will immediately close its
// connection to your device.
connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
Properties sslProps = new Properties();
sslProps.setProperty("com.ibm.ssl.protocol", "TLSv1.2");
connectOptions.setSSLProperties(sslProps);
// With Google Cloud IoT Core, the username field is ignored, however it must be set for the
// Paho client library to send the password field. The password field is used to transmit a JWT
// to authorize the device.
connectOptions.setUserName(options.userName);
DateTime iat = new DateTime();
if ("RS256".equals(options.algorithm)) {
connectOptions.setPassword(
createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
} else if ("ES256".equals(options.algorithm)) {
connectOptions.setPassword(
createJwtEs(options.projectId, options.privateKeyFileEC).toCharArray());
} else {
throw new IllegalArgumentException(
"Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
}
// [START iot_mqtt_publish]
// Create a client, and connect to the Google MQTT bridge.
MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
// Both connect and publish operations may fail. If they do, allow retries but with an
// exponential backoff time period.
long initialConnectIntervalMillis = 500L;
long maxConnectIntervalMillis = 6000L;
long maxConnectRetryTimeElapsedMillis = 900000L;
float intervalMultiplier = 1.5f;
long retryIntervalMs = initialConnectIntervalMillis;
long totalRetryTimeMs = 0;
while ((totalRetryTimeMs < maxConnectRetryTimeElapsedMillis) && !client.isConnected()) {
try {
client.connect(connectOptions);
} catch (MqttException e) {
int reason = e.getReasonCode();
// If the connection is lost or if the server cannot be connected, allow retries, but with
// exponential backoff.
System.out.println("An error occurred: " + e.getMessage());
if (reason == MqttException.REASON_CODE_CONNECTION_LOST
|| reason == MqttException.REASON_CODE_SERVER_CONNECT_ERROR) {
System.out.println("Retrying in " + retryIntervalMs / 1000.0 + " seconds.");
Thread.sleep(retryIntervalMs);
totalRetryTimeMs += retryIntervalMs;
retryIntervalMs *= intervalMultiplier;
if (retryIntervalMs > maxConnectIntervalMillis) {
retryIntervalMs = maxConnectIntervalMillis;
}
} else {
throw e;
}
}
}
attachCallback(client, options.deviceId);
// The MQTT topic that this device will publish telemetry data to. The MQTT topic name is
// required to be in the format below. Note that this is not the same as the device registry's
// Cloud Pub/Sub topic.
String mqttTopic = String.format("/devices/%s/%s", options.deviceId, options.messageType);
long secsSinceRefresh = ((new DateTime()).getMillis() - iat.getMillis()) / 1000;
if (secsSinceRefresh > (options.tokenExpMins * MINUTES_PER_HOUR)) {
System.out.format("\tRefreshing token after: %d seconds%n", secsSinceRefresh);
iat = new DateTime();
if ("RS256".equals(options.algorithm)) {
connectOptions.setPassword(
createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
} else if ("ES256".equals(options.algorithm)) {
connectOptions.setPassword(
createJwtEs(options.projectId, options.privateKeyFileEC).toCharArray());
} else {
throw new IllegalArgumentException(
"Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
}
client.disconnect();
client.connect(connectOptions);
attachCallback(client, options.deviceId);
}
MqttMessage message = new MqttMessage(data.getBytes(StandardCharsets.UTF_8.name()));
message.setQos(1);
client.publish(mqttTopic, message);
here's the options class
public class MqttExampleOptions {
String mqttBridgeHostname = "mqtt.googleapis.com";
short mqttBridgePort = 8883;
String projectId =
String cloudRegion = "europe-west1";
String userName = "unused";
String registryId = <I don't want to show>
String gatewayId = <I don't want to show>
String algorithm = "RS256";
String command = "demo";
String deviceId = <I don't want to show>
String privateKeyFile = "rsa_private_pkcs8";
String privateKeyFileEC = "ec_private_pkcs8";
int numMessages = 100;
int tokenExpMins = 20;
String telemetryData = "Specify with -telemetry_data";
String messageType = "config";
int waitTime = 120
}
When I try to publish message to topic "config" I get this error
ERROR 12556 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service()
for servlet [dispatcherServlet] in context with path [/iot-admin] threw exception [Request processing failed; nested exception is Connection Lost (32109) - java.io.EOFException] with root cause
java.io.EOFException: null
at java.base/java.io.DataInputStream.readByte(DataInputStream.java:273) ~[na:na]
at org.eclipse.paho.client.mqttv3.internal.wire.MqttInputStream.readMqttWireMessage(MqttInputStream.java:92) ~[org.eclipse.paho.client.mqttv3-1.2.5.jar:na]
at org.eclipse.paho.client.mqttv3.internal.CommsReceiver.run(CommsReceiver.java:137) ~[org.eclipse.paho.client.mqttv3-1.2.5.jar:na]
this is the message I am sending
{
"Led": {
"id": "e36b5877-2579-4db1-b595-0e06410bde11",
"rgbColors": [{
"id": "1488acfe-baa7-4de8-b4a2-4e01b9f89fc5",
"configName": "Terminal",
"rgbColor": [150, 150, 150]
}, {
"id": "b8ce6a35-4219-4dba-a8de-a9070f17f1d2",
"configName": "PayZone",
"rgbColor": [150, 150, 150]
}, {
"id": "bf62cef4-8e22-4804-a7d8-a0996bef392e",
"configName": "PayfreeLogo",
"rgbColor": [150, 150, 150]
}, {
"id": "c62d25a4-678b-4833-9123-fe3836863400",
"configName": "BagDetection",
"rgbColor": [200, 200, 200]
}, {
"id": "e19e1ff3-327e-4132-9661-073f853cf913",
"configName": "PersonDetection",
"rgbColor": [150, 150, 150]
}]
}
}
How can I properly send a message to a config topic without getting this error? I am able to send message to "state" topic, but not to "config" topic.
Here is the client:
string magicX = "123456789009876543211234567890";
int request[];
ArrayResize(request,StringLen(magicX));
char charArray[];
StringToCharArray(magicX,charArray,0,StringLen(magicX));
for(i = 0; i < ArraySize(charArray); i++)
{
request[i] = (int)charArray[i];
}
int arrSize = ArraySize(request);
if(!HttpSendRequestW(hReq, "", 0, request, arrSize))
{
InternetCloseHandle(hReq);
InternetCloseHandle(hConn);
InternetCloseHandle(hIntrn);
return (false);
}
Here is the server (Dart):
HttpServer server = await HttpServer.bind
InternetAddress.anyIPv4,
webServerPort,
);
await for (HttpRequest request in server) {
aLog.i('Server receive a ${request.headers.contentLength.toString()} header');
String aString = await utf8.decodeStream(request.asBroadcastStream());
print('Server receive aString: ${aString}');
aLog.i('Server receive url: ${request.uri}');
}
Here is the result:
┌───────────────────────────────────────────────────────────────────────────────
│ Server receive a 30 header
└───────────────────────────────────────────────────────────────────────────────
Server receive aString: 12345678
┌───────────────────────────────────────────────────────────────────────────────
│ Server receive url: /HelloWorld
└───────────────────────────────────────────────────────────────────────────────
As you can see the length is match, but the actual result is incorrect, any ideas? Thanks.
More info:
I use postman to post a long text to server as follow:
The Dart server can read all the text, but the HttpSendRequestW can't. Thanks.
This qeuestion is on consuming the messages using AMQP in .Net. The documentation recommends amqpnetlite: https://access.redhat.com/documentation/en-us/red_hat_amq/7.0/html-single/using_the_amq_.net_client/index
On subscribing to an address using AMQPNetLite, the address and the queue will be auto-created. The auto-created queue is always "unicast" though. I have not been able to auto-create
a multicast queue
that allowed any number of consumers.
Code:
private async Task RenewSession()
{
Connect = await Connection.Factory.CreateAsync(new Address("amqp://admin:admin#localhost:5672"), new Open() {ContainerId = "client-1"});
MqSession = new Session(Connect);
var receiver = new ReceiverLink(MqSession, DEFAULT_SUBSCRIPTION_NAME, GetSource("test-topic"), null);
receiver.Start(100, OnMessage);
}
private Source GetSource(string address)
{
var source = new Source
{
Address = address,
ExpiryPolicy = new Symbol("never"),
Durable = 2,
DefaultOutcome = new Modified
{
DeliveryFailed = true,
UndeliverableHere = false
}
};
return source;
}
Maybe I am missing some flags?
in AMQP, you choose between autocreating a queue (anycast routing) or a topic (multicast routing) by setting a capability.
The capability should be either new Symbol("queue") or new Symbol("topic").
public class SimpleAmqpTest
{
[Fact]
public async Task TestHelloWorld()
{
Address address = new Address("amqp://guest:guest#localhost:5672");
Connection connection = await Connection.Factory.CreateAsync(address);
Session session = new Session(connection);
Message message = new Message("Hello AMQP");
Target target = new Target
{
Address = "q1",
Capabilities = new Symbol[] { new Symbol("queue") }
};
SenderLink sender = new SenderLink(session, "sender-link", target, null);
await sender.SendAsync(message);
Source source = new Source
{
Address = "q1",
Capabilities = new Symbol[] { new Symbol("queue") }
};
ReceiverLink receiver = new ReceiverLink(session, "receiver-link", source, null);
message = await receiver.ReceiveAsync();
receiver.Accept(message);
await sender.CloseAsync();
await receiver.CloseAsync();
await session.CloseAsync();
await connection.CloseAsync();
}
}
Have a look at https://github.com/Azure/amqpnetlite/issues/286, where the code comes from.
You can choose whether the default routing will be multicast or anycast by setting default-address-routing-type in broker.xml, everything documented at https://activemq.apache.org/artemis/docs/2.6.0/address-model.html
The broker's multicastPrefix and anycastPrefix feature is not implemented for AMQP. https://issues.jboss.org/browse/ENTMQBR-795
In out cluster we have five nodes composite of:
2 seed nodes (backend)
1 worker
2 webapi on IIS
The cluster is joined, up and running; but the second IIS when perform the first message to the cluster via router make all cluster unreachable and dissociated.
In addition the second IIS can't deliver any message.
Here is my IIS config:
<hocon>
<![CDATA[
akka.loglevel = INFO
akka.log-config-on-start = off
akka.stdout-loglevel = INFO
akka.actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
deployment {
/Process {
router = round-robin-group
routees.paths = ["/user/Process"] # path of routee on each node
# nr-of-instances = 3 # max number of total routees
cluster {
enabled = on
allow-local-routees = off
use-role = Process
}
}
}
debug {
receive = on
autoreceive = on
lifecycle = on
event-stream = on
unhandled = on
}
}
akka.remote {
helios.tcp {
# transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
# applied-adapters = []
# transport-protocol = tcp
port = 0
hostname = 172.16.1.8
}
log-remote-lifecyclo-events = DEBUG
}
akka.cluster {
seed-nodes = [
"akka.tcp://ClusterActorSystem#172.16.1.8:2551",
"akka.tcp://ClusterActorSystem#172.16.1.8:2552"
]
roles = [Send]
auto-down-unreachable-after = 10s
# how often should the node send out gossip information?
gossip-interval = 1s
# discard incoming gossip messages if not handled within this duration
gossip-time-to-live = 2s
}
# http://getakka.net/docs/persistence/at-least-once-delivery
akka.persistence.at-least-once-delivery.redeliver-interval = 300s
# akka.persistence.at-least-once-delivery.redelivery-burst-limit =
# akka.persistence.at-least-once-delivery.warn-after-number-of-unconfirmed-attempts =
akka.persistence.at-least-once-delivery.max-unconfirmed-messages = 1000000
akka.persistence.journal.plugin = "akka.persistence.journal.sql-server"
akka.persistence.journal.publish-plugin-commands = on
akka.persistence.journal.sql-server {
class = "Akka.Persistence.SqlServer.Journal.SqlServerJournal, Akka.Persistence.SqlServer"
plugin-dispatcher = "akka.actor.default-dispatcher"
table-name = EventJournal
schema-name = dbo
auto-initialize = on
connection-string-name = "HubAkkaPersistence"
refresh-interval = 1s
connection-timeout = 30s
timestamp-provider = "Akka.Persistence.Sql.Common.Journal.DefaultTimestampProvider, Akka.Persistence.Sql.Common"
metadata-table-name = Metadata
}
akka.persistence.snapshot-store.plugin = ""akka.persistence.snapshot-store.sql-server""
akka.persistence.snapshot-store.sql-server {
class = "Akka.Persistence.SqlServer.Snapshot.SqlServerSnapshotStore, Akka.Persistence.SqlServer"
plugin-dispatcher = ""akka.actor.default-dispatcher""
connection-string-name = "HubAkkaPersistence"
schema-name = dbo
table-name = SnapshotStore
auto-initialize = on
}
]]>
</hocon>
inside the global.asax we create a new router to the cluster:
ClusterActorSystem = ActorSystem.Create("ClusterActorSystem");
var backendRouter =
ClusterActorSystem.ActorOf(
Props.Empty.WithRouter(FromConfig.Instance), "Process");
Send = SistemiHubClusterActorSystem.ActorOf(
Props.Create(() => new Common.Actors.Send(backendRouter)),
"Send");
and here is our backend config:
<hocon><![CDATA[
akka.loglevel = INFO
akka.log-config-on-start = on
akka.stdout-loglevel = INFO
akka.actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
debug {
receive = on
autoreceive = on
lifecycle = on
event-stream = on
unhandled = on
}
}
akka.remote {
helios.tcp {
# transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
# applied-adapters = []
# transport-protocol = tcp
#
# seed-node ports 2551 and 2552
# non-seed-node port 0
port = 2551
hostname = 172.16.1.8
}
log-remote-lifecyclo-events = INFO
}
akka.cluster {
seed-nodes = [
"akka.tcp://ClusterActorSystem#172.16.1.8:2551",
"akka.tcp://ClusterActorSystem#172.16.1.8:2552"
]
roles = [Process]
auto-down-unreachable-after = 10s
}
]]></hocon>
The issue in present using Akka 1.1 and Akka 1.2
UPDATE
I have found that the issue is related to our LoadBalancer (NetScaler) if I call each IIS directly is working fine. If called by the balancer I face the reported issue; the balancer is trasparent (it only add some headers to the request). What can I check to solve this issue?
Finally I found the problem, we are using akka.persistence that requires a specific value declination for the PersistenceId for each IIS.
I'm trying to create an rpc program to communicate hosts located on different networks and chose Router-Dealer configuration of NetMQ provided here: http://netmq.readthedocs.io/en/latest/router-dealer/#router-dealer
But the problem is that router always selects a random dealer when routing a message to backend.
Code which I used :
using (var frontend = new RouterSocket(string.Format("#tcp://{0}:{1}", "127.0.0.1", "5556")))//"#tcp://10.0.2.218:5559"
using (var backend = new DealerSocket(string.Format("#tcp://{0}:{1}", "127.0.0.1", "5557")))//"#tcp://10.0.2.218:5560"
{
// Handler for messages coming in to the frontend
frontend.ReceiveReady += (s, e) =>
{
Console.WriteLine("message arrived on frontEnd");
NetMQMessage msg = e.Socket.ReceiveMultipartMessage();
string clientAddress = msg[0].ConvertToString();
Console.WriteLine("Sending to :" + clientAddress);
//TODO: Make routing here
backend.SendMultipartMessage(msg); // Relay this message to the backend };
// Handler for messages coming in to the backend
backend.ReceiveReady += (s, e) =>
{
Console.WriteLine("message arrived on backend");
var msg = e.Socket.ReceiveMultipartMessage();
frontend.SendMultipartMessage(msg); // Relay this message to the frontend
};
using (var poller = new NetMQPoller { backend, frontend })
{
// Listen out for events on both sockets and raise events when messages come in
poller.Run();
}
}
Code for Client:
using (var client = new RequestSocket(">tcp://" + "127.0.0.1" + ":5556"))
{
var messageBytes = UTF8Encoding.UTF8.GetBytes("Hello");
var messageToServer = new NetMQMessage();
//messageToServer.AppendEmptyFrame();
messageToServer.Append("Server2");
messageToServer.Append(messageBytes);
WriteToConsoleVoid("======================================");
WriteToConsoleVoid(" OUTGOING MESSAGE TO SERVER ");
WriteToConsoleVoid("======================================");
//PrintFrames("Client Sending", messageToServer);
client.SendMultipartMessage(messageToServer);
NetMQMessage serverMessage = client.ReceiveMultipartMessage();
WriteToConsoleVoid("======================================");
WriteToConsoleVoid(" INCOMING MESSAGE FROM SERVER");
WriteToConsoleVoid("======================================");
//PrintFrames("Server receiving", clientMessage);
byte[] rpcByteArray = null;
if (serverMessage.FrameCount == 3)
{
var clientAddress = serverMessage[0];
rpcByteArray = serverMessage[2].ToByteArray();
}
WriteToConsoleVoid("======================================");
Console.ReadLine();
}
Code for Dealer:
using (var server = new ResponseSocket())
{
server.Options.Identity = UTF8Encoding.UTF8.GetBytes(confItem.ResponseServerID);
Console.WriteLine("Server ID:" + confItem.ResponseServerID);
server.Connect(string.Format("tcp://{0}:{1}", "127.0.0.1", "5557"));
using (var poller = new NetMQPoller { server })
{
server.ReceiveReady += (s, a) =>
{
byte[] response = null;
NetMQMessage serverMessage = null;
try
{
serverMessage = a.Socket.ReceiveMultipartMessage();
}
catch (Exception ex)
{
Console.WriteLine("Exception on ReceiveMultipartMessage : " + ex.ToString());
//continue;
}
byte[] eaBody = null;
string clientAddress = "";
if (serverMessage.FrameCount == 2)
{
clientAddress = serverMessage[0].ConvertToString();
Console.WriteLine("ClientAddress:" + clientAddress);
eaBody = serverMessage[1].ToByteArray();
Console.WriteLine("Received message from remote computer: {0} bytes , CurrentID : {1}", eaBody.Length, confItem.ResponseServerID);
}
else
{
Console.WriteLine("Received message from remote computer: CurrentID : {0}", confItem.ResponseServerID);
}
};
poller.Run();
}
}
Is it possible to choose a specific backend on frontend.ReceiveReady?
Thanks!
Your backend should be router as well. You need the worker to register or you need to know all the available workers and their identity. When send on the backend push the worker identity at the beginning of the server.
Take a look at the Majordomo example in the zeromq guide:
http://zguide.zeromq.org/page:all#toc72
http://zguide.zeromq.org/page:all#toc98