Akka HTTP REST API for producing to Kafka Performance - performance

I'm building an API with Akka that should produce to a Kafka bus. I have been load testing the application using Gatling. Noticed that when more than 1000 users are created in Gatling, the API starts to struggle. On average, about 170 requests per second are handled, which seems like very little to me.
The API's main entry point is this:
import akka.actor.{Props, ActorSystem}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.pattern.ask
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.unmarshalling.Unmarshaller
import akka.stream.ActorMaterializer
import com.typesafe.config.{Config, ConfigFactory}
import play.api.libs.json.{JsObject, Json}
import scala.concurrent.{Future, ExecutionContext}
import akka.http.scaladsl.server.Directives._
import akka.util.Timeout
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
case class PostMsg(msg:JsObject)
case object PostSuccess
case class PostFailure(msg:String)
class Msgapi(conf:Config) {
implicit val um:Unmarshaller[HttpEntity, JsObject] = {
Unmarshaller.byteStringUnmarshaller.mapWithCharset { (data, charset) =>
Json.parse(data.toArray).asInstanceOf[JsObject]
}
}
implicit val system = ActorSystem("MsgApi")
implicit val timeout = Timeout(5 seconds)
implicit val materializer = ActorMaterializer()
val router = system.actorOf(Props(new RouterActor(conf)))
val route = {
path("msg") {
post {
entity(as[JsObject]) {obj =>
if(!obj.keys.contains("key1") || !obj.keys.contains("key2") || !obj.keys.contains("key3")){
complete{
HttpResponse(status=StatusCodes.BadRequest, entity="Invalid json provided. Required fields: key1, key2, key3 \n")
}
} else {
onSuccess(router ? PostMsg(obj)){
case PostSuccess => {
complete{
Future{
HttpResponse(status = StatusCodes.OK, entity = "Post success")
}
}
}
case PostFailure(msg) =>{
complete{
Future{
HttpResponse(status = StatusCodes.InternalServerError, entity=msg)
}
}
}
case _ => {
complete{
Future{
HttpResponse(status = StatusCodes.InternalServerError, entity = "Unknown Server error occurred.")
}
}
}
}
}
}
}
}
}
def run():Unit = {
Http().bindAndHandle(route, interface = conf.getString("http.host"), port = conf.getInt("http.port"))
}
}
object RunMsgapi {
def main(Args: Array[String]):Unit = {
val conf = ConfigFactory.load()
val api = new Msgapi(conf)
api.run()
}
}
The router actor is as follows:
import akka.actor.{ActorSystem, Props, Actor}
import akka.http.scaladsl.server.RequestContext
import akka.routing.{Router, SmallestMailboxRoutingLogic, ActorRefRoutee}
import com.typesafe.config.Config
import play.api.libs.json.JsObject
class RouterActor(conf:Config) extends Actor{
val router = {
val routees = Vector.tabulate(conf.getInt("kafka.producer-number"))(n => {
val r = context.system.actorOf(Props(new KafkaProducerActor(conf, n )))
ActorRefRoutee(r)
})
Router(SmallestMailboxRoutingLogic(), routees)
}
def receive = {
case PostMsg(msg) => {
router.route(PostMsg(msg), sender())
}
}
}
And finally, the kafka producer actor:
import akka.actor.Actor
import java.util.Properties
import com.typesafe.config.Config
import kafka.message.NoCompressionCodec
import kafka.utils.Logging
import org.apache.kafka.clients.producer._
import play.api.libs.json.JsObject
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Await}
import ExecutionContext.Implicits.global
import scala.concurrent.{Future, Await}
import scala.util.{Failure, Success}
class KafkaProducerActor(conf:Config, id:Int) extends Actor with Logging {
var topic: String = conf.getString("kafka.topic")
val codec = NoCompressionCodec.codec
val props = new Properties()
props.put("bootstrap.servers", conf.getString("kafka.bootstrap-servers"))
props.put("acks", conf.getString("kafka.acks"))
props.put("retries", conf.getString("kafka.retries"))
props.put("batch.size", conf.getString("kafka.batch-size"))
props.put("linger.ms", conf.getString("kafka.linger-ms"))
props.put("buffer.memory", conf.getString("kafka.buffer-memory"))
props.put("key.serializer", conf.getString("kafka.key-serializer"))
props.put("value.serializer", conf.getString("kafka.value-serializer"))
val producer = new KafkaProducer[String, String](props)
def receive = {
case PostMsg(msg) => {
// push the msg to Kafka
try{
val res = Future{
producer.send(new ProducerRecord[String, String](topic, msg.toString()))
}
val result = Await.result(res, 1 second).get()
sender ! PostSuccess
} catch{
case e: Exception => {
println(e.printStackTrace())
sender ! PostFailure("Kafka push error")
}
}
}
}
}
The idea being that in application.conf I can easily specify how many producers there should be, allowing better horizontal scaling.
Now, however, it seems that the api or router is actually the bottleneck. As a test, I disabled the Kafka producing code, and replaced it with a simple: sender ! PostSuccess. With 3000 users in Gatling, I still had 6% of requests failing due to timeouts, which seems like a very long time to me.
The Gatling test I am executing is the following:
import io.gatling.core.Predef._ // 2
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicSimulation extends Simulation { // 3
val httpConf = http // 4
.baseURL("http://localhost:8080") // 5
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") // 6
.doNotTrackHeader("1")
.acceptLanguageHeader("en-US,en;q=0.5")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0")
.header("Content-Type", "application/json")
val scn = scenario("MsgLoadTest")
.repeat(100)(
pace(2 seconds)
.exec(http("request_1")
.post("/msg").body(StringBody("""{ "key1":"something", "key2": "somethingElse", "key3":2222}""")).asJSON)
)
setUp( // 11
scn.inject(rampUsers(3000) over (5 seconds)) // 12
).protocols(httpConf) // 13
}
update
Following some pointers from cmbaxter, I tried some things (see discussion in comments), and profiled the application using visualvm during the gatling load test. I don't quite know how to interpret these results though. It seems that a lot of time is spent in the ThreadPoolExecutor, but this might be ok?
Two screenshots from the profiling are below:

