大文件导入导出:用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-mode为lazy。 - 权衡:牺牲部分吞吐量(磁盘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是阿里巴巴开源的流量控制组件,适用于保护第三方接口调用,避免因下游服务不稳定导致上游系统崩溃。
核心功能:
-
限流(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)); -
熔断(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的完整方案
-
定义资源
使用Sentinel的@SentinelResource注解标记需保护的接口调用方法:@SentinelResource(value = "third_party_api", fallback = "fallbackMethod") public String callThirdPartyApi() { // 调用逻辑 } public String fallbackMethod(Throwable t) { return "fallback response"; } -
配置规则
通过控制台或代码设置限流和熔断规则,例如:- QPS限流:20次/秒
- 熔断条件:响应时间>1秒或错误率>50%
-
监控与告警
集成Dashboard实时监控流量,并配置异常告警(如Slack或邮件通知)。
注意事项
- 超时与熔断协同:超时时间应小于熔断的慢调用阈值,避免规则冲突。
- 线程池隔离:使用Hystrix或Sentinel的线程池隔离避免资源耗尽。
- 日志记录:详细记录超时和熔断事件,便于后续分析优化。
8485

被折叠的 条评论
为什么被折叠?



