我在netty中编写服务器,我需要调用memcached。我正在使用spymemcached,可以轻松地进行同步memcached调用。我希望这个memcached调用是异步的。那可能吗? netty提供的示例似乎没有帮助。
我尝试使用回调:在我的处理程序中创建了一个ExecutorService
池,并向该池提交了一个回调工作程序。像这样:
公共类MyHandler扩展了ChannelInboundMessageHandlerAdapter< MyPOJO>实现CallbackInterface {
... private static ExecutorService pool = Executors.newFixedThreadPool(20); @Override public void messageReceived(ChannelHandlerContext ctx, MyPOJO pojo) { ... CallingbackWorker worker = new CallingbackWorker(key, this); pool.submit(worker); ... } public void myCallback() { //get response this.ctx.nextOutboundMessageBuf().add(response); }
}
CallingbackWorker
看起来像:
public class CallingbackWorker实现Callable {
public CallingbackWorker(String key, CallbackInterface c) { this.c = c; this.key = key; } public Object call() { //get value from key c.myCallback(value); }
但是,当我执行此操作时,this.ctx.nextOutboundMessageBuf()
中的myCallback
会被卡住。
总的来说,我的问题是:如何在Netty中进行异步memcached调用?
答案 0 :(得分:3)
这里有两个问题:一个小问题,你尝试编写这个问题的方式,以及一个更大的问题,有许多库提供异步服务调用,但没有好办法充分利用它们像Netty这样的异步框架。这会迫使用户进入像这样的次优黑客,或者是一种不那么糟糕,但仍然不是理想的方法,我马上就会到达。
首先是编码问题。问题是您尝试从与处理程序关联的线程之外的线程调用ChannelHandlerContext方法,这是不允许的。这很容易修复,如下所示。您可以通过其他几种方式对其进行编码,但这可能是最简单的方法:
private static ExecutorService pool = Executors.newFixedThreadPool(20);
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
//...
final GetFuture<String> future = memcachedClient().getAsync("foo", stringTranscoder());
// first wait for the response on a pool thread
pool.execute(new Runnable() {
public void run() {
String value;
Exception err;
try {
value = future.get(3, TimeUnit.SECONDS); // or whatever timeout you want
err = null;
} catch (Exception e) {
err = e;
value = null;
}
// put results into final variables; compiler won't let us do it directly above
final fValue = value;
final fErr = err;
// now process the result on the ChannelHandler's thread
ctx.executor().execute(new Runnable() {
public void run() {
handleResult(fValue, fErr);
}
});
}
});
// note that we drop through to here right after calling pool.execute() and
// return, freeing up the handler thread while we wait on the pool thread.
}
private void handleResult(String value, Exception err) {
// handle it
}
这将有效,并且可能足以满足您的应用需求。但是你有一个固定大小的线程池,所以如果你要处理超过20个并发连接,这将成为一个瓶颈。您可以增加池大小,或使用无限制的大小,但此时,您可能也在Tomcat下运行,因为内存消耗和上下文切换开销开始成为问题,并且您失去了scalabilty,这是吸引力Netty首先!
事实上,Spymemcached是基于NIO的,事件驱动的,并且只使用一个线程来完成其所有工作,但却无法充分利用其事件驱动的特性。我希望他们能在不久之前修复它,就像Netty 4和Cassandra最近通过在Future对象上提供回调(监听器)方法一样。
与此同时,我和你在同一条船上,我研究了替代品,并且对我发现的东西不太满意,我写道(昨天)a Future tracker class可以以可配置的速率查询数千种期货,当你完成时,回复你选择的线程(执行者)。它只使用一个线程来执行此操作。我put it up on GitHub如果你想尝试一下,但是要警告它仍然是潮湿的,正如他们所说的那样。我在过去的一天里测试了很多,即使有10000个并发的模拟Future对象,每毫秒轮询一次,它的CPU利用率可以忽略不计,尽管它开始超过10000.使用它,上面的例子看起来像这样:
// in some globally-accessible class:
public static final ForeignFutureTracker FFT = new ForeignFutureTracker(1, TimeUnit.MILLISECONDS);
// in a handler class:
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
// ...
final GetFuture<String> future = memcachedClient().getAsync("foo", stringTranscoder());
// add a listener for the Future, with a timeout in 2 seconds, and pass
// the Executor for the current context so the callback will run
// on the same thread.
Global.FFT.addListener(future, 2, TimeUnit.SECONDS, ctx.executor(),
new ForeignFutureListener<String,GetFuture<String>>() {
public void operationSuccess(String value) {
// do something ...
ctx.fireChannelRead(someval);
}
public void operationTimeout(GetFuture<String> f) {
// do something ...
}
public void operationFailure(Exception e) {
// do something ...
}
});
}
您不希望任何时候激活一个或两个FFT实例,否则它们可能会成为CPU的消耗。但是,单个实例可以处理数千个未完成的期货;关于拥有第二个呼叫的唯一原因是,以较慢的轮询速率(例如10-20毫秒)处理更高延迟的呼叫,如S3。
轮询方法的一个缺点是它增加了少量延迟。例如,轮询一毫秒,平均来说它会在响应时间上增加500微秒。对于大多数应用程序而言,这不会是一个问题,而且我认为通过线程池方法可以节省内存和CPU。
我预计在一年左右的时间内这将是一个非问题,因为更多异步客户端提供回调机制,让您充分利用NIO和事件驱动模型。