面试准备笔记
技术栈与相关组件
- Bootstrap
- Elasticsearch:搜索引擎
- Sphinx:搜索引擎
- Element UI
- Vue
- Nuxt.js:SSR
- React
- Next.js:SSR
- PHP 框架:Laravel、Yii2、ThinkPHP(TP)
- Swoole:PHP 协程框架
- Hyperf:PHP 协程框架
- Electron
- Python:Flask、Django、Scrapy
- Go:Gin(HTTP 服务器框架)
- Kratos:微服务框架
- Nacos
- Nexus
Vue 相关
- Pinia
- Mutations
- defineProperty
- Proxy
- Provide / Inject
可能的面试问题
前端问题
- Vue 如何渲染长列表?编辑长列表时可能产生哪些问题?
- 前端搜索会发出多次请求,接口返回顺序不一致时如何处理?后端如何优化?
RabbitMQ 项目经验表达
我主要用 PHP 和 Node.js 开发,在之前的电商项目里,订单系统就用过 RabbitMQ。
比如用户下单后,订单服务发一条消息到队列,短信服务和库存服务各自消费。这样做的好处是用户不用等所有处理完成就能看到“下单成功”,系统也更稳定,高峰期不会崩。
PHP 高并发
在 PHP 中处理高并发问题需要多方面优化,包括缓存技术、异步处理、数据库优化、负载均衡、合适的系统架构以及服务器配置优化。
1. 使用缓存技术
缓存是提高 PHP 应用性能和应对高并发最有效的方法之一。常见的缓存方式包括:
- 页面缓存:将整个页面的 HTML 输出缓存到磁盘或内存中,减少重复生成页面的开销。
- 数据缓存:将频繁访问的数据缓存到内存中,例如使用 Redis 或 Memcached。
- 对象缓存:将对象实例缓存起来,避免每次请求都重新实例化对象。
2. 使用异步处理
异步处理可以将耗时任务(如发送邮件、生成报告)从主请求中剥离出来,使用消息队列(如 RabbitMQ、Kafka)处理这些任务。
3. 数据库优化
数据库优化在高并发场景下至关重要,可以从以下方面入手:
- 索引优化:确保数据库查询使用适当的索引,提高查询速度。
- 查询优化:避免使用复杂的 SQL 查询,必要时拆分大查询。
- 读写分离:使用主从数据库架构,将读请求分配到从库,提高数据库整体处理能力。
- 连接池:使用数据库连接池,减少数据库连接建立和释放的开销。
4. 使用负载均衡
负载均衡可以将用户请求分配到多个服务器,防止单台服务器过载。常见工具有 Nginx 等。
5. 采用合适的架构
- 微服务架构:将单一应用拆分为多个小型服务,每个服务独立部署和扩展。
- CDN:使用内容分发网络缓存静态资源,减轻源站压力。
- 无服务器架构:使用云厂商的无服务器计算服务(如 AWS Lambda),按需扩展。
6. 服务器配置优化
- 配置 PHP-FPM:调整 PHP-FPM 的进程数量和最大请求数。
- 优化 Web 服务器配置:例如调整 Nginx 的 Worker 进程数、开启 Gzip 压缩。
- 使用合适的硬件和云服务:根据业务选择性能合适的服务器和云服务(如 AWS、阿里云)。
面试回答总结
在 PHP 中处理高并发问题需要多方面优化,包括使用缓存技术、异步处理、数据库优化、负载均衡、选择合适的架构以及优化服务器配置。通过结合这些技术,可以显著提高 PHP 应用的并发处理能力,确保在高并发场景下依然提供稳定、高效的服务。
一个页面加载慢,如何排查
- 先打开浏览器 F12 的 Network 面板。
- 如果 TTFB 很大,直接去服务器查看 PHP-FPM 慢日志。慢日志会记录执行超过设定时间的脚本及其堆栈信息。
- 必要时使用 Xdebug 分析 PHP 代码。
- 检查 PHP 代码中是否存在循环查询数据库的问题。
- 检查是否调用外部接口,以及外部接口是否响应缓慢。
- 查看 MySQL 慢查询日志,找出没有索引、全表扫描或大量 JOIN 的 SQL。
- 如果日志没有开启,可以暂时在代码关键节点使用 microtime(true) 手动打点,快速定位耗时逻辑。
- 检查服务器负载:使用 top 或 htop 查看 CPU 和内存占用,确认是否存在磁盘 I/O 过高或内存不足导致频繁 Swap 的情况。
大数据量如何分表
PHP 处理数据量过大的表(通常是千万级以上)时,可以通过水平分表或垂直分表,将数据分散到多个物理表。
核心策略包括根据时间(月 / 年)或 ID 取模生成表名,例如 order_2023_10;也可以使用 Sharding-JDBC、MyCat 等组件处理分片逻辑。
一、分表方案分类
1. 水平分表(Sharding)
将一张大表按照特定规则拆分成多张表,表结构相同但数据不同。
适用场景:
- 单表行数超过约 500~1000 万。
- 表容量较大,例如超过 2 GB。
示例:user_00、user_01 …… user_99。
2. 垂直分表(Vertical Sharding)
将一张表中的列拆分到不同表中。
适用场景:将主键与不常用、体积较大的字段(如 TEXT、BLOB)分开,提高常用查询效率。
二、PHP 实现水平分表的步骤
1. 确定分表规则
- 按 ID 取模:table_id = id % table_count,适合数据均匀分布。
- 按时间分表:table_name = order_ + date(Ym, strtotime(time)),适合日志、订单等时序数据,便于按时间清理旧数据。
2. PHP 生成表名并写入
根据分片键计算目标表名,再使用 PDO 预处理语句写入对应分表。表名不能直接使用未经校验的用户输入,应通过白名单或程序生成。
3. 查询与归档
- 单条查询:根据分表键(如 user_id)计算表名后查询。
- 范围查询:如果查询跨表,需要在 PHP 端查询所有相关表,再使用 array_merge 合并结果。
- 数据迁移:当某张分表过大时,可以通过 PHP 脚本将旧数据归档到冷表,或按策略删除。
三、常见问题与注意事项
- 主键 ID 冲突:分表后不能直接依赖 MySQL 自增 ID,需要引入分布式 ID 生成器,例如雪花算法、UUID、Redis 自增或专用号段表。
- 查询限制:避免不带分片键(如 user_id)的查询,否则可能需要遍历所有子表。
- 开发复杂度:分表逻辑会增加开发和运维复杂度,建议优先考虑成熟的分库分表中间件。
四、其他优化手段(不分表)
在分表前,优先考虑以下方案:
- 优化索引和 SQL。
- 使用查询缓存。
- 使用主从架构实现读写分离。
订单表的分片键设计
订单表优先使用 user_id 分片,因为它通常匹配最高频的用户订单查询场景。
使用 user_id 分片的一个问题是:用户只提供订单号时,不知道应该去哪个库或分表查询。
方法一:订单号包含 user_id 信息
让订单号本身成为可路由的数字或编码,而不是随机、无规律的字符串。这样可以从订单号解析出用户信息或分片信息。
方法二:建立“订单号 → 用户 ID”的二级索引表
单独建立一张较小的索引表,只存储两列:
- order_no:主键。
- user_id:用于定位订单所在分片。
分片后需要考虑的问题
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 全局唯一 ID | 不能再直接使用 MySQL 自增 ID | 雪花算法、Redis INCR、数据库号段 |
| 跨库 JOIN | 订单表和用户表可能不在同一个库 | 数据冗余,例如保存用户昵称快照;或由应用层多次查询组装 |
| 跨库分页 | ORDER BY time LIMIT 10 OFFSET 100 很难跨库实现 | 业务上限制查询范围,或使用 ES 做检索 |
其他提醒
- 在真正实施分库分表之前,先尝试索引优化、读写分离和缓存。
- 分库分表是最后的重型方案,复杂度和运维成本都很高,不要为了分表而分表。
- 分片键一旦确定就不要轻易修改,数据迁移代价通常很大。
不同查询场景的建议
- 用户查数据:使用分片键精准命中一张表,性能最好。
- 管理端查数据:不要在分表上做复杂查询,可以将数据同步到 Elasticsearch,获得更灵活的检索能力。
- 不得不做跨表查询:控制分表数量(例如少于 64 张),使用分库分表中间件的并发查询能力,但要接受性能上限较低的事实。
网络卡顿导致重复支付,如何处理
场景:用户只提交一次支付,前端提示网络错误,但后端其实仍在执行,例如正在调用微信支付接口。此时不能只依赖前端提交锁,需要后端和对账机制共同保证一致性。
1. 前端提交锁
前端仍然应该增加提交锁,避免用户短时间内重复点击。但这只能减少重复请求,不能解决请求已经发出后网络中断的问题。
2. Redis 分布式锁
使用 Redis 分布式锁拦截并发支付请求,保证同一个订单在同一时间只允许一个支付流程执行。锁需要设置过期时间,并注意释放锁的归属校验,避免误删其他请求持有的锁。
3. MySQL 行级乐观锁
通过状态条件保证订单只能从待支付状态进入支付中或已处理状态,例如:
UPDATE orders
SET status = 1
WHERE id = 1 AND status = 0;
检查受影响行数:更新成功才继续支付,更新失败则说明订单已经被其他请求处理。
4. 支付结果查询与延时队列
支付过程中无法确定结果时,不要直接把订单标记为失败。可以投递延时消息到 RabbitMQ,等待一段时间后主动查询微信支付订单状态。
例如 3 分钟后由消费者查询微信订单:
- 已支付:更新本地订单并执行业务成功逻辑。
- 未支付:根据业务规则恢复待支付状态或进入人工处理。
- 状态未知:继续重试,并设置最大重试次数。
5. 定时对账
每天定时查询微信支付账单,与当天本地订单进行比对:
- 重复支付:申请退款,并记录退款流水。
- 微信已支付、本地未支付:补充本地订单状态和业务处理。
- 本地显示已支付、微信未支付:进入异常审核流程。
6. 总体原则
支付系统的关键不是依赖某一次接口调用的返回结果,而是通过订单状态机、幂等控制、支付回调、主动查询、延时重试和定期对账,最终保证本地订单与第三方支付状态一致。