Building components from separate parts

The router

At the very core of most Pylm servers, there is a router, and its architecture is the only profound idea in the whole pylm architecture. The goal is to manage the communication between the servers in a way as much similar to an actual network architecture as possible.

_images/core.png

The mission of this router sockets is to connect the parts that receive inbound messages with the parts that deal with outbound messages. The two tall blocks at each side of the table is a representation with such connection. If you know how an actual router works, a part would be a NIC, while the ROUTER socket and the routing table would be the switch. The router is documented in pylm.parts.core.Router.

The parts are also related to the router by the fact that they are all threads that run within the same process. In consequence, a pylm server could be described as a router and a series of parts that run in the same process.

The parts

There is a decent number of parts, each one covering some functionality within the PALM ecosystem. What follows is a classification of the several parts that are already available according to their characteristics.

First of all, parts can be services or connections. A service is a part that binds to a socket, which is an important detail when you design a cluster. A bind socket blocks waiting for a connection from a different thread or process. Therefore, a service is used to define the communication endpoint. All the available services are present in the module pylm.parts.services.

Connections are the complementary of servers, they are used in the client side of the communication, and are present in the module pylm.parts.connections.

On the second hand, parts can be standard or bypass. The former connects to the router, while the latter ignores the router completely. Bypass components inherit from pylm.parts.core.BypassInbound or from pylm.parts.core.BypassOutbound and also use the word bypass in its name, while standard components that connect to the router inherit from pylm.parts.core.Inbound and pylm.parts.core.Outbound. As an example, the part pylm.parts.services.CacheService, regardless of not being named as a bypass name, it exposes the internal cache of a server to workers and clients and does no communicate to the router in any case.

On the third hand, and related to the previous classification, parts can be inbound or outbound according to the direction of the first message respect to the router. Inbound services and components inherit from pylm.parts.core.Inbound and pylm.parts.core.BypassInbound, while outbound inherit from pylm.parts.core.Outbound and pylm.parts.core.BypassOutbound.

On the fourth hand, components may block or not depending on whether they expect the pair to send some message back. This behavior depends on the kind of ZeroMQ socket in use.

Warning

There is such a thing as a blocking outbound service. This means that the whole server is expecting some pair of an outbound service to send a message back. As you can imagine, these kind of parts must be handled with extreme care.

This classification may seem a little confusing, so we will offer plenty of examples covering most of the services and connections avialiable at the present version.

Services and connections

It’s time to build a component from a router and some services and parts that are already available. This way you will have a rough idea of how the high level API of pylm is built. Some of the details of the implementation are not described yet, but this example is a nice prologue about the things you need to know to master the low level API.

In this section, we have seen that the router is a crucial part of any server in pylm. The helper class pylm.parts.servers.ServerTemplate is designed to easily attach the parts to a router. The internal design of a master server can be seen in the following sketch.

_images/master_internals.png

A master server like the one used in the examples needs the router and four service parts.

  • A Pull part that receives the messages from the client
  • A Push part that sends the messages to the workers
  • A Pull part that gets the result from the workers
  • A Pub part that sends the results down the message pipeline or back to the client.

All parts are non-blocking, and the message stream is never interrupted. All the parts are services, meaning that the workers and the client connect to the respective sockets, since service parts bind to its respective outwards-facing socket.

The part library has a part for each one of the needs depicted above. There is a pylm.parts.services.PullService that binds a ZeroMQ Pull socket to the exterior, and sends the messages to the router. There is a pylm.parts.services.PubService that works exactly the other way around. It listens to the router, and forwards the messages to a ZeroMQ Push socket. There are also specific services to connect to worker servers, pylm.parts.services.WorkerPullService and pylm.parts.services.WorkerPushService, that are very similar to the two previously described services. With those pieces, we are ready to build a master server as follows

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pylm.parts.servers import ServerTemplate
from pylm.parts.services import PullService, PubService, WorkerPullService, WorkerPushService, \
    CacheService

server = ServerTemplate()

db_address = 'tcp://127.0.0.1:5559'
pull_address = 'tcp://127.0.0.1:5555'
pub_address = 'tcp://127.0.0.1:5556'
worker_pull_address = 'tcp://127.0.0.1:5557'
worker_push_address = 'tcp://127.0.0.1:5558'

server.register_inbound(PullService, 'Pull', pull_address, route='WorkerPush')
server.register_inbound(WorkerPullService, 'WorkerPull', worker_pull_address, route='Pub')
server.register_outbound(WorkerPushService, 'WorkerPush', worker_push_address)
server.register_outbound(PubService, 'Pub', pub_address)
server.register_bypass(CacheService, 'Cache', db_address)
server.preset_cache(name='server',
                    db_address=db_address,
                    pull_address=pull_address,
                    pub_address=pub_address,
                    worker_pull_address=worker_pull_address,
                    worker_push_address=worker_push_address)

if __name__ == '__main__':
    server.start()

Note

There is an additional type of service called bypass in this implementation, that will be described at the end of this section.

This server is functionally identical to the master server used in the first example of the section describing Servers. You can test it using the same client and workers.

Bypass parts

In the previous example one pylm.parts.services.CacheService was registered as a bypass part. These kind of parts also run in the same process as the router in a separate thread, but they do not interact with the router at all. The CacheService is a good example of that. It is the key-value store of the Master and Hub server, and it is one of those nice goodies of the high-level API. It has to be there, but it never waits for a message coming from the router.

Another part that is registered as bypass is the pylm.parts.gateways.HttpGateway.