as part of the software distribution. ... dispatcher is implemented in a distributed fashion, as a set of brokers inter- .... The API to the REDS dispatching service.
REDS: A Reconfigurable Dispatching System Gianpaolo Cugola and Gian Pietro Picco Dipartimento di Elettronica e Informazione, Politecnico di Milano, Italy [cugola,picco]@elet.polimi.it
Abstract. We present a new publish-subscribe middleware called REDS (REconfigurable Dispatching System), whose defining features are geared towards reconfiguration. On one hand, REDS provide programmers with the ability to change the configuration of the middleware modules, by choosing from an array of available strategies covering several aspects of publish-subscribe. On the other hand, REDS implements distributed algorithms that support dynamic reconfiguration of the publish-subscribe infrastructure, and therefore enable its use in scenarios characterized by fluid topologies, e.g., mobile computing and peer-to-peer networks. In this paper we present the API of REDS, illustrate its architecture and extension mechanisms, and report about its current implementation. Keywords. Publish-subscribe, content-based routing, reconfiguration, mobile computing.
1
Introduction
Modern distributed applications demand high degrees of decoupling and flexibility. Publish-subscribe middleware recently emerged as a promising approach to tackle these requirements. In this paper, we present the design and implementation of a new publish-subscribe middleware called REDS (REconfigurable Dispatching System). Reconfiguration is the distinctive and innovative feature of REDS, and is made available on two different planes. The first plane concerns the very configuration of the middleware architecture, and addresses the problem of enabling the selection of different mechanisms for different deployment scenarios. Thus, for instance, it is possible to select the routing strategy (or network transport, or matching semantics, and so on) that is most appropriate for the application at hand. Moreover, proper extension mechanisms are in place that enable programmers to define their own variants, if needed. This is achieved by exploiting a modular design, which decouples the various aspects of a publish-subscribe middleware into components that interact through well-defined interfaces and whose concrete implementation can be chosen at deployment time without affecting the rest of the system, by virtue of encapsulation. As a matter of fact, the core of the REDS middleware is constituted by a framework in the object-oriented sense. The second reconfiguration plane concerns instead the dynamic reconfiguration of the topology of the message dispatching network, and therefore addresses the needs of highly dynamic systems where the topology of the network
undergoes continuous change (e.g., mobile and peer-to-peer systems). This second form of reconfiguration is embedded in some of the components we provide as instantiation of the framework’s interfaces within the current release, and are entirely based on the experience accumulated by our research group on the topic [9, 13, 14, 20, 24, 27], to address the problems of maintaining the overlay network in the face of topological changes, efficiently restoring stale subscription information, and recover messages lost during the reconfiguration. To the best of our knowledge, REDS is the first publicly available and implemented system expressly designed to tolerate arbitrary topological reconfigurations of the message dispatching network independently from the underlying networking scenario. Moreover, as mentioned above, this capability comes with the added value provided by its original modular design, which enables the programmer to select, at deployment time, the best configuration of components for the applicative scenario at hand. In this paper we present the application programming interface (API) that REDS provides to developers of distributed applications, illustrate its architecture, and discuss the mechanisms offered for customization and extension. The concepts discussed are exemplified using the built-in components provided as part of the software distribution. REDS is publicly available at zeus.elet. polimi.it/reds as open source under the LGPL license. The paper is structured as follows. In Section 2 we first provide the reader with a concise overview of publish-subscribe, and then illustrate the motivation and research goals behind REDS. In Section 3 we put forth the core contribution of the paper, by presenting the REDS API, describing the middleware architecture and internals, and illustrating how the built-in modules can be redefined by the programmer. In Section 4 we draw some considerations and place REDS in the context of related work. Finally, in Section 5 we end the paper with brief concluding remarks.
2
Background and Motivation
In this section we first provide a concise overview of the many flavors of publishsubscribe, and then illustrate the motivation and research goals underlying the REDS system we present in this paper. 2.1
Publish-Subscribe: Systems and Features
Distributed applications exploiting publish-subscribe middleware are organized as a collection of client components, which interact by publishing messages and by subscribing to the classes of messages they are interested in. The core component of the middleware, the dispatcher, is responsible for collecting subscriptions and forwarding messages from publishers to subscribers, and in doing so achieves a high degree of decoupling among the communicating parties. These ideas have been recently popularized by a wealth of systems, which interpret the publishsubscribe paradigm in different ways [16].
A first point of differentiation is constituted by the very notions of message and subscription. Existing systems exhibit a wide spectrum of choices in this respect. Messages can be bare strings, untyped sequences of values (tuples), untyped name-value pairs, XML code, typed C-like structures, and even fullfledged, typed objects, complete of attributes and methods. As for subscriptions, the expressiveness of the subscription language draws a line between subject-based and content-based systems. In the first case, subscriptions contain only the name of a class of messages—usually called subject, channel, or topic—chosen among a set of pre-defined ones. Instead, in content-based systems the selection of a message is determined entirely by the client, which uses expressions (often called filters) that allow sophisticated matching on the message content. Clearly, the message format constraints the subscription language. For instance, if the message is simply a string, then regular expressions are the typical subscription language. Also, the expressive power of the subscription language impacts the inner working of the middleware. For instance, as described in [5], under some conditions it is possible to detect that a filter is subsumed by an already existing one, or even to fuse together two filters into a more general one, thus reducing the matching space. The actual algorithms, mechanisms, and techniques that are (or can be) exploited in the implementation of the system exhibit an even higher variety. A lot of research (e.g., [1, 6, 17]) focused on the problem of efficiently matching messages against subscriptions and subsequently determining the intended recipients. This process usually relies on subscription tables, dedicated data structures that hold information about the subscriptions (e.g., filter and source). The simplest strategy consists of matching a message against all the filters in the subscription table. Since the number of filters received can be very large, such strategy can be very inefficient. The layout of the subscription table, often designed together with the subscription language, is key to the alternative efficient matching and forwarding strategies found in the literature. The complexity and variety of the available alternatives boosts when the dispatcher is implemented in a distributed fashion, as a set of brokers interconnected in an overlay network, which cooperatively route the messages and subscriptions issued by the clients connected to them, therefore increasing the scalability of the system. In this context, the main design decisions concern the topology of interconnection and the routing strategy, which are clearly related. Although the first approaches based on a graph topology are starting to appear (e.g., [10]), most of available systems are based on a tree topology, as this simplifies routing (e.g., by avoiding the possibility of routing loops) and provides a high degree of scalability. Several tree-based routing strategies can be found in the literature (e.g., [3, 5, 12]), with the most basic ones shown in Figure 1. The simplest approach is message forwarding. A published message is forwarded by a broker to all the others along the dispatching tree. Instead, subscriptions are never propagated beyond the broker receiving them. This form of message flooding may generate high overhead, since messages are sent to all brokers regardless of their interests.
(a) Message forwarding
(b) Subscription forwarding
(c) Hierarchical forwarding
Fig. 1. Publish-subscribe routing strategies. To avoid cluttering the figure only brokers are shown, without clients. Broker S1 and S2 subscribed (through their clients) to the same “black” filter, while S3 subscribed to “gray”. Colored arrows represent the content of subscription tables for the corresponding filters. Broker P published a message matching the black filter but not the gray one. The path followed by this message is shown through thick, directed lines. Broker R is the root for hierarchical forwarding.
An alternative strategy, called subscription forwarding, limits this overhead by spreading knowledge about subscriptions throughout the system. When a broker receives a subscription from one of its neighbors, not only it stores the associated filter into its subscription table as in message forwarding, but it also forwards it to all the remaining neighboring brokers. During this propagation, each dispatcher behaves as a subscriber with respect to its neighbors. Consequently, each of them records the filter associated with the subscription in its own subscription table and re-forwards it to all its neighboring dispatchers except the one that sent it1 . This process effectively sets up routes for messages through the reverse path followed by subscriptions. Hierarchical forwarding strikes a balance between the two aforementioned strategies by assuming a rooted tree topology. Subscriptions are forwarded towards the root to establish the routes that published messages follow “downstream” towards subscribers. Messages, in fact, are always propagated “upstream” up to the root of the tree, while they flow “downstream” along the tree only if a matching subscription has been received from 1
This schema is optimized by avoiding forwarding of the same message filter in the same direction. Moreover, some systems (e.g., [5, 7, 19, 36]) performs even more aggressive optimizations by exploiting the aforementioned relations among filters.
the corresponding sub-tree. Figure 1 graphically illustrates and compares these three routing strategies. 2.2
Why a New System?
The brief overview we just provided for the state-of-the-art in publish-subscribe shows the variety of design choices exploited by available systems. Moreover, it is worth noting how these design choices often provide very different tradeoffs. For instance, this is particularly evident for what concerns the routing strategies. Message forwarding incurs the highest publishing overhead, but it does not require any propagation of subscriptions. In turn, subscription forwarding may require big subscription tables at each broker, but offers the best publishing performance. Hierarchical forwarding strikes a balance, but at the price of making the root broker the performance and reliability bottleneck for the whole system. Similar tradeoffs exist for essentially all the other aspects we discussed. No best solution exists: the choice must be ultimately evaluated against the application requirements at hand. Unfortunately, while all of these dimensions are largely orthogonal and could be decoupled from the rest of the architecture, they are usually hard-wired within existing publish-subscribe middleware, with little or no possibility to override them for a given application. This observation led us to the first motivation for designing REDS. Since the beginning, we strived for a design that sharply decouples the fundamental aspects of a publish-subscribe system into different components. Thus, for instance, the format of message and filters, the mechanisms for managing subscription tables and the related matching, the routing strategy, the underlying network transport protocol, and the management of the dispatching overlay network are all separate components in the REDS framework. In some cases (e.g., routing strategy, network transport, overlay network maintenance) our framework enables us to replace one of these components without affecting the rest of the architecture. In the cases where specific design choices or the very semantics of publish-subscribe prevents this desirable orthogonality (e.g., when designing a subscription table that relies on a specific subscription language), our framework provides a reference for understanding the relationship among components and the extent of their interactions. Accordingly, we believe that the open framework provided by REDS can be useful to both developers and researchers interested in publish-subscribe systems. To the former, REDS provides flexibility to deploy the components most suited for a given application scenario, as well as the basic building blocks for customizing such components when faced with new application challenges. To the latter, REDS provides the ability to experiment with and evaluate novel algorithms and mechanisms, without rebuilding a new system from scratch every time. This brings us to our second motivation for designing REDS. Our research group has recently been particularly active [9,10,13,14,20,24,27] in investigating how publish-subscribe can be exploited in dynamic environments characterized by frequent topological reconfigurations, e.g., those characterizing mobile computing and peer-to-peer networks. This is a recent topic, for which very few
studies have been published and where the tradeoffs of the various solutions are still under investigation. Moreover, it also appears like these tradeoffs strongly depend on the assumptions made on the deployment scenario, e.g., in terms of frequency of topological changes. In this situation, the modular design of REDS enables us precisely to implement and experiment with different solutions without the need to reinvent the wheel each time. Clearly, we cannot claim that our design works for any algorithm and design aspect of publish-subscribe—one can always conceive some novel, unanticipated feature that holds the potential to break some of the design assumptions underlying REDS. Nevertheless, REDS is an ongoing project where the shape of its framework is undergoing continuous validation as we implement more modules and progressively extend the range of features we are incorporating in the framework. Thus far, our design, described in the next section, has proven successful in accommodating our reconfiguration algorithms, as well as many other aspects of publish-subscribe systems.
3
A Reconfigurable Dispatching System
As mentioned earlier, REDS provides a framework of Java classes, which define: (i) the API to access the publish-subscribe services, and (ii) the architecture of a generic broker organized as a set of components, implementing some well-defined interfaces. The choice of the specific components used to build the brokers that compose the REDS distributed dispatcher, together with the way brokers are connected at run-time, determine the overall behavior of the system. The current release of REDS includes different versions for each component, with more being continuously added. Developers using REDS are also free of defining their own components if they need to adapt the behavior of the REDS dispatching infrastructure to specific needs. The rest of this section describes REDS in detail. Section 3.1 describes the client API used by application components to access the publish-subscribe services provided by the REDS dispatching network. Section 3.2 describes the architecture of a REDS broker, while Section 3.3 shows how its constituents interact in performing some common tasks. Section 3.4 describes the components included in the current distribution of REDS, while Section 3.5 shows some simple code snippets exemplifying how REDS is used in practice. 3.1
Client API
Client application components access the services provided by the REDS distributed dispatcher through the DispatchingService interface, shown in Figure 2. It provides methods to open a connection with a REDS broker, close it, get the identifier associated with the connection, subscribe to and unsubscribe from a set of messages, get messages dispatched to the component, and publish new messages.
Serializable
DispatchingService open() : void close() : void
Filter
Message
getID() : String getNextMessage() : Message hasMoreMessages() : boolean
matches(msg: Message) : boolean
subscribe(filter: Filter) : void
equals(o: Object) : boolean
unsubscribe(filter: Filter) : void
hashCode() : int
unsubscribeAll() : void publish(msg: Message) : void
isOpened() : boolean
ComparableFilter isCoveredBy(filter: ComparableFilter) : boolean
Fig. 2. The API to the REDS dispatching service.
The various implementations of the DispatchingService interface differ in the transport protocol used to connect with the REDS dispatcher and hide all the details about how the client is connected and how data is sent and received through the network. Moreover, it is independent from the specific format of messages and filters, as this was one of our goals in developing REDS. To this end, we defined three interfaces—one for messages and two for filters—that include the minimal set of methods strictly required by a publish-subscribe broker to operate. This way, developers are free to define their own format of messages and, even more important, filters. Since REDS uses standard Java serialization during communication, serializability is a requirement for REDS messages and filters. Therefore, all three interfaces extend java.io.Serializable. The Message interface does not impose any other constraint on the classes implementing it. Instead, the Filter interface defines a method matches that is supposed to be redefined with the filtering logic specified by the implementing class. In addition, the methods hashcode and equals are used internally by REDS brokers to store and compare filters, e.g., to avoid propagating them multiple times along the same link. Finally, the ComparableFilter interface represents filters that can be “covered” by others according to some notion of partial ordering. As in the case of matching, the method isCoveredBy is supposed to encode the coverage semantics and is used by REDS brokers to limit the propagation of subscriptions, e.g., as described in [5]. It is worth noting how the choice of encapsulating the filtering logic into a method empowers REDS programmers with great flexibility in defining their own filtering. New messages and filters with very different semantics can be developed, e.g., using XML and XPath. Clearly, programmers must be aware that this expressiveness, if not carefully managed, may lead to security problems. Once aware of this, they can put in place properly security mechanisms, e.g., by
SubscriptionTable
ConnectionManager Core
NeighborSet
Router
Routing layer +
Transport layer 0..*
Neighbor
Transport
Fig. 3. The internal architecture of a REDS broker.
Neighbor
Transport
publish(msg: Message) : void subscribe(filter: Filter) : void
setCore(core: Core) : void
unsubscribe(filter: Filter) : void
start() : void
forward(msg: InternalMessage) : void
stop() : void
disconnect() : void
openLink(url: String) : void
isBroker() : boolean
openLink(url: String,context: Serializable) : void
isClient() : boolean
closeLink(neighborID: String) : void
getID() : String
getURL() : String
isConnected() : boolean
Fig. 4. Interfaces in the transport layer.
sandboxing the execution of the matches method or by signing messages and filters. 3.2
Architecture
As shown in Figure 3, the architecture of a REDS broker is structured in two layers, routing and transport, each containing the interfaces that are going to be implemented by the components determining the actual behavior of the broker. Transport. As its name suggests, the transport layer provides the mechanisms used to transport messages, (un)subscriptions, and any other kind of brokerspecific control message through the network. Consequently, its main role is to hide the wire protocol adopted to move data around. Moreover, it also hides the mechanisms used to address and access brokers and clients, and to setup the dispatching network in case the dispatcher is distributed. The transport layer contains the interfaces Transport and Neighbor, shown in Figure 4. The former provides methods to open and close links towards other brokers, which can be used to define the initial layout of the dispatching network as well
Core connect(neighborID: String,n: Neighbor) : boolean connect(neighborID: String,n: Neighbor,context: Serializable) : boolean softDisconnect(neighborID: String) : void hardDisconnect(neighborID: String) : void subscribe(neighborID: String,filter: Filter) : void unsubscribe(neighborID: String,filter: Filter) : void unsubscribeAll(neighborID: String) : void publish(neighborID: String,message: Message) : void process(neighborID: String,message: InternalMessage) : void addInternalMessageProcessor(p: InternalMessageProcessor,subject: String) : void removeInternalMessageProcessor(p: InternalMessageProcessor,subject: String) : void getID() : String getSubscriptionTable() : SubscriptionTable getNeighborSet() : NeighborSet getTransport() : Transport setRouter(router: Router) : void setConnectionManager(connectionMgr: ConnectionManager) : void setTransport(transport: Transport) : void setSubscriptionTable(subscriptionTable: SubscriptionTable) : void setDebugLevel(debugLevel: int) : void getDebugLevel() : int
InternalMessage
InternalMessageProcessor process(neighborID: String,msg: InternalMessage) : void
subject : String payload : Serializable InternalMessage(subject: String) InternalMessage(subject: String,payload: Serializable) getSubject() : String getPayload() : Serializable setPayload(payload: Serializable) : void
Fig. 5. The Core interface.
as change its topology at run-time. It also includes methods to activate and deactivate the transport functionality, useful when it is implemented with concurrent activities, (e.g., to accept incoming connections on a TCP socket). Once activated, the transport waits for incoming requests from neighboring brokers or clients (e.g., connection requests, subscriptions, and so on), interprets them, and calls the appropriate methods on the Core component that, as we describe below, is the pivot of the whole architecture. Instead, objects implementing the interface Neighbor are used internally by the components of the routing layer as proxies to interact with the broker’s neighbors (i.e., the clients and other brokers it is directly connected to). Again, these objects provide high-level methods for the various operations (publish, subscribe, and so on) and hide all the access details concerned with the underlying network layer (e.g., protocol and addressing schema) therefore greatly simplifying the code of the components in the routing layer, described next. It is worth noting how Transport and Neighbor are closely related, since they abstract the same underlying transport media (e.g., TCP, UDP, or even email or SMS messages). As a consequence, a customized transport layer usually is composed of two classes, each implementing one of the two interfaces.
Router subscribe(neighborID: String,filter: Filter) : void
ConnectionManager connect(neighborID: String,n: Neighbor) : boolean
unsubscribe(neighborID: String,filter: Filter) : void
connect(neighborID: String,n: Neighbor,context: Serializable) : boolean
unsubscribeAll(neighborID: String) : void
softDisconnect(neighborID: String) : void
publish(neighborID: String,message: Message) : void
hardDisconnect(neighborID: String) : void
setCore(core: Core) : void
setCore(core: Core) : void
Fig. 6. The interfaces Router and ConnectionManager.
Routing. The main interfaces provided by this layer are those abstracting the components involved in routing (Core, Router, and ConnectionManager) and the one representing the data structure where subscriptions are stored for routing purposes (SubscriptionTable). A convenience class, NeighborSet, is also provided as a way to simplify the task of accessing, from within the routing components, information concerned with the broker’s neighbors represented by Neighbor instances. As its name suggests, the interface Core abstracts the central component of a REDS broker mediating the communication among the other components, which therefore do not have direct visibility of each other. As a consequence of its role, the Core interface mirrors most of the methods provided by the other interfaces. This design choice yields two benefits: it increases the decoupling among components, which need to be aware only of the existence of the Core, and provides a central place where all communication is funneled, therefore opening up opportunities for transparently intercepting and modifying messages before redirecting them to the intended components. As an example, we are currently exploiting this possibility in a Core component that stores incoming messages in a buffer, for enabling recovery of lost messages in scenarios where communication is unreliable by using the gossip protocol described in [9]. As a further responsibility, the Core is in charge of dispatching internal messages to the registered InternalMessageProcessors, as shown in Figure 5. Internal messages are control messages (i.e., not directly related with the messages published by clients) that are exchanged among brokers to assist them in their cooperative routing. Examples are messages containing a broker’s load (to avoid congested routes), location information (to implement location-aware routing schemes), or in the case of our aforementioned gossip protocol, messages containing the request for lost data. Internal messages are characterized by a subject (a simple string) and a payload (any serializable object). Objects implementing the InternalMessageProcessor interface can be registered to specific subjects. When the Core receives an internal message from the transport layer, it dispatches it to the registered processors based on its subject. The Router interface, shown in Figure 6, abstracts the component of a REDS broker that implements the specific routing strategy adopted (e.g., a centralized one, or any of the distributed ones discussed in Section 2.1). The methods of this interface are invoked by the transport layer, through the Core, to notify the Router component that a subscription, an unsubscription, or a message
SubscriptionTable
NeighborSet neighbors : Map
addSubscription(n: Neighbor,f: Filter) : void
NeighborSet()
removeSubscription(n: Neighbor,f: Filter) : void
add(n: Neighbor) : void
removeAllSubscriptions(n: Neighbor) : void
get(neighborID: String) : Neighbor
clear() : void
getAllNeighbors() : Collection
isSubscribed(n: Neighbor) : boolean
getAllNeighborsExcept(neighborID: String) : Collection
isFilterInTable(filter: Filter) : boolean
contains(neighborID: String) : boolean
getSingleSubscribedBroker(filter: Filter) : Neighbor
remove(neighborID: String) : void
getAllFilters(n: Neighbor) : Collection
clear() : void
getAllFilters(duplicate: boolean) : Collection
size() : int
getAllFiltersExcept(duplicate: boolean,n: Neighbor) : Collection
numberOfBrokers() : int
matches(message: Message) : Collection
numberOfClients() : int
getSubscribedNeighbors(f: Filter) : Collection
toString() : String
Fig. 7. The data structures shared by the components in the routing layer.
have been received from one of the broker’s neighbors, and need to be routed according to the strategy encapsulated in the component. Finally, the ConnectionManager interface, also shown in Figure 6, describes the component in charge of managing the overlay dispatching network. In particular, this component is notified by the transport (through the Core) when a new client or broker requested to connect (method connect) or to disconnect (method softDisconnect), and when the transport cannot reach a neighbor that was formerly connected (method hardDisconnect). In the presence of a distributed dispatcher, the ConnectionManager has a very critical role, since it must take care of maintaining the overlay dispatching network connected and efficiently rearrange the brokers’ routing tables when the topology of such network changes. Indeed, ConnectionManager is the key component of our architecture enabling support for publish-subscribe on a dynamic topology, and may encapsulate different strategies. More details are provided in Section 3.3 and 3.4. The components implementing the Core, Router, and ConnectionManager interfaces are expected to share an instance of the SubscriptionTable interface. The latter is shown in Figure 7 together with the aforementioned NeighborSet class, which is shared as well. SubscriptionTable provides methods for inserting and removing filters, as well as for selectively retrieve the filters that are relevant to a given neighbor or set of neighbors, or those matching a given message. The role of this interface is fundamental, in that it encapsulates the algorithm used to efficiently match messages against a set of filters. As with the other components, this logic is nicely encapsulated behind this interface, and can be redefined at will by programmers. 3.3
REDS in Action
To better understand the interaction among the various components of a REDS broker, in this section we discuss how they cooperate in performing some common tasks, as shown in Figure 8. Clearly, the actual sequence of operations for a given configuration may differ from the one we report here, since it ultimately depends on the implementation of the modules involved.
(a) A message is published.
(b) A new link is opened.
(c) A link failure is detected.
Fig. 8. How typical operations are accomplished in the REDS framework.
Publishing a Message. Figure 8(a) shows the typical sequence of operations involved in the publishing of a message msg by a broker that adopts a subscription forwarding strategy (subscriptions and unsubscriptions are handled similarly.) As soon as the message msg is received by the Transport component in the broker, this is passed to the Core, which delegates the Router to publish it. The Router queries the SubscriptionTable to determine the identifiers of the neighbors subscribed to msg. Then, it performs a loop to retrieve from the NeighborSet the Neighbor objects that correspond to the given identifiers, and calls their method publish to forward the message msg towards them. Handling Topological Reconfigurations. Figure 8(b) shows how the topology of a REDS dispatching network can be changed at run-time. In particular, it shows the typical sequence of operations involved in reacting to a request for opening a new connection coming from another broker, when a subscription forwarding routing strategy is in use. In this scenario, when the transport receives the request for opening the new connection, it calls the connect method of the Core, which delegates it to the ConnectionManager. After verifying that adding the new link does not break any topological constraint (e.g., does not create loops), the ConnectionManager adds the new neighbor to the NeighborSet and forwards to it all the subscriptions currently present into the SubscriptionTable. Removing of existing connections is handled similarly. Figure 8(c) shows how a broker reacts to the detection of a link failure. Here we consider a trivial ConnectionManager that does not attempt to reconnect the overlay network—unlike the components provided in the current REDS distribution, and discussed next. Under this hypothesis, when the ConnectionManager is notified by the Transport (through the Core) that a given neighboring broker can no longer be reached, it simply removes all the subscriptions formerly associated to this neighbor and removes it from the neighbor set. 3.4
Implemented Modules and Support Tools
REDS is an active project, with several new modules being under development and several others planned. Figure 9 shows the modules provided by the current version of REDS. They implement the interfaces defined in Section 3.2 and already allow programmers to choose among a number of different deployment solutions. As shown in the figure, the current version of REDS provides a set of classes that implement two kinds of transport layers: one using TCP links to connect each broker with its neighbors, and one based on UDP connections. Consequently two classes (not shown in figure) are also provided, which implement the DispatchingService interface. They allow clients to connect to brokers either using TCP or UDP. As for the routing layer, the class SimpleCore implements a basic core that delegates all its operations to the other components, i.e., the Router and the ConnectionManager, without any additional processing. Moreover, the classes SubscriptionForwardingRouter and MessageForwardingRouter implement the
Fig. 9. Currently available REDS modules.
corresponding routing strategies described in Section 2.1. Two different subscription tables are also provided. The class GenericTable implements the SubscriptionTable interface by maintaining a list of all the filters received for each neighbor. Matching occurs by invoking the method Filter.match on every element of this list. This approach is not particularly efficient but has the advantage of being simple and not making any assumption about the format of messages and filters. A radically different approach is taken by the PTreeSubscriptionTable, which implements the SubscriptionTable interface by using the more efficient algorithm described in [1]. However, it requires specialized messages and filters (PTreeMessage and PTreeFilter, not shown in figure), with the former organized as a set of attributes (i.e., key-value pairs) and the latter as a conjunction of predicates on the message attributes. Topological reconfiguration is supported in the routing layer by two alternatives, both implementing the ConnectionManager interface. The class DeferredUnsubCM implements the deferred unsubscription strategy described in [27], but does not provide any mechanism to attempt reconnection of the overlay network in presence of link failures. Conversely, ManetDeferredUnsubCM adopts the same strategy for reconciling subscription tables, but includes also a mechanism [24] to repair the overlay network, inspired by the MAODV multicast protocol [31] and geared towards mobile ad hoc networks. Alternative algorithms developed by our research group [9, 13, 20, 24] are also currently being integrated in REDS.
Fig. 10. The REDSMonitor inspecting a dispatching network with three brokers and four clients. Arrows represent the content of brokers’ subscription tables, by showing the route to be followed by matching events. public class Broker { public static void main(String[] args) { int tcpPort = 2000; SubscriptionForwardingRouter router = new SubscriptionForwardingRouter(); DeferredUnsubCM connectionManager = new DeferredUnsubCM(); TCPTransport stl = new TCPTransport(tcpPort); SubscriptionTable subscriptionTable = new GenericTable(); SimpleCore core = new SimpleCore(); core.setRouter(router); core.setConnectionManager(connectionManager); core.setSubscriptionTable(subscriptionTable); core.setTransport(stl); stl.start(); stl.openLink(‘‘reds-tcp:foo.polimi.it:2001’’); } }
Fig. 11. A simple broker.
Finally, the current release of REDS provides also a tool called REDSMonitor, shown in Figure 10, which provides support for testing and deploying the system. It is a graphical administration and monitoring tool, which provides facilities to remotely add and remove REDS brokers dynamically. It also allows special clients to be launched and attached to existing brokers. These clients can be remotely controlled through the REDSMonitor itself to subscribe, unsubscribe, and publish messages, thus enabling simple testing of the distributed infrastructure, as well as the collection of performance figures. 3.5
Programming Examples
Figure 11 shows how a broker can be easily setup and started. The last statement connects the new broker to an existing one running on the host foo.polimi.it and awaiting connections on the TCP port 2001. Note how, for the sake of simplicity, the new connection is opened just after starting the broker, while in principle it could be opened at any later time. Similarly, the closeLink method can be called to close an existing connection. Combining the two methods, the topology of the REDS dispatching network can be easily changed at run-time.
public class Publisher { public static void main(String[] args) { DispatchingService ds = new TCPDispatchingService(‘‘bar.polimi.it’’,2000); ds.open(); for(int i=0; i