Interface MessagingService
-
- All Superinterfaces:
Service
@DefaultServiceFactory(MessagingServiceFactory.class) public interface MessagingService extends Service
« start hereMain entry point to messaging API.Overview
Messaging service provides support for building message-oriented communications in the cluster of
Hekate
nodes. Message exchange is based on the concept of messaging channels. Such channels hide all the complexities of managing resources (like socket and threads) and provide a high level API for implementing various messaging patterns.- Messaging Channels
- Configuring Channels
- Accessing Channels
- Sending Messages
- Receiving Messages
- Routing and Load Balancing
- Thread Pooling
Messaging Channels
Messaging channel is a communication unit that can act as a sender, as a receiver or perform both of those roles simultaneously. Channels provide support for unicast messaging (node to node communication) and broadcast messaging (node to many nodes communication). Note that "unicast" and "broadcast" in this context are NOT related to UDP (all communications are TCP-based) and merely outline the communication patterns.
Configuring Channels
Configuration of a messaging channel is represented by the
MessagingChannelConfig
class.Instances of this class can be registered via the
MessagingServiceFactory.withChannel(MessagingChannelConfig)
method.Example:
// Configure channel that will support messages of String type (for simplicity). MessagingChannelConfig<String> channelCfg = MessagingChannelConfig.of(String.class) .withName("example.channel") // Channel name. // Message receiver (optional - if not specified then channel will act as a sender only) .withReceiver(msg -> { System.out.println("Request received: " + msg.payload()); // Reply (if this is a request and not a unidirectional notification). if (msg.mustReply()) { msg.reply("some response"); } }); // Start node. Hekate hekate = new HekateBootstrap() // Register channel to the messaging service. .withMessaging(messaging -> messaging.withChannel(channelCfg) ) .join();
Note: This example requires Spring Framework integration (see HekateSpringBootstrap).<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:h="http://www.hekate.io/spring/hekate-core" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.hekate.io/spring/hekate-core http://www.hekate.io/spring/hekate-core.xsd"> <h:node id="hekate"> <!-- Messaging service. --> <h:messaging> <h:channel name="example.channel"> <h:receiver> <bean class="foo.bar.SomeMessageReceiver"/> </h:receiver> <!-- ...other options... --> </h:channel> </h:messaging> <!-- ...other services... --> </h:node> </beans>
Note: This example requires Spring Framework integration (see HekateSpringBootstrap).<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hekate" class="io.hekate.spring.bean.HekateSpringBootstrap"> <property name="services"> <list> <!-- Messaging service. --> <bean class="io.hekate.messaging.MessagingServiceFactory"> <property name="channels"> <list> <bean id="example.channel" class="io.hekate.messaging.MessagingChannelConfig"> <property name="name" value="example.channel"/> <property name="receiver"> <bean class="foo.bar.SomeMessageReceiver"/> </property> <!-- ...other options... --> </bean> </list> </property> </bean> <!-- ...other services... --> </list> </property> </bean> </beans>
For more details about the configuration options please see the documentation of
MessagingChannelConfig
class.Accessing Channels
Channel can be accessed via the
channel(String, Class)
method, with the first parameter being thechannel name
and the second parameter being thebase type
of messages that can be transferred over that channel:MessagingChannel<String> channel = hekate.messaging().channel("example.channel", String.class);
Sending Messages
MessagingChannel
provides API for the following communication patterns:- Request - Submit a request and get a response
- Send - Submit a one-way message that doesn't need a response
- Subscribe - Submit a request (subscribe) and get multiple response chunks (updates)
- Aggregate - Submit a one-way message to multiple nodes simultaneously
- Broadcast - Submit a request to multiple nodes simultaneously and aggregate their responses
Request
Request
interface can be used for bidirectional communications with remote nodes using the request-response pattern:// Submit request and synchronously await for the response. String response = channel.newRequest("example request").response();
... or using a completely asynchronous callback-based approach:
// Submit request and process response in the asynchronous callback. channel.newRequest("example request").submit((err, rsp) -> { if (err == null) { System.out.println("Got response: " + rsp.payload()); } else { System.out.println("Request failed: " + err); } });
For more details please see the documentation of
Request
interface.Send
Send
interface provides support for unidirectional communications (i.e. when remote node doesn't need to send back a response) using the fire and forget approach:// Send message and synchronously await for the operation's result. channel.newSend("example message").sync();
... or using a completely asynchronous callback-based approach:
// Send message and process results in the asynchronous callback. channel.newSend("example message").submit(err -> { if (err == null) { System.out.println("Message sent."); } else { System.out.println("Sending failed: " + err); } });
For more details please see the documentation of
Send
interface.Subscribe
Subscribe
interface can be used for bidirectional communications with remote nodes using the request-response pattern. Unlike the basicRequest
operation, this operation doesn't end with the first response and continues receiving updates unless the very final response is received:// Submit request and synchronously await for the response. channel.newSubscribe("example request").submit((err, rsp) -> { if (rsp.isLastPart()) { System.out.println("Done: " + rsp.payload()); } else { System.out.println("Update: " + rsp.payload()); } });
For more details please see the documentation of
Subscribe
interface.Aggregate
Aggregate
interface can be used for bidirectional communications by submitting a message to multiple nodes and gathering (aggregating) replies from those nodes. Results of such aggregation are represented by theAggregateResult
interface. This interface provides methods for analyzing responses from remote nodes and checking for possible failures.Below is the example of synchronous aggregation:
// Submit aggregation request. channel.newAggregate("example message").results().forEach(rslt -> System.out.println("Got results: " + rslt) );
... or using a completely asynchronous callback-based approach:
// Asynchronously submit aggregation request. channel.newAggregate("example message").submit((err, rslts) -> { if (err == null) { rslts.forEach(rslt -> System.out.println("Got results: " + rslt) ); } else { System.out.println("Aggregation failure: " + err); } });
For more details please see the documentation of
Aggregate
interface.Broadcast
Broadcast
interface provides support for unidirectional broadcasting (i.e. when remote nodes do not need to send a reply and no aggregation should take place) using the fire and forget approach.Below is the example of synchronous broadcast:
// Broadcast message. channel.newBroadcast("example message").sync();
... or using a completely asynchronous callback-based approach:
// Asynchronously broadcast message. channel.newBroadcast("example message").submit((err, rslt) -> { if (err == null) { System.out.println("Broadcast success."); } else { System.out.println("Broadcast failure: " + err); } });
For more details please see the documentation of
Broadcast
interface.Receiving Messages
Messaging channel can receive messages from remote nodes by registering an instance of
MessageReceiver
interface via theMessagingChannelConfig.setReceiver(MessageReceiver)
method.Important: Only one receiver can be registered per each messaging channel.
Received messages are represented by the
Message
interface. This interface provides methods forgetting
the payload of a received message as well as methods forreplying
to that message.Below is the example of
MessageReceiver
implementation:public class ExampleReceiver implements MessageReceiver<String> { @Override public void receive(Message<String> msg) { // Get payload. String payload = msg.payload(); // Check if the sender expects a response. if (msg.mustReply()) { System.out.println("Request received: " + payload); // Send back a response. msg.reply("...some response..."); } else { // No need to send a response since this is a unidirectional message. System.out.println("Notification received: " + payload); } } }
Routing and Load Balancing
Every messaging channel uses an instance of
LoadBalancer
interface to perform routing of unicast operations (likesend(...)
andrequest(...)
). Load balancer can be pre-configured via theMessagingChannelConfig.setLoadBalancer(LoadBalancer)
method or specified dynamically via theMessagingChannel.withLoadBalancer(LoadBalancer)
method. If load balancer is not specified then messaging channel will fall back to theDefaultLoadBalancer
.Note that load balancing does not get applied to broadcast operations (like
MessagingChannel.newBroadcast(Object)
andMessagingChannel.newAggregate(Object)
). Such operations are submitted to all nodes within the channel's cluster topology. Please see the "Cluster topology filtering" section for details of how to control the channel's cluster topology.Consistent Routing
Applications can provide an affinity key to the
LoadBalancer
so that it could perform consistent routing based on some application-specific criteria. For example, if theDefaultLoadBalancer
is being used by the messaging channel then it will make sure that all messages with the same affinity key will always be routed to the same cluster node (unless the cluster topology doesn't change) by using the channel'spartition mapper
. Custom implementations of theLoadBalancer
interface can use their own algorithms for consistent routing.Affinity key can for unicast operations can be specified via the following methods:
Affinity key can for broadcast operations can be specified via the following methods:
Note: If affinity key is specified for a broadcast operation then messaging channel will use its
partition mapper
to select the targetPartition
for that key. Once the partition is selected then all of itsnodes
will be used for broadcast (i.e.primary node
+backup nodes
).Thread Affinity
Besides providing a hint to the
LoadBalancer
, specifying an affinity key also instructs the messaging channel to process all messages of the same affinity key on the same thread. This applies both to sending a message (seeSendCallback
orRequestCallback
) and to receiving a message (seeMessageReceiver.receive(Message)
).Cluster Topology Filtering
It is possible to narrow down the list of nodes that are visible to the
MessagingChannel
by setting aClusterNodeFilter
. Such filter can be pre-configured via theMessagingChannelConfig.setClusterFilter(ClusterNodeFilter)
method or set dynamically via theClusterFilterSupport.filter(ClusterNodeFilter)
method.Note that the
MessagingChannel
interface extends theClusterFilterSupport
interface, which gives it a number of shortcut methods for dynamic filtering of the cluster topology:ClusterFilterSupport.forRemotes()
ClusterFilterSupport.forRole(String)
ClusterFilterSupport.forProperty(String)
ClusterFilterSupport.forNode(ClusterNode)
ClusterFilterSupport.forOldest()
ClusterFilterSupport.forYoungest()
- ...
etc
If filter is specified then all messaging operations will be distributed among only those nodes that match the filter's criteria.
Thread Pooling
Messaging service manages a pool of threads for each of its registered channels. The following thread pools are managed:
-
NIO thread pool - thread pool for managing NIO socket channels. The size of this thread pool is controlled by the
MessagingConfigBase.setNioThreads(int)
configuration option. -
Worker thread pool - Optional thread pool to offload messages processing work from NIO threads. The size of this pool is
controlled by the
MessagingChannelConfig.setWorkerThreads(int)
configuration option. It is recommended to set this parameter in case if message processing is a heavy operation that can block NIO thread for a long time.
- See Also:
MessagingServiceFactory
-
-
Method Summary
All Methods Instance Methods Abstract Methods Modifier and Type Method Description List<MessagingChannel<?>>
allChannels()
Returns all channels that areregistered
within this service.MessagingChannel<Object>
channel(String name)
Returns an unchecked messaging channel for the specified name.<T> MessagingChannel<T>
channel(String name, Class<T> baseType)
Returns a type-safe messaging channel for the specified name.boolean
hasChannel(String channelName)
Returnstrue
if this service has a messaging channel with the specified name.
-
-
-
Method Detail
-
allChannels
List<MessagingChannel<?>> allChannels()
Returns all channels that areregistered
within this service.- Returns:
- Channels or an empty list if there are no registered channels.
-
channel
MessagingChannel<Object> channel(String name) throws IllegalArgumentException
Returns an unchecked messaging channel for the specified name.- Parameters:
name
- Channel name (seeMessagingChannelConfig.setName(String)
).- Returns:
- Messaging channel.
- Throws:
IllegalArgumentException
- if there is no such channel configuration with the specified name.- See Also:
MessagingChannelConfig
-
channel
<T> MessagingChannel<T> channel(String name, Class<T> baseType) throws IllegalArgumentException
Returns a type-safe messaging channel for the specified name.- Type Parameters:
T
- Base type of messages that can be supported by the messaging channel.- Parameters:
name
- Channel name (seeMessagingChannelConfig.setName(String)
).baseType
- Base type of messages that can be supported by the messaging channel (seeMessagingChannelConfig(Class)
).- Returns:
- Messaging channel.
- Throws:
IllegalArgumentException
- if there is no such channel configuration with the specified name.- See Also:
MessagingChannelConfig
-
hasChannel
boolean hasChannel(String channelName)
Returnstrue
if this service has a messaging channel with the specified name.- Parameters:
channelName
- Channel name (seeMessagingChannelConfig.setName(String)
).- Returns:
true
if messaging channel exists.
-
-