To exclude the Kafka producer, I removed the logic from the Actor. I was still getting performance issues. So, as a final test, I reworked the API to simply give a direct answer when a POST came in:
val route = {
path("msg") {
post {
entity(as[String]) { obj =>
complete(
HttpResponse(status = StatusCodes.OK, entity = "OK")
)
}
}
}
}
and I implemented the same route in Spray, to compare performance. The results were clear. Akka HTTP (at least in this current test setup) does not come close to Spray's performance. Perhaps there is some tweaking that can be done for Akka HTTP? I have attached two screenshots of response time graphs for 3000 concurrent users in Gatling, making a post request.
Akka HTTP
Spray

I would eliminate the KafkaProducerActor and router completely and call a Scala wrapped version of producer.send directly. Why create a possible bottleneck if not necessary? I could very well imagine the global execution context or the actor system becoming a bottleneck in your current setup.
Something like this should do the trick:
class KafkaScalaProducer(val producer : KafkaProducer[String, String](props)) {
def send(topic: String, msg : String) : Future[RecordMetadata] = {
val promise = Promise[RecordMetadata]()
try {
producer.send(new ProducerRecord[String, String](topic, msg), new Callback {
override def onCompletion(md : RecordMetadata, e : java.lang.Exception) {
if (md == null) promise.success(md)
else promise.failure(e)
}
})
} catch {
case e : BufferExhaustedException => promise.failure(e)
case e : KafkaException => promise.failure(e)
}
promise.future
}
def close = producer.close
}
(note: I have not actually tried this code. It should be interpreted as pseudo-code)
I would then simply transform the result of the future to a HttpResponse.
After that it's a question of tweaking configuration. Your bottleneck is now either the Kafka Producer or Akka Http.

Related

How to receive infinite chunked data in okhttp

