什么会导致00M与怎么解决

大文件导入导出:用easyexcel

Redis三剑客:穿透、击穿、雪崩

MQ消息积压:用多线程、加消费者、惰性队列

第三方接口调用:设置超时时间、Sentinel

这些都会导致OOM的罪魁祸首,以下是详细说明

解决大文件导入导出OOM问题的方法

使用EasyExcel可以有效避免大文件导入导出时的OOM(内存溢出)问题,主要依赖其基于SAX的解析方式和分批处理机制。

EasyExcel导入大文件

采用流式读取方式,逐行解析数据,避免一次性加载全部数据到内存。

// 导入数据监听器
public class DataListener extends AnalysisEventListener<YourModel> {
    private List<YourModel> cachedDataList = new ArrayList<>();
    private static final int BATCH_SIZE = 1000;

    @Override
    public void invoke(YourModel data, AnalysisContext context) {
        cachedDataList.add(data);
        if (cachedDataList.size() >= BATCH_SIZE) {
            saveData();
            cachedDataList.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
    }

    private void saveData() {
        // 批量处理逻辑
    }
}

// 调用方式
EasyExcel.read(file.getInputStream(), YourModel.class, new DataListener()).sheet().doRead();

EasyExcel导出大文件

采用分批次写入方式,避免创建大对象列表导致内存溢出。

// 分页查询数据写入
try (ExcelWriter excelWriter = EasyExcel.write(outputStream, YourModel.class).build()) {
    WriteSheet writeSheet = EasyExcel.writerSheet("sheet1").build();
    
    int page = 1;
    while (true) {
        List<YourModel> data = queryDataByPage(page, PAGE_SIZE);
        if (data.isEmpty()) {
            break;
        }
        excelWriter.write(data, writeSheet);
        page++;
    }
}

关键优化点

设置合理的缓存参数可进一步提升性能

// 内存参数调整
EasyExcel.read(inputStream)
    .headRowNumber(2)  // 表头行数
    .autoCloseStream(true)  // 自动关闭流
    .ignoreEmptyRow(true)  // 忽略空行
    .cacheRowLimit(100)  // 内存中缓存行数
    .registerReadListener(listener)
    .sheet()
    .doRead();

其他注意事项

对于超大数据量(百万级以上),建议结合数据库游标或分库分表策略处理 文件格式优先选择xlsx而非xls,xlsx格式更易流式处理 网络传输场景建议配合分块传输编码(chunked transfer encoding)

Redis缓存问题的三剑客:穿透、击穿、雪崩

缓存穿透
指查询一个根本不存在的数据,缓存和数据库都没有,导致每次请求都直接访问数据库。常见于恶意攻击或业务逻辑缺陷。
解决方案:

  • 布隆过滤器(Bloom Filter):在缓存层前加一层布隆过滤器,快速判断数据是否存在。
  • 空值缓存:对查询结果为空的请求,缓存一个短时间的空值(如key:null,TTL 5分钟)。

缓存击穿
指某个热点数据在缓存过期时,大量并发请求直接穿透到数据库,导致数据库压力骤增。
解决方案:

  • 互斥锁(Mutex Key):当缓存失效时,使用分布式锁(如Redis的SETNX)保证只有一个线程重建缓存。
  • 逻辑过期:不对物理TTL设限,在数据中存储逻辑过期时间,由后台线程异步更新。
    示例伪代码:
def get_data(key):
    data = redis.get(key)
    if not data:
        if redis.setnx("lock:" + key, 1, ex=10):  # 加锁
            data = db.query(key)
            redis.set(key, data, ex=3600)
            redis.delete("lock:" + key)
        else:
            time.sleep(0.1)  # 重试
            return get_data(key)
    return data

缓存雪崩
指大量缓存数据在同一时间过期或Redis宕机,导致所有请求直接打到数据库,引发连锁故障。
解决方案:

  • 差异化过期时间:在基础TTL上增加随机值(如3600 + random(0, 300)秒)。
  • 多级缓存:结合本地缓存(如Caffeine)和分布式缓存,降低单一依赖。
  • 熔断降级:通过Hystrix等工具在数据库压力过大时触发降级策略(如返回默认值)。

总结对比

问题类型触发条件核心解决方案
穿透数据不存在布隆过滤器 + 空值缓存
击穿热点数据突然失效互斥锁 + 逻辑过期
雪崩大量缓存同时失效/宕机多级缓存 + 熔断降级

MQ消息积压的解决方案

消息积压是MQ(消息队列)系统中常见的性能问题,通常由于生产者速率高于消费者处理能力导致。以下是几种高效解决方案:

多线程消费

通过增加消费者线程数提升消息处理能力。需注意线程安全和资源竞争问题。

  • 线程池配置:根据系统资源合理设置线程池大小,避免过多线程导致上下文切换开销。
  • 分区消费:如Kafka支持分区消费,确保不同线程处理不同分区的消息,避免重复消费。

示例代码(Java线程池):

ExecutorService executor = Executors.newFixedThreadPool(10);  
while (true) {  
    Message message = consumer.receive();  
    executor.submit(() -> processMessage(message));  
}  

增加消费者实例

横向扩展消费者服务实例,适用于集群部署场景。

