大型广告系统架构设计与实战:从0到1打造日均千亿曝光的广告平台

大型广告系统架构设计与实战:从0到1打造日均千亿曝光的广告平台

老板说要搞个广告系统,日均曝光要达到千亿级别,还要支持实时竞价、精准投放、效果追踪...听起来是不是很刺激?今天就来聊聊如何从0到1打造一个大型广告系统,让你的广告平台既能扛住高并发,又能实现精准投放!

一、广告系统的核心挑战

在开始设计架构之前,我们先来理解广告系统面临的挑战。

1.1 业务复杂性

// 广告系统的业务复杂性
public class AdSystemChallenges {
    
    public void businessComplexity() {
        System.out.println("=== 广告系统的核心挑战 ===");
        System.out.println("1. 高并发处理:日均千亿级曝光请求");
        System.out.println("2. 实时性要求:毫秒级响应");
        System.out.println("3. 精准投放:基于用户画像的个性化推荐");
        System.out.println("4. 实时竞价:RTB(Real Time Bidding)");
        System.out.println("5. 效果追踪:点击率、转化率等指标统计");
        System.out.println("6. 反作弊:识别和过滤无效流量");
        System.out.println("7. 数据分析:海量数据的实时处理");
    }
}

1.2 技术难点

广告系统的技术难点主要体现在以下几个方面:

// 技术难点分析
public class TechnicalChallenges {
    
    public void technicalDifficulties() {
        System.out.println("=== 广告系统的技术难点 ===");
        System.out.println("数据处理:");
        System.out.println("- 海量用户行为数据实时处理");
        System.out.println("- 多维度用户画像构建");
        System.out.println("- 实时特征计算");
        
        System.out.println("\n系统架构:");
        System.out.println("- 高可用性设计");
        System.out.println("- 水平扩展能力");
        System.out.println("- 容错和降级机制");
        
        System.out.println("\n算法优化:");
        System.out.println("- CTR预估模型");
        System.out.println("- 出价策略优化");
        System.out.println("- 排序算法设计");
    }
}

二、广告系统架构设计

2.1 整体架构图

graph TB
    A[用户请求] --> B[流量接入层]
    B --> C[广告检索服务]
    C --> D[用户画像服务]
    C --> E[广告库存服务]
    C --> F[竞价服务]
    D --> G[实时计算引擎]
    E --> H[广告库存数据库]
    F --> I[DSP平台]
    G --> J[用户画像存储]
    H --> K[广告管理平台]
    I --> L[竞价决策]
    L --> M[广告投放引擎]
    M --> N[广告展示]
    N --> O[效果追踪]
    O --> P[数据分析平台]
    
    style A fill:#ffe4c4,stroke:#333
    style B fill:#98fb98,stroke:#333
    style C fill:#87ceeb,stroke:#333
    style D fill:#dda0dd,stroke:#333
    style E fill:#ffb6c1,stroke:#333
    style F fill:#f0e68c,stroke:#333
    style G fill:#98fb98,stroke:#333
    style H fill:#87ceeb,stroke:#333
    style I fill:#dda0dd,stroke:#333
    style J fill:#ffb6c1,stroke:#333
    style K fill:#f0e68c,stroke:#333
    style L fill:#98fb98,stroke:#333
    style M fill:#87ceeb,stroke:#333
    style N fill:#dda0dd,stroke:#333
    style O fill:#ffb6c1,stroke:#333
    style P fill:#f0e68c,stroke:#333

2.2 核心模块设计

2.2.1 流量接入层

// 流量接入层设计
@Component
@Slf4j
public class TrafficAccessLayer {
    
    @Autowired
    private AdRetrievalService adRetrievalService;
    
    @Autowired
    private AntiCheatingService antiCheatingService;
    
    /**
     * 处理广告请求
     */
    public AdResponse handleAdRequest(AdRequest request) {
        try {
            // 1. 请求验证和反作弊检测
            if (!antiCheatingService.isValidRequest(request)) {
                log.warn("无效广告请求: requestId={}", request.getRequestId());
                return AdResponse.emptyResponse();
            }
            
            // 2. 参数解析和标准化
            StandardizedRequest standardizedRequest = standardizeRequest(request);
            
            // 3. 调用广告检索服务
            AdResponse response = adRetrievalService.retrieveAds(standardizedRequest);
            
            // 4. 响应处理和日志记录
            logAdRequest(standardizedRequest, response);
            
            return response;
        } catch (Exception e) {
            log.error("处理广告请求失败: requestId={}", request.getRequestId(), e);
            return AdResponse.errorResponse();
        }
    }
    
