1. 互联网大厂Java面试技术深度解析
作为一名经历过多次大厂面试的Java开发者,我深知面试中那些高频技术问题的分量。今天我想通过一个模拟面试场景,带大家深入剖析从音视频缓存到分布式事务的核心技术要点。这些不仅是面试常客,更是实际工作中必须掌握的硬核技能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 缓存策略与性能优化实战
2.1 缓存架构设计原则
在千万级DAU的音视频平台中,缓存设计直接影响系统性能和用户体验。合理的缓存架构需要考虑以下几个维度:
-
数据热度分层:根据二八定律,20%的热点内容承载80%的流量。我们需要建立多级缓存体系:
- 本地缓存(Caffeine/Guava Cache):纳秒级访问,适合极热数据
- 分布式缓存(Redis Cluster):毫秒级访问,适合热数据
- 数据库:兜底查询,适合冷数据
-
缓存粒度控制:
- 视频元数据(标题、作者、时长)适合全量缓存
- 视频内容本身建议按分片缓存(HLS的TS片段)
- 用户个性化数据(收藏、历史)需要按用户维度隔离
2.2 缓存问题解决方案
2.2.1 缓存穿透防护
java复制// 布隆过滤器实现示例
public class VideoBloomFilter {
private static final int EXPECTED_INSERTIONS = 1000000;
private static final double FPP = 0.01;
private final BloomFilter<String> bloomFilter;
public VideoBloomFilter() {
this.bloomFilter = BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
EXPECTED_INSERTIONS,
FPP);
}
public void addVideo(String videoId) {
bloomFilter.put(videoId);
}
public boolean mightContain(String videoId) {
return bloomFilter.mightContain(videoId);
}
}
注意事项:
- 布隆过滤器需要预热,系统启动时要加载所有有效videoId
- 误判率(FPP)需要根据内存容量权衡,通常设置在1%以下
- 对于明确不存在的key,建议缓存空对象并设置较短TTL(如30秒)
2.2.2 缓存雪崩预防
java复制// 随机过期时间实现
public class CacheTTLManager {
private static final int BASE_TTL = 1800; // 基础30分钟
private static final int RANDOM_RANGE = 600; // 随机10分钟
public static int getRandomTTL() {
return BASE_TTL + new Random().nextInt(RANDOM_RANGE);
}
}
多级缓存实现方案:
- 本地缓存:最大1000条,TTL 5分钟
- Redis缓存:最大10万条,TTL 30±10分钟
- 数据库:持久化存储,通过canal同步更新缓存
2.3 缓存一致性保障
对于音视频元数据的修改,需要采用双写策略:
java复制@Transactional
public void updateVideoMeta(String videoId, VideoMeta newMeta) {
// 1. 更新数据库
videoMapper.update(videoId, newMeta);
// 2. 更新缓存
redisTemplate.opsForValue().set(
"video:meta:" + videoId,
newMeta,
CacheTTLManager.getRandomTTL(),
TimeUnit.SECONDS);
// 3. 清除本地缓存
caffeineCache.invalidate("video:meta:" + videoId);
}
异常处理建议:
- 数据库操作与缓存操作要在同一事务中
- 设置重试机制(如Spring Retry)
- 最终一致性可通过数据库binlog补偿(如使用Alibaba Canal)
3. 微服务异步处理架构
3.1 消息队列选型对比
| 特性 | Kafka | RocketMQ | RabbitMQ |
|---|---|---|---|
| 吞吐量 | 100万+/秒 | 10万+/秒 | 5万+/秒 |
| 延迟 | 毫秒级 | 毫秒级 | 微秒级 |
| 事务消息 | 支持 | 支持 | 不支持 |
| 消息顺序 | 分区内有序 | 队列内有序 | 队列内有序 |
| 适用场景 | 大数据流处理 | 金融级交易 | 企业级应用 |
对于音视频处理场景,Kafka的高吞吐特性更为适合,特别是当需要处理海量上传任务时。
3.2 消息可靠性保障设计
3.2.1 生产者端保证
java复制@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.RETRIES_CONFIG, 3);
config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
return new DefaultKafkaProducerFactory<>(config);
}
}
@Service
public class VideoEventSender {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@Transactional
public void sendVideoEvent(VideoEvent event) {
// 1. 保存事件到本地数据库(确保可重试)
eventLogRepository.save(event);
// 2. 发送到Kafka
kafkaTemplate.send("video-events",
event.getVideoId(),
JSON.toJSONString(event))
.addCallback(
success -> log.info("Sent success"),
failure -> {
log.error("Send failed", failure);
throw new RuntimeException(failure);
});
}
}
3.2.2 消费者端保证
java复制@KafkaListener(topics = "video-events")
public void handleVideoEvent(ConsumerRecord<String, String> record) {
try {
VideoEvent event = parseEvent(record.value());
// 幂等处理检查
if (eventLogRepository.existsByEventId(event.getEventId())) {
log.warn("Duplicate event: {}", event.getEventId());
return;
}
// 业务处理
processVideo(event);
// 记录处理成功
eventLogRepository.saveProcessed(event);
} catch (Exception e) {
log.error("Process failed", e);
throw new RuntimeException(e);
}
}
3.3 处理流程状态管理
对于有依赖关系的处理步骤(如转码→审核→发布),推荐使用状态机模式:
java复制public enum VideoState {
UPLOADED,
TRANSCODING,
TRANSCODE_FAILED,
TRANSCODED,
REVIEWING,
REVIEW_FAILED,
PUBLISHED
}
@Service
public class VideoStateMachine {
@Autowired
private StateMachinePersist<VideoState, String, Video> persist;
public void transit(String videoId, VideoEvent event) {
StateMachine<VideoState, String> stateMachine = persist.restore(videoId);
if (event.getType() == TRANSCODE_SUCCESS) {
if (!stateMachine.sendEvent("TRANSCODE_DONE")) {
log.error("Invalid state transition");
// 触发补偿流程
}
}
// 其他事件处理...
persist.persist(stateMachine, videoId);
}
}
状态持久化建议:
- 使用Redis存储状态机上下文
- 关键状态变更要落数据库
- 设置状态超时监控(如转码超过2小时报警)
4. 分布式事务解决方案
4.1 典型方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 2PC | 强一致 | 差 | 高 | 数据库跨库事务 |
| TCC | 最终 | 中 | 高 | 资金交易 |
| SAGA | 最终 | 好 | 中 | 长流程业务 |
| 本地消息表 | 最终 | 好 | 低 | 异步通知 |
| 事务消息 | 最终 | 好 | 中 | MQ场景 |
4.2 Seata AT模式实现
java复制// 会员服务
@GlobalTransactional
public void activateVip(String userId, int months) {
// 1. 扣减账户余额
accountService.debit(userId, calculateFee(months));
// 2. 增加会员权益
vipService.addVipTime(userId, months);
// 3. 记录开通日志
logService.recordVipActivation(userId);
}
实现原理:
-
一阶段:
- 解析SQL生成before image
- 执行业务SQL
- 生成after image
- 注册分支事务
-
二阶段提交:
- 异步删除undo log
-
二阶段回滚:
- 根据before image还原数据
- 删除undo log
4.3 TCC模式设计示例
java复制// 会员服务TCC接口
public interface VipServiceTcc {
@TwoPhaseBusinessAction(name = "addVipTime", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepareAddVipTime(BusinessActionContext context,
@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "months") int months);
boolean commit(BusinessActionContext context);
boolean rollback(BusinessActionContext context);
}
// 实现类
@Service
public class VipServiceTccImpl implements VipServiceTcc {
@Override
public boolean prepareAddVipTime(BusinessActionContext context, String userId, int months) {
// 检查用户状态
// 冻结权益额度(记录到临时表)
return temporaryVipDao.reserve(userId, months);
}
@Override
public boolean commit(BusinessActionContext context) {
// 实际增加会员时长
String userId = (String) context.getActionContext("userId");
int months = (Integer) context.getActionContext("months");
return vipDao.addVipTime(userId, months) > 0;
}
@Override
public boolean rollback(BusinessActionContext context) {
// 释放冻结的权益
String userId = (String) context.getActionContext("userId");
return temporaryVipDao.release(userId);
}
}
设计要点:
- Try阶段:资源预留(冻结金额、占库存)
- Confirm阶段:实际执行(扣款、减库存)
- Cancel阶段:取消预留(解冻、释放)
- 必须实现幂等性
- 需要处理空回滚(Try未执行但收到Cancel)
4.4 分布式ID生成方案
对于支付幂等性控制,可靠的ID生成是关键:
java复制// 雪花算法实现
public class SnowflakeIdGenerator {
private final long datacenterId;
private final long machineId;
private long sequence = 0L;
private long lastTimestamp = -1L;
public SnowflakeIdGenerator(long datacenterId, long machineId) {
this.datacenterId = datacenterId;
this.machineId = machineId;
}
public synchronized long nextId() {
long timestamp = System.currentTimeMillis();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards");
}
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & 0xFFF;
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
return ((timestamp - 1288834974657L) << 22)
| (datacenterId << 17)
| (machineId << 12)
| sequence;
}
private long tilNextMillis(long lastTimestamp) {
long timestamp = System.currentTimeMillis();
while (timestamp <= lastTimestamp) {
timestamp = System.currentTimeMillis();
}
return timestamp;
}
}
使用建议:
- 每个服务实例配置唯一的datacenterId和machineId
- 客户端缓存一批ID减少请求次数
- 监控ID生成器的时钟回拨情况
5. 面试问题深度扩展
5.1 缓存热点key处理
当某个明星视频突然爆火,可能造成Redis单节点过热。解决方案:
- 本地缓存+随机过期:客户端缓存热点数据,各实例过期时间随机
- Key分片:将热点key拆分为多个子key(如video:meta:1234_part1)
- 多级缓存:Nginx层缓存静态内容
java复制// 热点探测与本地缓存
@Scheduled(fixedRate = 10000)
public void detectHotKeys() {
Map<String, Long> accessCounts = redisTemplate.opsForHash()
.entries("hotkey:access_count");
accessCounts.entrySet().stream()
.filter(e -> e.getValue() > 1000) // 阈值
.forEach(e -> {
Object value = redisTemplate.opsForValue().get(e.getKey());
localCache.put(e.getKey(), value);
});
}
5.2 消息积压处理
当视频处理速度跟不上上传速度时:
- 动态扩容:
- 监控Lag指标
- 自动增加消费者实例
- 降级策略:
- 降低转码分辨率
- 跳过非必要处理步骤
- 优先级队列:
- VIP用户视频优先处理
- 小文件优先处理
5.3 分布式事务监控
建立事务监控看板:
- 关键指标:
- 事务成功率/失败率
- 平均处理时间
- 资源锁定时间
- 告警规则:
- 连续失败超过阈值
- 事务悬挂(长时间未完成)
- 追踪工具:
- 集成Jaeger/SkyWalking
- 记录事务流程图
sql复制-- 事务监控表设计
CREATE TABLE distributed_transaction_monitor (
xid VARCHAR(128) PRIMARY KEY,
status VARCHAR(20),
begin_time TIMESTAMP,
end_time TIMESTAMP,
application_id VARCHAR(50),
service_method VARCHAR(100),
retry_count INT DEFAULT 0,
error_msg TEXT
);
在实际系统设计中,没有放之四海而皆准的完美方案。我在多个项目中总结的经验是:根据业务特点选择合适的技术组合,建立完善的监控和应急机制,比单纯追求技术先进性更重要。比如对于音视频处理这类允许最终一致性的场景,采用消息队列+本地事务往往比强一致的分布式事务更合适。
