Consistent partitioning

@wvanbergen this is a fairly non-trivial PR. I would prefer to do the commits one-at-a-time but we're only back to sane default behaviour once all three land. It is still definitely worth reviewing one commit at a time, and I gave each one a substantial stand-alone commit message to help with that.

The goal here is related to better behaviour in cases documented like #210 and #213. Specifically:
- When using a consistent partitioner (aka the `HashPartitioner`), if some partitions go away (all replicas gone) then those messages should fail fast and messages to other partitions should keep flowing.
- When using an inconsistent partitioner (round-robin, random, etc), if some partitions go away then all messages should keep flowing to the available partitions and none should fail.

This PR gets us a lot closer, but leaves a handful of pieces still to do:
- with a consistent partitioner, we don't fail "fast" (i.e. performance probably degrades quite a bit when a partition goes down entirely)
- with an inconsistent partitioner, messages that were in-flight to a partition that goes away will be retried on that same partition, killing performance briefly (although unlike the consistent case it will recover once they've all been returned as errors)
It might make the code more readable at the callsite if there was a constant for `AllPartitions` vs. `WritablePartitions`.