    private StandardizedRequest standardizeRequest(AdRequest request) {
        // 标准化请求参数
        StandardizedRequest standardized = new StandardizedRequest();
        standardized.setRequestId(request.getRequestId());
        standardized.setUserId(request.getUserId());
        standardized.setDeviceId(request.getDeviceId());
        standardized.setGeoLocation(request.getGeoLocation());
        standardized.setUserAgent(request.getUserAgent());
        standardized.setTimestamp(System.currentTimeMillis());
        return standardized;
    }
    
    private void logAdRequest(StandardizedRequest request, AdResponse response) {
        // 记录广告请求日志,用于后续分析
        AdRequestLog logEntry = new AdRequestLog();
        logEntry.setRequestId(request.getRequestId());
        logEntry.setUserId(request.getUserId());
        logEntry.setTimestamp(request.getTimestamp());
        logEntry.setAdCount(response.getAds().size());
        logEntry.setResponseTime(System.currentTimeMillis() - request.getTimestamp());
        
        // 异步写入日志系统
        logService.asyncLogAdRequest(logEntry);
    }
}

2.2.2 广告检索服务

@Service
@Slf4j
public class AdRetrievalService {
    
    @Autowired
    private UserProfileService userProfileService;
    
    @Autowired
    private AdInventoryService adInventoryService;
    
    @Autowired
    private BiddingService biddingService;
    
    @Autowired
    private RankingService rankingService;
    
    /**
     * 广告检索主流程
     */
    public AdResponse retrieveAds(StandardizedRequest request) {
        long startTime = System.currentTimeMillis();
        
        try {
            // 1. 获取用户画像
            UserProfile userProfile = userProfileService.getUserProfile(request.getUserId());
            
            // 2. 获取候选广告
            List<AdCandidate> candidates = adInventoryService.getCandidates(
                request.getGeoLocation(), request.getDeviceType());
            
            // 3. 实时竞价
            List<BidResult> bidResults = biddingService.realTimeBidding(candidates, request);
            
            // 4. 广告排序
            List<RankedAd> rankedAds = rankingService.rankAds(bidResults, userProfile, request);
            
            // 5. 广告过滤和截断
            List<RankedAd> filteredAds = filterAndLimitAds(rankedAds, request.getAdSlot());
            
            // 6. 构造响应
            AdResponse response = buildAdResponse(filteredAds, request);
            
            long endTime = System.currentTimeMillis();
            log.info("广告检索完成: requestId={}, candidateCount={}, finalCount={}, time={}ms", 
                    request.getRequestId(), candidates.size(), filteredAds.size(), 
                    endTime - startTime);
            
            return response;
        } catch (Exception e) {
            log.error("广告检索失败: requestId={}", request.getRequestId(), e);
            return AdResponse.errorResponse();
        }
    }
    
    private List<RankedAd> filterAndLimitAds(List<RankedAd> rankedAds, AdSlot adSlot) {
        return rankedAds.stream()
                .filter(ad -> ad.getScore() > 0) // 过滤低分广告
                .filter(ad -> ad.getBidPrice() <= adSlot.getMaxBid()) // 价格过滤
                .limit(adSlot.getCapacity()) // 数量限制
                .collect(Collectors.toList());
    }
    
    private AdResponse buildAdResponse(List<RankedAd> ads, StandardizedRequest request) {
        AdResponse response = new AdResponse();
        response.setRequestId(request.getRequestId());
        response.setAds(ads.stream()
                .map(this::convertToAdDto)
                .collect(Collectors.toList()));
        response.setTimestamp(System.currentTimeMillis());
        return response;
    }
    
    private AdDto convertToAdDto(RankedAd rankedAd) {
        AdDto dto = new AdDto();
        dto.setAdId(rankedAd.getAdId());
        dto.setCreativeId(rankedAd.getCreativeId());
        dto.setAdvertiserId(rankedAd.getAdvertiserId());
        dto.setBidPrice(rankedAd.getBidPrice());
        dto.setScore(rankedAd.getScore());
        dto.setClickUrl(rankedAd.getClickUrl());
        dto.setImpressionUrl(rankedAd.getImpressionUrl());
        return dto;
    }
}