  • 负载均衡:通过MQ的负载均衡机制(如RabbitMQ的轮询分发)分配消息到不同消费者。
  • 自动扩缩容:结合Kubernetes或云服务动态调整消费者实例数,根据积压阈值触发扩容。
惰性队列(Lazy Queue)

RabbitMQ的惰性队列将消息直接存储到磁盘,减少内存占用,适用于大量消息堆积场景。

  • 配置方式:声明队列时设置x-queue-modelazy
  • 权衡:牺牲部分吞吐量(磁盘I/O延迟),但避免内存溢出风险。

示例(RabbitMQ声明惰性队列):

Map<String, Object> args = new HashMap<>();  
args.put("x-queue-mode", "lazy");  
channel.queueDeclare("lazy.queue", true, false, false, args);  

消息批量处理

消费者采用批量拉取模式,减少网络开销和频繁处理的开销。

  • Kafka示例:配置max.poll.records调整单次拉取数量。
  • 批量提交:处理完成后批量提交偏移量,避免重复消费。
消息过期与死信处理

对非关键消息设置TTL(Time-To-Live),超时后转入死信队列,后续统一处理或丢弃。

  • RabbitMQ配置:通过x-message-ttl参数设置队列级别TTL。
  • 死信路由:配置死信交换器(DLX)处理过期消息。
监控与告警

实时监控积压指标(如Kafka的lag),设置阈值触发告警,便于及时干预。

  • 工具:使用Prometheus+Grafana或MQ自带管理界面(如RabbitMQ Management Plugin)。
优化消费者逻辑

分析消费者代码瓶颈,例如:

  • 异步化:将耗时操作(如数据库写入)异步处理。
  • 缓存:减少重复计算或查询,利用本地缓存或Redis。

通过上述方法综合应用,可有效缓解消息积压问题。需根据具体MQ类型(Kafka/RabbitMQ/RocketMQ等)和业务场景选择合适策略。

设置超时时间

在调用第三方接口时,设置合理的超时时间可以避免因接口响应过慢导致的系统阻塞。超时时间通常包括连接超时和读取超时。

连接超时(Connection Timeout):指建立连接的最大等待时间。若在指定时间内未能建立连接,则抛出超时异常。
读取超时(Read Timeout):指从连接建立成功到接收到数据的最大等待时间。若在指定时间内未收到数据,则抛出超时异常。

示例代码(Java + HttpClient)

RequestConfig config = RequestConfig.custom()
    .setConnectTimeout(5000) // 5秒连接超时
    .setSocketTimeout(10000) // 10秒读取超时
    .build();

CloseableHttpClient client = HttpClientBuilder.create()
    .setDefaultRequestConfig(config)
    .build();

关键点

  • 根据接口的SLA(服务等级协议)调整超时时间,避免设置过长或过短。
  • 结合重试机制,在超时后尝试有限次数的重试(注意幂等性)。

使用Sentinel实现限流与熔断

Sentinel是阿里巴巴开源的流量控制组件,适用于保护第三方接口调用,避免因下游服务不稳定导致上游系统崩溃。

核心功能

  1. 限流(Flow Control)
    通过QPS或线程数限制对第三方接口的调用频率。
    示例配置(Sentinel规则):

    FlowRule rule = new FlowRule();
    rule.setResource("third_party_api");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    rule.setCount(10); // 每秒最多10次调用
    FlowRuleManager.loadRules(Collections.singletonList(rule));
    

  2. 熔断(Circuit Breaking)
    当接口错误率或响应时间超过阈值时,自动熔断请求,避免雪崩效应。
    示例配置:

    DegradeRule degradeRule = new DegradeRule();
    degradeRule.setResource("third_party_api");
    degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    degradeRule.setCount(5); // 5次异常后熔断
    degradeRule.setTimeWindow(30); // 熔断30秒
    DegradeRuleManager.loadRules(Collections.singletonList(degradeRule));
    

最佳实践

  • Fallback处理:熔断时返回默认值或缓存数据,保证用户体验。
  • 动态规则:结合Nacos或ZooKeeper实现规则动态更新。

结合超时与Sentinel的完整方案

  1. 定义资源
    使用Sentinel的@SentinelResource注解标记需保护的接口调用方法:

    @SentinelResource(value = "third_party_api", fallback = "fallbackMethod")
    public String callThirdPartyApi() {
        // 调用逻辑
    }
    
    public String fallbackMethod(Throwable t) {
        return "fallback response";
    }
    

  2. 配置规则
    通过控制台或代码设置限流和熔断规则,例如:

    • QPS限流:20次/秒
    • 熔断条件:响应时间>1秒或错误率>50%
  3. 监控与告警
    集成Dashboard实时监控流量,并配置异常告警(如Slack或邮件通知)。


注意事项

  • 超时与熔断协同:超时时间应小于熔断的慢调用阈值,避免规则冲突。
  • 线程池隔离:使用Hystrix或Sentinel的线程池隔离避免资源耗尽。
  • 日志记录:详细记录超时和熔断事件,便于后续分析优化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值