[TOC]
百万级EXCEL读取
1、EasyExcel
基于POI的事件驱动模型,逐行解析,内存占用低
2、Apache POI (SAX模式)
事件驱动、流式读取
3、逐行读取后,及时批量处理,及时释放,因为等完全读取完整个excel的时候,内存也会爆炸
思考一:支持多线程读吗
理论上不支持,核心限制:EasyExcel的读取是顺序流式解析,XML解析器必须按顺序读取文件内容
其他场景可以考虑多线程,比如 多个 sheet 页面、读取出来后的数据处理
思考二:excel文件本身就很大,二进制流文件怎么处理的?内存不会溢出吗
EasyExcel:不加载整个XML到内存。边读边解析,遇到标签触发事件
1
2
3
4
5
6
7
8
9
物理文件 (磁盘)
↓ (文件流)
ZIP解压流 (xlsx本质是ZIP)
↓ (XML流)
XML解析器 (SAX)
↓ (事件)
EasyExcel监听器
↓ (对象)
你的业务处理
思考三:excel不是本地文件,而是通过http请求过来的,内存如何处理
1
2
3
4
5
// 整个文件加载到内存! 不推荐
byte[] bytes = file.getBytes();
// 直接使用文件流,不加载到内存
InputStream inputStream = file.getInputStream();
有一个POST接口,接收一个MultipartFile(实际上Spring MVC在处理MultipartFile时,如果文件超过一定大小,会临时存储到磁盘,但可以通过配置使其不存盘,直接流式读取)
方式一:使用MultipartFile,但Spring默认会使用临时文件,所以对于大文件,我们确保临时文件存在,然后EasyExcel从临时文件读取,这样也不会内存溢出,但会有磁盘IO
方式二:完全流式解析,不保存到临时文件。我们可以通过自定义解析HttpServletRequest来实现,但这样就需要自己处理multipart/form-data的解析。不过,Spring提供了Streaming API
实际上,Spring MVC的MultipartFile接口默认实现是将文件存储到临时文件,所以我们可以直接使用MultipartFile,然后通过EasyExcel读取这个临时文件。但是,如果不想落盘,我们也可以直接读取MultipartFile的InputStream,因为MultipartFile的InputStream也是从临时文件读取的,所以不会内存溢出
思考四:反过来,java输出excel文件,文件太大,如何处理
不推荐:POI XSSFWorkbook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 危险:使用POI的XSSFWorkbook
@GetMapping("/export-oom")
public void exportOOM(HttpServletResponse response) {
Workbook workbook = new XSSFWorkbook(); // 在内存中构建整个Excel
Sheet sheet = workbook.createSheet("数据");
// 写入100万行数据
for (int i = 0; i < 1000000; i++) {
Row row = sheet.createRow(i);
for (int j = 0; j < 20; j++) {
row.createCell(j).setCellValue("数据行-" + i + "-列-" + j);
}
// 每写入一行,内存占用就增加一点,直到OOM
}
// 永远执行不到这里...
response.setContentType("application/vnd.ms-excel");
workbook.write(response.getOutputStream());
}
推荐 POI SXSSF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@GetMapping("/export-safe")
public void exportSafe(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=large-file.xlsx");
// 🎯 关键:使用SXSSFWorkbook,设置行内存窗口
Workbook workbook = new SXSSFWorkbook(100); // 只在内存中保留100行
Sheet sheet = workbook.createSheet("数据");
try {
// 写入表头
Row headerRow = sheet.createRow(0);
for (int i = 0; i < 20; i++) {
headerRow.createCell(i).setCellValue("列头-" + i);
}
// 流式写入数据
for (int rowNum = 1; rowNum <= 1000000; rowNum++) {
Row row = sheet.createRow(rowNum);
for (int colNum = 0; colNum < 20; colNum++) {
row.createCell(colNum).setCellValue("数据-" + rowNum + "-" + colNum);
}
// 每1000行手动刷新一次(可选)
if (rowNum % 1000 == 0) {
((SXSSFSheet) sheet).flushRows(100); // 刷新并保留100行在内存
}
// 进度跟踪
if (rowNum % 10000 == 0) {
log.info("已生成 {} 行数据", rowNum);
}
}
workbook.write(response.getOutputStream());
} finally {
// 🎯 重要:清理临时文件
if (workbook instanceof SXSSFWorkbook) {
((SXSSFWorkbook) workbook).dispose();
}
workbook.close();
}
}
接口防刷
1、 分布式限流
redis + lua 脚本 实现计数器
令牌桶、固定窗口、滑动窗口
2、 设备指纹识别
3、 请求签名
4、 ip 黑名单
nacos宕机了,本地是否有缓存这个服务列表,是否还能正常远程调用
总结:只要服务生产者和消费者没有发生任何变动,即使 Nacos 一直宕机,服务之间的调用也完全正常
服务发现 (Naming) : 服务列表 (ServiceInfo) : nacos/naming 目录下 : 服务实例信息缓存与容灾
flowchart TD
A[服务列表查询请求] --> B{内存缓存是否存在且有效?}
B -- 是 --> C[直接使用内存缓存]
B -- 否 --> D[查询Nacos服务器]
D --> E{服务器查询成功?}
E -- 是 --> F[更新内存与本地快照]
E -- 否 --> G[读取本地快照文件]
F --> C
G --> C
C --> H[返回服务列表]
I[Nacos服务器] -- 推送变更 --> F
配置管理 (Config) : 配置参数 (如.properties, .yaml) : nacos/config 目录下 : 配置内容缓存与容灾
配置管理,Nacos客户端在获取配置时,通常会遵循 本地容灾文件 -> 本地快照文件 -> 内存缓存 -> Nacos服务器 的查询顺序,以最大程度保证自身的可用性
配置管理: 更侧重于数据可靠性和最终一致性。它会优先尝试读取你可能预先配置好的、非常重要的容灾文件,以防止配置完全丢失
服务列表: 查询则更侧重于速度和对服务状态变化的敏感度
Openfeign如何使用,A服务调用B服务发生了哪些事
总结:代理实现,拦截器帮助构造调用模版,发送http请求
flowchart TD
A[A服务业务代码] --> B[Feign客户端接口调用]
B --> C[Feign动态代理]
C --> D[MethodHandler处理]
D --> E[请求模板创建]
E --> F[编码器处理参数]
F --> G[服务发现与负载均衡]
G --> H{使用服务发现?}
H -- 是 --> I[从注册中心获取实例]
H -- 否 --> J[使用直接URL]
I --> K[负载均衡选择实例]
J --> L[构造完整URL]
K --> L
L --> M[HTTP客户端发送请求]
M --> N[服务B接收请求]
N --> O[服务B处理业务]
O --> P[服务B返回响应]
P --> Q[HTTP客户端接收响应]
Q --> R{响应状态}
R -- 2xx成功 --> S[解码器处理响应]
R -- 4xx/5xx错误 --> T[错误解码器处理]
S --> U[返回结果对象]
T --> V[抛出异常]
U --> W[A服务继续执行]
V --> W
加密后的数据如何模糊查询
| 类型 | 特点 | 常见算法 |
|---|---|---|
| 可逆加密 | 加密后可解密还原 | AES、DES、RSA、SM4 |
| 不可逆加密 | 加密后不可还原 | MD5、SHA、bcrypt、SM3 |
可逆加密算法(对称/非对称)
对称:加密解密使用相同密钥:AES、DES、SM4 非对称:公钥加密,私钥解密:RSA、ECC
一般采用 分词模糊搜索
注意:原数据按规则分词加密存储后,查询条件入参同样也要先分词,再去搜索
还一种思路:明文、密文 做映射表,但这样也失去了加密的初衷。但如果密文映射表,是独立的服务和数据库,从数据库权限上收口,也不是不行