1) 【一句话结论】
采用分层分布式架构,通过前端负载均衡、缓存热点数据、后端实时出价与预计算匹配结合,结合数据库分库分表与消息队列异步任务,实现百万级QPS下的低延迟和高可用。
2) 【原理/概念讲解】
老师口吻解释系统各组件职责:
- 前端:Nginx做负载均衡,分发请求到后端集群,实现请求分发与流量控制。
- 后端:拆分为请求解析(解析用户特征、广告位信息)、特征处理(清洗、标准化,如地理位置编码为经纬度)、匹配计算(根据用户特征和广告位信息,结合预计算模型或实时算法,计算匹配度)、出价计算(根据匹配结果动态计算CPM/CPC,如匹配度越高,出价越高)、结果组装(生成响应)。
- 缓存:Redis存储热点数据(用户特征、广告位信息、预计算匹配结果),减少数据库查询,降低延迟。
- 数据库:存储冷数据(用户历史行为、广告位详细配置、预算记录),支持持久化与事务。
- 消息队列:异步处理非实时任务(如用户行为日志写入、广告位预算更新),避免阻塞主流程。
- 分布式锁:保证并发场景下的资源竞争(如更新预算时防止超卖,用Redis锁,设置超时时间避免死锁)。
类比:缓存像“快速缓存”,数据库像“持久化仓库”,消息队列像“任务队列”,分布式锁像“互斥锁”,确保资源安全。
3) 【对比与适用场景】
| 组件 | 定义 | 特性 | 使用场景 | 注意点 |
|---|
| 缓存(Redis) | 内存键值存储,支持高并发读写 | 延迟低(毫秒级),支持数据过期 | 热点数据(用户特征、广告位信息)、预计算匹配结果 | 需处理缓存击穿(热点数据失效)、雪崩(大量数据失效)、过期策略(TTL) |
| 数据库(MySQL/ClickHouse) | 持久化存储,支持事务 | 事务支持、持久化、支持复杂查询 | 冷数据(用户历史行为、广告位配置、预算记录) | 需分库分表(水平扩展),读写分离(提升读性能) |
| 消息队列(Kafka/RabbitMQ) | 异步消息传输系统 | 高吞吐、持久化、支持事务 | 异步任务(日志写入、预算更新) | 消息持久化(如Kafka的持久化存储),确保不丢失 |
| 分布式锁(Redis锁) | 分布式互斥锁 | 高并发、低延迟、可重入 | 资源竞争(如预算更新) | 设置锁超时时间,避免死锁 |
4) 【示例】
- 请求示例:
{
"user_id": "u123",
"user_features": {
"location": "北京",
"device": "手机",
"history": ["商品A", "商品B"]
},
"ad_slot": {
"position": "首页Banner",
"size": "300x50",
"budget": 100
}
}
- 处理流程:
- 负载均衡(Nginx)分发请求到后端。
- 后端解析请求,从Redis获取用户特征(缓存未命中则数据库读取并缓存)。
- 从Redis获取广告位信息(同理,缓存未命中则数据库读取)。
- 匹配计算:布隆过滤器过滤无效广告位,减少数据库查询;预计算模型(如用户画像与广告标签的匹配矩阵)计算匹配得分。
- 出价计算:根据匹配得分动态计算CPM(如匹配得分*预算/1000),生成出价。
- 组装响应(广告ID、出价、点击率预估),返回客户端。
5) 【面试口播版答案】
面试官您好,设计高并发广告投放系统,核心是分层架构,结合实时出价、预计算匹配和异步处理。前端用Nginx做负载均衡,分发请求到后端集群。后端拆分为请求解析、特征处理、匹配计算、出价计算模块。缓存层用Redis存储用户特征、广告位信息(热点数据),数据库存储冷数据(用户行为、预算)。匹配计算采用布隆过滤器减少数据库查询,预计算模型存储在Redis的Hash或Sorted Set中。出价计算根据匹配结果动态计算CPM/CPC。消息队列异步处理日志和预算更新,避免阻塞。通过数据库分库分表(按用户ID/广告位ID)、读写分离,以及缓存预加载、互斥锁防雪崩,确保百万级QPS下的低延迟和高可用。
6) 【追问清单】
- 问题1:如何处理缓存击穿?
回答要点:设置热点数据预加载(如定时任务),或使用互斥锁+缓存(当缓存失效时,用锁保证只有一个请求去数据库加载并缓存,其他请求等待缓存)。
- 问题2:数据库如何分库分表?
回答要点:按用户ID或广告位ID分片(如用户ID取模分片,广告位按位置分片),结合读写分离(主从复制,读请求路由到从库,提升读性能)。
- 问题3:如何保证匹配算法的实时性?
回答要点:预计算常用广告位与用户特征的匹配规则(如用户画像与广告标签的匹配矩阵),实时计算时结合缓存加速,减少数据库查询。
- 问题4:系统如何扩容?
回答要点:水平扩容后端服务器,增加缓存节点(如Redis集群),调整数据库分片策略(如增加分片数量),重新分配缓存分片。
- 问题5:如何处理请求超时?
回答要点:设置请求超时时间(如500ms),结合重试机制(指数退避,如第一次重试1秒,第二次2秒,避免雪崩)。
7) 【常见坑/雷区】
- 坑1:忽略缓存雪崩,只考虑缓存击穿。反问:如果大量缓存同时失效,如何避免系统崩溃?答:设置随机过期时间(TTL随机),或使用熔断降级,避免集中失效。
- 坑2:没有异步处理,实时任务阻塞主流程。反问:如果用户行为写入数据库导致延迟,如何保证匹配计算不阻塞?答:使用消息队列异步写入,主流程不等待,提高吞吐。
- 坑3:匹配算法复杂导致延迟高。反问:如果匹配逻辑涉及机器学习模型,如何优化?答:模型训练后,将特征工程后的结果预计算并存储,实时查询时直接使用预计算结果,减少计算量。
- 坑4:数据库没有读写分离,影响读性能。反问:如果读请求过多,如何提升数据库读性能?答:主从复制,读请求路由到从库,主库负责写,提升读吞吐。
- 坑5:分布式锁使用不当,导致死锁。反问:如果多个服务同时更新预算,如何避免超卖?答:使用Redis分布式锁,设置锁超时时间(如10秒),避免死锁,超时后自动释放锁。