Implement basic failover for reading blocks

When a failure happens during reading, we can be robust to that failure by
trying again with the other datanode locations.

Additionally, to prevent us from lots of spurious failures, do some basic global
blacklisting - keep track of datanodes that have failed, and when they last
failed, so we can prioritize datanodes without failures and datanodes that
haven't failed as recently.