Skip to content

面试准备笔记

技术栈与相关组件

  • 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

可能的面试问题

前端问题

  1. Vue 如何渲染长列表?编辑长列表时可能产生哪些问题?
  2. 前端搜索会发出多次请求,接口返回顺序不一致时如何处理?后端如何优化?

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 应用的并发处理能力,确保在高并发场景下依然提供稳定、高效的服务。

一个页面加载慢,如何排查

  1. 先打开浏览器 F12 的 Network 面板。
  2. 如果 TTFB 很大,直接去服务器查看 PHP-FPM 慢日志。慢日志会记录执行超过设定时间的脚本及其堆栈信息。
  3. 必要时使用 Xdebug 分析 PHP 代码。
  4. 检查 PHP 代码中是否存在循环查询数据库的问题。
  5. 检查是否调用外部接口,以及外部接口是否响应缓慢。
  6. 查看 MySQL 慢查询日志,找出没有索引、全表扫描或大量 JOIN 的 SQL。
  7. 如果日志没有开启,可以暂时在代码关键节点使用 microtime(true) 手动打点,快速定位耗时逻辑。
  8. 检查服务器负载:使用 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. 总体原则

支付系统的关键不是依赖某一次接口调用的返回结果,而是通过订单状态机、幂等控制、支付回调、主动查询、延时重试和定期对账,最终保证本地订单与第三方支付状态一致。