说实话,线上系统出问题这事儿,谁都躲不过。哪怕你写的代码再规范,测试覆盖再高,一旦遇到并发、网络、磁盘、配置、依赖服务等这些复杂因素,事故就像定时炸弹一样。所以,怎么减少事故,更多的是靠“提前预防”和“遇事有兜底”。
下面我结合自己踩过的坑,聊几个常见的做法。
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. 日常演练,别怕麻烦
光写预案没用,一定要演练。比如断掉数据库主库,看系统能否自动切换;关掉一个服务实例,看流量能否自动打到其他节点。这些平时演练过,真出事了才不会慌。
提高系统稳定性,本质上就是防患于未然。配置合理、日志干净、调用有超时、服务有降级、数据库事务拆小、异步有线程池、监控到位、容灾和预案完善、灰度发布和演练常态化。
这些做好了,线上事故肯定会少很多。就算真出事,也能快速定位,减少损失。