三、核心算法实现

3.1 CTR预估模型

@Service
@Slf4j
public class CtrPredictionService {
    
    // 逻辑回归模型参数
    private double[] weights;
    private double bias;
    
    /**
     * CTR预估
     */
    public double predictCtr(AdFeature adFeature, UserFeature userFeature, ContextFeature contextFeature) {
        try {
            // 1. 特征工程
            double[] features = extractFeatures(adFeature, userFeature, contextFeature);
            
            // 2. 逻辑回归预测
            double linearScore = bias;
            for (int i = 0; i < features.length && i < weights.length; i++) {
                linearScore += features[i] * weights[i];
            }
            
            // 3. Sigmoid激活函数
            double ctr = sigmoid(linearScore);
            
            return Math.max(0.0, Math.min(1.0, ctr)); // 保证CTR在[0,1]范围内
        } catch (Exception e) {
            log.error("CTR预估失败", e);
            return 0.01; // 默认CTR
        }
    }
    
    /**
     * 特征提取
     */
    private double[] extractFeatures(AdFeature adFeature, UserFeature userFeature, ContextFeature contextFeature) {
        List<Double> featureList = new ArrayList<>();
        
        // 广告特征
        featureList.add(normalize(adFeature.getBidPrice(), 0, 10)); // 出价归一化
        featureList.add(adFeature.getAdvertiserQualityScore()); // 广告主质量分
        featureList.add(adFeature.getCreativeRelevanceScore()); // 创意相关性得分
        
        // 用户特征
        featureList.add(userFeature.getHistoricalCtr()); // 历史CTR
        featureList.add(userFeature.getConversionRate()); // 转化率
        featureList.add(userFeature.getEngagementScore()); // 用户活跃度
        
        // 上下文特征
        featureList.add(contextFeature.getTimeOfDayFactor()); // 时段因子
        featureList.add(contextFeature.getDeviceTypeFactor()); // 设备类型因子
        featureList.add(contextFeature.getGeoFactor()); // 地理位置因子
        
        return featureList.stream().mapToDouble(Double::doubleValue).toArray();
    }
    
    private double sigmoid(double x) {
        return 1.0 / (1.0 + Math.exp(-x));
    }
    
    private double normalize(double value, double min, double max) {
        return (value - min) / (max - min);
    }
}

3.2 实时竞价算法

@Service
@Slf4j
public class BiddingService {
    
    @Autowired
    private CtrPredictionService ctrPredictionService;
    
    @Autowired
    private UserModelService userModelService;
    
    /**
     * 实时竞价
     */
    public List<BidResult> realTimeBidding(List<AdCandidate> candidates, StandardizedRequest request) {
        List<BidResult> bidResults = new ArrayList<>();
        
        for (AdCandidate candidate : candidates) {
            try {
                // 1. CTR预估
                double ctr = ctrPredictionService.predictCtr(
                    candidate.getAdFeature(), 
                    candidate.getUserFeature(), 
                    candidate.getContextFeature());
                
                // 2. eCPM计算 (eCPM = 出价 * CTR * 1000)
                double ecpm = candidate.getBidPrice() * ctr * 1000;
                
                // 3. 出价调整(基于用户模型)
                double adjustedBid = adjustBid(candidate, request, ctr);
                
                BidResult bidResult = new BidResult();
                bidResult.setAdId(candidate.getAdId());
                bidResult.setBidPrice(adjustedBid);
                bidResult.setEcpm(ecpm);
                bidResult.setCtr(ctr);
                
                bidResults.add(bidResult);
            } catch (Exception e) {
                log.error("广告竞价失败: adId={}", candidate.getAdId(), e);
            }
        }
        
        // 4. 按eCPM排序
        bidResults.sort((a, b) -> Double.compare(b.getEcpm(), a.getEcpm()));
        
        return bidResults;
    }
    
