前端面经第二篇
有了Nginx为什么还需要网关?
业务层面:Nginx与网关的核心职责差异
Nginx 和网关(如 Spring Cloud Gateway、Kong 等)虽然都能处理“请求转发”,但职责边界完全不同:
Nginx 的核心定位是“网络层/应用层基础工具”,主要解决“流量的基础分发与网络层面的效率问题”,比如:
反向代理:隐藏后端服务的真实地址,统一对外暴露一个入口(如用户访问
api.xxx.com,Nginx 转发到内部的service1:8080或service2:8081);负载均衡:把请求均匀分配到多个后端实例(如 1000 个请求分给 3 台服务器,避免单台过载);
静态资源处理:直接返回 JS、CSS、图片等静态文件,减轻后端服务压力;
基础限流:基于 IP 或连接数限制请求(如限制单 IP 每秒最多 100 次请求)。
但 Nginx 本质是“无状态的网络工具”,不理解业务逻辑,无法处理和业务强相关的复杂需求。
网关的核心定位是“业务流量的中枢控制器”,主要解决“
微服务架构下的业务级流量治理问题”,比如:统一认证授权:所有服务的登录验证、权限判断都由网关处理(如检查请求头中的 Token 是否有效,无效则直接拦截),避免每个服务重复开发认证逻辑;
动态服务路由:结合服务注册中心(如 Eureka、Nacos),自动发现后端服务的地址(服务上下线时无需手动改配置),并能根据业务规则转发(如“VIP 用户的请求路由到高性能实例”“测试环境的请求路由到测试服务”);
精细化流量控制:基于业务维度限流(如“商品服务每秒最多处理 500 次下单请求”)、熔断(如“支付服务故障时,网关直接返回‘系统繁忙’,避免请求堆积拖垮服务”);
业务级监控与日志:收集每笔请求的业务信息(如用户 ID、订单号),生成业务日志(方便追踪“某用户的下单请求为何失败”),而 Nginx 只能记录 IP、URL 等网络层面信息;
跨服务业务处理:比如请求经过网关时,自动补充“用户地理位置”“设备信息”等公共参数,后端服务无需重复解析。
简单说:Nginx 负责“把请求高效送到后端服务门口”,网关负责“在门口检查、筛选、处理请求,再决定是否/如何交给后端服务”。在复杂业务场景(尤其是微服务架构)中,Nginx 的基础能力无法满足业务级的流量治理需求,必须由网关补充。
用“小区管理”类比
可以把整个系统比作一个大型小区,用户的请求就是“访客”,后端服务就是“小区里的住户”:
Nginx 相当于小区的“大门保安”:
他的工作很基础——指挥访客从哪个门进(反向代理)、告诉访客“A 栋在左边,B 栋在右边”(基础路由)、如果同时来10个访客,安排5个走东门、5个走西门(负载均衡)。
但他不管访客是谁、有没有权限进小区、进去要干嘛——哪怕是陌生人,只要知道地址,他都会指路。网关相当于小区的“管理处前台”:
访客到了门口,前台会先查:
有没有门禁卡(认证)?没有就不让进;
门禁卡权限够不够(授权)?比如只能进 A 栋的访客,不能去 B 栋;
现在小区里人太多了(限流),让后面的访客等10分钟再进;
如果 A 栋正在维修(服务熔断),会告诉访客“暂时去不了 A 栋,先记录需求”;
还会登记访客信息(日志):“张三,下午3点去了 A 栋 501”,方便后续追溯。
前台知道每个住户的实时状态(比如“501 住户今天在家,602 住户外出了”——对应服务注册中心的服务状态),能根据这些动态调整指引;而大门保安(Nginx)只认固定的“门牌地址”,住户不在家也会照样指路。
所以,大门保安(Nginx)管“怎么高效进出”,管理处前台(网关)管“谁能进、能去哪、出了问题怎么办”——两者分工不同,大型小区(复杂系统)缺一不可。
一次性导出10万条excel是数据,如何优化?
导出10万条数据到Excel时,核心挑战是避免内存溢出、减少响应时间、优化用户体验。
以下从后端、前端及前后端协同三个维度,提供具体可落地的优化方案:
后端优化:解决“生成效率”与“资源占用”问题
后端是处理大数据量的核心,需重点优化数据查询、Excel生成、内存占用三个环节。
1. 数据查询:分批读取,避免一次性加载全量数据
问题:10万条数据一次性从数据库加载到内存,可能导致OOM(内存溢出),尤其当每条数据字段较多时。
优化方案:
采用分页查询:按
limit offset或游标分页(如MySQL的id > lastId limit 1000),每次查询1000-5000条(根据字段大小调整),逐批处理。避免关联查询:如需多表数据,优先在SQL层通过
join合并(数据库优化器更高效),而非内存中拼接。索引优化:确保查询条件字段有索引(如时间、ID),避免全表扫描;禁用
select *,只查询需导出的字段。
示例(伪代码):
java// 游标分页查询(比limit offset更高效,适合大分页) long lastId = 0; int batchSize = 2000; do { // 每次查2000条,按id递增 List<Data> batchData = dataMapper.selectByCursor(lastId, batchSize); if (batchData.isEmpty()) break; // 处理当前批次数据(写入Excel) writeToExcel(batchData); lastId = batchData.get(batchData.size() - 1).getId(); // 更新游标 } while (true);
2. Excel生成:流式写入,降低内存占用
问题:传统Excel库(如POI的
XSSFWorkbook)会将全量数据缓存在内存,10万条数据可能占用数百MB内存,甚至OOM。优化方案:
使用流式Excel库:如Alibaba的
EasyExcel(Java)、Python的pandas(配合openpyxl的write-only模式),支持数据逐行/逐批写入磁盘,内存占用可控制在10MB以内。避免复杂格式:减少合并单元格、复杂公式、图片等,仅保留必要的表头样式(格式越简单,生成越快)。
选择合适格式:10万行数据需用
.xlsx(支持104万行),禁用.xls(仅支持65536行)。
示例(EasyExcel流式写入):
java// EasyExcel核心:不缓存数据,直接写入磁盘 EasyExcel.write(outputStream, Data.class) .sheet("数据") .doWrite(() -> { // 每次调用返回一批数据(分页查询结果) return getNextBatchData(); // 内部实现游标分页 });
3. 任务处理:异步生成,避免接口超时
问题:10万条数据生成Excel可能耗时10-30秒,超过接口超时时间(通常5-10秒),导致前端请求失败。
优化方案:
异步生成:用户发起导出请求后,后端立即返回“任务ID”,并在异步线程(如Java的
ThreadPoolExecutor、Python的Celery)中处理生成逻辑。进度追踪:用Redis记录任务进度(如“已处理30%”),前端通过任务ID轮询查询进度。
文件存储:生成的Excel暂存至分布式存储(如MinIO、OSS),设置1小时过期时间,避免占用服务器磁盘。
流程示例:
- 前端:发起
POST /export,携带筛选条件; - 后端:生成任务ID,存入Redis(状态:处理中),提交异步任务;
- 后端异步线程:分页查询→流式生成Excel→上传至OSS→更新Redis状态(完成+下载URL);
- 前端:轮询
GET /export/progress?taskId=xxx,获取进度,完成后跳转下载URL。
4. 资源控制:限制并发,避免服务器过载
问题:同时有多个10万条数据导出请求,可能耗尽CPU/内存资源,影响其他服务。
优化方案:
限制并发数:通过线程池控制异步任务的最大并发(如最多同时处理3个导出任务),超出则排队。
资源隔离:为导出任务分配独立的线程池,与核心业务线程池隔离,避免互相影响。
前端优化:解决“用户体验”与“下载稳定性”问题
前端无需处理数据生成,但需优化交互流程,让用户清晰感知进度,避免操作焦虑。
1. 交互引导:提前告知成本,避免重复操作
- 导出前提示:“当前数据量约10万条,生成需1-3分钟,是否继续?”
- 禁用重复提交:点击“导出”后按钮置灰+加载动画,防止用户多次点击触发重复任务。
2. 进度反馈:实时展示状态,减少等待焦虑
- 轮询进度:用任务ID定期(如每3秒)请求后端,获取“已处理XX条/总10万条”或百分比,显示进度条。
- 状态提示:区分“生成中(30%)”“上传中”“即将开始下载”等状态,避免用户误以为卡住。
3. 下载优化:大文件下载稳定性处理
- 避免前端处理二进制:后端生成文件后,返回预签名的OSS下载URL(如阿里云OSS的
getObjectUrl),前端直接通过<a href="url" download>触发浏览器原生下载,利用浏览器的断点续传能力。 - 处理下载失败:提供“重新下载”按钮,点击后前端重新请求最新的下载URL(避免URL过期)。
4. 数据筛选:从源头减少导出量
- 提供筛选条件:如时间范围、关键字搜索等,让用户仅导出所需数据(例如用户可能只需要最近1个月的1万条数据,而非全量10万条)。
- 预设“常用模板”:针对高频导出场景(如“每日汇总”“月度报表”),预定义筛选条件,减少无效数据。
前后端协同:提升整体流程效率
- 压缩传输:后端生成Excel后,用
gzip压缩(压缩率通常30%-50%),前端下载后由浏览器自动解压,减少传输时间。 - 格式协商:支持用户选择导出格式(如
.xlsx/.csv),csv格式生成更快、体积更小(适合纯文本数据,无格式需求场景)。 - 错误处理:
- 后端:生成失败时,在Redis记录错误原因(如“数据量过大”),前端轮询时展示具体提示。
- 前端:网络中断后,允许用户通过任务ID重新查询状态,避免从头开始。
总结
- 后端核心:分页查询+流式生成+异步处理,解决内存和效率问题;
- 前端核心:进度反馈+下载优化+用户引导,提升体验;
- 协同核心:任务化管理+状态同步,确保流程可控。
通过这套方案,10万条数据的导出可控制在1-3分钟内完成,内存占用低于50MB,用户体验清晰流畅。
Token 是存在 Cookie 还是 LocalStorage?
Token 既可以存在 Cookie 中,也可以存在 LocalStorage 中,具体取决于使用场景和需求,两者各有特点:
1. 存在 Cookie 中
特点:Cookie 是浏览器自带的存储机制,有大小限制(约4KB),且可设置过期时间、域名限制等属性。
适用场景:
需要利用浏览器自动携带 Token 的场景(如跨域请求时的身份验证)。
希望通过服务器端控制 Token 有效期、安全性(如设置
HttpOnly防止 XSS 攻击,Secure限制 HTTPS 传输)。
2. 存在 LocalStorage 中
特点:LocalStorage 是 HTML5 新增的本地存储方式,容量更大(通常5-10MB),数据持久存在(除非手动清除),不会随请求自动发送。
适用场景:
前端需要主动管理 Token 发送(如通过请求头携带)的场景。
存储除 Token 外的其他前端数据,或需要长期保存 Token 时(需注意安全风险)。
总结
追求安全性(防 XSS 等)时,优先用 Cookie 并配置安全属性。
需灵活控制 Token 发送或存储更多数据时,可选用 LocalStorage,但需手动处理安全问题(如防范 XSS 攻击)。
npm link是啥?在项目中有没有使用过
在面试中被问到npm link时,可从定义、工作原理、使用场景三个维度展开回答,既体现对命令的理解,也展示实际应用经验。以下是具体内容:
一、npm link 是什么?
npm link 是 npm 提供的用于本地包调试的命令,核心作用是在本地开发的 npm 包与依赖它的项目之间建立符号链接(symlink),让项目可以直接使用本地正在开发的包代码,而无需将包发布到 npm 仓库再安装。
简单说:比如你开发了一个工具包 utils,同时有个项目 my-project 依赖它,用 npm link 可以让 my-project 直接“引用”你本地 utils 文件夹的代码,修改 utils 后,my-project 无需重新安装就能实时看到效果,极大提升调试效率。
二、工作原理(核心)
npm link 的本质是创建两级符号链接,流程如下:
在被依赖的包(如
utils)目录下运行npm link:
npm 会在全局node_modules目录(如~/.nvm/versions/node/v16.14.0/lib/node_modules/)创建一个符号链接,指向utils的本地目录(相当于“全局注册”这个包)。在依赖项目(如
my-project)目录下运行npm link utils:
npm 会在my-project/node_modules目录下创建一个符号链接,指向全局node_modules中utils的链接,最终关联到本地utils目录。
这样,项目中 import 'utils' 时,实际加载的是本地 utils 文件夹的代码,修改后即时生效。
三、项目中常见使用案例
1. 开发独立 npm 包时的本地调试(最常用)
场景:你开发了一个 UI 组件库 my-ui,同时需要在 demo-project 中演示组件效果。
- 传统方式:每次修改
my-ui后,需npm publish发布,再在demo-project中npm update my-ui才能看到效果,效率极低。 - 用
npm link:bash之后修改# 1. 进入 my-ui 目录,全局注册包 cd path/to/my-ui npm link # 2. 进入 demo-project 目录,链接到本地 my-ui cd path/to/demo-project npm link my-uimy-ui的组件代码,demo-project刷新后直接看到效果,调试完成后用npm unlink解除链接即可。
2. 多项目共享本地私有模块
场景:公司内部有 3 个项目(A/B/C)都依赖同一个内部工具包 common-tools(未发布到公网 npm)。
- 用
npm link可让 3 个项目同时关联本地common-tools的代码,修改common-tools后,3 个项目同步生效,避免重复复制代码或手动替换。
3. 调试项目依赖的深层依赖包
场景:项目 my-app 依赖 libraryA,而 libraryA 又依赖 libraryB,你发现 libraryB 有 bug,需要本地修改调试。
- 直接在
my-app/node_modules/libraryB中修改很麻烦(可能被npm install覆盖),可用npm link:bash这样# 1. 本地克隆 libraryB 源码,修改后注册到全局 cd path/to/libraryB npm link # 2. 进入 libraryA 目录,链接本地 libraryB cd path/to/libraryA npm link libraryB # 3. 进入 my-app 目录,链接本地 libraryA(如果需要) cd path/to/my-app npm link libraryAmy-app会间接使用你修改后的libraryB代码,方便调试深层依赖。
四、面试回答框架
- 定义:
npm link是 npm 用于本地包调试的命令,通过创建符号链接,让项目直接使用本地开发的包代码,避免频繁发布安装。 - 核心价值:解决“本地开发的包与依赖项目之间的实时同步”问题,提升调试效率。
- 典型场景:
- 开发 npm 包时,在 demo 项目中实时调试;
- 多项目共享本地私有模块,避免代码冗余;
- 调试项目的深层依赖包(如 node_modules 里的第三方库)。
- 注意事项:
- 调试完成后需用
npm unlink解除链接,避免影响项目依赖; - 全局链接可能导致版本冲突(如本地包与项目
package.json中声明的版本不一致); - 部分构建工具(如 webpack)可能需要配置
resolve.symlinks: true才能正确处理符号链接。
- 调试完成后需用
Nginx怎么解决高并发问题?
在面试中回答“Nginx如何解决高并发问题”时,需要从架构设计、核心机制、配置优化三个层面展开,既要讲清原理,也要结合实际操作(配置示例),体现对Nginx的深度理解。以下是结构化回答:
一、核心思路:Nginx解决高并发的本质
Nginx能处理高并发(几万甚至几十万并发连接),核心在于其**“事件驱动、异步非阻塞”的架构设计**,从根本上解决了传统服务器(如Apache)“多进程/多线程”模型的性能瓶颈(进程切换开销大、内存占用高)。
二、具体解决手段(原理+操作)
1. 事件驱动与异步非阻塞模型(核心)
原理:
Nginx采用“单线程+事件驱动”模式,通过IO多路复用(如Linux的epoll、FreeBSD的kqueue)监听多个连接,无需为每个连接创建进程/线程。当连接有数据可读/可写时,Nginx才会处理,避免了“空等”导致的资源浪费。
举个例子:1万个并发连接中,只有100个有数据交互,Nginx只需处理这100个,其余9900个连接处于“待命”状态,不占用CPU资源。
操作:
Nginx默认启用事件驱动模型,可在配置文件(nginx.conf)中指定最优的IO多路复用机制:
events {
use epoll; # 对于Linux系统,优先使用epoll(性能最优)
worker_connections 10240; # 每个worker进程最大连接数
}2. 多进程模型充分利用多核CPU
原理:
Nginx启动多个worker进程(通常与CPU核心数一致),每个worker进程独立处理连接,既避免了单线程的CPU瓶颈,又减少了进程间切换的开销(worker进程间无共享内存,通过信号通信)。
操作:
配置worker进程数等于CPU核心数(充分利用多核):
worker_processes auto; # 自动设置为CPU核心数(推荐)
# 或手动指定(如4核CPU):worker_processes 4;3. 高效的连接管理(长连接+连接池)
原理:
- 长连接(Keep-Alive):复用TCP连接,避免频繁创建/关闭连接的开销(TCP三次握手/四次挥手耗时)。
- 连接池:Nginx会维护一个连接池,对后端服务器的连接进行复用,减少后端连接建立的开销。
操作:
配置长连接参数(http块中):
http {
keepalive_timeout 65; # 长连接超时时间(客户端)
keepalive_requests 100; # 一个长连接最多处理100个请求后关闭
keepalive_disable msie6; # 对老旧浏览器禁用长连接
# 反向代理时,与后端服务器的长连接配置
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 与后端保持32个长连接
}
}4. 静态资源高效处理(减少后端压力)
原理:
Nginx处理静态资源(HTML、CSS、JS、图片)的效率远高于动态服务器(如Tomcat),通过直接读取磁盘文件并返回,无需经过复杂的业务逻辑,可快速响应大量静态请求,减轻后端服务器的并发压力。
操作:
配置静态资源缓存与压缩(http块中):
http {
# 静态资源缓存(浏览器缓存)
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
root /usr/share/nginx/html; # 静态资源目录
expires 30d; # 浏览器缓存30天
add_header Cache-Control "public, max-age=2592000";
}
# 启用gzip压缩(减少传输数据量)
gzip on;
gzip_types text/css application/javascript image/png; # 对指定类型文件压缩
gzip_comp_level 5; # 压缩级别(1-9,5为平衡值)
}5. 反向代理与负载均衡(分散并发压力)
原理:
Nginx作为反向代理服务器,可将前端的高并发请求分发到多个后端服务器(如Tomcat集群),避免单台后端服务器过载,通过“分而治之”提高整体并发处理能力。
操作:
配置负载均衡(http块中):
http {
upstream backend_servers {
# 轮询策略(默认),可改为ip_hash、weight等
server 192.168.1.101:8080 weight=3; # 权重3,处理30%请求
server 192.168.1.102:8080 weight=2; # 权重2,处理20%请求
server 192.168.1.103:8080 backup; # 备份服务器(主服务器故障时启用)
}
# 反向代理到后端集群
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host; # 传递原始Host头
proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实IP
}
}6. 限流与请求过滤(保护系统稳定)
原理:
当并发请求超过系统承载上限时,Nginx可通过限流(限制每秒请求数)或过滤(拦截恶意请求)保护后端服务,避免被“冲垮”。
操作:
配置限流(http块中):
http {
# 定义限流规则(100r/s,burst=20,超出的20个请求排队)
limit_req_zone $binary_remote_addr zone=my_limit:10m rate=100r/s;
location /api {
limit_req zone=my_limit burst=20 nodelay; # 应用限流规则
proxy_pass http://backend_servers;
}
}三、总结
Nginx解决高并发的核心逻辑是:
- 以“事件驱动+异步非阻塞”为基础,用极少的资源处理大量连接;
- 通过多进程、长连接、静态资源优化减少开销;
- 用负载均衡、限流等手段分散压力、保护系统。
实际应用中,需结合业务场景(如静态资源占比、后端服务器性能)调整配置,才能最大化并发处理能力。
项目当中mixin和utils区别?
在前端开发中,mixin(混入)和 utils(工具函数)都是代码复用的手段,但它们的设计目的、使用场景和实现方式有显著区别。
一、核心定义与本质
mixin(混入)
是一种代码复用模式,主要用于组件/类之间共享“状态、方法、生命周期逻辑”,尤其在 Vue、React(早期)等框架中常见。
本质是“将一个对象的属性和方法‘混入’到另一个对象中”,复用的是带状态或上下文相关的逻辑(比如组件的 data、methods、钩子函数等)。utils(工具函数)
是通用功能的函数集合,用于封装无状态、纯逻辑的工具方法(与具体业务或组件上下文无关)。
本质是“独立的功能模块”,复用的是纯函数逻辑(输入输出确定,无副作用)。
二、核心区别对比
| 维度 | mixin | utils |
|---|---|---|
| 内容 | 可包含状态(如 data)、方法、生命周期钩子(如 Vue 的 created)等,与组件上下文强相关。 | 仅包含纯函数(如格式化日期、数组去重),无状态,不依赖具体上下文。 |
| 用途 | 复用“组件级别的逻辑”(如多个组件都需要的表单验证逻辑、权限判断逻辑)。 | 复用“通用功能逻辑”(如字符串处理、数据转换、工具计算等)。 |
| 依赖上下文 | 依赖组件实例(如 this 指向组件),可能使用组件的 props、data 等。 | 不依赖任何上下文,输入参数确定则输出结果唯一(纯函数)。 |
| 副作用 | 可能产生副作用(如修改组件状态、调用组件方法)。 | 无副作用(仅处理输入,不修改外部变量)。 |
| 命名冲突风险 | 高(混入的方法/数据可能与组件自身的命名冲突)。 | 低(函数独立封装,通过命名空间调用,如 utils.formatDate())。 |
| 使用方式 | 在组件中“注入”(如 Vue 中通过 mixins: [myMixin] 引入,混入的内容会合并到组件中)。 | 直接导入调用(如 import { formatDate } from './utils',然后 formatDate(...))。 |
三、举例说明
1. mixin 示例(Vue 中)
假设有多个组件都需要“登录状态判断”的逻辑:
// 登录状态判断的 mixin
const loginMixin = {
data() {
return {
isLogin: false
};
},
created() {
this.checkLogin(); // 调用混入的方法
},
methods: {
checkLogin() {
this.isLogin = localStorage.getItem('token') ? true : false;
}
}
};
// 组件中使用
export default {
mixins: [loginMixin], // 混入后,组件会拥有 isLogin、checkLogin 等
created() {
console.log(this.isLogin); // 可直接使用混入的状态
}
};这里的 mixin 依赖组件的 this 上下文,包含状态(isLogin)和与组件生命周期相关的逻辑(created 钩子)。
2. utils 示例
假设有多个地方需要“格式化日期”的功能:
// utils/date.js
export const formatDate = (date, format = 'YYYY-MM-DD') => {
// 纯函数逻辑:仅根据输入的 date 和 format 处理,无状态
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return format.replace('YYYY', year).replace('MM', month).replace('DD', day);
};
// 使用时直接导入调用
import { formatDate } from './utils/date';
console.log(formatDate(new Date())); // 2024-05-20这里的 formatDate 是纯函数,不依赖任何组件或上下文,输入输出完全由参数决定。
四、如何选择?
- 用 mixin:当需要复用的逻辑与组件状态、生命周期强相关(比如组件的共同数据、钩子函数逻辑)。
- 用 utils:当需要复用的逻辑是通用工具功能(比如数据处理、格式转换),且不依赖具体组件上下文。
补充:mixin 的局限性与替代方案
mixin 虽然能复用逻辑,但存在命名冲突、逻辑来源不清晰(多个 mixin 叠加后难以追溯)等问题。现代框架中,逐渐被更清晰的方案替代:
- Vue 3 推荐用 Composition API(组合式 API) 替代 mixin;
- React 推荐用 自定义 Hooks 替代 mixin。
而 utils 由于其“纯函数、无副作用”的特性,在任何场景下都是稳定且推荐的复用方式。