异步的feign请求报错:No thread-bound request found

解决Feign异步请求No thread-bound错误

接上一篇:https://blog.youkuaiyun.com/JFENG14/article/details/153473176?spm=1011.2415.3001.5331

问题:添加FeignHeaderConfig和HystrixConfig后,异步的feign请求报错:No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

解决方法:

  1. 非 Web 场景下不要尝试读取请求头
    FeignHeaderConfig 已经在 attributes == nullreturn;,这是正确做法。
    然而,Hystrix 线程里 attributes 不再为 null(被你包装过了),但实际请求已经结束(例如异步定时任务),这时 HttpServletRequest 对象已失效,再用它拿 Header 会触发上述异常。

  2. 把“需要透传的 Header”提前摘出来,放到线程变量里
    InheritableThreadLocal(或阿里推荐的 TransmittableThreadLocal)在主线程就把 Header 快照下来;
    后续任何线程(Hystrix、异步、Feign 重试)都读这份“快照”,而不再去碰 RequestContextHolder

1.定义一个快照容器

public final class RequestHeaderSnapshot {
	private static final TransmittableThreadLocal<Map<String, String>> SNAPSHOT =
		new TransmittableThreadLocal<>();

	/** Web 请求入口时调用:把指定 Header 快照下来 */
	public static void capture(Set<String> headerNames) {
		ServletRequestAttributes attr =
			(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if (attr == null) {
			SNAPSHOT.remove();
			return;
		}
		HttpServletRequest req = attr.getRequest();
		Map<String, String> map = new HashMap<>();
		headerNames.forEach(h -> {
			String v = req.getHeader(h);
			if (Func.isNotBlank(v)) {
				map.put(h, v);
			}
		});
		SNAPSHOT.set(map);
	}

	/** 任何地方获取快照 */
	public static Map<String, String> get() {
		return SNAPSHOT.get() == null ? Collections.emptyMap() : SNAPSHOT.get();
	}

	/* ---------- 外部写入(给 Hystrix 用)---------- */
	public static void set(Map<String, String> snapshot) {
		SNAPSHOT.set(snapshot == null ? Collections.emptyMap() : snapshot);
	}

	/** 请求结束后清理,防止线程复用串数据 */
	public static void clear() {
		SNAPSHOT.remove();
	}
}
2.在 Web 入口(拦截器/过滤器)里拍照
@Component
@Order(Ordered.LOWEST_PRECEDENCE)   // 最后执行
public class HeaderSnapshotFilter extends OncePerRequestFilter {
	private static final Set<String> HEADER_NAMES =new HashSet<>(Arrays.asList(
		"Authorization",
		"X-Request-Id",
		"X-User-Id",
		"X-User-Name"
	));

	@Override
	protected void doFilterInternal(HttpServletRequest request,
									HttpServletResponse response,
									FilterChain chain)
		throws ServletException, IOException {
		try {
			RequestHeaderSnapshot.capture(HEADER_NAMES);
			chain.doFilter(request, response);
		} finally {
			RequestHeaderSnapshot.clear();   // 一定要清理
		}
	}
}
3.修改 FeignHeaderConfig,不再直接读 Request
@Configuration
public class FeignHeaderConfig {

	@Bean
	public RequestInterceptor headerRequestInterceptor() {
		return template -> {
			// 1. 读快照
			Map<String, String> snapshot = RequestHeaderSnapshot.get();
			snapshot.forEach(template::header);
		};
	}

}

4.修改HystrixConfig,获取快照中的内容,而不是请求头中的

@Configuration
public class HystrixConfig {

	@Bean
	public HystrixConcurrencyStrategy hystrixConcurrencyStrategy() {
		return new HystrixConcurrencyStrategy() {
			@Override
			public <T> Callable<T> wrapCallable(Callable<T> callable) {
				// 拿快照,而不是 RequestAttributes
				Map<String, String> snapshot = RequestHeaderSnapshot.get();
				return () -> {
					try {
						RequestHeaderSnapshot.set(snapshot);   // 先写快照
						return callable.call();
					} finally {
						RequestHeaderSnapshot.clear();
					}
				};
			}
		};
	}
}

✅ 结果

  • Web 场景:主线程拍照 → Feign 在任何线程都能拿到快照。

  • 非 Web 场景(定时任务、MQ):快照为空,Feign 只追加自定义参数,不会抛异常。

  • 无侵入:不用改 Hystrix、Ribbon、异步线程池;快照用完后立即清理,线程安全。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值