Editing pass for scalability.adoc
I improved readability and consistency and corrected sentence errors. No questions this time. Updating code review changes as a part of merge
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
:batch-asciidoc: http://docs.spring.io/spring-batch/reference/html/
|
||||
:batch-asciidoc: ./
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
|
||||
@@ -10,14 +10,18 @@ Many batch processing problems can be solved with single threaded,
|
||||
single process jobs, so it is always a good idea to properly check if that
|
||||
meets your needs before thinking about more complex implementations. Measure
|
||||
the performance of a realistic job and see if the simplest implementation
|
||||
meets your needs first: you can read and write a file of several hundred
|
||||
meets your needs first. You can read and write a file of several hundred
|
||||
megabytes in well under a minute, even with standard hardware.
|
||||
|
||||
When you are ready to start implementing a job with some parallel
|
||||
processing, Spring Batch offers a range of options, which are described in
|
||||
this chapter, although some features are covered elsewhere. At a high level
|
||||
there are two modes of parallel processing: single process, multi-threaded;
|
||||
and multi-process. These break down into categories as well, as
|
||||
this chapter, although some features are covered elsewhere. At a high level,
|
||||
there are two modes of parallel processing:
|
||||
|
||||
* Single process, multi-threaded
|
||||
* Multi-process
|
||||
|
||||
These break down into categories as well, as
|
||||
follows:
|
||||
|
||||
|
||||
@@ -32,7 +36,7 @@ When you are ready to start implementing a job with some parallel
|
||||
|
||||
* Partitioning a Step (single or multi process)
|
||||
|
||||
Next we review the single-process options first, and then the
|
||||
First, we review the single-process options. Then we review the
|
||||
multi-process options.
|
||||
|
||||
[[multithreadedStep]]
|
||||
@@ -41,8 +45,8 @@ Next we review the single-process options first, and then the
|
||||
=== Multi-threaded Step
|
||||
|
||||
The simplest way to start parallel processing is to add a
|
||||
`TaskExecutor` to your Step configuration, e.g. as an
|
||||
attribute of the `tasklet`:
|
||||
`TaskExecutor` to your Step configuration. For example, you might add an
|
||||
attribute of the `tasklet`, as shown in the following example:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -52,24 +56,24 @@ The simplest way to start parallel processing is to add a
|
||||
</step>
|
||||
----
|
||||
|
||||
In this example the taskExecutor is a reference to another bean
|
||||
definition, implementing the `TaskExecutor`
|
||||
interface. `TaskExecutor` is a standard Spring
|
||||
In this example, the taskExecutor is a reference to another bean
|
||||
definition that implements the `TaskExecutor`
|
||||
interface. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/task/TaskExecutor.html[`TaskExecutor`] is a standard Spring
|
||||
interface, so consult the Spring User Guide for details of available
|
||||
implementations. The simplest multi-threaded
|
||||
`TaskExecutor` is a
|
||||
`SimpleAsyncTaskExecutor`.
|
||||
|
||||
The result of the above configuration will be that the Step
|
||||
executes by reading, processing and writing each chunk of items
|
||||
The result of the above configuration is that the `Step`
|
||||
executes by reading, processing, and writing each chunk of items
|
||||
(each commit interval) in a separate thread of execution. Note
|
||||
that this means there is no fixed order for the items to be
|
||||
processed, and a chunk might contain items that are
|
||||
non-consecutive compared to the single-threaded case. In addition
|
||||
to any limits placed by the task executor (e.g. if it is backed by
|
||||
to any limits placed by the task executor (such as whether it is backed by
|
||||
a thread pool), there is a throttle limit in the tasklet
|
||||
configuration which defaults to 4. You may need to increase this
|
||||
to ensure that a thread pool is fully utilised, e.g.
|
||||
to ensure that a thread pool is fully utilized, as shown in the following example:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -86,28 +90,28 @@ Note also that there may be limits placed on concurrency by
|
||||
those resources at least as large as the desired number of
|
||||
concurrent threads in the step.
|
||||
|
||||
There are some practical limitations of using multi-threaded Steps
|
||||
for some common Batch use cases. Many participants in a Step (e.g. readers
|
||||
and writers) are stateful, and if the state is not segregated by thread,
|
||||
then those components are not usable in a multi-threaded Step. In
|
||||
particular most of the off-the-shelf readers and writers from Spring Batch
|
||||
There are some practical limitations of using multi-threaded `Step` implementations
|
||||
for some common batch use cases. Many participants in a `Step` (such as readers
|
||||
and writers) are stateful. If the state is not segregated by thread,
|
||||
then those components are not usable in a multi-threaded `Step`. In
|
||||
particular, most of the off-the-shelf readers and writers from Spring Batch
|
||||
are not designed for multi-threaded use. It is, however, possible to work
|
||||
with stateless or thread safe readers and writers, and there is a sample
|
||||
(parallelJob) in the Spring Batch Samples that show the use of a process
|
||||
(called `parallelJob`) in the https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples[Spring Batch Samples] that show the use of a process
|
||||
indicator (see <<readersAndWriters.adoc#process-indicator,Preventing State Persistence>>) to keep
|
||||
track of items that have been processed in a database input table.
|
||||
|
||||
Spring Batch provides some implementations of
|
||||
ItemWriter and
|
||||
`ItemReader`. Usually they say in the
|
||||
Javadocs if they are thread safe or not, or what you have to do to
|
||||
`ItemWriter` and
|
||||
`ItemReader`. Usually, they say in the
|
||||
Javadoc if they are thread safe or not or what you have to do to
|
||||
avoid problems in a concurrent environment. If there is no
|
||||
information in Javadocs, you can check the implementation to see
|
||||
information in the Javadoc, you can check the implementation to see
|
||||
if there is any state. If a reader is not thread safe, it may
|
||||
still be efficient to use it in your own synchronizing delegator.
|
||||
You can synchronize the call to `read()` and as
|
||||
long as the processing and writing is the most expensive part of
|
||||
the chunk your step may still complete much faster than in a
|
||||
the chunk, your step may still complete much faster than it would in a
|
||||
single threaded configuration.
|
||||
|
||||
|
||||
@@ -117,11 +121,11 @@ Spring Batch provides some implementations of
|
||||
=== Parallel Steps
|
||||
|
||||
As long as the application logic that needs to be parallelized can
|
||||
be split into distinct responsibilities, and assigned to individual steps
|
||||
be split into distinct responsibilities and assigned to individual steps,
|
||||
then it can be parallelized in a single process. Parallel Step execution
|
||||
is easy to configure and use, for example, to execute steps
|
||||
`(step1,step2)` in parallel with
|
||||
`step3`, you could configure a flow like this:
|
||||
`step3`, as shown in the following example:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -146,7 +150,7 @@ The configurable "task-executor" attribute is used to specify which
|
||||
`TaskExecutor` implementation should be used to execute the individual
|
||||
flows. The default is `SyncTaskExecutor`, but an
|
||||
asynchronous `TaskExecutor` is required to run the steps in parallel. Note
|
||||
that the job will ensure that every flow in the split completes before
|
||||
that the job ensures that every flow in the split completes before
|
||||
aggregating the exit statuses and transitioning.
|
||||
|
||||
See the section on <<step.adoc#split-flows,Split Flows>> for more
|
||||
@@ -157,38 +161,37 @@ See the section on <<step.adoc#split-flows,Split Flows>> for more
|
||||
|
||||
=== Remote Chunking
|
||||
|
||||
In Remote Chunking the Step processing is split across multiple
|
||||
processes, communicating with each other through some middleware. Here is
|
||||
a picture of the pattern in action:
|
||||
In remote chunking, the `Step` processing is split across multiple
|
||||
processes, communicating with each other through some middleware. The following
|
||||
image shows the pattern:
|
||||
|
||||
.Remote Chunking
|
||||
image::{batch-asciidoc}images/remote-chunking.png[Remote Chunking, scaledwidth="60%"]
|
||||
|
||||
The Master component is a single process, and the Slaves are
|
||||
multiple remote processes. Clearly this pattern works best if the Master
|
||||
The master component is a single process, and the slaves are
|
||||
multiple remote processes. This pattern works best if the master
|
||||
is not a bottleneck, so the processing must be more expensive than the
|
||||
reading of items (this is often the case in practice).
|
||||
reading of items (as is often the case in practice).
|
||||
|
||||
The Master is just an implementation of a Spring Batch
|
||||
Step, with the `ItemWriter` replaced with a generic
|
||||
The master is an implementation of a Spring Batch
|
||||
`Step` with the `ItemWriter` replaced by a generic
|
||||
version that knows how to send chunks of items to the middleware as
|
||||
messages. The Slaves are standard listeners for whatever middleware is
|
||||
being used (e.g. with JMS they would be
|
||||
`MesssageListeners`), and their role is to process
|
||||
messages. The slaves are standard listeners for whatever middleware is
|
||||
being used (for example, with JMS, they would be
|
||||
`MesssageListener` implementations), and their role is to process
|
||||
the chunks of items using a standard `ItemWriter` or
|
||||
`ItemProcessor` plus
|
||||
`ItemWriter`, through the
|
||||
`ChunkProcessor` interface. One of the advantages of
|
||||
using this pattern is that the reader, processor and writer components are
|
||||
using this pattern is that the reader, processor, and writer components are
|
||||
off-the-shelf (the same as would be used for a local execution of the
|
||||
step). The items are divided up dynamically and work is shared through the
|
||||
middleware, so if the listeners are all eager consumers, then load
|
||||
middleware, so that, if the listeners are all eager consumers, then load
|
||||
balancing is automatic.
|
||||
|
||||
The middleware has to be durable, with guaranteed delivery and
|
||||
The middleware has to be durable, with guaranteed delivery and a
|
||||
single consumer for each message. JMS is the obvious candidate, but other
|
||||
options exist in the grid computing and shared memory product space (e.g.
|
||||
Java Spaces).
|
||||
options (such as Java Spaces exist in the grid computing and shared memory product space.
|
||||
|
||||
[[partitioning]]
|
||||
|
||||
@@ -196,37 +199,37 @@ The middleware has to be durable, with guaranteed delivery and
|
||||
=== Partitioning
|
||||
|
||||
Spring Batch also provides an SPI for partitioning a `Step` execution
|
||||
and executing it remotely. In this case the remote participants are simply
|
||||
and executing it remotely. In this case, the remote participants are
|
||||
`Step` instances that could just as easily have been configured and used for
|
||||
local processing. Here is a picture of the pattern in action:
|
||||
local processing. The following image shows the pattern:
|
||||
|
||||
.Partitioning
|
||||
image::{batch-asciidoc}images/partitioning-overview.png[Partitioning Overview, scaledwidth="60%"]
|
||||
|
||||
The `Job` is executing on the left hand side as a sequence of `Steps`,
|
||||
and one of the `Steps` is labelled as a Master. The Slaves in this picture
|
||||
The `Job` runs on the left-hand side as a sequence of `Step` instances,
|
||||
and one of the `Step` instances is labeled as a master. The slaves in this picture
|
||||
are all identical instances of a `Step`, which could in fact take the place
|
||||
of the Master resulting in the same outcome for the `Job`. The Slaves are
|
||||
typically going to be remote services, but could also be local threads of
|
||||
execution. The messages sent by the Master to the Slaves in this pattern
|
||||
do not need to be durable, or have guaranteed delivery: Spring Batch
|
||||
meta-data in the JobRepository will ensure that
|
||||
each Slave is executed once and only once for each `Job` execution.
|
||||
of the master, resulting in the same outcome for the `Job`. The slaves are
|
||||
typically going to be remote services but could also be local threads of
|
||||
execution. The messages sent by the master to the slaves in this pattern
|
||||
do not need to be durable or have guaranteed delivery. Spring Batch
|
||||
metadata in the JobRepository ensures that
|
||||
each slave is executed once and only once for each `Job` execution.
|
||||
|
||||
The SPI in Spring Batch consists of a special implementation of `Step`
|
||||
(the `PartitionStep`), and two strategy interfaces
|
||||
(called the `PartitionStep`) and two strategy interfaces
|
||||
that need to be implemented for the specific environment. The strategy
|
||||
interfaces are `PartitionHandler` and
|
||||
`StepExecutionSplitter`, and their role is show in
|
||||
the sequence diagram below:
|
||||
`StepExecutionSplitter`, and their role is shown in
|
||||
the following sequence diagram:
|
||||
|
||||
.Partitioning SPI
|
||||
image::{batch-asciidoc}images/partitioning-spi.png[Partitioning SPI, scaledwidth="60%"]
|
||||
|
||||
The `Step` on the right in this case is the "remote" Slave, so
|
||||
potentially there are many objects and or processes playing this role, and
|
||||
the `PartitionStep` is shown driving the execution. The `PartitionStep`
|
||||
configuration looks like this:
|
||||
The `Step` on the right in this case is the "remote" slave, so,
|
||||
potentially, there are many objects and or processes playing this role, and
|
||||
the `PartitionStep` is shown driving the execution. The following example shows the `PartitionStep`
|
||||
configuration:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -238,19 +241,19 @@ The `Step` on the right in this case is the "remote" Slave, so
|
||||
</step>
|
||||
----
|
||||
|
||||
Similar to the multi-threaded step's throttle-limit
|
||||
attribute, the grid-size attribute prevents the task executor from
|
||||
Similar to the multi-threaded step's `throttle-limit`
|
||||
attribute, the `grid-size` attribute prevents the task executor from
|
||||
being saturated with requests from a single step.
|
||||
|
||||
There is a simple example which can be copied and extended in the
|
||||
unit test suite for Spring Batch Samples (see
|
||||
`*PartitionJob.xml` configuration).
|
||||
There is a simple example that can be copied and extended in the
|
||||
unit test suite for https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples/src/main/resources/jobs[Spring Batch Samples] (see
|
||||
`Partition*Job.xml` configuration).
|
||||
|
||||
Spring Batch creates step executions for the partitions called
|
||||
"step1:partition0", etc., so many people prefer to call the master step
|
||||
"step1:master" for consistency. With Spring 3.0 you can do this using an
|
||||
alias for the step (specifying the `name` attribute
|
||||
instead of the `id`).
|
||||
"step1:partition0", and so on. Many people prefer to call the master step
|
||||
"step1:master" for consistency. You can use an
|
||||
alias for the step (by specifying the `name` attribute
|
||||
instead of the `id` attribute).
|
||||
|
||||
[[partitionHandler]]
|
||||
|
||||
@@ -260,30 +263,30 @@ Spring Batch creates step executions for the partitions called
|
||||
The `PartitionHandler` is the component that
|
||||
knows about the fabric of the remoting or grid environment. It is able
|
||||
to send `StepExecution` requests to the remote
|
||||
`Steps`, wrapped in some fabric-specific format, like a DTO. It does not
|
||||
have to know how to split up the input data, or how to aggregate the
|
||||
result of multiple `Step` executions. Generally speaking it probably also
|
||||
doesn't need to know about resilience or failover, since those are
|
||||
features of the fabric in many cases, and anyway Spring Batch always
|
||||
provides restartability independent of the fabric: a failed `Job` can
|
||||
always be restarted and only the failed `Steps` will be
|
||||
`Step` instances, wrapped in some fabric-specific format, like a DTO. It does not
|
||||
have to know how to split the input data or how to aggregate the
|
||||
result of multiple `Step` executions. Generally speaking, it probably also
|
||||
does not need to know about resilience or failover, since those are
|
||||
features of the fabric in many cases. In any case, Spring Batch always
|
||||
provides restartability independent of the fabric. A failed `Job` can
|
||||
always be restarted and only the failed `Steps` are
|
||||
re-executed.
|
||||
|
||||
The `PartitionHandler` interface can have
|
||||
specialized implementations for a variety of fabric types: e.g. simple
|
||||
specialized implementations for a variety of fabric types, including simple
|
||||
RMI remoting, EJB remoting, custom web service, JMS, Java Spaces, shared
|
||||
memory grids (like Terracotta or Coherence), grid execution fabrics
|
||||
memory grids (like Terracotta or Coherence), and grid execution fabrics
|
||||
(like GridGain). Spring Batch does not contain implementations for any
|
||||
proprietary grid or remoting fabrics.
|
||||
|
||||
Spring Batch does however provide a useful implementation of
|
||||
`PartitionHandler` that executes `Steps` locally in
|
||||
Spring Batch does, however, provide a useful implementation of
|
||||
`PartitionHandler` that executes `Step` instances locally in
|
||||
separate threads of execution, using the
|
||||
`TaskExecutor` strategy from Spring. The
|
||||
implementation is called
|
||||
`TaskExecutorPartitionHandler`, and it is the
|
||||
default for a step configured with the XML namespace as above. It can
|
||||
also be configured explicitly like this:
|
||||
default for a step configured with the XML namespace shown previously. It can
|
||||
also be configured explicitly, as shown in the following example:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -299,17 +302,17 @@ Spring Batch does however provide a useful implementation of
|
||||
</bean>
|
||||
----
|
||||
|
||||
The `gridSize` determines the number of separate
|
||||
The `gridSize` attribute determines the number of separate
|
||||
step executions to create, so it can be matched to the size of the
|
||||
thread pool in the `TaskExecutor`, or else it can
|
||||
be set to be larger than the number of threads available, in which case
|
||||
the blocks of work are smaller.
|
||||
thread pool in the `TaskExecutor`. Alternatively, it can
|
||||
be set to be larger than the number of threads available, which makes
|
||||
the blocks of work smaller.
|
||||
|
||||
The `TaskExecutorPartitionHandler` is quite
|
||||
useful for IO intensive `Steps`, like copying large numbers of files or
|
||||
The `TaskExecutorPartitionHandler` is
|
||||
useful for IO-intensive `Step` instances, such as copying large numbers of files or
|
||||
replicating filesystems into content management systems. It can also be
|
||||
used for remote execution by providing a `Step` implementation that is a
|
||||
proxy for a remote invocation (e.g. using Spring Remoting).
|
||||
proxy for a remote invocation (such as using Spring Remoting).
|
||||
|
||||
[[stepExecutionSplitter]]
|
||||
|
||||
@@ -318,7 +321,7 @@ The `TaskExecutorPartitionHandler` is quite
|
||||
|
||||
The `Partitioner` has a simpler responsibility: to generate
|
||||
execution contexts as input parameters for new step executions only (no
|
||||
need to worry about restarts). It has a single method:
|
||||
need to worry about restarts). It has a single method, as shown in the following interface definition:
|
||||
|
||||
|
||||
[source, java]
|
||||
@@ -329,12 +332,12 @@ public interface Partitioner {
|
||||
----
|
||||
|
||||
The return value from this method associates a unique name for
|
||||
each step execution (the `String`), with input
|
||||
each step execution (the `String`) with input
|
||||
parameters in the form of an `ExecutionContext`.
|
||||
The names show up later in the Batch meta data as the step name in the
|
||||
The names show up later in the Batch metadata as the step name in the
|
||||
partitioned `StepExecutions`. The
|
||||
`ExecutionContext` is just a bag of name-value
|
||||
pairs, so it might contain a range of primary keys, or line numbers, or
|
||||
pairs, so it might contain a range of primary keys, line numbers, or
|
||||
the location of an input file. The remote `Step`
|
||||
then normally binds to the context input using `#{...}`
|
||||
placeholders (late binding in step scope), as illustrated in the next
|
||||
@@ -343,21 +346,21 @@ The return value from this method associates a unique name for
|
||||
The names of the step executions (the keys in the
|
||||
`Map` returned by
|
||||
`Partitioner`) need to be unique amongst the step
|
||||
executions of a `Job`, but do not have any other specific requirements.
|
||||
The easiest way to do this, and to make the names meaningful for users,
|
||||
executions of a `Job` but do not have any other specific requirements.
|
||||
The easiest way to do this (and to make the names meaningful for users)
|
||||
is to use a prefix+suffix naming convention, where the prefix is the
|
||||
name of the step that is being executed (which itself is unique in the
|
||||
`Job`), and the suffix is just a counter. There is
|
||||
a `SimplePartitioner` in the framework that uses
|
||||
this convention.
|
||||
|
||||
An optional interface
|
||||
An optional interface called
|
||||
`PartitionNameProvider` can be used to
|
||||
provide the partition names separately from the partitions
|
||||
themselves. If a Partitioner implements
|
||||
this interface then on a restart only the names will be queried.
|
||||
If partitioning is expensive this can be a useful optimisation.
|
||||
Obviously the names provided by the
|
||||
themselves. If a `Partitioner` implements
|
||||
this interface, then, on a restart, only the names are queried.
|
||||
If partitioning is expensive, this can be a useful optimization.
|
||||
The names provided by the
|
||||
`PartitionNameProvider` must match those
|
||||
provided by the `Partitioner`.
|
||||
|
||||
@@ -367,15 +370,15 @@ An optional interface
|
||||
==== Binding Input Data to Steps
|
||||
|
||||
It is very efficient for the steps that are executed by the
|
||||
`PartitionHandler` to have identical configuration, and for their input
|
||||
`PartitionHandler` to have identical configuration and for their input
|
||||
parameters to be bound at runtime from the `ExecutionContext`. This is
|
||||
easy to do with the StepScope feature of Spring Batch (covered in more
|
||||
detail in the section on <<step.adoc#late-binding,Late Binding>>). For example
|
||||
detail in the section on <<step.adoc#late-binding,Late Binding>>). For example,
|
||||
if the `Partitioner` creates
|
||||
`ExecutionContext` instances with an attribute key
|
||||
`ExecutionContext` instances with an attribute key called
|
||||
`fileName`, pointing to a different file (or
|
||||
directory) for each step invocation, the
|
||||
`Partitioner` output might look like this:
|
||||
`Partitioner` output might resemble the content of the following table:
|
||||
|
||||
.Example step execution name to execution context provided by `Partitioner` targeting directory processing
|
||||
|
||||
@@ -388,7 +391,7 @@ It is very efficient for the steps that are executed by the
|
||||
|
||||
|
||||
Then the file name can be bound to a step using late binding to
|
||||
the execution context:
|
||||
the execution context, as shown in the following example:
|
||||
|
||||
|
||||
[source, xml]
|
||||
@@ -398,4 +401,3 @@ Then the file name can be bound to a step using late binding to
|
||||
<property name="resource" value="#{stepExecutionContext[fileName]}/*"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
|
||||
Reference in New Issue
Block a user