    /**
     * 出价调整
     */
    private double adjustBid(AdCandidate candidate, StandardizedRequest request, double ctr) {
        // 获取用户出价模型
        UserBiddingModel userModel = userModelService.getBiddingModel(request.getUserId());
        
        // 基于用户历史行为调整出价
        double adjustmentFactor = 1.0;
        if (userModel != null) {
            // 如果用户历史转化率高,可以适当提高出价
            if (userModel.getConversionRate() > 0.05) {
                adjustmentFactor = 1.2;
            }
            // 如果用户活跃度低,降低出价
            else if (userModel.getActivityScore() < 0.3) {
                adjustmentFactor = 0.8;
            }
        }
        
        // 结合CTR进行调整
        double ctrFactor = Math.min(ctr * 100, 2.0); // CTR越高,出价越高,但有上限
        
        return candidate.getBidPrice() * adjustmentFactor * ctrFactor;
    }
}

四、高并发架构优化

4.1 缓存策略

@Service
@Slf4j
public class CacheOptimizationService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private LoadingCache<String, UserProfile> userProfileCache;
    
    // 用户画像缓存(本地+Redis)
    private final LoadingCache<String, UserProfile> userProfileLocalCache = 
        Caffeine.newBuilder()
            .maximumSize(100000)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .recordStats()
            .build(this::loadUserProfile);
    
    /**
     * 多级缓存获取用户画像
     */
    public UserProfile getUserProfile(String userId) {
        // 1. 本地缓存
        UserProfile profile = userProfileLocalCache.getIfPresent(userId);
        if (profile != null) {
            return profile;
        }
        
        // 2. Redis缓存
        String redisKey = "user:profile:" + userId;
        profile = (UserProfile) redisTemplate.opsForValue().get(redisKey);
        if (profile != null) {
            userProfileLocalCache.put(userId, profile);
            return profile;
        }
        
        // 3. 数据库查询
        profile = loadUserProfileFromDatabase(userId);
        if (profile != null) {
            // 回写缓存
            redisTemplate.opsForValue().set(redisKey, profile, Duration.ofHours(1));
            userProfileLocalCache.put(userId, profile);
        }
        
        return profile != null ? profile : new DefaultUserProfile();
    }
    
    /**
     * 批量预热缓存
     */
    public void preheatUserProfiles(List<String> userIds) {
        try {
            List<UserProfile> profiles = loadUserProfilesFromDatabase(userIds);
            for (UserProfile profile : profiles) {
                String redisKey = "user:profile:" + profile.getUserId();
                redisTemplate.opsForValue().set(redisKey, profile, Duration.ofHours(1));
                userProfileLocalCache.put(profile.getUserId(), profile);
            }
            log.info("预热用户画像缓存完成: count={}", profiles.size());
        } catch (Exception e) {
            log.error("预热用户画像缓存失败", e);
        }
    }
    
    private UserProfile loadUserProfile(String userId) {
        return loadUserProfileFromDatabase(userId);
    }
    
    private UserProfile loadUserProfileFromDatabase(String userId) {
        // 数据库查询逻辑
        return null; // 简化实现
    }
    
    private List<UserProfile> loadUserProfilesFromDatabase(List<String> userIds) {
        // 批量数据库查询逻辑
        return new ArrayList<>(); // 简化实现
    }
}

4.2 异步处理

@Component
@Slf4j
public class AsyncProcessingService {
    
    @Autowired
    private ExecutorService adProcessingExecutor;
    
    @Autowired
    private KafkaTemplate<String, Object> kafkaTemplate;
    
    /**
     * 异步处理广告效果数据
     */
    @Async
    public void asyncProcessAdEffect(AdEffectData effectData) {
        try {
            // 1. 写入消息队列
            kafkaTemplate.send("ad-effect-topic", effectData.getRequestId(), effectData);
            
            // 2. 更新实时统计
            updateRealTimeMetrics(effectData);
            
            // 3. 异步写入数据仓库
            asyncWriteToDataWarehouse(effectData);
        } catch (Exception e) {
            log.error("异步处理广告效果数据失败: requestId={}", effectData.getRequestId(), e);
        }
    }
    
    /**
     * 批量处理广告数据
     */
    public void batchProcessAdEffects(List<AdEffectData> effectDataList) {
        adProcessingExecutor.submit(() -> {
            try {
                // 批量处理逻辑
                processAdEffectsInBatch(effectDataList);
            } catch (Exception e) {
                log.error("批量处理广告效果数据失败", e);
            }
        });
    }
    
    private void updateRealTimeMetrics(AdEffectData effectData) {
        // 更新实时指标统计
    }
    
