首页
学习
活动
专区
圈层
工具
发布

如何提高系统稳定性,减少线上事故?

说实话,线上系统出问题这事儿,谁都躲不过。哪怕你写的代码再规范,测试覆盖再高,一旦遇到并发、网络、磁盘、配置、依赖服务等这些复杂因素,事故就像定时炸弹一样。所以,怎么减少事故,更多的是靠“提前预防”和“遇事有兜底”。

下面我结合自己踩过的坑,聊几个常见的做法。

1. 配置要合适,别迷信默认值

很多框架都号称“开箱即用”,但默认配置基本只适合 demo。比如 Spring Boot 默认数据库连接池最大连接数才 10 个,在测试环境完全没问题,上了生产分分钟撑爆。

spring.datasource.hikari.maximum-pool-size=50

spring.datasource.hikari.minimum-idle=10

spring.datasource.hikari.connection-timeout=30000

这几个参数一定要结合实际场景调。太小会卡死,太大反而浪费内存。尤其是connection-timeout,别放无限大,不然连接泄漏了你都发现不了。

2. 日志别乱打,该收敛的要收敛

线上事故排查靠日志,但日志太多等于没有。比如默认的 INFO 级别,长时间跑下来几百 G,硬盘撑爆也不是没见过。

常见做法是分级别,把 DEBUG 只留在本地开发,线上建议 INFO 打关键业务,ERROR 留异常。

logging.level.root=INFO

logging.file.name=app.log

logging.logback.rollingpolicy.max-file-size=100MB

logging.logback.rollingpolicy.max-history=30

日志最好能结合链路追踪(traceId),出了问题能串起来整个请求路径。

3. 超时和重试策略要有

线上最常见的坑就是调用链太长,某个下游慢了,整条链都跟着拖垮。Feign、RestTemplate 这些客户端如果不设超时,调用就会挂死。

@Configuration

public class FeignConfig {

  @Bean

  public Request.Options options() {

      return new Request.Options(5, TimeUnit.SECONDS, 10, TimeUnit.SECONDS, true);

  }

}

这里设置了连接 5 秒,读取 10 秒,超时就断。重试也要慎用,能幂等的接口可以加,不幂等的最好别乱配,不然可能“火上浇油”。

4. 限流和降级一定要有

流量打爆数据库这种事,双十一、秒杀的时候太常见了。限流可以在网关做,比如 Nginx 或者 Spring Cloud Gateway,也可以接入 Sentinel。

降级更像保险丝,比如短信服务挂了,可以记录下来后补发,而不是整个下单流程失败。

@HystrixCommand(fallbackMethod = "fallbackSend")

public String sendSms(String phone) {

  // 调用第三方短信服务

}

public String fallbackSend(String phone) {

  // 写入补偿队列,后续补发

  return "暂时无法发送短信";

}

5. 数据库事务别太“大”

很多人喜欢一个事务包住所有逻辑,看似安全,其实很危险。事务时间一长,锁就会拖住其他请求,整个系统都慢。

更好的方式是分批处理,每次处理一小部分。

@Transactional(timeout = 30, rollbackFor = Exception.class)

public void batchProcess(List<Data> dataList) {

  int batchSize = 100;

  for (int i = 0; i < dataList.size(); i += batchSize) {

      List<Data> batch = dataList.subList(i, Math.min(i + batchSize, dataList.size()));

      processBatch(batch);

  }

}

这样即使某一批失败,也不会影响整体。

6. 异步任务要走线程池

Java 里@Async默认用的是SimpleAsyncTaskExecutor,每次都新建线程,线上很容易 OOM。正确的做法是自定义线程池。

@Configuration

public class AsyncConfig {

  @Bean("taskExecutor")

  public Executor taskExecutor() {

      ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

      executor.setCorePoolSize(8);

      executor.setMaxPoolSize(16);

      executor.setQueueCapacity(100);

      executor.setKeepAliveSeconds(60);

      executor.setThreadNamePrefix("async-task-");

      executor.initialize();

      return executor;

  }

}

CPU 密集型任务线程数 ≈ 核心数,IO 密集型可以是核心数的 2~3 倍。

7. 监控要到位

没有监控的系统,就像开车不看仪表盘。最少要有三类:

系统层:CPU、内存、磁盘、网络;

应用层:请求量、响应时间、错误率;

业务层:订单量、支付成功率。

工具可以用 Prometheus + Grafana,也可以用 SkyWalking 做链路追踪。出了事故能第一时间定位,而不是在日志里翻半天。

8. 容灾和预案不能少

单点挂掉是最致命的,所以高可用架构要上。数据库要主从,服务要多副本,消息队列要多副本。

同时预案也要有,比如数据库挂了,能否切换到只读模式;某个服务挂了,能否先提示“稍后再试”。这些都要提前想清楚,不是出事了才临时补救。

9. 灰度发布,别一刀切

很多事故都是上线引起的。灰度就是先放一小部分流量上新版本,观察没问题再全量。Java 圈里常见的是配合 Nginx 做流量分流,或者用 Spring Cloud 的路由规则。

这样即使有 bug,也只会影响一小部分人。

10. 日常演练,别怕麻烦

光写预案没用,一定要演练。比如断掉数据库主库,看系统能否自动切换;关掉一个服务实例,看流量能否自动打到其他节点。这些平时演练过,真出事了才不会慌。

提高系统稳定性,本质上就是防患于未然。配置合理、日志干净、调用有超时、服务有降级、数据库事务拆小、异步有线程池、监控到位、容灾和预案完善、灰度发布和演练常态化。

这些做好了,线上事故肯定会少很多。就算真出事,也能快速定位,减少损失。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OOb1I1cy8q9D0nbxm6W6AkDQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券