Class HttpRemoteQueryDispatcher

java.lang.Object
io.axoniq.framework.springcloud.query.HttpRemoteQueryDispatcher
All Implemented Interfaces:
RemoteQueryDispatcher

public class HttpRemoteQueryDispatcher extends Object implements RemoteQueryDispatcher
Sends queries to other members over HTTP, reading the responses they stream back as Server-Sent Events.

Reading a stream blocks for as long as the answering member keeps it open, so each query is read on the given executor rather than on the thread that dispatched it. A virtual-thread executor suits this: the threads spend their lives waiting on a socket.

Responses are buffered in a queue of a fixed size. A member answering faster than this application consumes fills that queue, and the query then fails rather than growing the buffer until memory runs out. Where a bounded buffer is the wrong trade for a particular application, the size is configurable.

Every query is given a deadline, after which its responses are no longer waited for. This is a backstop rather than the usual way a query ends: the answering member closes its own stream first, on a shorter timeout of its own. It exists for the member that stops answering without saying so — one that was killed, or partitioned away — whose socket would otherwise never report anything, leaving the query unanswered and its thread parked for as long as the operating system allows.

Since:
5.4.0
Author:
Allard Buijze
  • Field Details

    • DEFAULT_BUFFER_SIZE

      public static final int DEFAULT_BUFFER_SIZE
      The number of responses buffered per query when no other size is configured.
      See Also:
    • DEFAULT_RESPONSE_TIMEOUT

      public static final Duration DEFAULT_RESPONSE_TIMEOUT
      How long a query's responses are waited for when no other deadline is configured.
    • DEFAULT_SUBSCRIPTION_INACTIVITY_TIMEOUT

      public static final Duration DEFAULT_SUBSCRIPTION_INACTIVITY_TIMEOUT
      How long a subscription may hear nothing at all from the answering member before it is given up on, when no other window is configured.

      Not a deadline on the subscription, which lasts as long as the subscriber wants it to, but on silence: the answering member sends a keep-alive well inside this window, so a subscription that hears nothing for the whole of it is one whose member is gone.

      Silence is measured by looking every half a window, so a departed member is given up on somewhere between one and one and a half times this. It is the order of magnitude that matters, not the exact moment.

  • Constructor Details

    • HttpRemoteQueryDispatcher

      public HttpRemoteQueryDispatcher(org.springframework.web.client.RestClient restClient, String queryEndpoint, Executor executor, MessageConverter converter)
      Constructs an HttpRemoteQueryDispatcher buffering DEFAULT_BUFFER_SIZE responses per query.
      Parameters:
      restClient - the client sending the queries
      queryEndpoint - the path other members receive queries under
      executor - the executor each query's response stream is read on
      converter - the converter reading the data of each response event, and attached to the responses read from it for inline payload conversion
    • HttpRemoteQueryDispatcher

      public HttpRemoteQueryDispatcher(org.springframework.web.client.RestClient restClient, String queryEndpoint, Executor executor, MessageConverter converter, int bufferSize, Duration responseTimeout, Duration subscriptionInactivityTimeout, ScheduledExecutorService scheduler)
      Constructs an HttpRemoteQueryDispatcher buffering bufferSize responses per query.
      Parameters:
      restClient - the client sending the queries
      queryEndpoint - the path other members receive queries under
      executor - the executor each query's response stream is read on
      converter - the converter reading the data of each response event, and attached to the responses read from it for inline payload conversion
      bufferSize - how many responses to a single query are held before the answering member outpacing this application fails the query.
      responseTimeout - how long a query's responses are waited for before it is given up on. Should exceed the timeout the answering members apply to their own response streams, so that a member which is merely slow ends the query itself rather than being given up on here.
      subscriptionInactivityTimeout - how long a subscription may hear nothing at all from the answering member before it is given up on. Must exceed the interval at which members keep an idle subscription alive.
      scheduler - runs the deadline of each query and the silence check of each subscription. Cancelled deadlines are removed from it, so a query that completes quickly does not leave its responses reachable until the deadline would have elapsed.
  • Method Details

    • dispatch

      public MessageStream<QueryResponseMessage> dispatch(Member member, QueryMessage query)
      Description copied from interface: RemoteQueryDispatcher
      Sends the given query to the given member, reporting the responses it streams back.

      The returned stream is live: responses appear on it as the answering member produces them, and it completes when that member has answered in full. A failure reported by the answering member, or one reaching it, ends the stream in error. Closing the stream stops the member from being read any further.

      Specified by:
      dispatch in interface RemoteQueryDispatcher
      Parameters:
      member - the member to send the query to
      query - the query to send
      Returns:
      the stream of responses the given member answers with
    • openSubscriptionQueryUpdateStream

      public MessageStream<QueryResponseMessage> openSubscriptionQueryUpdateStream(Member member, QueryMessage query, int updateBufferSize, RemoteQueryDispatcher.SubscriptionListener listener)
      Description copied from interface: RemoteQueryDispatcher
      Opens the stream of updates the given query produces on the given member.

      A streaming query like any other, except that it does not end of its own accord: the member keeps it open, emitting an update whenever the state the query reads changes, until it is released or the member reports a failure. Closing the stream releases the subscription.

      The stream completes normally only when the member says why it ends: because the subscription is over, or because the member is leaving the cluster and has stopped emitting updates. A stream the member ends without saying either fails, as does a lost connection, since the subscriber cannot know which updates it missed.

      Carries updates alone. The initial result of a subscription query is a query like any other, asked for with RemoteQueryDispatcher.dispatch(Member, QueryMessage) once the update stream of every member advertising the query is open, so that an update emitted while that result is being produced still has somewhere to arrive. RemoteQueryDispatcher.SubscriptionListener.opened() is what says when that is for the given member: it runs once the member has registered the subscription, and so before any update it emits.

      Specified by:
      openSubscriptionQueryUpdateStream in interface RemoteQueryDispatcher
      Parameters:
      member - the member to open the update stream on
      query - the query to subscribe with
      updateBufferSize - how many updates to hold for this subscriber before failing the subscription
      listener - told when the member has registered the subscription, and when it reports the subscription over
      Returns:
      the updates the given member emits for the given query