Introduce PartitionedChannel (#8617)

* Introduce `PartitionedChannel`

* Implement a `PartitionedChannel` as an extension of the `AbstractExecutorChannel`
* Supply this channel with a `PartitionedDispatcher` which is an extension of the `AbstractDispatcher`
* The target partition is essentially a `UnicastingDispatcher` with a single thead executor

* * Fix language in Javadocs
* Add docs for `PartitionedChannel` into `channel.adoc`
* Pre-populate partitions in the `PartitionedDispatcher`
to ensure a thread number reflection of the partition it is used for (for default `ThreadFactory`)
* Add Javadocs to `public` methods
* Add Java DSL `PartitionedChannelSpec` and respective factory methods into `Channels`
* Use `IntegrationMessageHeaderAccessor.CORRELATION_ID` header as a default partition key

* * Fix language in Javadocs
* Fix Checkstyle violations
* Mark `PartitionedDispatcher.prePopulatedPartitions()` with `synchronized`

* * Populate partitions from `PartitionedDispatcher` ctor
and when a custom `ThreadFactory` is set

* * Clear `executors` in `populatedPartitions()`

* * Bring back `synchronized populatedPartitions()` and use it in the `dispatch()`
This commit is contained in:
Artem Bilan
2023-05-10 15:49:33 -04:00
committed by GitHub
parent 32b6d823c0
commit 5f1e12ea35
8 changed files with 651 additions and 2 deletions

View File

@@ -203,6 +203,30 @@ CAUTION: The sender can sometimes block.
For example, when using a `TaskExecutor` with a rejection policy that throttles the client (such as the `ThreadPoolExecutor.CallerRunsPolicy`), the sender's thread can execute the method any time the thread pool is at its maximum capacity and the executor's work queue is full.
Since that situation would only occur in a non-predictable way, you should not rely upon it for transactions.
[[partitioned-channel]]
===== `PartitionedChannel`
Starting with version 6.1, a `PartitionedChannel` implementation is provided.
This is an extension of `AbstractExecutorChannel` and represents point-to-point dispatching logic where the actual consumption is processed on a specific thread, determined by the partition key evaluated from a message sent to this channel.
This channel is similar to the `ExecutorChannel` mentioned above, but with the difference that messages with the same partition key are always handled in the same thread, preserving ordering.
It does not require an external `TaskExecutor`, but can be configured with a custom `ThreadFactory` (e.g. `Thread.ofVirtual().name("partition-", 0).factory()`).
This factory is used to populate single-thread executors into a `MessageDispatcher` delegate, per partition.
By default, the `IntegrationMessageHeaderAccessor.CORRELATION_ID` message header is used as the partition key.
This channel can be configured as a simple bean:
====
[source,java]
----
@Bean
PartitionedChannel somePartitionedChannel() {
return new PartitionedChannel(3, (message) -> message.getHeaders().get("partitionKey"));
}
----
====
The channel will have `3` partitions - dedicated threads; will use the `partitionKey` header to determine in which partition the message will be handled.
See `PartitionedChannel` class Javadocs for more information.
[[flux-message-channel]]
===== `FluxMessageChannel`

View File

@@ -34,6 +34,11 @@ See <<./handler-advice.adoc#context-holder-advice, Context Holder Advice>> for m
The `IntegrationFlow` can now end with a convenient `handleReactive(ReactiveMessageHandler)` operator.
See <<./reactive-streams.adoc#reactive-message-handler, `ReactiveMessageHandler`>> for more information.
[[x6.1-partitioned-channel]]
==== `PartitionedChannel`
A new `PartitionedChannel` has been introduced to process messages with the same partition key in the same thread.
See <<./channel.adoc#partitioned-channel, `PartitionedChannel`>> for more information.
[[x6.1-general]]
=== General Changes