既然說到了電視轉播,我想到了在我的大學生涯中有協助執行一個研究案 《NetFlix 亞洲地區閱聽眾愛好集群側寫分析》,這是一個非常有意思的研究項目,我有幸看到了當時 Netflix 某個時間區段的關於觀眾的偏好影片資料集與各種電影連續劇的的分類資料表,當時最終目標是要協助建立多族群對於不同影視作品類型的偏好網絡圖,來協助建立推送模型。
說遠了,我們把注意力轉到 Netflix 的內容管理上。想像 Netflix 需要為全球數百個內容製作工作室、發行商和版權方提供一個統一的影片管理平台,但每個「租戶」都有完全不同的需求和權限。而多租戶架構也需要為不同狀態的「租戶」提供差異化的服務。
所有的工作室與團隊都在使用各自的雲端系統,但實際上它是建立在一個 大池子中。
抽象概念:一個應用程式同時服務多個「租戶」,每個租戶認為自己在使用專屬的系統
核心設計哲學四指標:
隔離性 (Isolation):行銷團隊絕對不能看到還在製作中的未發布內容。每個租戶的資料和操作必須完全隔離。
共享性 (Sharing):所有工作室都共享同一套影片編碼、儲存、分發的基礎設施。沒有人需要自己建置一套全球 CDN 網路。
客製化 (Customization):有些工作室需要 4K HDR 的高品質製作流程,有些獨立製片只需要 1080p。平台必須支援不同等級的服務。
可擴展性 (Scalability):當新的製作工作室加入時,系統應該能夠無縫擴展,而不是重新架構整個平台。
共享儲存,共享資料結構 、共享基礎設施,獨立儲存空間、完全獨立的基礎設施
這就像所有製作工作室的影片都存放在同一個巨大的 S3 儲存庫中,但透過資料夾結構和存取權限來區分。
graph TB
subgraph "共享 S3 Bucket: netflix-content-hub"
A[統一管理介面] --> B[單一 S3 Bucket]
subgraph "disney-studio/ 資料夾"
C[disney-studio/production/marvel-movie-2024.mp4]
C1[disney-studio/published/frozen-3.mp4]
C2[disney-studio/archived/old-cartoons/]
end
subgraph "hbo-max/ 資料夾"
D[hbo-max/production/got-prequel.mp4]
D1[hbo-max/published/house-of-dragon.mp4]
D2[hbo-max/archived/old-series/]
end
subgraph "netflix-originals/ 資料夾"
E[netflix-originals/production/stranger-things-5.mp4]
E1[netflix-originals/published/wednesday.mp4]
E2[netflix-originals/archived/old-shows/]
end
B --> C
B --> C1
B --> C2
B --> D
B --> D1
B --> D2
B --> E
B --> E1
B --> E2
end
subgraph "存取控制"
F[IAM 政策: 只能存取自己的前綴]
F --> G[disney-studio/* 權限給迪士尼]
F --> H[hbo-max/* 權限給 HBO]
F --> I[netflix-originals/* 權限給 Netflix]
end
B --> F
影片生命週期在共享模式中的體現:
production/
):高頻讀寫,需要版本控制,多人協作published/
):高頻讀取,需要全球 CDN 分發archived/
):低頻存取,可以移至較便宜的儲存層級S3 儲存策略:
# 基於影片狀態的 S3 儲存策略
S3_STORAGE_POLICIES = {
'production': {
'storage_class': 'STANDARD',
'versioning': True,
'encryption': 'aws:kms',
'backup_frequency': 'hourly',
'cost_per_gb_month': 0.023
},
'published': {
'storage_class': 'STANDARD',
'cdn_distribution': True,
'global_replication': True,
'cost_per_gb_month': 0.023
},
'archived': {
'storage_class': 'GLACIER_FLEXIBLE_RETRIEVAL',
'versioning': False,
'retrieval_time': '1-5 minutes',
'cost_per_gb_month': 0.0036
}
}
優點:
缺點:
這就像每個製作工作室都有自己專屬的「倉庫」,但共享同一套物流配送系統。
graph TB
subgraph "共享管理平台"
A[Netflix Content Management Platform] --> B[統一 API Gateway]
B --> C[租戶路由服務]
end
subgraph "製作工作室專屬儲存"
C --> D[disney-content-bucket]
C --> E[hbo-max-content-bucket]
C --> F[netflix-originals-bucket]
C --> G[independent-films-bucket]
subgraph "Disney 的影片生命週期"
D --> D1[製作中: marvel-movie-2024/<br/>4K RAW 素材]
D --> D2[放送中: frozen-3/<br/>多語言版本]
D --> D3[入庫: classic-animations/<br/>Glacier 長期保存]
end
subgraph "HBO Max 的影片生命週期"
E --> E1[製作中: got-prequel/<br/>8K HDR 素材]
E --> E2[放送中: house-of-dragon/<br/>全球分發版本]
E --> E3[入庫: classic-hbo/<br/>Deep Archive]
end
end
subgraph "共享基礎服務"
H[共享 CloudFront CDN]
I[共享 MediaConvert 轉碼]
J[共享 Rekognition AI 分析]
K[共享監控與分析]
D --> H
E --> H
F --> H
D --> I
E --> I
F --> I
end
影片狀態驅動的自動化管理:
class ContentLifecycleManager:
"""基於影片狀態的自動化生命週期管理"""
def __init__(self, tenant_id):
self.tenant_bucket = f"{tenant_id}-content-bucket"
self.lifecycle_policies = {
'production': {
'standard_ia_transition': 30, # 30天後轉入 IA
'glacier_transition': 90, # 90天後轉入 Glacier
'expiration': None # 永不刪除
},
'published': {
'standard_ia_transition': 365, # 1年後轉入 IA
'glacier_transition': 1095, # 3年後轉入 Glacier
'expiration': None
},
'archived': {
'immediate_glacier': True, # 立即轉入 Glacier
'deep_archive_transition': 90, # 90天後轉入 Deep Archive
'expiration': 2555 # 7年後刪除
}
}
async def transition_content_status(self, content_id, from_status, to_status):
"""內容狀態轉換觸發儲存策略變更"""
if from_status == 'production' and to_status == 'published':
# 製作完成,準備發行
await self.create_distribution_copies(content_id)
await self.enable_global_cdn(content_id)
await self.setup_drm_protection(content_id)
elif from_status == 'published' and to_status == 'archived':
# 版權到期,歸檔處理
await self.disable_cdn_distribution(content_id)
await self.transition_to_glacier(content_id)
await self.update_metadata_only_access(content_id)
async def create_distribution_copies(self, content_id):
"""為發行創建多個格式版本"""
source_key = f"production/{content_id}/master.mp4"
# 創建不同品質版本
encoding_jobs = [
{'resolution': '4K', 'bitrate': '15000k', 'target': 'premium'},
{'resolution': '1080p', 'bitrate': '5000k', 'target': 'standard'},
{'resolution': '720p', 'bitrate': '2500k', 'target': 'mobile'},
{'resolution': '480p', 'bitrate': '1000k', 'target': 'low_bandwidth'}
]
for job in encoding_jobs:
await self.submit_encoding_job(source_key, job)
async def intelligent_cost_optimization(self):
"""基於觀看數據的智能成本優化"""
# 分析最近90天的觀看數據
viewing_analytics = await self.get_viewing_analytics(days=90)
for content_id, analytics in viewing_analytics.items():
if analytics['views_per_day'] < 1:
# 低觀看量內容,考慮降級儲存
await self.suggest_storage_downgrade(content_id)
elif analytics['views_per_day'] > 1000:
# 高觀看量內容,考慮升級儲存
await self.suggest_storage_upgrade(content_id)
優點:
缺點:
這就像每個大型製作工作室都有自己完整的製作、儲存、分發基礎設施。
graph TB
subgraph "迪士尼專屬基礎設施"
A1[Disney AWS Account] --> B1[專屬 S3 Buckets]
A1 --> C1[專屬 CloudFront Distribution]
A1 --> D1[專屬 MediaConvert Pipeline]
A1 --> E1[專屬 VPC 網路]
subgraph "Disney 內容分層"
B1 --> F1[製作中: 4K RAW<br/>S3 Standard + 版本控制]
B1 --> F2[發行中: 多格式<br/>S3 + 全球 CDN]
B1 --> F3[經典館藏: 老電影<br/>S3 Glacier Deep Archive]
end
end
subgraph "HBO Max 專屬基礎設施"
A2[HBO AWS Account] --> B2[專屬 S3 Buckets]
A2 --> C2[專屬 CloudFront Distribution]
A2 --> D2[專屬 MediaConvert Pipeline]
A2 --> E2[專屬 VPC 網路]
subgraph "HBO 內容分層"
B2 --> G1[製作中: 8K HDR<br/>S3 Standard + 即時備份]
B2 --> G2[熱播中: 影集<br/>S3 + Edge 優化]
B2 --> G3[完結影集: 舊內容<br/>S3 IA + 選擇性恢復]
end
end
subgraph "獨立製片工作室共享基礎設施"
A3[Independent Films Shared Infrastructure] --> B3[共享 S3 Buckets]
A3 --> C3[共享 CloudFront]
A3 --> D3[共享轉碼服務]
subgraph "獨立製片內容管理"
B3 --> H1[低預算製作<br/>S3 IA + 基礎轉碼]
B3 --> H2[小眾發行<br/>按需 CDN]
B3 --> H3[舊片庫存<br/>Glacier + 手動恢復]
end
end
subgraph "Netflix 中央協調系統"
I[Netflix 合作夥伴入口]
I --> J[統一認證與授權]
I --> K[跨平台分析儀表板]
I --> L[統一版權管理]
A1 --> I
A2 --> I
A3 --> I
end
企業級內容管理架構:
class EnterpriseContentInfrastructure:
"""企業級租戶的專屬基礎設施"""
def __init__(self, tenant_id, tier='enterprise'):
self.tenant_id = tenant_id
self.tier = tier
self.infrastructure_config = self.get_tier_config(tier)
def get_tier_config(self, tier):
"""根據租戶等級配置基礎設施"""
configs = {
'enterprise': {
'dedicated_account': True,
'vpc': 'dedicated',
's3_buckets': 'multiple_specialized',
'cloudfront': 'dedicated_distribution',
'mediaconvert': 'reserved_capacity',
'monitoring': 'detailed_metrics',
'support': '24/7_premium',
'monthly_cost': 50000,
'content_limits': 'unlimited'
},
'professional': {
'dedicated_account': False,
'vpc': 'shared_with_isolation',
's3_buckets': 'dedicated_bucket',
'cloudfront': 'shared_distribution',
'mediaconvert': 'on_demand',
'monitoring': 'standard_metrics',
'support': 'business_hours',
'monthly_cost': 5000,
'content_limits': '10TB'
},
'indie': {
'dedicated_account': False,
'vpc': 'shared',
's3_buckets': 'shared_with_prefix',
'cloudfront': 'shared_basic',
'mediaconvert': 'spot_instances',
'monitoring': 'basic_metrics',
'support': 'community',
'monthly_cost': 500,
'content_limits': '1TB'
}
}
return configs.get(tier, configs['indie'])
async def provision_dedicated_infrastructure(self):
"""為企業租戶配置專屬基礎設施"""
if self.tier == 'enterprise':
# 創建專屬的 AWS 帳戶
dedicated_account = await self.create_dedicated_aws_account()
# 設定專屬的內容生命週期策略
await self.setup_content_lifecycle_policies(dedicated_account)
# 配置全球分發網路
await self.setup_global_distribution_network(dedicated_account)
# 設定專屬的安全與合規措施
await self.setup_security_compliance(dedicated_account)
async def setup_content_lifecycle_policies(self, account_id):
"""設定內容生命週期自動化"""
lifecycle_rules = {
'production_content': {
'versioning': True,
'mfa_delete': True,
'backup_frequency': 'real_time',
'retention_policy': 'indefinite',
'access_logging': True
},
'published_content': {
'replication': 'cross_region',
'cdn_integration': True,
'analytics_tracking': True,
'drm_protection': True
},
'archived_content': {
'storage_class_transition': {
'to_ia': 30,
'to_glacier': 90,
'to_deep_archive': 365
},
'retrieval_policy': 'expedited_available',
'metadata_preservation': True
}
}
for content_type, rules in lifecycle_rules.items():
await self.apply_lifecycle_rules(account_id, content_type, rules)
優點:
缺點:
我們來試試看模擬一下現實情境,在實際的 Netflix 內容平台中應該會根據內容的商業價值、製作成本和觀看預期來動態分配資源,所以我們設計上來說會採用混合模式。
graph TD
A[新內容上架評估] --> B{內容分類評估}
B -->|好萊塢大片<br/>製作成本 > $100M| C[模式三: 專屬基礎設施<br/>Disney/Marvel 級別]
B -->|熱門影集<br/>預期觀看 > 10M| D[模式二: 專屬儲存<br/>HBO/Netflix Originals 級別]
B -->|獨立製片<br/>利基市場| E[模式一: 共享基礎設施<br/>Indie Films 級別]
subgraph "動態升級路徑"
E -->|爆紅內容| F[自動升級觸發]
F --> D
D -->|全球現象級| G[人工評估升級]
G --> C
end
subgraph "成本最佳化"
H[觀看數據分析] --> I{成本效益評估}
I -->|ROI < 閾值| J[降級儲存策略]
I -->|ROI > 閾值| K[升級服務等級]
C --> H
D --> H
E --> H
end
subgraph "內容生命週期自動化"
L[製作中] --> M[發行準備]
M --> N[熱播期]
N --> O[穩定期]
O --> P[歸檔期]
L --> Q[S3 Standard + 版本控制]
M --> R[多格式轉碼 + CDN 預熱]
N --> S[全球分發 + 即時分析]
O --> T[標準分發 + 定期分析]
P --> U[Glacier + 元數據保留]
end
class NetflixContentMultiTenantPlatform:
"""Netflix 風格的多租戶內容平台"""
def __init__(self):
self.tenant_strategies = {
'hollywood_studio': {
'model': 'dedicated_infrastructure',
'storage': 'dedicated_s3_account',
'cdn': 'dedicated_cloudfront',
'encoding': 'reserved_mediaconvert',
'isolation': 'account_level',
'max_content_size': 'unlimited',
'monthly_cost': 50000,
'sla': '99.99%'
},
'streaming_platform': {
'model': 'dedicated_storage',
'storage': 'dedicated_s3_bucket',
'cdn': 'shared_cloudfront_with_dedicated_behaviors',
'encoding': 'on_demand_mediaconvert',
'isolation': 'bucket_level',
'max_content_size': '100TB',
'monthly_cost': 5000,
'sla': '99.9%'
},
'independent_creator': {
'model': 'shared_infrastructure',
'storage': 'shared_s3_with_prefix',
'cdn': 'shared_cloudfront_basic',
'encoding': 'spot_mediaconvert',
'isolation': 'prefix_level',
'max_content_size': '1TB',
'monthly_cost': 500,
'sla': '99.5%'
}
}
# AWS 服務客戶端
self.s3 = boto3.client('s3')
self.cloudfront = boto3.client('cloudfront')
self.mediaconvert = boto3.client('mediaconvert')
self.organizations = boto3.client('organizations')
async def onboard_content_provider(self, provider_info):
"""為新的內容提供者配置基礎設施"""
# 1. 評估提供者等級
tier = await self.evaluate_provider_tier(provider_info)
strategy = self.tenant_strategies[tier]
# 2. 配置儲存基礎設施
storage_config = await self.setup_storage_infrastructure(provider_info, strategy)
# 3. 配置內容分發網路
cdn_config = await self.setup_cdn_infrastructure(provider_info, strategy)
# 4. 設定內容生命週期管理
lifecycle_config = await self.setup_content_lifecycle(provider_info, strategy)
# 5. 建立監控和計費
monitoring_config = await self.setup_monitoring_billing(provider_info, strategy)
return {
'provider_id': provider_info['id'],
'tier': tier,
'storage': storage_config,
'cdn': cdn_config,
'lifecycle': lifecycle_config,
'monitoring': monitoring_config,
'monthly_cost_estimate': strategy['monthly_cost']
}
async def setup_storage_infrastructure(self, provider_info, strategy):
"""根據策略設定儲存基礎設施"""
if strategy['model'] == 'dedicated_infrastructure':
# 為好萊塢工作室創建專屬 AWS 帳戶
account_config = await self.create_dedicated_aws_account(provider_info)
bucket_config = await self.create_dedicated_account_buckets(
account_config['account_id'], provider_info
)
elif strategy['model'] == 'dedicated_storage':
# 為串流平台創建專屬 S3 bucket
bucket_config = await self.create_dedicated_bucket(provider_info)
else: # shared_infrastructure
# 為獨立創作者分配共享 bucket 的前綴
bucket_config = await self.allocate_shared_bucket_prefix(provider_info)
# 設定生命週期政策
await self.configure_content_lifecycle_policies(bucket_config, strategy)
return bucket_config
async def create_dedicated_bucket(self, provider_info):
"""為中等規模租戶創建專屬 bucket"""
bucket_name = f"{provider_info['id']}-content-{uuid.uuid4().hex[:8]}"
# 創建主要內容 bucket
await self.s3.create_bucket(
Bucket=bucket_name,
CreateBucketConfiguration={'LocationConstraint': 'us-west-2'}
)
# 啟用版本控制
await self.s3.put_bucket_versioning(
Bucket=bucket_name,
VersioningConfiguration={'Status': 'Enabled'}
)
# 設定加密
await self.s3.put_bucket_encryption(
Bucket=bucket_name,
ServerSideEncryptionConfiguration={
'Rules': [{
'ApplyServerSideEncryptionByDefault': {
'SSEAlgorithm': 'aws:kms',
'KMSMasterKeyID': f"arn:aws:kms:us-west-2:account:key/{provider_info['kms_key']}"
}
}]
}
)
# 設定存取日誌
await self.s3.put_bucket_logging(
Bucket=bucket_name,
BucketLoggingStatus={
'LoggingEnabled': {
'TargetBucket': 'netflix-access-logs',
'TargetPrefix': f"content-access/{provider_info['id']}/"
}
}
)
return {
'bucket_name': bucket_name,
'type': 'dedicated',
'provider_id': provider_info['id'],
'versioning': True,
'encryption': 'kms',
'regions': ['us-west-2', 'us-east-1', 'eu-west-1']
}
async def configure_content_lifecycle_policies(self, bucket_config, strategy):
"""配置基於內容狀態的生命週期政策"""
lifecycle_rules = []
# 製作中內容的生命週期
production_rule = {
'ID': 'ProductionContentLifecycle',
'Status': 'Enabled',
'Filter': {'Prefix': 'production/'},
'Transitions': [
{
'Days': 30,
'StorageClass': 'STANDARD_IA'
},
{
'Days': 90,
'StorageClass': 'GLACIER'
}
]
}
# 發行中內容的生命週期
published_rule = {
'ID': 'PublishedContentLifecycle',
'Status': 'Enabled',
'Filter': {'Prefix': 'published/'},
'Transitions': [
{
'Days': 365,
'StorageClass': 'STANDARD_IA'
},
{
'Days': 1095, # 3 years
'StorageClass': 'GLACIER'
}
]
}
# 歸檔內容的生命週期
archived_rule = {
'ID': 'ArchivedContentLifecycle',
'Status': 'Enabled',
'Filter': {'Prefix': 'archived/'},
'Transitions': [
{
'Days': 1,
'StorageClass': 'GLACIER'
},
{
'Days': 90,
'StorageClass': 'DEEP_ARCHIVE'
}
]
}
lifecycle_rules.extend([production_rule, published_rule, archived_rule])
# 應用生命週期政策
await self.s3.put_bucket_lifecycle_configuration(
Bucket=bucket_config['bucket_name'],
LifecycleConfiguration={'Rules': lifecycle_rules}
)
async def intelligent_content_placement(self, content_metadata):
"""基於內容特性的智能放置決策"""
# 分析內容特性
analysis = {
'production_budget': content_metadata.get('budget', 0),
'expected_viewership': content_metadata.get('expected_views', 0),
'content_type': content_metadata.get('type', 'unknown'),
'target_audience': content_metadata.get('audience', 'general'),
'release_strategy': content_metadata.get('release', 'standard')
}
# 決定儲存策略
if analysis['production_budget'] > 100_000_000: # $100M+
storage_tier = 'premium'
storage_class = 'STANDARD'
replication = 'cross_region'
cdn_tier = 'dedicated_edge_locations'
elif analysis['expected_viewership'] > 10_000_000: # 10M+ views
storage_tier = 'standard'
storage_class = 'STANDARD'
replication = 'same_region'
cdn_tier = 'shared_optimized'
else:
storage_tier = 'basic'
storage_class = 'STANDARD_IA'
replication = 'none'
cdn_tier = 'shared_basic'
return {
'storage_tier': storage_tier,
'storage_class': storage_class,
'replication_strategy': replication,
'cdn_configuration': cdn_tier,
'estimated_monthly_cost': self.calculate_storage_cost(analysis, storage_tier)
}
async def handle_viral_content_scaling(self, content_id, metrics):
"""處理爆紅內容的自動擴展"""
# 檢測爆紅指標
if (metrics['views_per_hour'] > 100000 and
metrics['growth_rate'] > 5.0 and
metrics['geographic_spread'] > 10):
# 自動升級儲存等級
await self.upgrade_content_storage_tier(content_id, 'premium')
# 啟用全球邊緣快取
await self.enable_global_edge_caching(content_id)
# 增加 CDN 容量
await self.scale_cdn_capacity(content_id, multiplier=10)
# 通知內容團隊
await self.notify_viral_content_detected(content_id, metrics)
async def cost_optimization_analysis(self, provider_id):
"""為租戶進行成本最佳化分析"""
# 收集過去30天的使用數據
usage_metrics = await self.collect_usage_metrics(provider_id, days=30)
# 分析內容存取模式
access_patterns = await self.analyze_access_patterns(usage_metrics)
# 生成最佳化建議
recommendations = []
for content_item in access_patterns:
if content_item['views_per_day'] < 1:
recommendations.append({
'content_id': content_item['id'],
'action': 'move_to_glacier',
'potential_savings': content_item['current_cost'] * 0.8,
'impact': 'minimal'
})
elif content_item['views_per_day'] > 1000:
recommendations.append({
'content_id': content_item['id'],
'action': 'upgrade_to_premium',
'additional_cost': content_item['current_cost'] * 0.3,
'benefit': 'improved_user_experience'
})
return {
'current_monthly_cost': sum(item['current_cost'] for item in access_patterns),
'potential_monthly_savings': sum(rec['potential_savings'] for rec in recommendations if 'potential_savings' in rec),
'recommendations': recommendations,
'optimization_score': self.calculate_optimization_score(recommendations)
}
通過 Netflix 內容平台的比喻,我們可以看到多租戶架構的本質:
選擇指南:
為不同需求的用戶提供恰到好處的服務,既不浪費資源,也不犧牲體驗 乃是多租戶架構的智慧。