Hedis的自动流水线

January 4, 2014

Hedis是Haskelk目前最完善的redis绑定,而且有个独特的特性:自动流水线,号称是最优的。

conn <- connect defaultConnectInfo

runRedis conn $ do
    foo <- get "foo"
    bar <- get "bar"
    liftIO $ print (foo,bar)

在这个简单的例子里,据作者说这两个get命令被pipeline,完全不需用户干预。我用Wireshark查看发现两个get请求会在一个连接上分两次发送,没有达到号称的最优。更严重的问题是自动流水线是通过Lazy IO实现的,而且跨过多个runRedis块,然后问题就来了:

conn <- connect defaultConnectInfo

let fast    = runRedis conn (get "nextid")
    blocked = runRedis conn (blpop ["queue"] 10)

forkIO $ blocked >>= print

threadDelay (10^6)

forkIO $ fast >>= print

我们分别在两个线程里执行runRedis,理想情况下blpop会阻塞一下,而get会马上返回。但是Hedis会把这两个请求放在同一个连接上分两次发送,结果get必须等blpop返回之后才能返回。

如果你没有在runRedis里面对结果求值,请求就可能和其他runRedis里的请求共用一个连接。

我的临时应对方案就是在runRedis的最后对结果进行求值,强制流水线结束,防止对后续请求产生影响(实际上会让后续请求使用连接池里其他空闲连接):

runRedis' conn cmd = runRedis conn $ do
    result <- cmd
    result `seq` return result

也就是说,有大量blpop这些可能阻塞的请求时,就要用很大的连接池来防止非阻塞请求被阻塞。

Lazy IO真是坑,Hedis的自动流水线反而让这库变得难用了。