blob: 58a32a67a59296b96420095152df41f9a2bebcdd [file] [log] [blame] [view]
## Netty pipeline
With the [protocol layer](../native_protocol) in place, the next step is to build the logic for a
single server connection.
We use [Netty](https://netty.io/) for network I/O (to learn more about Netty, [this
book](https://www.manning.com/books/netty-in-action) is an excellent resource).
```ditaa
+----------------+
| ChannelFactory |
+----------------+
| connect() |
+-------+--------+
| Application
|creates +----------------------------------------------+
V | Outgoing |
+-------+--------+ | | +---------------------+ ^ |
| DriverChannel | | | | ProtocolInitHandler | | |
+-------+--------+ | | +---------------------+ | |
| | | | |
+-------+--------+ | | +---------------------+ | |
| Channel | | | | InFlightHandler | | |
| (Netty) | | | +---------------------+ | |
+-------+--------+ | | | |
| | | +---------------------+ | |
+-------+--------+ | | | Heartbeathandler | | |
|ChannelPipeline +---+ | +---------------------+ | |
| (Netty) | | | | |
+----------------+ | | +--------------+ +--------------+ | |
| | | FrameEncoder | | FrameDecoder | | |
| | +--------------+ +--------------+ | |
| | | |
| | +---------------------+ | |
| | | SslHandler | | |
| | | (Netty) | | |
| V +---------------------+ | |
| Incoming |
+----------------------------------------------+
Network
```
Each Cassandra connection is based on a Netty `Channel`. We wrap it into our own `DriverChannel`,
that exposes higher-level operations. `ChannelFactory` is the entry point for other driver
components; it handles protocol negotiation for the first channel.
A Netty channel has a *pipeline*, that contains a sequence of *handlers*. As a request is sent, it
goes through the pipeline top to bottom; each successive handler processes the input, and passes the
result to the next handler. Incoming responses go the other way.
Our pipeline is configured with the following handlers:
### SslHandler
The implementation is provided by Netty (all the others handlers are custom implementations).
Internally, handler instances are provided by `SslHandlerFactory`. At the user-facing level, this is
abstracted behind `SslEngineFactory`, based on Java's default SSL implementation.
See also the [Extension points](#extension-points) section below.
### FrameEncoder and FrameDecoder
This is where we integrate the protocol layer, as explained
[here](../native_protocol/#integration-in-the-driver).
Unlike the other pipeline stages, we use separate handlers for incoming and outgoing messages.
### HeartbeatHandler
The heartbeat is a background request sent on inactive connections (no reads since x seconds), to
make sure that they are still alive, and prevent them from being dropped by a firewall. This is
similar to TCP_KeepAlive, but we provide an application-side alternative because users don't always
have full control over their network configuration.
`HeartbeatHandler` is based on Netty's built-in `IdleStateHandler`, so there's not much in there
apart from the details of the control request.
### InFlightHandler
This handler is where most of the connection logic resides. It is responsible for:
* writing regular requests:
* find an available stream id;
* store the `ResponseCallback` provided by the client under that id;
* when the response comes in, retrieve the callback and complete it;
* cancelling a request;
* switching the connection to a new keyspace (if a USE statement was executed through the session);
* handling shutdown: gracefully (allow all request to complete), or forcefully (error out all
requests).
The two most important methods are:
* `write(ChannelHandlerContext, Object, ChannelPromise)`: processes outgoing messages. We accept
different types of messages, because cancellation and shutdown also use that path. See
`DriverChannel`, which abstracts those details.
* `channelRead`: processes incoming responses.
Netty handlers are confined to the channel's event loop (a.k.a I/O thread). Therefore the code
doesn't have to be concurrent, fields can be non-volatile and methods are guaranteed not to race
with each other.
In particular, a big difference from driver 3 is that stream ids are assigned within the event loop,
instead of from client code before writing to the channel (see also [connection
pooling](../request_execution/#connection_pooling)). `StreamIdGenerator` is not thread-safe.
All communication between the handler and the outside world must be done through messages or channel
events. There are 3 exceptions to this rule: `getAvailableIds`, `getInflight` and `getOrphanIds`,
which are based on volatile fields. They are all used for metrics, and `getAvailableIds` is also
used to balance the load over connections to the same node (see `ChannelSet`).
### ProtocolInitHandler
This handler manages the protocol initialization sequence on a newly established connection (see the
`STARTUP` message in the protocol specification).
Most of the logic resides in `InitRequest.onResponse`, which acts as a simple state machine based on
the last request sent.
There is also a bit of custom code to ensure that the channel is not made available to clients
before the protocol is ready. This is abstracted in the parent class `ConnectInitHandler`.
Once the initialization is complete, `ProtocolInitHandler` removes itself from the pipeline.
### Extension points
#### NettyOptions
The `advanced.netty` section in the [configuration](../../core/configuration/reference/) exposes a
few high-level options.
For more elaborate customizations, you can [extend the
context](../common/context/#overriding-a-context-component) to plug in a custom `NettyOptions`
implementation. This allows you to do things such as:
* reusing existing event loops;
* using Netty's [native Epoll transport](https://netty.io/wiki/native-transports.html);
* adding custom handlers to the pipeline.
#### SslHandlerFactory
The [user-facing API](../../core/ssl/) (`advanced.ssl-engine-factory` in the configuration, or
`SessionBuilder.withSslContext` / `SessionBuilder.withSslEngineFactory`) only supports Java's
default SSL implementation.
The driver can also work with Netty's [native
integration](https://netty.io/wiki/requirements-for-4.x.html#tls-with-openssl) with OpenSSL or
boringssl. This requires a bit of custom development against the internal API:
* add a dependency to one of the `netty-tcnative` artifacts, following [these
instructions](http://netty.io/wiki/forked-tomcat-native.html);
* implement `SslHandlerFactory`. Typically:
* the constructor will create a Netty [SslContext] with [SslContextBuilder.forClient], and store
it in a field;
* `newSslHandler` will delegate to one of the [SslContext.newHandler] methods;
* [extend the context](../common/context/#overriding-a-context-component) and override
`buildSslHandlerFactory` to plug your custom implementation.
[SslContext]: https://netty.io/4.1/api/io/netty/handler/ssl/SslContext.html
[SslContext.newHandler]: https://netty.io/4.1/api/io/netty/handler/ssl/SslContext.html#newHandler-io.netty.buffer.ByteBufAllocator-
[SslContextBuilder.forClient]: https://netty.io/4.1/api/io/netty/handler/ssl/SslContextBuilder.html#forClient--