ProxyTCP helper for tests

Add a helper for proxying TCP connections. It has a method to kill all
currently connected sessions, which will be used for testing reconnects in
response to network disruption. It's modelled mostly on httptest.Server.
Redis reconnect after error

The radix/redis client closes the connection to the server if it experiences
an error. This is what causes the error caught in 8c907f8. Improve our
reliability by reconnecting the client if we encounter a network or IO
related error (but not a semantic error like an invalid command).

This will not block the operation that first caused the error, meaning that
PublishURL() may submit one already crawled URL and ReadFromQueue() may
reject (requeue) an item.

The reconnect will run in another goroutine but subsequent operations will
be blocking, causing PublishURL() and ReadFromQueue() to then stop
processing until Redis is available again. This is a desirable result
because without the cache we'd expend resources re-crawling already
up-to-date pages.

There is a 2s delay between reconnect attempts so as not to overwhelm the
client or server. But no limit on how many times it will try to reconnect.

I'm unsure about how we're going to monitor this because the health check
endpoint which is going to use ttlHashSet.Ping() will also block and we
don't have any logging inside of ttlHashSet.
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.
Merge pull request #5 from alphagov/redis_reconnect

[#73135596] Redis reconnect after error