You’ve already sniffed out the right ROUTER–DEALER pattern. But there is a nuance here: the broker must keep track of which client request is handled by which backend, so the response goes back to the right client.
You can try Router-Dealer-Router pattern. This is why this will work:
Frontend: ROUTER socket (bind TCP) — talks to clients.
Broker middle: DEALER socket — talks to all backend handler threads.
Backends: each backend is a ROUTER socket, connected to the broker DEALER over inproc://.
So, the chain is:
CLIENT <---> ROUTER (broker frontend) <---> DEALER (broker backend) <---> ROUTER (per backend)
This lets you:
Use the built-in zmq_proxy() for non-blocking fair queuing.
Keep request identity frames intact.
Have each backend handle its own routing logic for responses.