MySQL, Oracle, Linux, 软件架构及大数据技术知识分享平台

网站首页 > 精选文章 / 正文

亿级流量下的性能屠龙术:高并发接口优化的13个杀手锏

2025-02-28 16:00 huorong 精选文章 1 ℃ 0 评论


索引缺失引发的慢查询风暴

痛点案例:用户订单查询接口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
  • 重复创建线程:使用线程池、协程

总结:通过池化思想进行资源复用,提高资源利用率

数据压缩的资源节省

解决方案:通过数据压缩来减少资源使用、性能提升

  • 日志酌情进行压缩
  • 缓冲数据进行压缩
  • 高效数据结构设计(使用比特位实现打卡功能)

架构师视角的全局优化方案

  1. 监控先行:搭建Prometheus+Grafana监控体系,核心指标包括:

接口QPS/RT

慢SQL发生率

缓存命中率

线程池活跃度

  1. 链路追踪:集成SkyWalking,重点关注:

跨服务调用链路

数据库访问耗时

Redis操作瓶颈点

  1. 容量规划:定期进行全链路压测,建立:

单机性能基线

集群扩容水位线

降级触发阈值

  1. 代码规约:在CI流程中加入:

索引命中检查(通过EXPLAIN分析)

事务超时检测(@Transactional timeout)

大对象扫描(FindBugs插件)


了解更多干货知识,关注微信公众号:Jonny玩编程

以往干货:

十年架构师揭秘:如何秒读文件?

Tags:grafana windows

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言