I have a Http server. When client send a http request to the server, the server side will hold the http connection and send chunked string infinite to the client. I know it will be better using websocket in today, but it is a old project, and I can't change the server side code.
// server.kt
package com.example.long_http
import io.vertx.core.AbstractVerticle
import io.vertx.core.Promise
import io.vertx.core.Vertx
class MainVerticle : AbstractVerticle() {
override fun start(startPromise: Promise<Void>) {
vertx
.createHttpServer()
.requestHandler { req ->
var i = 0
req.response().setChunked(true).putHeader("Content-Type", "text/plain")
val timer = vertx.setPeriodic(2000) {
req.response().write("hello ${System.currentTimeMillis()}")
println("write ${System.currentTimeMillis()}")
}
req.response().closeHandler {
vertx.cancelTimer(timer)
println("close")
}
}
.listen(8888) { http ->
if (http.succeeded()) {
startPromise.complete()
println("HTTP server started on port 8888")
} else {
startPromise.fail(http.cause());
}
}
}
}
fun main() {
Vertx.vertx().deployVerticle(MainVerticle())
}
I try to receive chunked string using okhttp, but it dont work.
// client.kt
package com.example.long_http
import okhttp3.*
import java.io.IOException
fun main() {
val client = OkHttpClient()
val request = Request.Builder().url("http://localhost:8888").build()
client.newCall(request).enqueue(handler())
}
class handler : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
println("onResponse")
val stream = response.body!!.byteStream().bufferedReader()
while (true) {
var line = stream.readLine()
println(line)
}
}
}

Okio Throttler integration with OkHttp

My team is suffering from this issue with slack integration to upload files, so following the comments in that issue I would like to throttle the requests in our Kotlin implementation.
I am trying to integrate Okio Throttler within an OkHttp interceptor, so I have the setup:
val client = OkHttpClient.Builder()
.retryOnConnectionFailure(false)
.addInterceptor { chain ->
val request = chain.request()
val originalRequestBody = request.body
val newRequest = if (originalRequestBody != null) {
val wrappedRequestBody = ThrottledRequestBody(originalRequestBody)
request.newBuilder()
.method(request.method, wrappedRequestBody)
.build()
} else {
request
}
chain.proceed(newRequest)
}
.build()
class ThrottledRequestBody(private val delegate: RequestBody) : RequestBody() {
private val throttler = Throttler().apply {
bytesPerSecond(1024, 1024 * 4, 1024 * 8)
}
override fun contentType(): MediaType? {
return delegate.contentType()
}
override fun writeTo(sink: BufferedSink) {
delegate.writeTo(throttler.sink(sink).buffer())
}
}
It seems throttler.sink returns a Sink, but a BufferedSink is required to the method delegate.writeTo, so I called buffer() to get that BufferedSink.
Am I doing it wrong ? Is the call for .buffer() breaking the integration?
It's almost perfect. You just need to flush the buffer when you're done otherwise it'll finish with a few bytes inside.
override fun writeTo(sink: BufferedSink) {
throttler.sink(sink).buffer().use {
delegate.writeTo(it)
}
}

Connecting two NestJS gRPC microservices on localhost

