常见问题排查
服务注册与发现
1. 服务无法注册
问题现象:
- 服务启动后在 Nacos 控制台看不到
- 服务列表为空
排查步骤:
# 1. 检查 Nacos 服务是否正常
curl http://localhost:8848/nacos/v1/console/health/readiness
# 2. 检查服务配置
cat application.yml | grep nacos
# 3. 检查网络连通性
telnet localhost 8848
# 4. 查看服务日志
tail -f logs/application.log | grep nacos
常见原因:
# 配置错误
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8849 # 端口错误
namespace: publick # 命名空间拼写错误
group: DEFAULT_GROUPK # 分组拼写错误
# 服务名错误
spring:
application:
name: user_service # 不应包含下划线
解决方案:
# 正确配置
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
register-enabled: true
2. 服务发现失败
问题现象:
- Feign 调用报错:No instances available
- 负载均衡失败
排查步骤:
# 1. 检查服务是否注册成功
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=user-service
# 2. 检查服务健康状态
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=user-service&healthyOnly=true
# 3. 查看 Feign 日志
logging:
level:
com.example.client: DEBUG
常见原因:
// 服务名不匹配
@FeignClient(name = "USER-SERVICE") // 大写
public interface UserClient { }
// 没有启用负载均衡
@Configuration
public class FeignConfig {
@Bean
@LoadBalanced // 缺少此注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
解决方案:
// 使用小写服务名
@FeignClient(name = "user-service")
public interface UserClient { }
// 启用负载均衡
@Configuration
@EnableDiscoveryClient
public class Application {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
配置中心
1. 配置不生效
问题现象:
- 配置已修改但应用未生效
- 配置刷新失败
排查步骤:
# 1. 检查配置是否发布
curl http://localhost:8848/nacos/v1/cs/configs?dataId=user-service.yaml&group=DEFAULT_GROUP
# 2. 检查配置优先级
# Nacos 配置 > 本地配置
# 3. 查看配置加载日志
logging:
level:
com.alibaba.cloud.nacos: DEBUG
常见原因:
# 配置文件名错误
spring:
cloud:
nacos:
config:
file-extension: yaml # 实际是 properties
prefix: user-service # 与实际不符
# 缺少@RefreshScope
@RestController
public class ConfigController { // 缺少@RefreshScope
@Value("${config.value}")
private String value;
}
解决方案:
# 正确配置
spring:
cloud:
nacos:
config:
server-addr: localhost:8848
file-extension: yaml
refresh-enabled: true
# 添加@RefreshScope
@RestController
@RefreshScope
public class ConfigController {
@Value("${config.value}")
private String value;
}
2. 配置无法刷新
问题现象:
- Nacos 配置已修改
- 应用配置未更新
排查步骤:
# 1. 检查长轮询是否正常
tail -f logs/application.log | grep "long-pulling"
# 2. 检查 Bean 是否支持刷新
# 必须是@RefreshScope 或@ConfigurationProperties
# 3. 手动刷新
curl -X POST http://localhost:8080/actuator/refresh
解决方案:
// 使用@RefreshScope
@RestController
@RefreshScope
public class ConfigController {
@Value("${config.value}")
private String value;
}
// 或使用@ConfigurationProperties
@Component
@ConfigurationProperties(prefix = "config")
public class ConfigProperties {
private String value;
// getter/setter
}
服务调用
1. Feign 调用超时
问题现象:
- Read timed out
- Connect timed out
排查步骤:
# 1. 检查服务响应时间
curl -w "@curl-format.txt" http://localhost:8080/api/users/1
# 2. 查看 Feign 日志
logging:
level:
com.example.client: DEBUG
# 3. 检查网络延迟
ping user-service
常见原因:
# 超时配置过短
feign:
client:
config:
default:
connectTimeout: 1000 # 1 秒,太短
readTimeout: 1000 # 1 秒,太短
解决方案:
# 增加超时时间
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
user-service:
connectTimeout: 10000
readTimeout: 30000
# 或配置 Ribbon
ribbon:
ReadTimeout: 10000
ConnectTimeout: 5000
2. Feign 调用 404
问题现象:
- Feign 调用返回 404
- 直接访问 URL 正常
排查步骤:
# 1. 检查路径配置
curl http://user-service/api/users/1
# 2. 检查 Feign 路径
@FeignClient(name = "user-service", path = "/api")
# 3. 查看请求日志
feign:
client:
config:
default:
loggerLevel: FULL
常见原因:
// 路径重复
@FeignClient(name = "user-service", path = "/api")
public interface UserClient {
@GetMapping("/api/users/{id}") // 重复/api
Result<User> getUser(@PathVariable("id") Long id);
}
// 路径不匹配
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/user/{id}") // 实际是/users
Result<User> getUser(@PathVariable("id") Long id);
}
解决方案:
// 修正路径
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
Result<User> getUser(@PathVariable("id") Long id);
}
// 或配置路径
@FeignClient(name = "user-service", path = "/api")
public interface UserClient {
@GetMapping("/users/{id}")
Result<User> getUser(@PathVariable("id") Long id);
}
网关
1. 网关无法访问
问题现象:
- 访问网关返回 503
- 服务不可用
排查步骤:
# 1. 检查网关配置
curl http://localhost:8080/actuator/gateway/routes
# 2. 检查服务是否注册
curl http://localhost:8848/nacos/v1/ns/instance/list?serviceName=user-service
# 3. 查看网关日志
tail -f logs/application.log | grep gateway
常见原因:
# 路由配置错误
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://USER-SERVICE # 服务名大写
predicates:
- Path=/api/users/**
filters:
- StripPrefix=2 # 路径截取过多
解决方案:
# 正确配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- StripPrefix=1
2. 网关鉴权失败
问题现象:
- 访问需要鉴权的接口返回 401
- Token 验证失败
排查步骤:
# 1. 检查 Token 格式
curl -H "Authorization: Bearer xxx" http://localhost/api/users/1
# 2. 检查鉴权过滤器
# 查看过滤器执行顺序
# 3. 查看鉴权日志
logging:
level:
com.example.gateway: DEBUG
常见原因:
// Token 解析失败
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 未检查 Bearer 前缀
if (!jwtUtil.validateToken(token)) { // token 包含"Bearer "
return unauthorized(exchange);
}
return chain.filter(exchange);
}
}
解决方案:
// 正确处理 Token
public class AuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authorization = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
return unauthorized(exchange);
}
String token = authorization.substring(7);
if (!jwtUtil.validateToken(token)) {
return unauthorized(exchange);
}
return chain.filter(exchange);
}
}
熔断降级
1. 熔断不生效
问题现象:
- 配置了熔断规则
- 服务故障时未熔断
排查步骤:
# 1. 检查 Sentinel 控制台
curl http://localhost:8080/machine
# 2. 检查规则配置
curl http://localhost:8080/machine/rules
# 3. 查看 Sentinel 日志
tail -f ~/logs/csp/sentinel-block.log
常见原因:
// 缺少@SentinelResource
@Service
public class UserService {
public User getUserById(Long id) { // 缺少@SentinelResource
return userRepository.findById(id);
}
}
// 配置错误
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
DegradeRule rule = new DegradeRule();
rule.setResource("getUserById");
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
rule.setCount(0.5);
rule.setTimeWindow(10);
// 未加载规则
// DegradeRuleManager.loadRules(rules);
}
}
解决方案:
// 添加@SentinelResource
@Service
public class UserService {
@SentinelResource(
value = "getUserById",
fallback = "getUserFallback"
)
public User getUserById(Long id) {
return userRepository.findById(id);
}
public User getUserFallback(Long id, Throwable ex) {
return User.getDefault();
}
}
// 加载规则
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource("getUserById");
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
rule.setCount(0.5);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
2. 降级不生效
问题现象:
- 服务异常时未降级
- 直接抛出异常
排查步骤:
# 1. 检查异常类型
# 业务异常 vs 系统异常
# 2. 检查 fallback 配置
# fallback 处理业务异常
# blockHandler 处理限流异常
# 3. 查看 Sentinel 日志
tail -f ~/logs/csp/sentinel-block.log
常见原因:
// fallback 和 blockHandler 混淆
@SentinelResource(
value = "getUserById",
blockHandler = "handleBlock" // 只处理限流异常
)
public User getUserById(Long id) {
throw new BusinessException("业务异常"); // 不会触发 blockHandler
}
// fallback 参数不匹配
@SentinelResource(fallback = "handleFallback")
public User getUserById(Long id) { }
public User handleFallback() { } // 缺少参数
解决方案:
// 正确配置
@SentinelResource(
value = "getUserById",
fallback = "getUserFallback", // 处理业务异常
blockHandler = "handleBlock" // 处理限流异常
)
public User getUserById(Long id) {
return userRepository.findById(id);
}
// 业务异常降级
public User getUserFallback(Long id, Throwable ex) {
log.error("业务异常", ex);
return User.getDefault();
}
// 限流降级
public User handleBlock(Long id, BlockException ex) {
log.warn("限流", ex);
return User.getDefault();
}
分布式事务
1. 事务不生效
问题现象:
- @GlobalTransactional 不生效
- 服务异常未回滚
排查步骤:
# 1. 检查 Seata 配置
curl http://localhost:8091
# 2. 检查 undo_log 表
SELECT * FROM undo_log WHERE xid = 'xxx';
# 3. 查看 Seata 日志
tail -f logs/seata/seata.log
常见原因:
# Seata 未启用
seata:
enabled: false # 未启用
# 事务组配置错误
seata:
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default # TC 集群名错误
// 数据源配置错误
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new DruidDataSource(); // 未使用 DataSourceProxy
}
}
解决方案:
# 正确配置
seata:
enabled: true
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
registry:
type: nacos
nacos:
server-addr: localhost:8848
// 使用 DataSourceProxy
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}
2. 事务回滚失败
问题现象:
- 事务回滚
- 数据未恢复
排查步骤:
# 1. 检查 undo_log
SELECT * FROM undo_log WHERE xid = 'xxx';
# 2. 检查全局锁
SELECT * FROM lock_table WHERE xid = 'xxx';
# 3. 查看 TC 日志
tail -f logs/seata/tc.log
常见原因:
-- undo_log 表不存在
-- 缺少唯一索引
CREATE TABLE undo_log (
branch_id BIGINT NOT NULL,
xid VARCHAR(128) NOT NULL,
-- 缺少 UNIQUE KEY ux_undo_log (xid, branch_id)
);
-- 全局锁冲突
-- 其他事务持有锁
解决方案:
-- 创建 undo_log 表
CREATE TABLE `undo_log` (
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL,
`log_status` INT(11) NOT NULL,
`log_created` DATETIME(6) NOT NULL,
`log_modified` DATETIME(6) NOT NULL,
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`),
KEY `ix_log_created` (`log_created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
性能问题
1. 接口响应慢
排查步骤:
# 1. 查看链路追踪
# SkyWalking 查看调用链
# 2. 查看慢 SQL
SELECT * FROM information_schema.PROCESSLIST WHERE TIME > 1;
# 3. 查看 JVM 状态
jstat -gcutil <pid> 1000
# 4. 查看线程状态
jstack <pid> | grep -A 20 "BLOCKED"
常见原因:
- 数据库慢查询
- 外部接口慢
- 锁竞争
- GC 频繁
解决方案:
-- 优化 SQL
CREATE INDEX idx_user_status ON orders (user_id, status);
-- 增加缓存
@Cacheable(value = "user", key = "#id")
public User getUser(Long id) { }
-- 异步处理
@Async
public void sendEmail(User user) { }
2. 内存泄漏
排查步骤:
# 1. 查看堆内存
jmap -heap <pid>
# 2. 导出堆 dump
jmap -dump:format=b,file=heap.hprof <pid>
# 3. 分析堆 dump
jhat heap.hprof
# 4. 查看 GC 日志
jstat -gcutil <pid> 1000
常见原因:
- 静态集合持有对象
- 未关闭资源
- 线程池未关闭
- 缓存无限制增长
解决方案:
// 使用弱引用
private static Map<String, Object> cache = new WeakHashMap<>();
// 及时关闭资源
try (Connection conn = dataSource.getConnection()) {
// 使用资源
}
// 限制缓存大小
Cache<String, Object> cache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
最佳实践
1. 问题排查流程
1. 复现问题
↓
2. 收集信息(日志、监控、链路)
↓
3. 定位原因
↓
4. 制定方案
↓
5. 验证修复
↓
6. 总结复盘
2. 监控告警
关键指标:
- 服务可用性 < 99.9%
- 接口 P95 响应时间 > 500ms
- 错误率 > 1%
- CPU 使用率 > 80%
- 内存使用率 > 85%
告警分级:
- P0:核心功能不可用 → 电话 + 短信
- P1:重要功能受影响 → 短信 + IM
- P2:部分功能异常 → IM
- P3:轻微问题 → 邮件
3. 故障预案
服务故障:
- 降级非核心功能
- 切换到备用服务
- 限流保护
数据库故障:
- 切换到从库
- 启用读写分离
- 使用缓存
中间件故障:
- 切换到备用节点
- 使用本地缓存
- 异步转同步
总结
微服务问题排查需要系统性的方法和完善的工具支持。
建立完善的监控告警体系,可以快速发现和定位问题。
遵循最佳实践,建立故障预案,可以提高系统的可用性和稳定性。