Don't block Redis ops during reconnect

This addresses the concern in the previous commit about breaking the Ping()
health check while another goroutine is attempting to reconnect. It also
makes the behaviour of other operations more predictable by returning errors
while the reconnect is in progress. These errors will be surfaced outside of
ttlHashSet, so we don't necessarily need to log the fact that we're
reconnecting.

I've had to introduce an additional flag protected by an r/w mutex to ensure
that we only create one reconnect goroutine. I've put this in the util
package because I suspect we may use the same pattern for RabbitMQ.

The tests have been adapted to assert that we receive roughly the right
amount of errors for the correct period of time based on the reconnect
interval. Then ensure we can reconnect again afterwards.

This now means that PublishURLs() will write duplicate and already crawled
URLs to the queue. However ReadFromQueue() should reject them until Redis is
available again. So we shouldn't waste too much resource by re-crawling
them.