    private void asyncWriteToDataWarehouse(AdEffectData effectData) {
        // 异步写入数据仓库
    }
    
    private void processAdEffectsInBatch(List<AdEffectData> effectDataList) {
        // 批量处理逻辑
    }
}

五、数据处理与分析

5.1 实时计算引擎

@Component
@Slf4j
public class RealTimeComputationEngine {
    
    @Autowired
    private StreamProcessingService streamProcessingService;
    
    /**
     * 实时特征计算
     */
    public void computeRealTimeFeatures() {
        // 1. 用户行为流处理
        streamProcessingService.processUserBehaviorStream()
            .filter(event -> isValidEvent(event))
            .map(event -> extractFeatures(event))
            .keyBy(feature -> feature.getUserId())
            .window(TumblingProcessingTimeWindows.of(Time.minutes(5)))
            .aggregate(new UserFeatureAggregator())
            .addSink(new UserFeatureSink());
            
        // 2. 广告效果流处理
        streamProcessingService.processAdEffectStream()
            .filter(effect -> isValidEffect(effect))
            .keyBy(effect -> effect.getAdId())
            .window(SlidingProcessingTimeWindows.of(Time.minutes(10), Time.minutes(1)))
            .aggregate(new AdEffectAggregator())
            .addSink(new AdEffectSink());
    }
    
    /**
     * 用户特征聚合器
     */
    public static class UserFeatureAggregator implements AggregateFunction<UserEvent, UserFeatureAccumulator, UserFeature> {
        
        @Override
        public UserFeatureAccumulator createAccumulator() {
            return new UserFeatureAccumulator();
        }
        
        @Override
        public UserFeatureAccumulator add(UserEvent event, UserFeatureAccumulator accumulator) {
            accumulator.addEvent(event);
            return accumulator;
        }
        
        @Override
        public UserFeature getResult(UserFeatureAccumulator accumulator) {
            return accumulator.toUserFeature();
        }
        
        @Override
        public UserFeatureAccumulator merge(UserFeatureAccumulator a, UserFeatureAccumulator b) {
            return a.merge(b);
        }
    }
}

5.2 离线数据分析

@Service
@Slf4j
public class OfflineDataAnalysisService {
    
    @Autowired
    private SparkSession sparkSession;
    
    /**
     * 广告效果分析
     */
    public AdPerformanceReport analyzeAdPerformance(String dateRange) {
        try {
            // 1. 加载数据
            Dataset<Row> adImpressions = sparkSession.read()
                .parquet("hdfs://ad-impressions/" + dateRange);
                
            Dataset<Row> adClicks = sparkSession.read()
                .parquet("hdfs://ad-clicks/" + dateRange);
            
            // 2. 计算CTR
            Dataset<Row> ctrAnalysis = adImpressions
                .join(adClicks, "ad_id")
                .groupBy("ad_id", "advertiser_id")
                .agg(
                    count("impression_id").as("impressions"),
                    count("click_id").as("clicks"),
                    (count("click_id").divide(count("impression_id"))).as("ctr")
                );
            
            // 3. 生成报告
            AdPerformanceReport report = new AdPerformanceReport();
            report.setDateRange(dateRange);
            report.setTotalImpressions(ctrAnalysis.agg(sum("impressions")).first().getLong(0));
            report.setTotalClicks(ctrAnalysis.agg(sum("clicks")).first().getLong(0));
            report.setAverageCtr(ctrAnalysis.agg(avg("ctr")).first().getDouble(0));
            
            return report;
        } catch (Exception e) {
            log.error("广告效果分析失败", e);
            throw new RuntimeException("数据分析失败", e);
        }
    }
    
    /**
     * 用户画像分析
     */
    public UserProfileAnalysis analyzeUserProfiles(String dateRange) {
        try {
            Dataset<Row> userData = sparkSession.read()
                .parquet("hdfs://user-profiles/" + dateRange);
                
            // 用户分群分析
            Dataset<Row> userSegments = userData
                .groupBy("age_group", "interest_category")
                .agg(count("*").as("user_count"))
                .orderBy(desc("user_count"));
                
            UserProfileAnalysis analysis = new UserProfileAnalysis();
            analysis.setDateRange(dateRange);
            // 设置分析结果...
            
            return analysis;
        } catch (Exception e) {
            log.error("用户画像分析失败", e);
            throw new RuntimeException("用户分析失败", e);
        }
    }
}

