业务实现
你是怎么独立完成一个业务功能的?比如草稿箱功能?
我按照需求→设计→开发→测试→上线的完整流程推进:
需求分析:明确草稿箱解决用户填写表单时信息丢失问题,梳理核心流程:手动保存、自动保存、草稿列表、恢复、删除。
系统设计:
- 数据库:因为每个审批流程的表单字段不一致,所以用longtext存储表单数据(longtext 更通用、性能更好、跨版本更兼容,也更适合存大型 JSON)
- 接口:设计POST/draft/save、GET/draft/list等RESTful接口
开发实现: - 后端:分层架构,先检查该用户是否已存在草稿,若存在则更新,不存在则创建。
- 前端:localStorage存储草稿数据,30秒定时自动保存,优化用户体验
测试上线:功能、并发、兼容性、场景测试,灰度发布后全量上线。
自动保存草稿时,如何保证幂等(idempotent)?
幂等(Idempotency)是编程与分布式系统中的核心概念,核心定义是:同一操作(或请求)被执行一次,与被执行多次的结果完全一致,且不会对系统状态造成额外副作用。
我主要做了两层保证:
数据库唯一索引:
- 草稿表添加
(user_id, definition_id)唯一索引,同一个用户 + 同一个流程定义,只允许存在一个草稿。 - 定义
definition_id为审批流程的唯一标识,确保每个流程的草稿独立存储。
- 草稿表添加
前端请求幂等:
- 前端在保存草稿时,添加
requestId到请求体,后端根据此 ID 检查是否重复请求。 - 如果重复请求,后端直接返回之前的保存结果,不重复处理。
- 前端在保存草稿时,添加
你做草稿功能遇到过什么坑?
坑一:自动保存和手动保存冲突
问题描述:
用户手动点"保存"的同时,自动保存定时器一起触发,会覆盖掉前者。
解决办法:
- 自动保存打上标记,前端做优先级控制
- 手动保存优先级高于自动保存
- 自动保存只能更新非关键字段(比如更新时间)
坑二:恢复时表单版本不一致
问题描述:
流程设计器更新了表单字段,但历史草稿还存在旧结构 → 恢复会报错。
解决办法:
保存草稿时记录表单版本号:
{ "form_data": {...}, "form_version": "1.2.0" }恢复时进行版本比对:
- 版本一致 → 正常恢复
- 版本不一致 → 兼容模式(只恢复匹配字段)
扫码登录如何实现?
核心流程:
生成二维码(UUID)
后端生成一个临时loginToken,存 Redis,设短期过期(如 60 秒)。
前端展示二维码,二维码内容为:https://xxx.com/qrLogin?token=xxxx轮询登录状态
网页端每 1~2 秒轮询 Redis 查询该token是否被确认。手机扫码 → 授权
用户打开 App/小程序 → 扫码 → 将登录授权请求发送给后端:loginToken + 用户凭证(如 userId / session / jwt)后端验证后将 Redis 中
token状态改为 “已确认并绑定 userId”。网页端完成登录
轮询收到状态变为已确认 → 后端生成正式 JWT → 浏览器完成登录。
为什么微信只能扫码登录?
因为 PC 端无法直接拿到微信账号体系的凭证。
微信的登录凭证(OpenID、SessionKey)只能通过 微信客户端或小程序内部调用微信 API 获得,网页环境无法拿到微信的用户授权。
所以微信 PC 端必须通过手机授权(扫码登录)才能确认用户身份。
分享邀请如何判断邀请来源?如何防刷?
一、如何判断是谁邀请的?
给邀请链接加 inviterId:
https://xxx.com/register?inviterId=12345新人注册时带上此字段,后端记录邀请关系即可。
二、防刷方案:
限制同设备/同 IP 的注册次数
- 设备指纹
- IP 限流(如 1 小时只能注册 1 次)
手机号唯一性
一个手机号只能被邀请一次。风控校验
- 注册号段黑名单
- 代理 IP / 云主机 IP 自动拒绝
- 频繁注册的设备直接拉黑
行为验证
- 图形验证码
- 滑块验证
- 短信验证码限制(1 小时 n 次)
延迟发放奖励
如:被邀请者满足 登录+使用 3 天 才给奖励,减少恶意批量注册。
假设有一个用户量级达上千万或上亿的系统,举办营销活动让用户抢同一个产品,如何保证该产品只能被一个人抢到?
核心方案:Redis分布式锁(Redisson)+ 数据库乐观锁(version/库存字段)双校验,配合库存预扣减+异步确认,步骤如下:
- 用户抢单先抢Redis锁(按商品ID设锁,防并发);
- 抢到锁后,数据库执行
update 商品表 set 库存=库存-1 where 商品ID=xxx and 库存>0(乐观锁本质,update语句原子操作防超卖); - 若更新行数>0,抢单成功;否则失败,释放Redis锁;
- 异步同步订单状态,超时未支付自动回滚库存。
有商品售卖系统和商品后台管理系统两个系统,商家在运营管理系统更新商品数量(如苹果今日上限100个、明日上限200个)时,如何让数据实时同步,使用户在售卖系统查询时能看到最新数量?
1. 直接数据库同步(简单但实时性好)
- 两个系统用同一个数据库表
- 后台管理系统更新库存后,售卖系统直接查数据库拿最新数据
- 优点:实时性强、实现简单
- 缺点:数据库压力大,高并发时可能拖垮系统
2. 缓存 + 消息队列异步刷新(高性能)
- 缓存层:售卖系统把商品库存放 Redis 里
- 消息队列:
- 商家在后台系统更新库存表后,发个消息到 MQ(像 Kafka、RabbitMQ 那种)
- 售卖系统订阅 MQ,收到消息就更新 Redis 缓存
- 查询逻辑:售卖系统先查 Redis,没有再查数据库
- 优点:高并发时查询快、数据库压力小
- 需要注意:确保消息靠谱投递,别让库存数据对不上
3. 事件驱动 + 缓存预热(再优化一下)
- 对每日限量、秒杀库存这些特殊情况,后台管理系统一更新就立刻刷新 Redis
- 可以配合过期时间或者预扣库存机制,保证售卖系统在高并发时看到的库存和后台一致
如何实现用户积分排行榜用啥?
回答:
我会根据数据规模选择不同方案:
1. 小规模(百万级以下)
MySQL + 索引优化:
- 用户表添加积分字段,创建降序索引
- 查询:
SELECT user_id, score FROM users ORDER BY score DESC LIMIT 100 - 优点:简单可靠,数据一致性好
2. 中等规模(百万-千万级)
Redis Sorted Set:
- 使用ZADD更新用户积分,key为"leaderboard"
- 查询:
ZREVRANGE leaderboard 0 99 WITHSCORES - 优点:性能高,天然支持排序,适合实时更新
3. 大规模(千万级以上)
Redis + MySQL混合方案:
- Redis存储Top N排行榜(如Top 1000)
- MySQL存储全量数据,定期同步到Redis
- 查询:先查Redis,未命中再查MySQL
- 优点:兼顾性能与数据完整性
4. 超大规模(亿级以上)
分片Redis + 缓存预热:
- 按用户ID哈希分片到多个Redis实例
- 热点数据(Top 100)多级缓存(本地缓存+Redis)
- 异步更新积分,批量同步到排行榜
- 优点:水平扩展,支持极高并发
关键优化点:
- 积分变更时异步更新排行榜,避免阻塞主流程
- 设置合理缓存过期时间,定期刷新
- 对用户自身排名使用LRU缓存,减少重复计算
- 考虑使用布隆过滤器快速判断用户是否在排行榜中
如何优化系统架构设计来缓解流量压力提升并发性能?
核心策略:
1. 负载均衡与水平扩展
- Nginx反向代理 + 多台应用服务器
- 数据库读写分离,主从复制
- 微服务拆分,按业务垂直切分
2. 缓存体系优化
- 多级缓存:浏览器缓存 → CDN → Redis → 本地缓存
- 热点数据预加载,冷数据延迟加载
- 缓存穿透/击穿/雪崩防护(布隆过滤器、空值缓存、随机过期)
3. 异步化处理
- 消息队列削峰填谷(Kafka/RabbitMQ)
- 核心链路异步化,非核心操作降级处理
- 事件驱动架构,解耦系统依赖
4. 资源池化与限流
- 连接池(数据库、Redis、HTTP)
- 线程池优化,合理配置核心参数
- 令牌桶/漏桶算法限流,保护系统稳定性
如果让你实现一个RPC(Remote Procedure Call)框架,会考虑用哪些技术解决哪些问题?
核心组件与技术选型:
1. 通信层
- Netty:高性能网络通信,支持长连接、心跳检测
- 协议设计:自定义二进制协议(如dubbo协议),减少序列化开销
2. 序列化
- Protobuf/Hessian:高效序列化,减少网络传输量
- 支持多种序列化方式,兼容不同语言
3. 服务注册与发现
- Zookeeper/Nacos:服务注册中心,支持健康检查
- 实时更新服务提供者列表,故障自动剔除
4. 负载均衡
- 轮询、随机、加权轮询、一致性哈希等策略
- 支持自定义负载均衡算法
5. 容错机制
- 熔断器:Hystrix/Sentinel,防止雪崩效应
- 重试机制:可配置重试次数与策略
- 降级处理:服务不可用时返回默认值
6. 监控与治理
- 链路追踪(Trace ID),分布式调用链监控
- 性能指标收集(QPS、响应时间、成功率)
- 动态配置中心,运行时参数调整
订单到期关单如何实现
实现方案
1. 定时任务方案
实现方式:
- 使用
@Scheduled注解或Quartz框架,定期扫描数据库中到期的订单 - 触发关单逻辑,更新订单状态为已关闭
代码示例:
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void closeExpiredOrders() {
LocalDateTime now = LocalDateTime.now();
List<Order> expiredOrders = orderMapper.selectExpiredOrders(now);
for (Order order : expiredOrders) {
order.setStatus(OrderStatus.CLOSED);
order.setCloseTime(now);
orderMapper.updateById(order);
// 处理后续逻辑(如库存回滚、通知用户等)
}
}2. 延迟队列方案
实现方式:
- 使用Redis的ZSet或RocketMQ的延迟消息
- 订单创建时,将订单ID和到期时间放入延迟队列
- 到期后,触发关单逻辑
Redis ZSet实现:
// 订单创建时,添加到延迟队列
public void addOrderToDelayQueue(Order order) {
// 将到期时间戳作为分数
redisTemplate.opsForZSet().add("delay:order:close",
order.getId(), order.getExpireTime().toEpochSecond(ZoneOffset.UTC));
}
// 定时从延迟队列中获取到期订单(每秒钟执行一次)
@Scheduled(fixedRate = 1000)
public void processDelayQueue() {
long now = System.currentTimeMillis() / 1000;
Set<String> expiredOrderIds = redisTemplate.opsForZSet()
.rangeByScore("delay:order:close", 0, now);
if (expiredOrderIds != null && !expiredOrderIds.isEmpty()) {
for (String orderId : expiredOrderIds) {
// 关单逻辑
closeOrder(orderId);
// 从延迟队列中删除
redisTemplate.opsForZSet().remove("delay:order:close", orderId);
}
}
}3. 消息队列延迟消息方案
实现方式:
- 使用RocketMQ、RabbitMQ等支持延迟消息的MQ
- 订单创建时,发送延迟消息
- 消息到期后,消费端触发关单逻辑
RocketMQ实现:
// 发送延迟消息
public void sendDelayMessage(Order order) {
Message message = new Message("order-close-topic",
order.getId().getBytes());
// 设置延迟级别(如延迟30分钟)
message.setDelayTimeLevel(3); // 根据MQ配置,对应不同的延迟时间
rocketMQTemplate.send(message);
}
// 消费延迟消息,触发关单
@RocketMQMessageListener(topic = "order-close-topic", consumerGroup = "order-close-group")
public class OrderCloseConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt message) {
String orderId = new String(message.getBody());
closeOrder(orderId);
}
}三种方案对比
| 方案 | 实时性 | 性能 | 实现复杂度 | 可靠性 | 适用场景 |
|---|---|---|---|---|---|
| 定时任务方案 | 低(轮询延迟) | 低(批量查询) | 低 | 中(任务中断风险) | 小型系统,订单量不大 |
| 延迟队列方案 | 高(到期触发) | 高(内存队列) | 中 | 高(持久化保证) | 中大型系统,要求实时性 |
| 消息队列延迟消息方案 | 高(到期触发) | 最高(分布式) | 高 | 最高(集群容错) | 大规模系统,高并发场景 |
如何实现排行榜,实现分数相同按照时间顺序排序,怎么做?
实现方式:ZSet 组合 score
Redis ZSet介绍:
ZSet(有序集合)是Redis中的一种数据结构,每个成员关联一个分数,根据分数自动排序。但ZSet只能按单一维度排序,因此需要将"分数+时间"组合成一个复合分数。
核心思路:
将业务分数和时间戳组合成一个复合分数,使分数高的排在前面,分数相同时时间早的排在前面。
实现公式:
finalScore = 分数 * 1_000_000_0000 + (最大时间 - 时间戳)公式解析:
- 分数乘以大数:确保分数差异在最终分数中占主导地位
- 最大时间减时间戳:使时间越早,计算出的值越大
- 组合结果:分数大的排前面,分数相同则时间早的排前面
科学计数法说明:
代码中使用的 1e10 是科学计数法表示法,等同于 1_000_000_0000(100亿):
1e10表示 1 × 10¹⁰ = 10,000,000,000- Java中两种写法完全等价,但科学计数法更简洁易读
代码实现:
// 添加到排行榜
double finalScore = score * 1e10 + (MAX_TIME - timestamp);
jedis.zadd("rank", finalScore, userId);
// 获取排行榜前N名
Set<String> topUsers = jedis.zrevrange("rank", 0, n-1);用 Redis ZSet 做排行榜,把"分数 + 时间"组合成一个 score。分数越大排前,当分数相同时,让时间越早的 finalScore 更大,从而排前,实现同分按时间排序。
项目是自己做的吗?项目实现过程中有遇到哪些问题?
是的,项目是我独立开发的。问题的话有:
- 需求变更频繁:因为是自己开发的项目,可能明天睡醒就想加一个新功能(通过敏捷开发方法,采用迭代式开发)
- 依赖冲突:项目中使用了许多第三方库和框架,版本之间不兼容(需要手动分析,exclude 对应的依赖,或者统一版本)
- 运行后可能出现问题,但是没有找到原因:建立完善的监控和日志系统,及时发现和解决线上问题,确保系统的稳定性和可靠性。
项目中的缓存怎么设计的?缓存的更新策略怎么设计的?
缓存设计:
- 缓存层级:采用多级缓存架构,包括本地缓存(Caffeine)和分布式缓存(Redis),提高缓存命中率和系统性能。
- 缓存键设计:遵循业务语义,包含业务类型、主键等信息,确保缓存键的唯一性和可读性。
- 缓存大小限制:设置合理的缓存大小和过期时间,避免缓存占用过多内存资源。
- 热点数据处理:对热点数据进行特殊处理,如预热、限流、降级等,避免缓存击穿和雪崩。
缓存更新策略:
- 更新数据库后删除缓存:避免脏数据,下次读取时从数据库重新加载。
- 延迟双删:更新数据库→删除缓存→延迟→再删除缓存,确保脏数据清除。
- 基于消息队列异步更新:通过MQ异步更新缓存,保证最终一致性。
- 定时任务刷新:对于一些低频更新的数据,采用定时任务定期刷新缓存。
- 版本号控制:结合版本号或时间戳判断,避免删除新数据。
平时用大模型干什么?
我平时主要使用大模型进行学习:
代码开发辅助:
- 生成代码片段和模板
- 解释复杂代码逻辑
- 代码重构建议
- 修复代码bug
- 编写单元测试
技术学习和研究:
- 学习新技术和框架
- 解释技术概念和原理
- 对比不同技术方案
- 生成学习计划和大纲
- 解答技术疑问
文档编写和优化:
- 生成技术文档和API文档
需求分析和设计:
- 分析用户需求
- 绘制UML图(类图、序列图等)
报错分析与解决:
使用的主要大模型:
- GPT系列
- grok
- 通义千问
用户反馈首次进入网站特别慢,第二次和后面访问相对正常,并且不同地区用户体验不一样有快有慢,为什么?
原因分析:
缓存机制差异:首次访问时,浏览器、CDN、服务器均无缓存,需加载所有静态资源(CSS/JS/图片)、建立数据库连接、执行初始化逻辑;后续访问可直接命中各级缓存(浏览器缓存、CDN缓存、服务器缓存),跳过重复加载步骤。
CDN节点分布:不同地区CDN节点覆盖度和负载不同,偏远地区可能缺乏就近节点,需跨区域拉取资源,导致延迟增加。
网络链路差异:不同地区到源站的网络路径长度、带宽、拥塞程度不同,导致基础网络延迟差异。
首次访问特有开销:首次访问会触发DNS解析、TCP连接建立(三次握手)、TLS握手(HTTPS)等一次性开销,后续访问可复用连接。
优化方向:
- 合理配置浏览器缓存策略,延长静态资源的缓存有效期(减少重复下载,加快后续访问)
- 优化CDN节点覆盖,增加边缘节点部署
- 启用HTTP/2+,减少连接建立开销
- 实施DNS预解析和资源预加载
- 对首次访问的关键资源进行缓存预热
核心结论: 首次访问慢主要是缓存未命中和一次性连接开销导致;地区差异则与CDN覆盖和网络链路有关,通过分层缓存优化和CDN节点优化可有效改善。