索引缺失引发的慢查询风暴
痛点案例:用户订单查询接口TP99从50ms飙升至2s,日志显示WHERE status=1未走索引
核心知识点:联合索引最左匹配原则、索引下推、覆盖索引
-- 原始问题SQL
SELECT * FROM orders WHERE user_id=10086 AND status=1 ORDER BY create_time DESC;
-- 优化方案
ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
EXPLAIN SELECT id FROM orders WHERE user_id=10086 AND status=1;
-- 使用覆盖索引
解决手段:
- 使用SHOW INDEX分析现有索引
- 通过EXPLAIN验证索引使用情况
- 对高频查询字段建立联合索引,包含排序字段
总结:索引是成本最低的性能优化手段,需定期使用pt-index-usage分析索引有效性
N+1查询导致的数据库雪崩
痛点案例:批量查询200个用户信息,循环调用SELECT * FROM users WHERE id=?
核心知识点:批量查询优化、IN语句执行原理
// 错误写法
List users = ids
.stream()
.map(id -> userMapper.selectById(id)).collect(Collectors.toList());
// 正确方案
List users = userMapper.selectBatchIds(ids);
-- SQL优化
SELECT * FROM users WHERE id IN (1001,1002,...,1200)
解决手段:
- 使用MyBatis的实现批量查询
- 控制单次IN查询参数数量(建议≤500)
- 配合连接池配置合理的maxActive参数
总结:批量操作减少网络IO次数,是提升吞吐量的关键
分布式服务调用的连环超时
痛点案例:用户详情接口需串行调用用户服务(200ms)+ 积分服务(150ms)+ 成长值服务(180ms),总耗时530ms 核心知识点:CompletableFuture并行编程、线程池资源隔离
ExecutorService executor = Executors.newFixedThreadPool(3);
CompletableFuture future1 = CompletableFuture.runAsync(() -> {
userService.getUserInfo();
}, executor);
CompletableFuture future2 = CompletableFuture.runAsync(() -> {
bonusService.getBonusInfo();
}, executor);
CompletableFuture.allOf(future1, future2).join();
解决手段:
- 使用@Async注解实现异步调用
- 为不同服务配置独立线程池避免相互影响
- 设置合理的future超时时间
总结:并行化改造可将耗时降为最慢单次调用时间,需配合Hystrix熔断机制
缓存穿透引发的数据库过载
痛点案例:恶意请求不存在的商品ID,导致大量请求穿透到DB
核心知识点:布隆过滤器、空值缓存、互斥锁
// 使用Redisson布隆过滤器
RBloomFilter bloomFilter = redisson.getBloomFilter("productBloom");
bloomFilter.tryInit(1000000L, 0.03);
public Product getProduct(String id) {
if (!bloomFilter.contains(id)) return null;
String key = "product:" + id;
Product product = redis.get(key);
if (product == null) {
RLock lock = redisson.getLock(key);
try {
lock.lock();
// 双重检查锁
product = redis.get(key);
if (product == null) {
product = db.get(id);
redis.setex(key, 300, product != null ? product : NULL_OBJ);
}
} finally {
lock.unlock();
}
}
return NULL_OBJ.equals(product) ? null : product;
}
解决手段:
- 预热热点数据到Redis
- 对不存在的key缓存短时间的空值
- 使用分布式锁保证单线程回源
总结:缓存设计需考虑穿透、雪崩、击穿三大问题场景
大事务导致的连接池耗尽
痛点案例:@Transactional包含RPC调用+文件上传+DB操作,事务持续10s+
核心知识点:事务拆分、异步提交、柔性事务
// 错误示例
@Transactional
public void createOrder(Order order) {
// 本地DB操作
orderDao.insert(order);
// 远程调用库存服务
stockFeignClient.deduct(order.getSkuId());
// 上传日志到OSS
logService.uploadOperationLog();
} // 优化方案
public void createOrder(Order order) {
orderDao.insert(order);
// 本地事务
CompletableFuture.runAsync(() -> {
stockFeignClient.deduct(order.getSkuId());
}, executor);
rocketMQTemplate.asyncSend("LOG_TOPIC", logDTO);
}
解决手段:
- 将RPC调用移出事务边界
- 非核心操作改用消息队列异步处理
- 使用TCC补偿事务保证最终一致性
总结:事务粒度要小,遵循"短平快"原则
锁竞争引发的接口性能骤降
痛点案例:秒杀场景下synchronized导致TPS不到100
核心知识点:分段锁、Redis分布式锁、CAS自旋
// 优化前
public synchronized void deductStock(Long itemId) {
// 扣减库存
}
// 优化方案:分段锁
private static final Map SEGMENT_LOCKS = new ConcurrentHashMap<>();
public void deductStock(Long itemId) {
Object lock = SEGMENT_LOCKS.computeIfAbsent(itemId % 16, k -> new Object());
synchronized (lock) {
// 扣减库存
} }
// Redisson分布式锁
public void deductStock(Long itemId) {
RLock lock = redisson.getLock("ITEM_LOCK:" + itemId);
try {
lock.lock();
// 扣减库存
} finally {
lock.unlock();
}
}
解决手段:
- 库存数据预拆分到多个slot
- 使用Redis+Lua实现原子操作
- 本地缓存+定时同步兜底方案
总结:减少锁粒度是提升并发能力的关键
深度分页导致的IO过载
痛点案例:SELECT * FROM logs LIMIT 1000000,20 执行超时 核心知识点:游标分页、ES搜索优化、ID分段
-- 优化方案1:游标分页
SELECT * FROM logs WHERE id > 1000000 ORDER BY id LIMIT 20;
-- 优化方案2:ES方案
GET /logs/_search {
"search_after": [1000000],
"size": 20,
"sort": ["id"]
}
解决手段:
- 禁止直接暴露分页参数给前端
- 使用时间范围+ID的复合条件查询
- 大数据量场景迁移到Elasticsearch
总结:分页深度与性能成反比,需设计合理的分页策略
日志同步写盘导致的IO瓶颈
痛点案例:每个请求都同步写操作日志,导致磁盘IO利用率100% 核心知识点:异步刷盘、批量提交、MMAP内存映射
// 优化前
public void saveLog(Log log) {
logMapper.insert(log);
// 同步写入
}
// 优化方案:Disruptor队列异步处理
Disruptor disruptor = new Disruptor<>(...);
disruptor.handleEventsWith(new LogEventHandler());
RingBuffer ringBuffer = disruptor.start();
public void saveLog(Log log) {
long sequence = ringBuffer.next();
LogEvent event = ringBuffer.get(sequence);
event.setLog(log);
ringBuffer.publish(sequence);
}
解决手段:
- 使用LMAX Disruptor无锁队列
- 采用GroupCommit机制批量提交
- 重要日志采用WAL+异步双写
总结:IO密集型操作必须异步化处理
数据异构带来的查询加速
痛点案例:订单列表需要关联查询用户表+商品表+商家表 核心知识点:CQRS模式、宽表构建、CDC同步
-- 原始复杂查询
SELECT o.*,u.name,g.title,s.shop_name FROM orders o LEFT JOIN users u ON o.user_id=u.id LEFT JOIN goods g ON o.goods_id=g.id LEFT JOIN shops s ON o.shop_id=s.id
-- 优化方案:构建宽表
CREATE TABLE order_wide (
order_id BIGINT PRIMARY KEY,
user_name VARCHAR,
goods_title VARCHAR,
shop_name VARCHAR, ... )
ENGINE=InnoDB;
-- 使用Canal监听binlog同步数据
解决手段:
- 使用Flink实现实时宽表构建
- 将多表关联查询转为单表查询
- 定期全量+增量同步保障数据一致性
总结:空间换时间是提升复杂查询的终极方案
流量突增导致的服务过载
痛点案例:大促期间API网关CPU飙升至90% 核心知识点:动态扩容、服务降级、流量染色
# Sentinel降级规则配置
resource: queryOrderAPI
grade: RT count: 100
timeWindow: 10
# Hystrix线程池隔离配置
hystrix.threadpool.default.coreSize=20
hystrix.threadpool.default.maxQueueSize=1000
解决手段:
- 配置弹性伸缩策略(K8s HPA)
- 非核心功能降级(如关闭历史订单查询)
- 实施全链路压测提前发现瓶颈
总结:高并发场景需要"防、抗、疏"三位一体策略
对象序列化的性能黑洞
痛点案例:POJO包含60+字段导致JSON序列化耗时50ms+ 核心知识点:Protobuf编码、字段裁剪、零拷贝
// 使用Protostuff优化序列化
Schema schema = RuntimeSchema.getSchema(Order.class);
LinkedBuffer buffer = LinkedBuffer.allocate(512);
byte[] bytes = ProtostuffIOUtil.toByteArray(order, schema, buffer);
// 字段过滤注解
public class OrderDTO {
@JsonInclude(Include.NON_NULL)
private String sensitiveField; }
解决手段:
- 定义精简的API专用DTO
- 采用二进制序列化协议
- 启用Jackson Afterburner模块
总结:网络传输优化是分布式系统的最后一道防线
池化思想的高效复用
痛点案例:每次请求都新创建实例,每次创建都耗时、耗资源导致影响效率 核心知识点:池化思想,线程池、对象池、连接池。
// 优化前,每次都创建连接
@Slf4j @Service public class HelloService {
public HelloResponseDTO hello() throws InterruptedException {
ManagedChannel channel = Grpc.newChannelBuilder("127.0.0.1:5001", InsecureChannelCredentials.create()).build();
try{
HelloServiceGrpc.HelloServiceBlockingStub blockingStub = HelloServiceGrpc.newBlockingStub(channel);
HelloResponse helloResponse = blockingStub.hello(helloRequest());
return HelloResponseDTO
.builder()
.name(helloResponse.getName())
.time(helloResponse.getTime())
.message(helloResponse.getMessage())
.build();
}finally {
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
private HelloRequest helloRequest() {
return HelloRequest.newBuilder()
.setName("Hello")
.build();
}
} // 优化后,复用连接
@Repository
public class StubRepository {
ThreadLocal channelHolder = new ThreadLocal<>();
ThreadLocal stubHolder = new ThreadLocal<>();
ThreadLocal creatTimeHolder = new ThreadLocal<>();
private ManagedChannel channel() {
ManagedChannel managedChannel = channelHolder.get();
if (managedChannel != null){
managedChannel.shutdownNow();
}
managedChannel = Grpc.newChannelBuilder("127.0.0.1:5001", InsecureChannelCredentials.create()).build();
channelHolder.set(managedChannel);
return managedChannel;
}
private HelloServiceGrpc.HelloServiceBlockingStub stub() {
HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel());
stubHolder.set(stub);
return stub;
}
public HelloServiceGrpc.HelloServiceBlockingStub getStub() {
Long createTime = creatTimeHolder.get();
Long now = System.currentTimeMillis();
if (createTime == null || now - createTime > 5000){
creatTimeHolder.set(now);
return stub();
}
return stubHolder.get();
}
public HelloServiceGrpc.HelloServiceBlockingStub rebuildStub() {
creatTimeHolder.set(System.currentTimeMillis());
return stub();
}
}
解决手段:
- 重复创建对象:使用对象池、原型模式
- 重复创建连接:使用连接池、HTTP1.1
- 重复创建线程:使用线程池、协程
总结:通过池化思想进行资源复用,提高资源利用率
数据压缩的资源节省
解决方案:通过数据压缩来减少资源使用、性能提升
- 日志酌情进行压缩
- 缓冲数据进行压缩
- 高效数据结构设计(使用比特位实现打卡功能)
架构师视角的全局优化方案
- 监控先行:搭建Prometheus+Grafana监控体系,核心指标包括:
接口QPS/RT
慢SQL发生率
缓存命中率
线程池活跃度
- 链路追踪:集成SkyWalking,重点关注:
跨服务调用链路
数据库访问耗时
Redis操作瓶颈点
- 容量规划:定期进行全链路压测,建立:
单机性能基线
集群扩容水位线
降级触发阈值
- 代码规约:在CI流程中加入:
索引命中检查(通过EXPLAIN分析)
事务超时检测(@Transactional timeout)
大对象扫描(FindBugs插件)
了解更多干货知识,关注微信公众号:Jonny玩编程
以往干货:
Tags:grafana windows