六、监控与告警

6.1 核心指标监控

@Component
public class AdSystemMetrics {
    
    private final MeterRegistry meterRegistry;
    
    // QPS指标
    private final Counter qpsCounter;
    private final Timer responseTimeTimer;
    
    // 业务指标
    private final Counter impressionCounter;
    private final Counter clickCounter;
    private final Gauge ctrGauge;
    
    // 系统指标
    private final Gauge cacheHitRateGauge;
    private final Counter errorCounter;
    
    public AdSystemMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        this.qpsCounter = Counter.builder("ad.system.qps")
                .description("广告系统QPS")
                .register(meterRegistry);
                
        this.responseTimeTimer = Timer.builder("ad.system.response.time")
                .description("广告系统响应时间")
                .register(meterRegistry);
                
        this.impressionCounter = Counter.builder("ad.impressions")
                .description("广告曝光数")
                .register(meterRegistry);
                
        this.clickCounter = Counter.builder("ad.clicks")
                .description("广告点击数")
                .register(meterRegistry);
                
        this.ctrGauge = Gauge.builder("ad.ctr")
                .description("点击率")
                .register(meterRegistry, this, AdSystemMetrics::calculateCurrentCtr);
                
        this.cacheHitRateGauge = Gauge.builder("ad.cache.hit.rate")
                .description("缓存命中率")
                .register(meterRegistry, this, AdSystemMetrics::getCacheHitRate);
                
        this.errorCounter = Counter.builder("ad.errors")
                .description("广告系统错误数")
                .register(meterRegistry);
    }
    
    public void recordAdRequest() {
        qpsCounter.increment();
    }
    
    public Sample startResponseTimer() {
        return Timer.start(meterRegistry);
    }
    
    public void recordResponseTime(Sample sample) {
        sample.stop(responseTimeTimer);
    }
    
    public void recordImpression() {
        impressionCounter.increment();
    }
    
    public void recordClick() {
        clickCounter.increment();
    }
    
    public void recordError() {
        errorCounter.increment();
    }
    
    private double calculateCurrentCtr() {
        double impressions = impressionCounter.count();
        double clicks = clickCounter.count();
        return impressions > 0 ? clicks / impressions : 0.0;
    }
    
    private double getCacheHitRate() {
        // 获取缓存命中率逻辑
        return 0.95; // 示例值
    }
}

6.2 告警规则配置

# Prometheus告警规则
groups:
- name: ad_system.rules
  rules:
  # QPS异常告警
  - alert: HighQPS
    expr: rate(ad_system_qps[5m]) > 1000000
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "广告系统QPS过高"
      description: "5分钟平均QPS超过100万: {{ $value }}"
      
  # 响应时间告警
  - alert: HighResponseTime
    expr: histogram_quantile(0.95, rate(ad_system_response_time_seconds_bucket[5m])) > 0.1
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "广告系统响应时间过长"
      description: "95%请求响应时间超过100ms: {{ $value }}"
      
  # 错误率告警
  - alert: HighErrorRate
    expr: rate(ad_errors[5m]) / rate(ad_system_qps[5m]) > 0.01
    for: 1m
    labels:
      severity: warning
    annotations:
      summary: "广告系统错误率过高"
      description: "错误率超过1%: {{ $value }}"
      
  # CTR异常告警
  - alert: LowCTR
    expr: ad_ctr < 0.001
    for: 10m
    labels:
      severity: warning
    annotations:
      summary: "广告点击率过低"
      description: "CTR低于0.1%: {{ $value }}"

七、性能优化实践

7.1 数据库优化

@Repository
public class AdInventoryRepository {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    /**
     * 批量查询广告库存
     */
    public List<AdInventory> batchQueryInventories(List<Long> adIds) {
        String sql = "SELECT * FROM ad_inventory WHERE ad_id IN (" + 
                    adIds.stream().map(String::valueOf).collect(Collectors.joining(",")) + 
                    ") AND status = 1 AND remaining_budget > 0";
        
        return jdbcTemplate.query(sql, new AdInventoryRowMapper());
    }
    
