跳转到内容

异步处理请求

如果你的请求里会做数据库查询、远程 HTTP 调用、文件处理或任何明显耗时的工作,就不应该一直把这些操作压在主处理流程里。
这篇文档讲的就是:什么时候需要异步,以及在 Feat 里应该怎么写。

如果你的请求处理只是:

  • 读取几个参数
  • 拼一段字符串
  • 返回一个很快就能得到的结果

那通常没必要上异步。

但如果你的处理链路里有这些动作,就值得认真看完这页:

  • 数据库查询
  • 第三方 API 调用
  • 文件上传或转码
  • 明显会阻塞的计算或等待

Feat 的服务端异步处理不是魔法,它依赖的是 HttpHandler 里的另一个 handle(...) 入口:

void handle(HttpRequest request, CompletableFuture<Void> future)

你要做的事只有两件:

  1. 把耗时逻辑挪到自己的线程池里执行
  2. 完成后记得调用 future.complete(null)

如果第二步忘了做,请求就会一直挂着。

下面这个例子几乎就是仓库示例的核心写法:

AsyncHttpDemo.java
import tech.smartboot.feat.core.server.HttpHandler;
import tech.smartboot.feat.core.server.HttpRequest;
import tech.smartboot.feat.core.server.HttpServer;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AsyncHttpDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
HttpServer server = new HttpServer();
server.options().debug(true);
server.httpHandler(new HttpHandler() {
@Override
public void handle(HttpRequest request, CompletableFuture<Void> future) throws IOException {
executorService.execute(() -> {
try {
Thread.sleep(1000);
request.getResponse().write(
("currentThread: " + Thread.currentThread() + " at " + new Date()).getBytes()
);
} catch (Exception e) {
e.printStackTrace();
} finally {
future.complete(null);
}
});
}
@Override
public void handle(HttpRequest request) {
// 异步模式下这里通常不再承担主逻辑
}
});
server.listen(8080);
}
}

很多人会把注意力放在 Thread.sleep(1000) 这种演示代码上,但真正重要的是这三行:

executorService.execute(() -> {
// 耗时工作
future.complete(null);
});

它们分别表达了:

  • “主线程不要做这件事”
  • “把工作交给另外的执行器”
  • “做完后显式通知 Feat 这次请求已经可以结束”

启动服务后访问 http://localhost:8080,你会在大约 1 秒后拿到响应。
响应内容里会带上执行线程信息,说明这段逻辑不是在主请求线程里同步跑完的。

这正是异步处理的价值:

  • 请求入口更快释放
  • 主处理线程不用被单个慢请求拖住
  • 你可以用自己的线程池管理耗时任务

如果你已经在使用 Feat.httpServer(...),也可以直接把异步处理器挂进去:

Feat.httpServer(options -> options.debug(true))
.httpHandler(new HttpHandler() {
@Override
public void handle(HttpRequest request, CompletableFuture<Void> future) {
businessExecutor.execute(() -> {
try {
String result = callSlowService();
request.getResponse().write(result);
} finally {
future.complete(null);
}
});
}
@Override
public void handle(HttpRequest request) {
}
})
.listen();

这里最需要控制的是 businessExecutor,而不是 Feat 本身。
换句话说,异步处理真正的核心问题通常是你的业务线程池策略。

这是最典型的问题。
结果通常是:服务端看起来没报错,但客户端请求一直不结束。

2. 在异步线程里抛异常,却没有兜底

Section titled “2. 在异步线程里抛异常,却没有兜底”

至少应该确保无论成功还是失败,都能结束 future:

executorService.execute(() -> {
try {
doSomething(request);
} catch (Throwable e) {
e.printStackTrace();
} finally {
future.complete(null);
}
});

3. 把所有异步任务都丢给无限增长的线程池

Section titled “3. 把所有异步任务都丢给无限增长的线程池”

异步不是“越多线程越好”。
如果线程池不受控,最终只是把阻塞从主线程搬到了另一堆线程上。

如果你已经确认自己需要异步,接下来通常会遇到两类问题:

如果你已经打算切到注解式开发模型,再回去看 Feat Cloud 导览 会更合适。