Introduction
I am quite new to NestJS, but really like how powerful it is so far, so I wanted to see how far I could get with using Kafka, gRPC and NestJS together.
My end goal is to have the following design, since I want to replicate my Kafka Producers:
gRPC client <---> gRPC server and Kafka client <----> Kafka
Kafka
I have a Kafka cluster which I have built in Kubernetes and which I reach via an L4 Load Balancer from NestJS with ease.
The Kafka side in NestJS is fine. I even rely on kafkajs and simply build out Consumers and Prodcuers using the kafkajs.Producer and kafkajs.Consumer classes accordingly, defining the configuration for the Kafka instance with a ConfigService.
gRPC
It is the gRPC side which I am struggling with as I am trying to let a gRPC server forward a request to a Kafka producer on behalf of the gRPC client.
Problem
I can start my gRPC server, but not my gRPC client. The client returns the following error from #grpc/grpc-js/src/server.ts:569:
[Nest] 50525 - 11/03/2022, 16:09:13 LOG [NestFactory] Starting Nest application...
[Nest] 50525 - 11/03/2022, 16:09:13 LOG [InstanceLoader] AppModule dependencies initialized +17ms
[Nest] 50525 - 11/03/2022, 16:09:13 LOG [InstanceLoader] ClientsModule dependencies initialized +0ms
[Nest] 50525 - 11/03/2022, 16:09:13 LOG [InstanceLoader] GrpcClientModule dependencies initialized +0ms
E No address added out of total 2 resolved
/Users/mattia/github/microservices/kafka-grpc-client/node_modules/#grpc/grpc-js/src/server.ts:569
deferredCallback(new Error(errorString), 0);
^
Error: No address added out of total 2 resolved
at bindResultPromise.then.errorString (/Users/mattia/github/microservices/kafka-grpc-client/node_modules/#grpc/grpc-js/src/server.ts:569:32)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
I am not sure why this happens, but I am suspicious that it has to do with the gRPC channel, or in other words communication link between the server and the client. However, I cannot find any documentation on this matter. Help would be greatly appreciated.
If you follow the stack trace you should notice the following in #grpc/grpc-js/src/server.ts:569:32, in other words that this error is in the #grpc/grpcjs library:
const resolverListener: ResolverListener = {
onSuccessfulResolution: (
addressList,
serviceConfig,
serviceConfigError
) => {
// We only want one resolution result. Discard all future results
resolverListener.onSuccessfulResolution = () => {};
if (addressList.length === 0) {
deferredCallback(new Error(`No addresses resolved for port ${port}`), 0);
return;
}
let bindResultPromise: Promise<BindResult>;
if (isTcpSubchannelAddress(addressList[0])) {
if (addressList[0].port === 0) {
bindResultPromise = bindWildcardPort(addressList);
} else {
bindResultPromise = bindSpecificPort(
addressList,
addressList[0].port,
0
);
}
} else {
// Use an arbitrary non-zero port for non-TCP addresses
bindResultPromise = bindSpecificPort(addressList, 1, 0);
}
bindResultPromise.then(
(bindResult) => {
if (bindResult.count === 0) {
const errorString = `No address added out of total ${addressList.length} resolved`;
logging.log(LogVerbosity.ERROR, errorString);
deferredCallback(new Error(errorString), 0);
} else {
if (bindResult.count < addressList.length) {
logging.log(
LogVerbosity.INFO,
`WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved`
);
}
deferredCallback(null, bindResult.port);
}
},
(error) => {
const errorString = `No address added out of total ${addressList.length} resolved`;
logging.log(LogVerbosity.ERROR, errorString);
deferredCallback(new Error(errorString), 0);
}
);
},
onError: (error) => {
deferredCallback(new Error(error.details), 0);
},
};
My code
Because gRPC requires a shared .proto file, I use the following:
syntax = "proto3";
package KAFKA_GRPC_SERVICE;
service KafkaGrpcService {
rpc Produce(ProducerRequest) returns(Empty) {}
}
// See `kafkajs/types/ProducerRecord`
message ProducerRequest {
// See `kafkajs/types/Message`
message Message {
required string value = 1;
optional string key = 2;
optional int32 partition = 3;
optional string timestamp = 4;
}
required string topic = 1;
repeated Message messages = 2;
optional int32 acks = 3;
optional int32 timeout = 4;
}
message Empty {}
I then autogenerate the interface required which gives me:
/**
* This file is auto-generated by nestjs-proto-gen-ts
*/
import { Observable } from 'rxjs';
import { Metadata } from '#grpc/grpc-js';
export namespace KAFKA_GRPC_SERVICE {
export interface KafkaGrpcService {
produce(data: ProducerRequest, metadata?: Metadata): Observable<Empty>;
}
// See `kafkajs/types/ProducerRecord`
export interface ProducerRequest {
topic: string;
messages: ProducerRequest.Message[];
acks?: number;
timeout?: number;
}
export namespace ProducerRequest {
// See `kafkajs/types/ProducerRecord`
// See `kafkajs/types/Message`
export interface Message {
value: string;
key?: string;
partition?: number;
timestamp?: string;
}
}
// tslint:disable-next-line:no-empty-interface
export interface Empty {
}
}
Note I have tweaked some of the interface since it provides certain elements as optional, when I need a few of them to be required to be compatible with kafkajs/ProducerRecord.
My repository has two different NestJS apps, one called kafka-grpc-server and another called kafka-grpc-client respectively, so the code is slightly different. I won't post the ProducerService here for sake of brevity.
Server
Myconfig for the gRPC service is defined in a grpc.options.ts file that looks as follows:
import { Transport } from "#nestjs/microservices";
import { join } from "path";
export const grpcOptions = {
transport: Transport.GRPC,
options: {
package: 'KAFKA_GRPC_SERVICE',
url: 'localhost:5000',
protoPath: join(__dirname, 'grpc/proto/kafkagrpcservice.proto'),
},
}
My Controller on the Server side looks like this:
import { Controller,Logger } from "#nestjs/common";
import { GrpcMethod } from "#nestjs/microservices";
import { Observable } from "rxjs";
import { KAFKA_GRPC_SERVICE } from './grpc/interfaces/kafkagrpcservice';
import { ProducerService } from "./kafka/producer/producer.service";
#Controller()
export class KafkaGrpcController implements KAFKA_GRPC_SERVICE.KafkaGrpcService {
private logger = new Logger(KafkaGrpcController.name)
constructor(private readonly kafkaProducer: ProducerService) {}
#GrpcMethod('KafkaGrpcService', 'Produce')
produce(data: KAFKA_GRPC_SERVICE.ProducerRequest, metadata: any): Observable<any> {
this.logger.log('Producing message: {} with metadata: {}', data, metadata.toString());
this.kafkaProducer.produce(data);
return;
}
}
and my main.ts server side is like this:
import { Logger } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { grpcOptions } from './grpc.options';
import { KafkaGrpcServerModule } from './kafkagrpcserver.module';
async function bootstrap() {
const logger = new Logger('Main')
const app = await NestFactory.createMicroservice(KafkaGrpcServerModule, grpcOptions);
await app.listen();
logger.log(`Microservice is listening on '${grpcOptions.options.url}'`);
}
bootstrap();
I can successfully start the server without any issues, although I do notice that there is one process with two file descriptors listening on :5000, with TCP. I am not sure whether this is standard. First question I have is why are there two services, and not one when I just create one microservice? Is this a NestJS thing?
node 50355 mattia 27u IPv6 * 0t0 TCP localhost:commplex-main (LISTEN)
node 50355 mattia 29u IPv4 * 0t0 TCP localhost:commplex-main (LISTEN)
Client
Client side is slightly different. I use the same .proto file. However, the grpc.options.ts file is slightly different since this needs to be a client:
export const grpcOptions: ClientOptions = {
transport: Transport.GRPC,
options: {
package: 'KAFKA_GRPC_SERVICE',
url: 'localhost:5000',
protoPath: join(__dirname, 'grpc/proto/kafkagrpcservice.proto'),
},
}
As you can see, ClientOptions are used for the client, but not for the server.
Client side, I have a GrpcClientModule which looks like this:
import { Module } from "#nestjs/common";
import { ClientsModule } from "#nestjs/microservices";
import { grpcOptions } from "../grpc.options";
import { GrpcClientController } from "./grpcclient.controller";
#Module({
imports: [
ClientsModule.register([
{
name: 'KAFKA_GRPC_SERVICE',
...grpcOptions,
}
])
],
controllers: [
GrpcClientController,
],
})
export class GrpcClientModule {}
and the GrpClientController is like this:
import { Metadata } from "#grpc/grpc-js";
import { Body, Controller, Get, Inject, OnModuleInit, Post } from "#nestjs/common";
import { Client, ClientGrpc } from "#nestjs/microservices";
import { Observable } from "rxjs";
import { grpcOptions } from "src/grpc.options";
import { KAFKA_GRPC_SERVICE } from "./interfaces/kafkagrpcservice";
#Controller()
export class GrpcClientController implements OnModuleInit {
constructor(#Inject('KAFKA_GRPC_SERVICE') private client: ClientGrpc) {}
private grpcService: KAFKA_GRPC_SERVICE.KafkaGrpcService;
onModuleInit(): void {
this.grpcService = this.client.getService<KAFKA_GRPC_SERVICE.KafkaGrpcService>('KafkaRpcService')
}
#Post('produce')
async produce(data: KAFKA_GRPC_SERVICE.ProducerRequest): Promise<Observable<KAFKA_GRPC_SERVICE.ProducerResponse>> {
const metadata = new Metadata();
metadata.add('Set-Cookie', 'my_cookie=in_milk');
return this.grpcService.produce( { topic: data.topic, messages: data.messages }, metadata );
}
}
I start my client as follows:
import { NestFactory } from '#nestjs/core';
import { grpcOptions } from './grpc.options';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, grpcOptions);
await app.listen();
}
bootstrap();
Any help would be great!
So, I found the answer. It seems that it is impossible to start a single microservice as a gRPC server. Indeed, whenever I turn the microservice into a hybrid application as follows:
import { Logger } from '#nestjs/common';
import { NestFactory } from '#nestjs/core';
import { grpcOptions } from './grpc.options';
import { KafkaGrpcServerModule } from './kafkagrpcserver.module';
async function bootstrap() {
const logger = new Logger('Main')
const app = await NestFactory.create(KafkaGrpcServerModule);
const grpcServer = await NestFactory.createMicroservice(KafkaGrpcServerModule, grpcOptions);
await app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();
I am able to connect the client.

Kotlin coroutines within a Spring REST endpoint

Given a REST endpoint and two asynchronous coroutines each returning an integer, I want this endpoint to return their sum. The two functions (funA and funB) should run in parallel, in such a way that the whole computation should take ~3secs.
I'm using SpringBoot 2.6.3 and Kotlin Coroutines 1.6.0. Here is my attempt:
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
#RestController
class Controllers {
#GetMapping(
value = ["/summing"]
)
fun summing(): String {
val finalResult = runBlocking {
val result = async { sum() }
println("Your result: ${result.await()}")
return#runBlocking result
}
println("Final Result: $finalResult")
return "$finalResult"
}
suspend fun funA(): Int {
delay(3000)
return 10
}
suspend fun funB(): Int {
delay(2000)
return 90
}
fun sum() = runBlocking {
val resultSum = async { funA().await() + funB().await() }
return#runBlocking resultSum
}
}
The problem is that this code does not compile as await() is not recognised as a valid method. If I remove await() the two functions are executed in series (total time ~5 secs) and instead of the expected result (100), I get:
Your result: DeferredCoroutine{Completed}#1c1fe804
Final Result: DeferredCoroutine{Completed}#48a622de
and so the endpoint returns "DeferredCoroutine{Completed}#48a622de".
I want the endpoint to return "100" instead and within ~3secs. How can I achieve this?
You really messed this up ;-) There are several problems with your code:
Use runBlocking() only to use another runBlocking() inside it.
Use async() and immediately call await() on it (in summing()) - it does nothing.
funA().await() and funB().await() don't make any sense really. These functions return integers, you can't await() on already acquired integers.
Generally, using much more code than needed.
The solution is pretty simple: use runBlocking() once to jump into coroutine world and then use async() to start both functions concurrently to each other:
runBlocking {
val a = async { funA() }
val b = async { funB() }
a.await() + b.await()
}
Or alternatively:
runBlocking {
listOf(
async { funA() },
async { funB() },
).awaitAll().sum()
}
Or (a little shorter, but I consider this less readable):
runBlocking {
val a = async { funA() }
funB() + a.await()
}
Also,runBlocking() is not ideal. I believe Spring has support for coroutines, so it would be better to make summing() function suspend and use coroutineScope() instead of runBlocking() - this way the code won't block any threads.