    /**
     * 分页查询广告
     */
    public List<AdInventory> queryAdsByPage(String conditions, int offset, int limit) {
        String sql = "SELECT * FROM ad_inventory WHERE " + conditions + 
                    " ORDER BY priority DESC, bid_price DESC LIMIT ?, ?";
        
        return jdbcTemplate.query(sql, new AdInventoryRowMapper(), offset, limit);
    }
    
    /**
     * 更新广告预算
     */
    @Modifying
    @Transactional
    public int updateAdBudget(Long adId, BigDecimal consumedAmount) {
        String sql = "UPDATE ad_inventory SET remaining_budget = remaining_budget - ?, " +
                    "consumed_budget = consumed_budget + ? WHERE ad_id = ? AND remaining_budget >= ?";
        
        return jdbcTemplate.update(sql, consumedAmount, consumedAmount, adId, consumedAmount);
    }
}

@Component
public class AdInventoryRowMapper implements RowMapper<AdInventory> {
    
    @Override
    public AdInventory mapRow(ResultSet rs, int rowNum) throws SQLException {
        AdInventory inventory = new AdInventory();
        inventory.setAdId(rs.getLong("ad_id"));
        inventory.setAdvertiserId(rs.getLong("advertiser_id"));
        inventory.setBidPrice(rs.getBigDecimal("bid_price"));
        inventory.setRemainingBudget(rs.getBigDecimal("remaining_budget"));
        inventory.setDailyBudget(rs.getBigDecimal("daily_budget"));
        inventory.setPriority(rs.getInt("priority"));
        inventory.setStatus(rs.getInt("status"));
        inventory.setStartTime(rs.getTimestamp("start_time"));
        inventory.setEndTime(rs.getTimestamp("end_time"));
        return inventory;
    }
}

7.2 内存优化

@Service
@Slf4j
public class MemoryOptimizationService {
    
    // 对象池减少GC压力
    private final ObjectPool<AdRequestContext> contextPool = 
        new GenericObjectPool<>(new AdRequestContextFactory());
    
    /**
     * 获取请求上下文(从对象池)
     */
    public AdRequestContext borrowContext() {
        try {
            return contextPool.borrowObject();
        } catch (Exception e) {
            log.error("从对象池获取上下文失败", e);
            return new AdRequestContext(); // 备用创建
        }
    }
    
    /**
     * 归还请求上下文(到对象池)
     */
    public void returnContext(AdRequestContext context) {
        try {
            context.reset(); // 重置状态
            contextPool.returnObject(context);
        } catch (Exception e) {
            log.error("归还上下文到对象池失败", e);
        }
    }
    
    /**
     * 使用Off-Heap内存存储热点数据
     */
    public void storeHotDataOffHeap(String key, byte[] data) {
        // 使用Off-Heap内存库(如Ehcache的Terracotta)
        OffHeapStore.getInstance().put(key, data);
    }
    
    public byte[] getHotDataOffHeap(String key) {
        return OffHeapStore.getInstance().get(key);
    }
}

八、容错与降级

8.1 熔断机制

@Service
@Slf4j
public class CircuitBreakerService {
    
    // 用户画像服务熔断器
    private final CircuitBreaker userProfileCircuitBreaker = 
        CircuitBreaker.ofDefaults("userProfileService");
    
    // 广告库存服务熔断器
    private final CircuitBreaker inventoryCircuitBreaker = 
        CircuitBreaker.ofDefaults("inventoryService");
    
    @Autowired
    private UserProfileService userProfileService;
    
    @Autowired
    private AdInventoryService adInventoryService;
    
    /**
     * 带熔断的用户画像获取
     */
    public UserProfile getUserProfileSafely(String userId) {
        return CircuitBreaker.decorateSupplier(userProfileCircuitBreaker,
            () -> userProfileService.getUserProfile(userId))
            .recover(throwable -> {
                log.warn("用户画像服务熔断,使用默认画像: userId={}", userId);
                return new DefaultUserProfile();
            })
            .get();
    }
    
    /**
     * 带熔断的广告库存获取
     */
    public List<AdCandidate> getInventorySafely(GeoLocation location, DeviceType deviceType) {
        return CircuitBreaker.decorateSupplier(inventoryCircuitBreaker,
            () -> adInventoryService.getCandidates(location, deviceType))
            .recover(throwable -> {
                log.warn("广告库存服务熔断,使用备用库存");
                return getFallbackInventory();
            })
            .get();
    }
    
