基于JMH基准测试对比不同HTTP客户端(OkHttp/WebClient/Feign)调用美团API的性能差异
在微服务架构中,调用第三方API(如美团开放平台)是常见场景。选择高性能、低延迟的HTTP客户端对系统整体吞吐量至关重要。本文使用 JMH(Java Microbenchmark Harness)对 OkHttp、Spring WebClient 和 OpenFeign 三种主流 HTTP 客户端进行基准测试,量化其在同步调用美团订单创建接口时的性能表现。
1. 测试环境与依赖配置
测试环境:JDK 17、Spring Boot 3.2、JMH 1.37。
Maven 依赖如下(仅关键部分):
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>13.1</version>
</dependency>

2. 模拟美团API服务端
为避免真实网络波动干扰,使用 WireMock 启动本地模拟服务:
package baodanbao.com.cn.benchmark;
import com.github.tomakehurst.wiremock.WireMockServer;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
public class MockMeituanServer {
public static WireMockServer start() {
WireMockServer server = new WireMockServer(9999);
server.start();
stubFor(post(urlEqualTo("/api/v1/order"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("{\"code\":0,\"msg\":\"success\",\"data\":{\"orderId\":\"123456\"}}")));
return server;
}
}
3. OkHttp 客户端实现
package baodanbao.com.cn.benchmark.client;
import okhttp3.*;
import java.io.IOException;
public class OkHttpMeituanClient {
private static final OkHttpClient client = new OkHttpClient();
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
public String createOrder(String jsonBody) throws IOException {
RequestBody body = RequestBody.create(jsonBody, JSON);
Request request = new Request.Builder()
.url("http://localhost:9999/api/v1/order")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
}
4. WebClient 客户端实现
package baodanbao.com.cn.benchmark.client;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class WebClientMeituanClient {
private final WebClient webClient = WebClient.builder()
.baseUrl("http://localhost:9999")
.build();
public String createOrder(String jsonBody) {
return webClient.post()
.uri("/api/v1/order")
.bodyValue(jsonBody)
.retrieve()
.bodyToMono(String.class)
.block(); // 强制同步
}
}
5. Feign 客户端实现
package baodanbao.com.cn.benchmark.client;
import feign.Feign;
import feign.Headers;
import feign.RequestLine;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.okhttp.OkHttpClient;
public interface FeignMeituanClient {
@RequestLine("POST /api/v1/order")
@Headers("Content-Type: application/json")
String createOrder(String jsonBody);
static FeignMeituanClient create() {
return Feign.builder()
.client(new OkHttpClient())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(FeignMeituanClient.class, "http://localhost:9999");
}
}
6. JMH 基准测试类
package baodanbao.com.cn.benchmark;
import baodanbao.com.cn.benchmark.client.*;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Benchmark)
@Fork(value = 1, warmups = 1)
@Warmup(iterations = 3, time = 2)
@Measurement(iterations = 5, time = 3)
public class HttpClientsBenchmark {
private static final String ORDER_JSON = "{\"shopId\":\"1001\",\"items\":[{\"skuId\":\"2001\",\"count\":2}]}";
private OkHttpMeituanClient okHttpClient;
private WebClientMeituanClient webClient;
private FeignMeituanClient feignClient;
@Setup
public void setup() {
MockMeituanServer.start(); // 实际应全局启动一次,此处简化
okHttpClient = new OkHttpMeituanClient();
webClient = new WebClientMeituanClient();
feignClient = FeignMeituanClient.create();
}
@Benchmark
public void testOkHttp(Blackhole blackhole) {
try {
String result = okHttpClient.createOrder(ORDER_JSON);
blackhole.consume(result);
} catch (Exception e) {
blackhole.consume(e);
}
}
@Benchmark
public void testWebClient(Blackhole blackhole) {
try {
String result = webClient.createOrder(ORDER_JSON);
blackhole.consume(result);
} catch (Exception e) {
blackhole.consume(e);
}
}
@Benchmark
public void testFeign(Blackhole blackhole) {
try {
String result = feignClient.createOrder(ORDER_JSON);
blackhole.consume(result);
} catch (Exception e) {
blackhole.consume(e);
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(HttpClientsBenchmark.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
7. 测试结果分析(示例数据)
在 Intel i7-13700K、32GB RAM 环境下运行,典型结果如下(单位:微秒):
- OkHttp: 平均 185 μs
- WebClient: 平均 312 μs
- Feign (with OkHttp): 平均 210 μs
OkHttp 因其轻量、无反射、连接池复用等特性,在纯同步调用场景下表现最优。WebClient 虽基于 Reactor,但因强制 block() 导致上下文切换开销增加。Feign 在底层使用 OkHttp 时性能接近原生,但增加了代理和序列化层开销。
8. 注意事项
- 所有客户端应复用实例(如 OkHttpClient、WebClient),避免重复创建。
- Feign 若使用默认 HttpURLConnection 性能会显著下降,务必替换为 OkHttp。
- JMH 测试需关闭 JIT 预热干扰,确保
@Warmup充分。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
1681

被折叠的 条评论
为什么被折叠?



