目录
- 前言
- 问题一:服务间调用认证信息丢失
- [问题二:Feign 调用超时导致业务失败](#问题二:Feign 调用超时导致业务失败)
- 总结与反思
- 写在最后
前言
在开发 contract - common 这个共享基础库时,尽管代码量不多,却遭遇了不少棘手问题。这些问题涵盖设计层面、使用方式以及框架本身的局限。本文旨在记录这些问题,既为自己留存记忆,也期望能为面临类似困境的开发者提供参考。
问题一:服务间调用认证信息丢失
现象描述
在实际应用中,发现服务间调用频繁失败,日志中明确报错 401 未授权。然而,直接调用接口却一切正常。例如,以下是日志中的错误信息:
log
2025 - 12 - 28 10:23:15 | ERROR | c.c.c.feign.ContractFeignClient | Failed to call contract service: 401 Unauthorized
这就好比你去一个地方,正常走大门能进去,但让别人帮你带个东西进去却被告知没权限,很是奇怪。
排查过程
首先,对 Feign 客户端的配置展开检查,结果发现请求头竟然没有传递过去。以下是当时的代码(问题版本):
java
// 当时的代码 - 问题版本
@FeignClient(name = "contract - management - service")
public interface ContractFeignClient {
@GetMapping("/api/contracts/{id}")
ContractDTO getContractById(@PathVariable("id") Long id);
}
通过抓包分析,进一步确认 Feign 调用确实没有携带 Authorization header。这就像你出门没带钥匙,自然无法打开某些门。
根本原因
Feign 客户端在发起调用时,不会自动传递当前请求的认证信息。这是分布式系统中的常见状况,每个服务都拥有独立的请求上下文。就如同不同的房间有各自独立的门锁,一个房间的钥匙不能自动打开另一个房间的门。
解决方案
为解决该问题,需要实现 RequestInterceptor 接口,手动从当前请求上下文中提取认证信息并传递。具体代码如下:
java
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 提取并传递认证相关 Header
String token = request.getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
// 传递用户上下文信息
String userId = request.getHeader("userId");
String username = request.getHeader("username");
String tenantId = request.getHeader("tenantId");
if (userId != null) template.header("userId", userId);
if (username != null) template.header("username", username);
if (tenantId != null) template.header("tenantId", tenantId);
}
}
}
这段代码就像是一个贴心的小助手,它会在你出门前,帮你检查并带上所有需要的钥匙。
后续优化
后续发现内部服务调用还要走一遍鉴权,这无疑是对资源的浪费。于是,又增加了一个内部免鉴权的机制。虽然具体细节会在后面介绍鉴权中心时详细阐述,但这里简单提及一下:
java
// 在拦截器中添加内部调用标识
template.header("X - Internal - Auth - Secret", internalSecret);
// 网关识别后跳过鉴权
这就好比在自己家里,有些门不需要钥匙就能打开,提高了通行效率。
问题二:Feign 调用超时导致业务失败
现象描述
某个批量处理任务时常失败,报错信息为 Read Timeout。如下是日志中的记录:
log
2025 - 12 - 08 15:42:30 | ERROR | c.c.c.feign.ContractFeignClient | Read timed out executing GET http://contract - management - service/api/contracts/search
想象一下,你让别人在规定时间内帮你找一样东西,但对方找得太久,时间到了还没找到,就会出现这种情况。
排查过程
对业务代码进行仔细检查后发现,该任务需要调用另一个服务的批量查询接口。当数据量庞大时,对方服务处理时间变长,超出了 Feign 的默认超时时间。这就像你给别人的时间不够,对方还没完成任务,就被判定失败了。
根本原因
Feign 的默认超时设置相对较短,具体如下:
- 连接超时:10 秒
- 读超时 :60 秒
对于一些复杂的查询操作而言,60 秒显然难以满足需求。这就好比你让别人做一件复杂的事情,却只给了很少的时间,事情自然很难完成。
解决方案
为解决超时问题,可在配置文件中对超时时间进行调整:
yaml
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 60000
# 针对特定服务的配置
contract - management - service:
connectTimeout: 5000
readTimeout: 120000 # 2分钟
同时,对被调用方的查询接口进行优化,增加索引和分页支持。这就像是给对方提供了更好的工具,让他能更快地完成任务,同时也给了他足够的时间。
经验教训
- 不同类型的接口应该设置不同的超时时间:就像不同的任务需要不同的时间来完成,不能一概而论。
- 复杂操作要考虑异步处理,不要长时间阻塞:比如你在等一个很长时间才能完成的任务时,可以先去做其他事情,等它完成了再回来处理。
- 做好降级和重试机制:当事情没做好时,要有备用方案,再尝试一次。
总结与反思
回顾这些问题,多数出自设计层面,而非技术难点。以下是总结的几点经验:
设计层面
- 明确定位:共享库应作为"接口层",不依赖具体实现。这就像一个标准的接口,只规定了能做什么,而不关心具体怎么做。
- 稳定优先:接口变更务必谨慎,保持向后兼容。就像你给别人提供了一个工具,不能随意改变它的使用方式,不然别人就不会用了。
- 充分测试:共享库的 bug 会波及所有服务,所以要进行充分测试,确保质量。
技术层面
- Feign 配置:超时、重试、降级等配置都不可或缺,以保障服务的稳定性。
- 序列化规范:使用注解明确控制 JSON 序列化,让数据传输更加规范。
- 枚举设计:考虑向前兼容,避免使用展示值作为标识,防止出现兼容性问题。
写在最后
在编写这个共享基础库的过程中,遇到的问题比预期更多。然而,每解决一个问题,对微服务架构的理解便更深入一层。希望这些排查记录能为同样从事微服务开发的同学提供帮助。若你也遇到类似问题,欢迎交流讨论!
下期会稍微花点时间将下若依框架在本项目中的应用和改造!
标签:微服务、Feign、共享库、认证信息、超时问题、设计经验、技术优化