    private List<AdCandidate> getFallbackInventory() {
        // 返回备用广告库存
        return new ArrayList<>();
    }
}

8.2 降级策略

@Service
@Slf4j
public class DegradationStrategyService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 降级处理策略
     */
    public AdResponse handleDegradation(StandardizedRequest request, DegradationLevel level) {
        switch (level) {
            case LIGHT:
                // 轻度降级:减少广告数量,使用缓存数据
                return handleLightDegradation(request);
                
            case MEDIUM:
                // 中度降级:只返回高优先级广告,关闭实时竞价
                return handleMediumDegradation(request);
                
            case HEAVY:
                // 重度降级:返回默认广告,关闭个性化
                return handleHeavyDegradation(request);
                
            default:
                return AdResponse.errorResponse();
        }
    }
    
    private AdResponse handleLightDegradation(StandardizedRequest request) {
        // 减少广告返回数量
        // 使用本地缓存的用户画像
        log.info("执行轻度降级策略: requestId={}", request.getRequestId());
        return buildDegradedResponse(request, 3); // 返回3个广告
    }
    
    private AdResponse handleMediumDegradation(StandardizedRequest request) {
        // 只返回高优先级广告
        // 关闭实时竞价,使用固定出价
        log.info("执行中度降级策略: requestId={}", request.getRequestId());
        return buildDegradedResponse(request, 2); // 返回2个广告
    }
    
    private AdResponse handleHeavyDegradation(StandardizedRequest request) {
        // 返回默认广告
        // 关闭个性化推荐
        log.info("执行重度降级策略: requestId={}", request.getRequestId());
        return buildDefaultResponse(request); // 返回默认广告
    }
    
    private AdResponse buildDegradedResponse(StandardizedRequest request, int adCount) {
        // 构建降级响应
        return new AdResponse();
    }
    
    private AdResponse buildDefaultResponse(StandardizedRequest request) {
        // 构建默认响应
        return new AdResponse();
    }
}

九、最佳实践总结

9.1 架构设计原则

public class ArchitecturePrinciples {
    
    public void explainPrinciples() {
        System.out.println("=== 广告系统架构设计原则 ===");
        System.out.println("1. 高内聚低耦合:各模块职责清晰");
        System.out.println("2. 可扩展性:支持水平扩展");
        System.out.println("3. 高可用性:具备容错和降级能力");
        System.out.println("4. 性能优先:毫秒级响应要求");
        System.out.println("5. 数据驱动:基于数据的决策优化");
        System.out.println("6. 安全可靠:防作弊和数据保护");
    }
}

9.2 开发运维规范

public class DevOpsPractices {
    
    public void explainPractices() {
        System.out.println("=== 广告系统开发运维规范 ===");
        System.out.println("开发规范:");
        System.out.println("- 代码审查制度");
        System.out.println("- 单元测试覆盖率>80%");
        System.out.println("- 性能基准测试");
        System.out.println("- A/B测试机制");
        
        System.out.println("\n运维规范:");
        System.out.println("- 自动化部署");
        System.out.println("- 灰度发布策略");
        System.out.println("- 容量规划");
        System.out.println("- 故障演练");
        System.out.println("- 监控告警体系");
    }
}

结语

打造一个大型广告系统是一个复杂而充满挑战的过程,需要综合考虑业务需求、技术架构、性能优化、监控运维等多个方面。通过合理的架构设计和持续的优化,我们可以构建出既能满足高并发需求,又能实现精准投放的广告平台。

关键要点总结:

  1. 合理的架构分层:流量接入、广告检索、实时计算、数据存储等层次清晰
  2. 核心算法优化:CTR预估、实时竞价、排序算法等是系统的核心竞争力
  3. 高并发处理:缓存策略、异步处理、数据库优化等手段提升系统性能
  4. 监控告警体系:完善的指标收集和告警机制保障系统稳定运行
  5. 容错降级机制:熔断、降级等手段保证系统在异常情况下的可用性

如果你觉得这篇文章对你有帮助,欢迎分享给更多的朋友。在构建高并发广告系统的路上,我们一起成长!


关注「服务端技术精选」,获取更多干货技术文章!


标题:大型广告系统架构设计与实战:从0到1打造日均千亿曝光的广告平台
作者:jiangyi
地址:http://www.jiangyi.space/articles/2025/12/21/1766304292859.html

    0 评论
avatar