本文目录导读:

我来分享几个典型的Java流量突增案例,以及对应的排查思路和解决方案。
双11大促流量突增导致服务雪崩
场景描述
- 背景:电商系统,双11零点促销活动
- 现象:流量从平常的1000 QPS突增到10000 QPS,系统响应时间从50ms飙升到5秒,部分节点出现OOM
排查过程
// 1. 首先查看系统整体状态 jstat -gcutil <pid> 1000 // 查看GC情况 jmap -heap <pid> // 查看堆内存使用 top -Hp <pid> // 查看线程CPU占用 // 发现的问题: // - Full GC频繁,达到每分钟50次 // - 老年代使用率99% // - CPU使用率飙升至95%
根因分析
// 问题代码示例
public class OrderService {
// 问题1:无限缓存用户信息
private static Map<Long, UserInfo> userCache = new HashMap<>();
// 问题2:同步调用过多
public Order createOrder(OrderRequest request) {
// 同步调用库存服务(2ms)
StockInfo stock = stockClient.queryStock(request.getSkuId());
// 同步调用用户服务(3ms)
UserInfo user = userClient.queryUser(request.getUserId());
// 同步调用优惠券服务(2ms)
CouponInfo coupon = couponClient.queryCoupon(request.getUserId());
// 同步调用支付服务(1ms)
PayInfo pay = payClient.queryPay(request.getOrderId());
// 总耗时:8ms,但流量突增时每个服务都会变慢
return buildOrder(stock, user, coupon, pay);
}
}
解决方案
// 方案1:缓存本地化
@Component
public class UserCacheManager {
private LoadingCache<Long, UserInfo> cache = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(new CacheLoader<Long, UserInfo>() {
@Override
public UserInfo load(Long userId) {
return userClient.queryUser(userId);
}
});
public UserInfo getUser(Long userId) {
try {
return cache.get(userId);
} catch (Exception e) {
return userClient.queryUser(userId);
}
}
}
// 方案2:异步化改造
public CompletableFuture<Order> createOrderAsync(OrderRequest request) {
CompletableFuture<StockInfo> stockFuture =
CompletableFuture.supplyAsync(() -> stockClient.queryStock(request.getSkuId()));
CompletableFuture<UserInfo> userFuture =
CompletableFuture.supplyAsync(() -> userClient.queryUser(request.getUserId()));
CompletableFuture<CouponInfo> couponFuture =
CompletableFuture.supplyAsync(() -> couponClient.queryCoupon(request.getUserId()));
CompletableFuture.allOf(stockFuture, userFuture, couponFuture).join();
return CompletableFuture.completedFuture(
buildOrder(stockFuture.get(), userFuture.get(), couponFuture.get(), null)
);
}
// 方案3:限流熔断
@Bean
public RateLimiter rateLimiter() {
return RateLimiter.create(5000); // 每秒钟只允许5个请求
}
public Order createOrderWithLimit(OrderRequest request) {
if (!rateLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {
throw new BusyException("系统繁忙,请稍后重试");
}
// 业务逻辑
}
热点商品抢购导致的线程池耗尽
场景描述
- 现象:核心线程池全部被占满,新请求排队等待,甚至出现RejectedExecutionException
- 特征:某一个SKU的请求量特别大
排查过程
// 查看线程池状态 ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPoolTaskExecutor.getThreadPoolExecutor(); executor.getActiveCount(); // 当前活跃线程数 executor.getQueue().size(); // 队列大小 executor.getPoolSize(); // 线程池大小 // 通过Arthas查看线程状态 thread -n 3 -v // 查看最忙的3个线程 // 发现大量线程阻塞在:redis.clients.jedis.Jedis.get()
解决方案
// 方案1:热点Key隔离
public class HotKeyAwareService {
private static final int HOT_THRESHOLD = 100;
private volatile Map<String, AtomicInteger> hotKeyCounter = new ConcurrentHashMap<>();
private ExecutorService hotKeyExecutor = Executors.newFixedThreadPool(50);
private ExecutorService normalExecutor = Executors.newFixedThreadPool(200);
public void processRequest(String skuId, Request request) {
AtomicInteger counter = hotKeyCounter.computeIfAbsent(skuId, k -> new AtomicInteger(0));
if (counter.incrementAndGet() > HOT_THRESHOLD) {
// 热点SKU,使用独立线程池
hotKeyExecutor.submit(() -> handleHotRequest(skuId, request));
} else {
// 正常请求
normalExecutor.submit(() -> handleNormalRequest(skuId, request));
}
}
}
// 方案2:布隆过滤器+多级缓存
public class CacheStrategy {
private BloomFilter<String> hotSkuFilter = BloomFilter.create(Funnels.stringFunnel(), 10000, 0.01);
private LoadingCache<String, Object> localCache = cacheConfig.createLocalCache();
private RedisTemplate<String, Object> redisTemplate;
public Object getSkuInfo(String skuId) {
// 1. 本地缓存
Object localData = localCache.getIfPresent(skuId);
if (localData != null) return localData;
// 2. Redis缓存
Object redisData = redisTemplate.opsForValue().get("sku:" + skuId);
if (redisData != null) {
localCache.put(skuId, redisData);
return redisData;
}
// 3. 数据库查询(加分布式锁防止缓存击穿)
String lockKey = "sku:lock:" + skuId;
boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (lock) {
try {
Object dbData = queryFromDB(skuId);
redisTemplate.opsForValue().set("sku:" + skuId, dbData, 10, TimeUnit.MINUTES);
return dbData;
} finally {
redisTemplate.delete(lockKey);
}
}
// 等待重试
try { Thread.sleep(50); } catch (InterruptedException e) {}
return redisTemplate.opsForValue().get("sku:" + skuId);
}
}
日志打爆磁盘IO
场景描述
- 现象:磁盘IO使用率100%,应用响应变慢
- 原因:流量突增时,错误日志疯狂打印,特别是DEBUG级别的日志
排查过程
// 查看磁盘和日志情况 df -h // 查看磁盘使用 iostat -x 1 // 查看IO情况 du -sh logs/* // 查看各日志文件大小 // 通过日志分析工具查看 grep -c "ERROR" error.log | tail -n 100
解决方案
// 方案1:日志分级和限流
@Component
public class LoggingInterceptor {
private static final org.slf4j.Logger LOGGER =
LoggerFactory.getLogger(LoggingInterceptor.class);
// 限流日志
private RateLimiter errorLogLimiter = RateLimiter.create(10);
private RateLimiter warnLogLimiter = RateLimiter.create(5);
public void logError(String traceId, String message, Throwable e) {
if (errorLogLimiter.tryAcquire()) {
// 避免打印堆栈过多
LOGGER.error("traceId: {}, error: {}", traceId, message);
} else {
// 记录到内存缓冲区,定期汇总
logBuffer.add(new LogEntry(traceId, message));
}
}
}
// 方案2:异步日志+批量写入
<configuration>
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>true</neverBlock>
<appender-ref ref="FILE"/>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.example" level="INFO"/>
<logger name="org.springframework" level="WARN"/>
<root level="INFO">
<appender-ref ref="ASYNC"/>
</root>
</configuration>
流量突增排查工具清单
# 1. 系统层面 top # CPU使用情况 free -m # 内存使用 vmstat 1 # 系统活动 iostat -x 1 # IO状态 netstat -ant | wc -l # 连接数 # 2. JVM层面 jps # 查看java进程 jstat -gcutil <pid> 1000 # GC情况 jmap -heap <pid> # 堆内存详情 jstack <pid> > dump.log # 线程栈 # 3. 应用层面 curl -X GET /actuator/metrics # Spring Boot指标 curl -X GET /actuator/health # 健康检查 # 4. 中间件 redis-cli info # Redis状态 kafka-consumer-groups --describe --group <group> # Kafka消费情况
通用优化建议
// 1. 配置好连接池
@Configuration
public class ConnectionPoolConfig {
@Bean
public HikariDataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(50);
config.setMinimumIdle(10);
config.setConnectionTimeout(30000);
config.setMaxLifetime(1800000);
// 关键:设置连接泄漏检测
config.setLeakDetectionThreshold(60000);
return new HikariDataSource(config);
}
}
// 2. 使用内嵌缓存+Caffeine
@Configuration
public class CacheConfig {
@Bean
public Caffeine caffeineConfig() {
return Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(60, TimeUnit.SECONDS)
.recordStats();
}
}
// 3. 服务降级策略
@Component
public class DegradeStrategy {
private boolean isHealthy = true;
private CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("myservice");
public Object callWithDegrade() {
return circuitBreaker.executeSupplier(() -> {
if (!isHealthy) return fallback();
return doBusiness();
});
}
private Object fallback() {
return Collections.emptyMap(); // 返回缓存或默认值
}
}
- 提前预防:流量突增前做好压测,预先把线程池、连接池调大
- 快速定位:使用Arthas、JProfiler等工具快速定位瓶颈
- 弹性设计:使用Hystrix/Resilience4j做好熔断降级
- 缓存优先:尽可能使用本地缓存+Redis多级缓存
- 异步化:非核心链路使用异步处理
- 监控报警:配置好Prometheus+Grafana,当流量达到警戒线时自动告警
面试重点:能针对突增流量设计出合理的解决方案,并解释为什么这么设计,以及考虑系统的高可用性和降级策略。