Skip to content
清晨的一缕阳光
返回

常见问题排查

常见问题排查

服务注册与发现

1. 服务无法注册

问题现象

排查步骤

# 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. 服务发现失败

问题现象

排查步骤

# 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. 配置无法刷新

问题现象

排查步骤

# 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 调用超时

问题现象

排查步骤

# 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

问题现象

排查步骤

# 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. 网关无法访问

问题现象

排查步骤

# 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. 网关鉴权失败

问题现象

排查步骤

# 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. 事务不生效

问题现象

排查步骤

# 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"

常见原因

解决方案

-- 优化 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. 监控告警

关键指标

告警分级

3. 故障预案

服务故障

数据库故障

中间件故障

总结

微服务问题排查需要系统性的方法和完善的工具支持。

建立完善的监控告警体系,可以快速发现和定位问题。

遵循最佳实践,建立故障预案,可以提高系统的可用性和稳定性。


分享这篇文章到:

上一篇文章
MySQL 数据类型详解
下一篇文章
Redis 核心配置详解