场景决策面试题集
大约 18 分钟约 5402 字
场景决策面试题集
简介
场景决策面试(也叫情景面试)考察候选人在面对技术选型、架构决策、迁移策略等实际问题时,如何分析权衡、做出合理的决策并清晰表达理由。与系统设计不同,场景决策更侧重于"为什么选 A 而不是 B"的思维能力。本文涵盖技术选型、架构决策记录、容量规划、故障模式分析、迁移策略等典型场景,每题提供结构化的答题框架。
特点
答题框架
ADR(Architecture Decision Record)模式
每个技术决策都应包含以下要素:
1. 背景(Context)— 为什么需要做这个决策?
2. 决策(Decision)— 选择了什么方案?
3. 备选方案(Alternatives)— 考虑了哪些其他方案?
4. 理由(Rationale)— 为什么选择这个方案?
5. 后果(Consequences)— 选择后带来的影响(正面+负面)
6. 约束(Constraints)— 有哪些限制条件?场景一:SQL vs NoSQL
题目描述
你负责设计一个电商平台的商品管理系统。商品属性差异很大(手机有屏幕尺寸,
服装有尺码颜色),并且需要支持全文搜索。你会如何选择数据库?分析与决策
/// <summary>
/// 场景分析:电商商品管理系统
/// </summary>
public class SqlVsNosql
{
/// 对比分析
/// ┌──────────┬─────────────────────┬─────────────────────┐
/// │ 维度 │ SQL (PostgreSQL) │ NoSQL (MongoDB) │
/// ├──────────┼─────────────────────┼─────────────────────┤
/// │ 数据模型 │ 固定 schema │ 灵活 document │
/// │ 属性差异 │ EAV 模式或 JSON 列 │ 天然支持嵌套文档 │
/// │ 事务 │ 强 ACID │ 4.0+ 支持多文档事务 │
/// │ 查询 │ SQL 丰富 │ 查询能力有限 │
/// │ 全文搜索 │ 内置 tsvector │ 需要 Atlas Search │
/// │ 扩展性 │ 垂直为主 │ 水平扩展容易 │
/// │ 团队熟悉度 │ 高 │ 中等 │
/// └──────────┴─────────────────────┴─────────────────────┘
/// 推荐方案:PostgreSQL(混合方案)
/// 理由:
/// 1. PostgreSQL 的 JSONB 列支持灵活属性
/// 2. 电商核心(订单、库存)需要强事务
/// 3. 内置全文搜索能力(tsvector + GIN 索引)
/// 4. 团队 SQL 经验丰富
/// 5. 运维和生态更成熟
/// <summary>
/// PostgreSQL 混合方案实现
/// </summary>
public class ProductRepository
{
private readonly AppDbContext _db;
/// 核心属性用关系型列,扩展属性用 JSONB
public async Task<int> CreateProductAsync(CreateProductRequest request)
{
var product = new ProductEntity
{
Name = request.Name,
Category = request.Category,
Price = request.Price,
// 灵活属性存储在 JSONB 列
Attributes = request.Attributes,
// 全文搜索向量
SearchVector = BuildSearchVector(request.Name, request.Description)
};
_db.Products.Add(product);
await _db.SaveChangesAsync();
return product.Id;
}
/// JSONB 属性查询
public async Task<List<ProductEntity>> SearchByAttributeAsync(
string key, string value)
{
// PostgreSQL JSONB 查询
// SELECT * FROM products WHERE attributes->>'color' = 'red'
return await _db.Products
.Where(p => EF.Functions.JsonExists(p.Attributes, key))
.Where(p => EF.Functions.JsonValue(p.Attributes, key) == value)
.ToListAsync();
}
/// 全文搜索
public async Task<List<ProductEntity>> FullTextSearchAsync(string query)
{
// 使用 PostgreSQL 内置全文搜索
return await _db.Products
.Where(p => EF.Functions.ToTsVector("chinese",
p.Name + " " + p.Description)
.Matches(EF.Functions.ToTsQuery("chinese", query)))
.OrderByDescending(p => p.SearchRank)
.Take(20)
.ToListAsync();
}
private string BuildSearchVector(string name, string? description)
{
return $"{name} {description ?? ""}";
}
}
}
// 数据模型:核心列 + JSONB 扩展属性
public class ProductEntity
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Category { get; set; } = "";
public decimal Price { get; set; }
public string? Description { get; set; }
// JSONB 列:存储灵活属性
public Dictionary<string, object> Attributes { get; set; } = new()
{
// 手机: {"screen": "6.7\"", "ram": "8GB", "storage": "256GB"}
// 服装: {"sizes": ["S","M","L"], "colors": ["red","blue"]}
};
public string SearchVector { get; set; } = "";
public double SearchRank { get; set; }
}
public record CreateProductRequest
{
public string Name { get; init; } = "";
public string Category { get; init; } = "";
public decimal Price { get; init; }
public string? Description { get; init; }
public Dictionary<string, object> Attributes { get; init; } = new();
}追问
- Q: 数据量增长到亿级怎么办? A: 读写分离 + 分库分表 + Elasticsearch 负责搜索
- Q: 如何处理跨商品类型的属性查询? A: 动态属性表 + Elasticsearch 聚合
场景二:REST vs gRPC
题目描述
你的微服务之间需要高频通信(每秒上万次调用),数据格式为结构化的业务对象。
你应该选择 REST 还是 gRPC?分析与决策
/// <summary>
/// REST vs gRPC 决策分析
/// </summary>
public class RestVsGrpc
{
/// 对比分析
/// ┌──────────────┬─────────────────────┬─────────────────────┐
/// │ 维度 │ REST │ gRPC │
/// ├──────────────┼─────────────────────┼─────────────────────┤
/// │ 序列化 │ JSON (文本) │ Protobuf (二进制) │
/// │ 性能 │ 中等 │ 高(3-10x REST) │
/// │ 代码生成 │ 手写/NSwag │ 自动生成客户端 │
/// │ 流式支持 │ SSE/WebSocket │ 原生双向流 │
/// │ 浏览器支持 │ 原生 │ 需要 gRPC-Web │
/// │ 调试难度 │ 低(可读) │ 高(二进制) │
/// │ 生态 │ 极丰富 │ 丰富 │
/// │ 穿透性 │ 防火墙友好 │ HTTP/2 要求 │
/// └──────────────┴─────────────────────┴─────────────────────┘
/// 推荐方案:混合使用
/// - 外部 API(对前端/第三方):REST + JSON
/// - 内部服务间通信:gRPC + Protobuf
/// <summary>
/// gRPC 服务定义和实现
/// </summary>
/// proto 文件: order_service.proto
/// service OrderService {
/// rpc GetOrder (GetOrderRequest) returns (OrderResponse);
/// rpc StreamOrders (StreamOrdersRequest) returns (stream OrderResponse);
/// }
/// <summary>
/// gRPC 服务实现
/// </summary>
public class OrderGrpcService : OrderService.OrderServiceBase
{
private readonly IOrderRepository _orderRepo;
public OrderGrpcService(IOrderRepository orderRepo)
{
_orderRepo = orderRepo;
}
/// 一元调用(类似 REST 的请求-响应)
public override async Task<OrderResponse> GetOrder(
GetOrderRequest request, ServerCallContext context)
{
var order = await _orderRepo.GetByIdAsync(request.OrderId);
if (order == null)
throw new RpcException(new Status(StatusCode.NotFound, "订单不存在"));
return new OrderResponse
{
OrderId = order.Id,
UserId = order.UserId,
Amount = (double)order.Amount,
Status = order.Status,
CreatedAt = Timestamp.FromDateTime(order.CreatedAt)
};
}
/// 服务端流(REST 无法原生实现)
public override async Task StreamOrders(
StreamOrdersRequest request,
IServerStreamWriter<OrderResponse> responseStream,
ServerCallContext context)
{
await foreach (var order in _orderRepo.GetRecentOrdersAsync(
request.UserId, context.CancellationToken))
{
await responseStream.WriteAsync(new OrderResponse
{
OrderId = order.Id,
Amount = (double)order.Amount,
Status = order.Status
});
}
}
}
/// <summary>
/// REST 与 gRPC 共存的 ASP.NET Core 配置
/// </summary>
public static class ServiceRegistration
{
public static WebApplicationBuilder AddBothProtocols(this WebApplicationBuilder builder)
{
// REST Controllers
builder.Services.AddControllers();
// gRPC
builder.Services.AddGrpc(options =>
{
options.EnableDetailedErrors = true;
options.MaxReceiveMessageSize = 4 * 1024 * 1024; // 4MB
});
// gRPC JSON Transcoding(将 gRPC 自动暴露为 REST)
builder.Services.AddGrpcSwagger();
builder.Services.AddSwaggerGen();
return builder;
}
public static WebApplication MapBothProtocols(this WebApplication app)
{
// REST 端点
app.MapControllers();
// gRPC 端点
app.MapGrpcService<OrderGrpcService>();
// gRPC 反射(用于 grpcurl 调试)
app.MapGrpcReflectionService();
return app;
}
}
}
// gRPC 生成的类(简化)
public static class OrderService { public abstract class OrderServiceBase { public virtual Task<OrderResponse> GetOrder(GetOrderRequest r, ServerCallContext c) => throw new NotImplementedException(); public virtual Task StreamOrders(StreamOrdersRequest r, IServerStreamWriter<OrderResponse> s, ServerCallContext c) => throw new NotImplementedException(); } }
public class GetOrderRequest { public string OrderId { get; set; } = ""; }
public class StreamOrdersRequest { public string UserId { get; set; } = ""; }
public class OrderResponse { public string OrderId { get; set; } = ""; public string UserId { get; set; } = ""; public double Amount { get; set; } public string Status { get; set; } = ""; public Timestamp CreatedAt { get; set; } = new(); }
public class Timestamp { public static Timestamp FromDateTime(DateTime dt) => new(); public DateTime ToDateTime() => DateTime.UtcNow; }
public class ServerCallContext { public CancellationToken CancellationToken => default; }
public class Status { public Status(StatusCode code, string msg) { } }
public enum StatusCode { NotFound }
public class RpcException : Exception { public RpcException(Status status) { } }
public interface IServerStreamWriter<T> { Task WriteAsync(T message); }
public interface IOrderRepository { Task<Order?> GetByIdAsync(string id); IAsyncEnumerable<Order> GetRecentOrdersAsync(string userId, CancellationToken ct); }追问
- Q: 如何处理 gRPC 的版本兼容? A: Protobuf 向后兼容规则 + 新字段编号
- Q: gRPC 服务如何做负载均衡? A: 客户端负载均衡(grpc-lb)或 Service Mesh
场景三:单体 vs 微服务
题目描述
你的团队有 8 人,负责一个中型 SaaS 产品(用户管理、订单、支付、通知)。
当前是单体架构,正在考虑是否拆分为微服务。你的建议是什么?分析与决策
决策分析框架:
1. 团队规模
- 8 人团队 → 建议单体或模块化单体
- 微服务需要至少 15-20 人才能有效管理
2. 业务复杂度
- 4 个核心模块 → 可以通过模块化单体解决
- 不需要独立部署和扩展
3. 技术挑战
- 单体优势:简单的事务、调试、部署
- 微服务挑战:分布式事务、服务发现、网络延迟
4. 推荐方案:模块化单体(Modular Monolith)
- 单个部署单元
- 内部按领域模块组织
- 模块间通过接口而非直接数据库访问
- 未来需要时可以平滑拆分为微服务/// <summary>
/// 模块化单体架构实现
/// </summary>
public class ModularMonolith
{
/// <summary>
/// 模块化单体:每个模块有独立领域和接口
/// </summary>
// === 订单模块 ===
namespace Ordering
{
public interface IOrderService
{
Task<Order> CreateOrderAsync(CreateOrderCommand cmd);
Task<Order> GetOrderAsync(int orderId);
}
public class OrderService : IOrderService
{
private readonly IOrderRepository _repo;
private readonly IEventBus _eventBus;
private readonly Payment.IPaymentService _payment; // 通过接口引用支付模块
public async Task<Order> CreateOrderAsync(CreateOrderCommand cmd)
{
var order = new Order
{
UserId = cmd.UserId,
Items = cmd.Items,
Status = "Pending",
CreatedAt = DateTime.UtcNow
};
await _repo.SaveAsync(order);
// 发布领域事件(其他模块可以订阅)
await _eventBus.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
UserId = order.UserId,
Amount = order.TotalAmount
});
return order;
}
public Task<Order> GetOrderAsync(int orderId)
=> _repo.GetByIdAsync(orderId);
}
public record CreateOrderCommand(int UserId, List<OrderItem> Items);
public class Order { public int Id { get; set; } public int UserId { get; set; } public List<OrderItem> Items { get; set; } = new(); public string Status { get; set; } = ""; public DateTime CreatedAt { get; set; } public decimal TotalAmount => Items.Sum(i => i.Price * i.Quantity); }
public record OrderItem(int ProductId, decimal Price, int Quantity);
public record OrderCreatedEvent { public int OrderId { get; init; } public int UserId { get; init; } public decimal Amount { get; init; } }
}
// === 支付模块 ===
namespace Payment
{
public interface IPaymentService
{
Task<PaymentResult> ProcessPaymentAsync(int orderId, decimal amount);
Task RefundAsync(int orderId);
}
public class PaymentService : IPaymentService
{
private readonly IEventBus _eventBus;
public async Task<PaymentResult> ProcessPaymentAsync(int orderId, decimal amount)
{
// 调用支付网关
var result = await CallPaymentGatewayAsync(orderId, amount);
if (result.Success)
{
await _eventBus.PublishAsync(new PaymentCompletedEvent
{
OrderId = orderId,
TransactionId = result.TransactionId
});
}
return result;
}
public async Task RefundAsync(int orderId)
{
// 退款逻辑
await _eventBus.PublishAsync(new PaymentRefundedEvent { OrderId = orderId });
}
private Task<PaymentResult> CallPaymentGatewayAsync(int orderId, decimal amount)
=> Task.FromResult(new PaymentResult { Success = true, TransactionId = Guid.NewGuid().ToString() });
}
public record PaymentResult { public bool Success { get; init; } public string TransactionId { get; init; } = ""; }
public record PaymentCompletedEvent { public int OrderId { get; init; } public string TransactionId { get; init; } = ""; }
public record PaymentRefundedEvent { public int OrderId { get; init; } }
}
// === 通知模块 ===
namespace Notification
{
public class NotificationHandler
{
private readonly IEmailSender _email;
// 订阅订单创建事件
public async Task OnOrderCreatedAsync(Ordering.OrderCreatedEvent evt)
{
await _email.SendAsync(evt.UserId.ToString(),
$"订单确认 - #{evt.OrderId}",
$"您的订单 #{evt.OrderId} 已创建,金额: {evt.Amount:C}");
}
// 订阅支付完成事件
public async Task OnPaymentCompletedAsync(Payment.PaymentCompletedEvent evt)
{
await _email.SendAsync("",
$"支付成功 - #{evt.OrderId}",
$"订单 #{evt.OrderId} 支付完成,交易号: {evt.TransactionId}");
}
}
public interface IEmailSender { Task SendAsync(string to, string subject, string body); }
}
// === 模块注册 ===
public static class ModuleRegistration
{
public static IServiceCollection AddAllModules(this IServiceCollection services)
{
// 每个模块独立注册
services.AddScoped<Ordering.IOrderService, Ordering.OrderService>();
services.AddScoped<Payment.IPaymentService, Payment.PaymentService>();
services.AddSingleton<IEventBus, InMemoryEventBus>();
return services;
}
}
// === 模块间通信:内存事件总线(单体优势:进程内通信,零延迟) ===
public interface IEventBus
{
Task PublishAsync<T>(T evt) where T : class;
void Subscribe<T>(Func<T, Task> handler) where T : class;
}
public class InMemoryEventBus : IEventBus
{
private readonly Dictionary<Type, List<Func<object, Task>>> _handlers = new();
public void Subscribe<T>(Func<T, Task> handler) where T : class
{
if (!_handlers.TryGetValue(typeof(T), out var list))
{
list = new List<Func<object, Task>>();
_handlers[typeof(T)] = list;
}
list.Add(async evt => await handler((T)evt));
}
public async Task PublishAsync<T>(T evt) where T : class
{
if (_handlers.TryGetValue(typeof(T), out var handlers))
{
foreach (var handler in handlers)
await handler(evt);
}
}
}
}
public interface IOrderRepository { Task SaveAsync(object order); Task<object> GetByIdAsync(int id); }追问
- Q: 什么时候应该从模块化单体迁移到微服务? A: 团队超过 15 人、模块需要独立扩展、部署冲突频繁
- Q: 模块间如何共享数据库? A: 每个模块有自己的 Schema,不直接访问其他模块的表
场景四:容量规划
题目描述
你的系统需要在双十一期间支撑 10 倍日常流量。当前日均 QPS 为 5000,
P99 延迟 200ms。如何做容量规划和保障?分析与决策
/// <summary>
/// 容量规划实战
/// </summary>
public class CapacityPlanning
{
/// <summary>
/// 步骤1:基线数据收集
/// </summary>
public class Baseline
{
/// 日常指标:
/// - QPS: 5,000(平均)/ 15,000(峰值)
/// - P50 延迟: 50ms
/// - P99 延迟: 200ms
/// - 错误率: 0.1%
/// - CPU 使用率: 30%
/// - 内存使用率: 60%
/// - 数据库连接数: 50/200
///
/// 双十一目标:
/// - 峰值 QPS: 50,000(10x 峰值)
/// - P99 延迟: < 500ms
/// - 错误率: < 0.5%
}
/// <summary>
/// 步骤2:资源估算
/// </summary>
public class ResourceEstimation
{
/// 应用服务器:
/// 当前 4 台 x 4C8G,CPU 30%
/// 10x 流量 → CPU 300%(超载)
/// 需要: 300% / 80%(目标利用率)≈ 4 台
/// 考虑冗余: 4 x 1.5 = 6 台
/// 总计: 10 台 x 4C8G(含 4 台冗余)
///
/// 数据库:
/// 当前主库 CPU 40%
/// 10x 流量 → 读扩展(读写比 10:1)
/// 方案: 1主 + 5从 + 读写分离
/// 热点查询走缓存,减轻数据库压力
///
/// Redis:
/// 当前 1 主 2 从
/// 10x 流量 → 1 主 4 从(分片集群)
///
/// 带宽:
/// 当前 500Mbps
/// 10x → 5Gbps(需要升级)
}
/// <summary>
/// 步骤3:技术保障方案
/// </summary>
public class TechnicalMeasures
{
/// <summary>
/// 措施1:多级缓存
/// </summary>
public static void ConfigureMultiLevelCache(WebApplicationBuilder builder)
{
// L1: 本地缓存(进程内)
builder.Services.AddMemoryCache();
// L2: 分布式缓存(Redis)
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "redis:6379";
});
// L3: CDN 静态资源
// 使用 CDN 加速图片、JS、CSS
}
/// <summary>
/// 措施2:限流降级
/// </summary>
public static void ConfigureRateLimiting(WebApplicationBuilder builder)
{
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("api", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromSeconds(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 50;
});
// 降级:返回缓存或默认数据
options.RejectionStatusCode = 429;
});
}
/// <summary>
/// 措施3:预热与预加载
/// </summary>
public class WarmupService : IHostedService
{
public async Task StartAsync(CancellationToken ct)
{
// 预热数据库连接池
// 预加载热点数据到缓存
// 预编译正则和序列化器
await Task.CompletedTask;
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
}
}场景五:故障模式分析
题目描述
你的支付服务依赖外部支付网关,该网关偶尔超时或返回错误。
如何设计系统以保证支付服务的可靠性?分析与决策
/// <summary>
/// 故障模式分析与韧性设计
/// </summary>
public class ResilienceDesign
{
/// <summary>
/// 故障模式识别:
/// 1. 支付网关超时(网络延迟)
/// 2. 支付网关返回 5xx(服务端错误)
/// 3. 支付网关返回 4xx(请求参数错误)
/// 4. 支付网关完全不可用
/// 5. 我方服务与网关之间网络中断
/// </summary>
/// <summary>
/// 解决方案:Polly + 重试 + 熔断 + 降级
/// </summary>
public static class ResiliencePipeline
{
public static IServiceCollection AddPaymentResilience(this IServiceCollection services)
{
// 使用 Microsoft.Extensions.Resilience(.NET 8+)
services.AddHttpClient("payment-gateway")
.AddStandardResilienceHandler(options =>
{
// 总超时
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(30);
// 重试策略
options.Retry.MaxRetryAttempts = 3;
options.Retry.Delay = TimeSpan.FromMilliseconds(500);
options.Retry.BackoffType = DelayBackoffType.Exponential;
options.Retry.UseJitter = true;
// 只重试可重试的异常(超时、5xx)
options.Retry.ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(r => (int)r.StatusCode >= 500);
// 熔断策略
options.CircuitBreaker.FailureRatio = 0.5; // 50% 失败率
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
options.CircuitBreaker.MinimumThroughput = 10;
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(30);
// 尝试超时
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
});
return services;
}
}
/// <summary>
/// 补偿事务:支付异常时的处理策略
/// </summary>
public class PaymentCompensation
{
private readonly IPaymentGateway _gateway;
private readonly IPaymentRepository _repo;
private readonly ILogger _logger;
public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
{
// 1. 创建支付记录(状态:待处理)
var payment = new PaymentRecord
{
OrderId = request.OrderId,
Amount = request.Amount,
Status = PaymentStatus.Pending,
CreatedAt = DateTime.UtcNow
};
await _repo.SaveAsync(payment);
try
{
// 2. 调用支付网关(带重试)
var response = await _gateway.ChargeAsync(
request.Token, request.Amount);
// 3. 更新状态
payment.Status = PaymentStatus.Completed;
payment.TransactionId = response.TransactionId;
await _repo.UpdateAsync(payment);
return PaymentResult.Success(payment.TransactionId);
}
catch (TimeoutException)
{
// 超时:不确定支付是否成功,需要人工对账
payment.Status = PaymentStatus.Uncertain;
await _repo.UpdateAsync(payment);
_logger.LogWarning("支付超时,需要人工对账: {OrderId}", request.OrderId);
// 触发异步对账任务
_ = Task.Run(() => ReconcileAsync(payment.Id));
return PaymentResult.Uncertain("支付超时,正在核实");
}
catch (Exception ex)
{
payment.Status = PaymentStatus.Failed;
payment.ErrorMessage = ex.Message;
await _repo.UpdateAsync(payment);
return PaymentResult.Failed(ex.Message);
}
}
/// <summary>
/// 异步对账:确认超时支付的实际状态
/// </summary>
private async Task ReconcileAsync(int paymentId)
{
var payment = await _repo.GetByIdAsync(paymentId);
var status = await _gateway.QueryStatusAsync(payment!.TransactionId ?? "");
if (status == GatewayPaymentStatus.Paid)
{
payment.Status = PaymentStatus.Completed;
await _repo.UpdateAsync(payment);
}
else
{
payment.Status = PaymentStatus.Failed;
await _repo.UpdateAsync(payment);
}
}
}
}
public interface IPaymentGateway { Task<GatewayResponse> ChargeAsync(string token, decimal amount); Task<GatewayPaymentStatus> QueryStatusAsync(string transactionId); }
public class GatewayResponse { public string TransactionId { get; set; } = ""; }
public enum GatewayPaymentStatus { Unknown, Paid, Failed }
public record PaymentRequest(int OrderId, string Token, decimal Amount);
public record PaymentResult { public bool IsSuccess { get; init; } public string Message { get; init; } = ""; public string? TransactionId { get; init; } public static PaymentResult Success(string txId) => new() { IsSuccess = true, TransactionId = txId }; public static PaymentResult Failed(string msg) => new() { Message = msg }; public static PaymentResult Uncertain(string msg) => new() { Message = msg }; }
public class PaymentRecord { public int Id { get; set; } public int OrderId { get; set; } public decimal Amount { get; set; } public PaymentStatus Status { get; set; } public string? TransactionId { get; set; } public string? ErrorMessage { get; set; } public DateTime CreatedAt { get; set; } }
public enum PaymentStatus { Pending, Completed, Failed, Uncertain }
public interface IPaymentRepository { Task SaveAsync(PaymentRecord payment); Task UpdateAsync(PaymentRecord payment); Task<PaymentRecord?> GetByIdAsync(int id); }
public class PredicateBuilder<T> { public PredicateBuilder<T> Handle<TException>() where TException : Exception => this; public PredicateBuilder<T> HandleResult(Func<T, bool> predicate) => this; }
public class DelayBackoffType { public static DelayBackoffType Exponential { get; } = new(); }场景六:数据迁移策略
题目描述
你需要将用户表从 MySQL 迁移到 PostgreSQL,涉及 5000 万条数据,
迁移期间服务不能停机。你会如何设计迁移方案?分析与决策
迁移策略:双写 + 渐进式切换
阶段1:双写开启
- 所有新写入同时写 MySQL 和 PostgreSQL
- 读取仍从 MySQL
- 使用变更数据捕获(CDC)同步历史数据
阶段2:数据验证
- 对比两库数据一致性
- 修复差异记录
- 验证性能和延迟
阶段3:读取切换
- 逐步将读取流量切到 PostgreSQL
- 先切 10% → 50% → 100%
- 监控延迟和错误率
阶段4:停止双写
- 确认所有读都走 PostgreSQL
- 停止写 MySQL
- 保留 MySQL 一段时间作为回退
风险控制:
- 灰度切换,随时可回退
- 自动化数据对比
- 性能监控和告警/// <summary>
/// 双写迁移模式实现
/// </summary>
public class DualWriteMigration
{
/// <summary>
/// 双写 Repository 装饰器
/// </summary>
public class DualWriteUserRepository : IUserRepository
{
private readonly IUserRepository _mysql; // 主库(旧)
private readonly IUserRepository _postgres; // 新库
private readonly ILogger _logger;
private readonly MigrationConfig _config;
public async Task<User> SaveAsync(User user)
{
// 先写主库
var saved = await _mysql.SaveAsync(user);
if (_config.DualWriteEnabled)
{
try
{
// 异步写新库(不阻塞主流程)
_ = Task.Run(async () =>
{
try
{
await _postgres.SaveAsync(user);
}
catch (Exception ex)
{
_logger.LogError(ex, "双写新库失败: {UserId}", user.Id);
// 记录失败,后续补偿
await RecordFailureAsync(user.Id);
}
});
}
catch (Exception ex)
{
// 双写失败不影响主流程
_logger.LogWarning(ex, "双写任务启动失败");
}
}
return saved;
}
public async Task<User?> GetByIdAsync(int id)
{
// 根据灰度比例决定读哪个库
if (_config.ReadFromNewDbPercentage > 0 &&
Random.Shared.Next(100) < _config.ReadFromNewDbPercentage)
{
try
{
var user = await _postgres.GetByIdAsync(id);
if (user != null) return user;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "新库读取失败,降级到旧库");
}
}
return await _mysql.GetByIdAsync(id);
}
private Task RecordFailureAsync(int userId) => Task.CompletedTask;
}
/// <summary>
/// 数据一致性校验
/// </summary>
public class DataConsistencyChecker
{
private readonly IUserRepository _mysql;
private readonly IUserRepository _postgres;
public async Task<ConsistencyReport> CheckAsync(int batchSize)
{
int totalChecked = 0;
int mismatchCount = 0;
int missingInNew = 0;
for (int offset = 0; ; offset += batchSize)
{
var mysqlUsers = await _mysql.GetBatchAsync(offset, batchSize);
if (mysqlUsers.Count == 0) break;
foreach (var mysqlUser in mysqlUsers)
{
var pgUser = await _postgres.GetByIdAsync(mysqlUser.Id);
if (pgUser == null)
{
missingInNew++;
}
else if (!DataEquals(mysqlUser, pgUser))
{
mismatchCount++;
}
totalChecked++;
}
}
return new ConsistencyReport
{
TotalChecked = totalChecked,
MismatchCount = mismatchCount,
MissingInNewCount = missingInNew,
IsConsistent = mismatchCount == 0 && missingInNew == 0
};
}
private bool DataEquals(User a, User b) =>
a.Id == b.Id && a.Name == b.Name && a.Email == b.Email;
}
}
public class MigrationConfig
{
public bool DualWriteEnabled { get; set; }
public int ReadFromNewDbPercentage { get; set; } // 0-100
}
public record User(int Id, string Name, string Email);
public interface IUserRepository { Task<User> SaveAsync(User user); Task<User?> GetByIdAsync(int id); Task<List<User>> GetBatchAsync(int offset, int batchSize); }
public record ConsistencyReport { public int TotalChecked { get; init; } public int MismatchCount { get; init; } public int MissingInNewCount { get; init; } public bool IsConsistent { get; init; } }场景七:日志与可观测性选型
题目描述
你的系统有 20 个微服务,需要统一日志、指标和追踪。预算有限,
你会选择自建还是使用云服务?具体选什么方案?分析与决策
对比分析:
┌──────────────┬─────────────────────┬─────────────────────┐
│ 方案 │ 自建 ELK │ 云服务 (Application │
│ │ │ Insights/Datadog) │
├──────────────┼─────────────────────┼─────────────────────┤
│ 初始成本 │ 低(开源) │ 按量付费 │
│ 运维成本 │ 高(需要专人维护) │ 低(托管服务) │
│ 功能完整度 │ 需要组装 │ 开箱即用 │
│ 扩展性 │ 自由扩展 │ 受限于云服务 │
│ 数据安全 │ 完全自控 │ 数据在云端 │
│ 学习曲线 │ 陡峭 │ 平缓 │
└──────────────┴─────────────────────┴─────────────────────┘
推荐方案:OpenTelemetry + 混合部署
- 追踪:OpenTelemetry + Jaeger(自建,成本低)
- 指标:Prometheus + Grafana(自建,成熟方案)
- 日志:Seq(开发环境)+ ELK(生产环境)
- 原因:标准化协议、避免厂商锁定、成本可控场景八:认证方案选型
题目描述
你的 SaaS 产品需要支持多种登录方式(邮箱、手机、Google、GitHub),
并且未来可能支持 SSO。你会选择 JWT 还是 Session?自建还是用 IdentityServer?分析与决策
/// <summary>
/// 认证方案对比与实现
/// </summary>
public class AuthDecision
{
/// JWT vs Session:
/// ┌──────────────┬─────────────────┬─────────────────┐
/// │ 维度 │ JWT │ Session + Cookie │
/// ├──────────────┼─────────────────┼─────────────────┤
/// │ 无状态 │ 是 │ 否(需服务端存储) │
/// │ 水平扩展 │ 简单 │ 需要 Session 共享 │
/// │ 撤销能力 │ 弱(需黑名单) │ 强(直接删 Session)│
/// │ 适用场景 │ API / 微服务 │ 传统 Web 应用 │
/// │ 安全性 │ 需注意 XSS │ 需注意 CSRF │
/// └──────────────┴─────────────────┴─────────────────┘
/// 推荐:JWT(Access Token + Refresh Token)+ IdentityServer/Duende
/// 理由:
/// 1. SaaS 产品以 API 为主,JWT 更合适
/// 2. 多种登录方式由外部 IdP 处理
/// 3. IdentityServer 支持标准协议(OIDC/OAuth2)
/// 4. Refresh Token 解决撤销问题
/// <summary>
/// ASP.NET Core JWT 配置
/// </summary>
public static WebApplicationBuilder ConfigureAuth(this WebApplicationBuilder builder)
{
// JWT Bearer 认证
builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = "https://auth.mysaas.com";
options.Audience = "my-saas-api";
options.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30)
};
});
// 外部登录提供者
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = builder.Configuration["Auth:Google:ClientId"]!;
options.ClientSecret = builder.Configuration["Auth:Google:ClientSecret"]!;
})
.AddGitHub(options =>
{
options.ClientId = builder.Configuration["Auth:GitHub:ClientId"]!;
options.ClientSecret = builder.Configuration["Auth:GitHub:ClientSecret"]!;
});
// 授权策略
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy => policy.RequireRole("admin"))
.AddPolicy("TenantAccess", policy =>
policy.Requirements.Add(new TenantAccessRequirement()));
return builder;
}
}
public class TenantAccessRequirement : Microsoft.AspNetCore.Authorization.IAuthorizationRequirement { }
public class TokenValidationParameters { public bool ValidateIssuer { get; set; } public bool ValidateAudience { get; set; } public bool ValidateLifetime { get; set; } public TimeSpan ClockSkew { get; set; } }关键知识点
- 技术选型应从业务需求出发,而非技术偏好
- ADR 是记录技术决策的标准格式
- 权衡分析应包含正面和负面影响
- 容量规划需要基于数据而非猜测
- 渐进式迁移比大爆炸式更安全
- 模块化单体是微服务的安全替代方案
常见误区
| 误区 | 正确理解 |
|---|---|
| 新技术一定更好 | 技术选型应匹配团队能力和业务需求 |
| 微服务是终极架构 | 大多数场景模块化单体更合适 |
| 性能是唯一考虑 | 可维护性、团队能力、成本同样重要 |
| 一次性做出完美决策 | 决策应该可逆、渐进式推进 |
| 跟随大厂的技术选型 | 大厂的场景和资源与小团队完全不同 |
进阶路线
- 入门阶段:了解常见技术选型的对比维度
- 进阶阶段:能使用 ADR 格式记录技术决策
- 高级阶段:能从业务、技术、团队、成本多维度分析
- 专家阶段:能在不确定性下做出合理决策并管理风险
适用场景
- 架构师面试
- 技术负责人面试
- 技术方案评审
- 架构决策讨论
落地建议
- 在团队中推广 ADR 格式记录重要技术决策
- 每个重大决策都应包含"为什么不选其他方案"的理由
- 定期回顾过去的决策,评估效果
- 建立技术雷达,跟踪团队对不同技术的成熟度评估
排错清单
复盘问题
- 你做过的最困难的技术选型是什么?为什么困难?
- 如何在有限信息下做出技术决策?
- 决策失误后如何调整方向?