Why zmq ( inproc:// )-connection's order matters, unlike for ( tcp:// )?

When launching a zmq server and client, in any random order, communicating over the tcp:// transport-class, they are smart enough to connect/reconnect regardless of the order.
However, when trying to run the same over the inproc:// transport-class, I see that it works only if the client starts after the server. How can we avoid this?
MCVE-code :
Here are some kotlin MCVE-code examples, to reproduce the claim (this is a modified version of the well known weather example)
server.kt - run this to run the server standalone
package sandbox.zmq
import org.zeromq.ZMQ
import org.zeromq.ZMQ.Context
import sandbox.util.Util.sout
import java.util.*
fun main(args: Array<String>) {
server(
context = ZMQ.context(1),
// publishTo = "tcp://localhost:5556"
publishTo = "tcp://localhost:5557"
)
}
fun server(context: Context, publishTo: String) {
val publisher = context.socket(ZMQ.PUB)
publisher.bind(publishTo)
// Initialize random number generator
val srandom = Random(System.currentTimeMillis())
while (!Thread.currentThread().isInterrupted) {
// Get values that will fool the boss
val zipcode: Int
val temperature: Int
val relhumidity: Int
zipcode = 10000 + srandom.nextInt(10)
temperature = srandom.nextInt(215) - 80 + 1
relhumidity = srandom.nextInt(50) + 10 + 1
// Send message to all subscribers
val update = String.format("%05d %d %d", zipcode, temperature, relhumidity)
println("server >> $update")
publisher.send(update, 0)
Thread.sleep(500)
}
publisher.close()
context.term()
}
client.kt - run this for the client standalone
package sandbox.zmq
import org.zeromq.ZMQ
import org.zeromq.ZMQ.Context
import java.util.*
fun main(args: Array<String>) {
client(
context = ZMQ.context(1),
readFrom = "tcp://localhost:5557"
)
}
fun client(context: Context, readFrom: String) {
// Socket to talk to server
println("Collecting updates from weather server")
val subscriber = context.socket(ZMQ.SUB)
// subscriber.connect("tcp://localhost:");
subscriber.connect(readFrom)
// Subscribe to zipcode, default is NYC, 10001
subscriber.subscribe("".toByteArray())
// Process 100 updates
var update_nbr: Int
var total_temp: Long = 0
update_nbr = 0
while (update_nbr < 10000) {
// Use trim to remove the tailing '0' character
val string = subscriber.recvStr(0).trim { it <= ' ' }
println("client << $string")
val sscanf = StringTokenizer(string, " ")
val zipcode = Integer.valueOf(sscanf.nextToken())
val temperature = Integer.valueOf(sscanf.nextToken())
val relhumidity = Integer.valueOf(sscanf.nextToken())
total_temp += temperature.toLong()
update_nbr++
}
subscriber.close()
}
inproc.kt - run this and modify which sample is called for the inproc:// scenarios
package sandbox.zmq
import org.zeromq.ZMQ
import kotlin.concurrent.thread
fun main(args: Array<String>) {
// clientFirst()
clientLast()
}
fun println(string: String) {
System.out.println("${Thread.currentThread().name} : $string")
}
fun clientFirst() {
val context = ZMQ.context(1)
val client = thread {
client(
context = context,
readFrom = "inproc://backend"
)
}
// use this to maintain order
Thread.sleep(10)
val server = thread {
server(
context = context,
publishTo = "inproc://backend"
)
}
readLine()
client.interrupt()
server.interrupt()
}
fun clientLast() {
val context = ZMQ.context(1)
val server = thread {
server(
context = context,
publishTo = "inproc://backend"
)
}
// use this to maintain order
Thread.sleep(10)
val client = thread {
client(
context = context,
readFrom = "inproc://backend"
)
}
readLine()
client.interrupt()
server.interrupt()
}
Why zmq inproc:// connection order matters, unlike for tcp://?
Well, this is a by-design behaviour
Given the native ZeroMQ API warns about this by-design behaviour ( since ever ), the issue is not a problem, but an intended property.
Plus one additional property has to be also met:
The name [ meant an_endpoint_name in .connect("inproc://<_an_endpoint_name_>")] must have been previously created by assigning it to at least one socketwithin the same ØMQ context as the socket being connected.
Newer versions of the native ZeroMQ API ( post 4.0 ), if indeed deployed under one's respective language binding / wrapper, may allow to release the former of these requirements:
Since version 4.0 the order of zmq_bind() and zmq_connect() does not matter just like for the tcp transport type.
How can we avoid this?
Well, a much harder part ...
if not already got an easy way above the ZeroMQ native API v4.2+, one may roll up one's sleeves and either re-factor the pre-4.x language wrapper / binding, so as to make the engine get there, or, may be, test if Martin SUSTRIK's second lovely child, the nanomsg could fit the scene for achieving this.

Resources