我在使用 Feign 客户端进行服务间通信的 Spring Boot 应用程序中遇到循环错误模式。此问题特别出现在以一分钟间隔发出 HTTP POST 请求的计划任务中。观察到的行为明显是循环的:如果应用程序启动并且第一个计划请求返回 HTTP 400(错误请求),则该会话中的所有后续请求将继续返回 HTTP 400 错误。如果我终止进程并重新启动应用程序,行为就会相反 - 这次,所有请求都会成功,没有任何错误。每次重新启动应用程序时都会重复此模式:以错误启动的会话将始终出现错误,而正确启动的会话将始终正确运行。
这个周期性问题仍然存在,无需对服务器进行任何更改,服务器配置为一致地处理请求。
示例场景:
启动应用程序 - 第一个请求返回 HTTP 400。
此会话中的所有后续请求也返回 HTTP 400。
终止应用程序进程并重新启动。
这次重启后第一次请求就成功了。
此新会话中的所有后续请求均成功。
终止并重新启动进程,步骤 1 中的问题会重复。
这是我的通信中涉及的客户端和服务器代码的简化版本:
@FeignClient(name = "withdraw-feign-client", url = "127.0.0.1:8081")
public interface WithdrawFeignClient {
@PostMapping("/withdraw/pullWithdraw")
@ResponseBody
CommonResult<List<WithdrawVO>> pullWithdraw(@RequestBody RequestData<PullWithdrawReq> req);
}
RequestData requestData = new RequestData();
requestData.setChain(Chain.FIL);
requestData.setDataString("123");
requestData.setData(pullWithdrawReq);
CommonResult<List<com.dhlfy.financerpcclient.WithdrawVO>> result = withdrawFeignClient.pullWithdraw(requestData);
@PostMapping("/pullWithdraw")
public CommonResult<List<WithdrawVO>> pullWithdraw(@RequestBody RequestData<PullWithdrawReq> req) {
String k = formatParma(req);
PullWithdrawReq withdrawReq = JSONObject.parseObject(k, PullWithdrawReq.class);
checkSign(withdrawReq);
return CommonResult.success(withdrawService.pullWithdraw(req.getChain().name(), withdrawReq));
}
我遇到了几乎相同的问题,但面临完全相同的场景:使用 Feign 从计划任务中触发
POST
HTTP 请求。在我的例子中,唯一的区别是来自 @SpringBootTest
的 HTTP 调用将始终返回 200,而来自计划任务内的所有调用只会返回 400。
我在通过套接字发送数据之前放置了一个断点 (
L100: feign.SynchronousMethodHandler
) 并捕获了 2 个堆栈跟踪。比较它们,除了最开始的级别之外,调用堆栈似乎没有任何差异,这让我困惑了很长一段时间。
在那个阶段一无所知,然后我意识到我没有配置任何特定的客户端实现(Apache、OkHttp 等),因此我尝试在配置中指定一个并观察其行为。我使用了 OkHttp 和以下配置:
pom.xml
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
...
<dependencies>
...
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>13.3</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.18.3</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
</dependencies>
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
HttpClientsConfig.java
package house.x1337.livefeed.config;
import feign.Client;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import okhttp3.OkHttpClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableFeignClients(basePackages = {
"house.x1337.livefeed.client"
})
public class HttpClientsConfig {
@Bean
public Decoder decoder() {
return new JacksonDecoder();
}
@Bean
public Encoder encoder() {
return new JacksonEncoder();
}
@Bean
public Client client() {
return new feign.okhttp.OkHttpClient(new OkHttpClient());
}
}
然后一切就神奇地开始工作了。这是我见过的最奇怪的问题之一,因为我从未深入了解它。最初我认为这与自动配置生命周期或线程级上下文有关。在这两个区域调试了很多组件,但没有什么明显的。
值得一提的是,通过从请求中删除“Host”标头,我能够在 Postman 上按需重现 400。这是一个受限制的标头,应该由 JVM 在运行时隐式设置。这是 Feign 中的一个已知问题,如下所述:https://github.com/OpenFeign/feign/issues/747。 可能是,根据项目的配置,由于某种原因,此标头不会通过套接字发送。这是一个核心标头,大多数(如果不是所有)服务器都希望将请求适当地分派到下游,如果不存在,请求将被拒绝为无效。