一、百万TPS的技术挑战
百万TPS(Transaction Per Second)不是简单的"堆机器",而是从网络层、计算层、存储层、架构设计每个环节的精细化工程。以下是主要挑战:
- 网络IO:100万并发长连接,单机网卡带宽成为瓶颈(10Gbps网卡约1250MB/s)
- 内存带宽:每请求KB级数据,100万TPS需要内存带宽达数GB/s
- 锁竞争:共享资源(数据库、缓存)成为全局瓶颈
- 长尾延迟:P99/P999延迟决定用户体验,GC停顿是最大敌人
- 热点数据:1%的Key承担99%的流量
1.1 延迟分解:100ms响应的16ms法则
// 100ms响应时间的组成(典型)
// 网络延迟:30ms(跨地域/公网)
// DNS解析:5ms
// TCP连接(3次握手):10ms
// SSL握手:15ms
// 应用处理:30ms(业务逻辑 + DB/缓存)
// 合计:约90ms(不含排队延迟)
// 16ms帧法则(端到端P99 < 50ms):
// 如果目标P99=50ms,允许的最大等待队列长度为:
// 队列长度 = 目标QPS × P99延迟
// = 1,000,000 × 0.05s = 50,000个请求
// 即:系统需要能在50ms内处理完50,000个请求的吞吐量
// TPS与QPS的关系:
// TPS(Transaction Per Second):含完整业务逻辑的事务
// QPS(Query Per Second):单个查询
// 1个TPS可能包含多个QPS(如下单TPS = 商品查询QPS + 库存查询QPS + 库存扣减QPS + 订单创建QPS)
// 百万TPS系统通常指QPS,1个TPS ≈ 3-5个QPS
二、全链路压测与容量规划
2.1 全链路压测方案
// 全链路压测三要素:压测数据隔离 + 流量染色 + 监控告警
// 1. 流量染色(TraceId透传)
// 在HTTP Header中注入压测标记
public class TraceFilter {
public static final String TRACE_HEADER = "X-TRACE-ID";
public static final String TRACE_TYPE = "X-TRACE-TYPE"; // "normal" | "stress"
@Override
public Mono filter(ServerWebExchange exchange, WebFilterChain chain) {
String traceId = Optional.ofNullable(
exchange.getRequest().getHeaders().getFirst(TRACE_HEADER)
).orElse(UUID.randomUUID().toString());
String traceType = Optional.ofNullable(
exchange.getRequest().getHeaders().getFirst(TRACE_TYPE)
).orElse("normal");
exchange.getAttributes().put("traceId", traceId);
exchange.getAttributes().put("traceType", traceType); // 压测标记
// 透传给下游
exchange.getResponse().getHeaders().add(TRACE_HEADER, traceId);
exchange.getResponse().getHeaders().add(TRACE_TYPE, traceType);
return chain.filter(exchange);
}
}
// 2. 影子库/影子缓存:压测数据与生产数据隔离
// Redis压测隔离:压测流量使用不同的Redis前缀
if ("stress".equals(traceType)) {
redisKey = "shadow:" + realKey;
} else {
redisKey = realKey;
}
// MySQL影子库:压测库前缀 shadow_
String tableName = "stress".equals(traceType)
? "shadow_orders"
: "orders";
// 3. JMeter + 分布式压测架构
// JMeter Server Cluster: 50台压测机 × 500并发 = 25000并发
// 目标:25000并发 × 40 QPS/并发 = 100万TPS
// JMeter分布式压测配置
# jmeter.properties (压测机)
remote_hosts=10.0.0.1,10.0.0.2,...,10.0.0.50
client.rmi.localport=1099
server.rmi.create_ca_cert=true
// 4. 压测指标监控
// Prometheus + Grafana
// - 请求QPS: rate(http_requests_total[1m])
// - P99延迟: histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m]))
// - 错误率: rate(http_requests_total{status=~"5.."}[1m])
// - JVM GC: rate(jvm_gc_pause_seconds_sum[1m])
2.2 容量规划模型
// 容量规划公式
// 目标QPS = 预估TPS × 倍数(峰值通常是日常的3-5倍)
// 机器数 = ceil(目标QPS / 单机QPS × 安全系数)
// 估算单机QPS(实测数据)
// Nginx反向代理:50,000 QPS(纯代理,无业务逻辑)
// Java API服务(无IO):80,000-100,000 QPS
// Java API服务(Redis缓存命中):20,000-30,000 QPS
// Java API服务(MySQL查询):5,000-10,000 QPS
// Go API服务(同上):2-3倍Java
// 示例:100万TPS的订单系统容量规划
// 目标:100万 QPS (含缓存,缓存命中率90%)
// 单机(8核16G):15,000 QPS(含Redis缓存命中 + MySQL写)
// 安全系数:1.5(应对突发流量)
// 机器数 = 1,000,000 / 15,000 × 1.5 ≈ 100台API服务
// Nginx层:
// 每台Nginx:80,000 QPS(CPU绑定)
// 100万 / 8万 = 13台Nginx(+2台冗余)= 15台
// Redis集群:
// 单机QPS:50,000(内存操作)
// 100万 / 5万 = 20台(+主从=40台)
// 推荐:3主3从,每个主节点16GB内存,每台可承载60万QPS
// MySQL:
// 每主库写QPS:10,000(4核16G,NVMe SSD)
// 读写分离后写占比20%,则100万×20%=20万写QPS
// 20万 / 1万 = 20台...不可行,需要分库分表
// 分库后:每库5万写QPS,需要4个分库,每个分库1主2从
三、流量治理:限流 → 熔断 → 降级
3.1 多级限流策略
// 限流维度:单机限流 → 网关限流 → 服务限流
// 算法:滑动窗口 → 令牌桶 → 漏桶
// 1. 滑动窗口算法(Redis + Lua)
-- 原理:将时间窗口划分为N个桶,每个桶记录请求数
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2]) -- 窗口大小(秒)
local limit = tonumber(ARGV[3]) -- 限流阈值
local bucket_size = 10 -- 10个桶,滑动精度
local bucket_time = window / bucket_size
-- 删除过期桶
redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)
-- 统计当前窗口内的请求数
local current = redis.call('ZCARD', key)
if current >= limit then
return 0 -- 0=拒绝
end
-- 记录本次请求
redis.call('ZADD', key, now * 1000, now * 1000 + math.random())
redis.call('PEXPIRE', key, window * 1000)
return 1 -- 1=通过
// 2. Sentinel限流(阿里的流量治理库)
@Configuration
public class SentinelConfig {
@PostConstruct
public void initRules() {
// QPS限流规则
FlowRuleManager.loadRules(List.of(
new FlowRule("orderService")
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(100000) // 10万QPS
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)
.setStrategy(RuleConstant.STRATEGY_DIRECT)
.setLimitApp("default")
));
}
}
// 3. 热点参数限流(Sentinel内置)
// 针对itemId=1001的热点商品进行单独限流
ParamFlowRuleManager.loadRules(List.of(
new ParamFlowRule("orderService")
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setCount(50000)
.setParamIdx(1) // 第二个参数(itemId)的位置
));
3.2 熔断与降级
// Resilience4j 熔断器配置
@Configuration
public class Resilience4jConfig {
public CircuitBreakerConfig circuitBreakerConfig() {
return CircuitBreakerConfig.custom()
// 熔断器统计窗口(10秒内)
.slidingWindowType(SlidingWindowType.COUNT_BASED)
.slidingWindowSize(100)
// 失败率 > 50% 则熔断
.failureRateThreshold(50)
// 熔断持续时间30秒
.waitDurationInOpenState(Duration.ofSeconds(30))
// 熔断半开时允许10个请求
.permittedNumberOfCallsInHalfOpenState(10)
// 慢调用阈值:调用时长 > 2秒 且 > 50% 则熔断
.slowCallRateThreshold(50)
.slowCallDurationThreshold(Duration.ofSeconds(2))
.build();
}
}
// 服务降级:核心链路保护
@Service
public class OrderService {
@HystrixCommand(
fallbackMethod = "placeOrderFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "500"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000")
}
)
public Order placeOrder(OrderRequest req) {
return orderRepository.save(req);
}
// 降级方法:库存不检查直接下单(允许超卖,降级保护)
public Order placeOrderFallback(OrderRequest req, Throwable t) {
log.warn("订单服务降级,错误: {}", t.getMessage());
Order order = new Order();
order.setId(generateId());
order.setStatus("PENDING_CHECK"); // 待核实状态
order.setUserId(req.getUserId());
return orderRepository.save(order);
}
}
// Sentinel + Spring Cloud 热点降级
@SentinelResource(value = "getProduct",
blockHandler = "getProductBlock",
fallback = "getProductFallback")
public Product getProduct(Long itemId) {
return productService.findById(itemId);
}
// Fallback:热点商品降级返回
public Product getProductFallback(Long itemId, Throwable t) {
// 返回缓存的静态数据或热门商品数据
return hotProductCache.get(itemId).orElse(Product.DEFAULT);
}
四、高性能缓存架构
4.1 多级缓存架构
// 典型多级缓存:本地缓存 → 分布式缓存 → DB
// L1: Caffeine本地缓存(热点数据,TTL短)
// L2: Redis集群(分布式,数据一致性强)
// L3: MySQL(最终一致性,数据源)
// Spring Cache + Caffeine + Redis 多级缓存
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager(
@Qualifier("caffeineCacheManager") CacheManager cm1,
@Qualifier("redisCacheManager") CacheManager cm2) {
// CompositeCacheManager 按顺序查找
CompositeCacheManager manager = new CompositeCacheManager(cm1, cm2);
return manager;
}
@Bean
public Cache caffeineCache() {
return Caffeine.newBuilder()
.maximumSize(10000) // L1最多1万个商品
.expireAfterWrite(30, TimeUnit.SECONDS) // 30秒后过期
.recordStats() // 开启统计
.build();
}
}
// Cache Aside + Write-Through 双写一致性
@Service
public class ProductCacheService {
@Autowired Cache l1Cache;
@Autowired StringRedisTemplate redisTemplate;
@Autowired ProductRepository productRepo;
public Product getProduct(Long id) {
// L1: 本地缓存
Product p = l1Cache.getIfPresent(id);
if (p != null) return p;
// L2: Redis
String redisKey = "product:" + id;
p = redisTemplate.opsForValue().get(redisKey);
if (p != null) {
l1Cache.put(id, p); // 回填L1
return p;
}
// L3: DB
p = productRepo.findById(id).orElse(null);
if (p != null) {
redisTemplate.opsForValue().set(redisKey, p, 10, TimeUnit.MINUTES);
l1Cache.put(id, p);
}
return p;
}
@Transactional
public void updateProduct(Long id, Product p) {
// Write-Through: 同时写L2和DB
productRepo.save(p);
redisTemplate.opsForValue().set("product:" + id, p, 10, TimeUnit.MINUTES);
l1Cache.invalidate(id); // 删除L1
}
}
4.2 Redis Cluster分片与热点数据
// 热点数据:本地缓存 + 主动推送
// 推送方案:Canal监听MySQL binlog,变更时主动推送到所有应用实例
// Canal + Redis: MySQL变更 → Canal → 消息队列 → 应用 → 本地缓存
// Canal配置:
canal.instance.gtin.ignoreTransaction = false
canal.instance.default.connection.url = jdbc:mysql://mysql-master:3306
canal.destinations = products_db
// 热点Key打散:HashTag + 一致性Hash
// 大促期间,将热门商品的库存拆分到多个Key
SET stock:hot:{itemId}:pool1 5000
SET stock:hot:{itemId}:pool2 5000
SET stock:hot:{itemId}:pool3 5000
SET stock:hot:{itemId}:pool4 5000
// Lua脚本:随机选取一个库存池扣减
local pools = {'pool1','pool2','pool3','pool4'}
for i, pool in ipairs(pools) do
local key = 'stock:hot:'..ARGV[1]..':'..pool
local stock = tonumber(redis.call('GET', key) or 0)
if stock >= tonumber(ARGV[2]) then
redis.call('DECRBY', key, ARGV[2])
return pool
end
end
return nil -- 所有库存池不足
五、GC调优:无停顿架构
// Java百万TPS系统的GC策略
// ZGC(Java 11+,推荐):P99停顿 < 1ms,支持TB级堆
# JVM参数
-XX:MaxGCPauseMillis=1
-XX:+UseZGC
-XX:+ZGenerational # Java 21+ 开启ZGC分代
-Xms32G -Xmx32G
-XX:ParallelGCThreads=16
-XX:ConcGCThreads=4
// 或 G1 + 精细调参(Java 8-17)
-XX:MaxGCPauseMillis=10
-XX:+UseG1GC
-XX:G1NewSizePercent=30 # 新生代30%
-XX:G1MaxNewSizePercent=40
-XX:G1HeapRegionSize=8M # 8MB region
-XX:InitiatingHeapOccupancyPercent=45 # 45%触发并发标记
-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M # 固定元空间
// GC监控
# JFR(Java Flight Recorder)生产环境采样
-XX:+UnlockCommercialFeatures
-XX:+FlightRecorder
-XX:StartFlightRecording=dumponexit=true,filename=gc.jfr
// OTel(OpenTelemetry)埋点
@Bean
public MeterRegistry meterRegistry() {
return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
}
@Bean
public GcMetrics gcMetrics(MeterRegistry registry) {
return new GcMetrics(registry);
}
// 避免Full GC的根本策略:
// 1. 对象不可变(String, Long, LocalDateTime)
// 2. 禁止new Object()在热路径上
// 3. 使用对象池(Recycler, DirectByteBufferPool)
// 4. 直播/大促:提前预热JVM(-XX:PreTouch)
5.1 百万TPS系统架构全景
// 百万TPS系统推荐架构
// -----------------------------------------------
// 接入层:15台 Nginx (8万QPS/台)
// └─→ IP黑名单 / WAF / SSL卸载
// └─→ 限流(Nginx-Lua令牌桶)
//
// 网关层:50台 Kong/APISIX (2万QPS/台)
// └─→ 路由 / 鉴权 / 限流(Sentinel)
// └─→ 参数校验 / 降级
//
// 业务层:100台 Java微服务 (1.5万QPS/台)
// └─→ 多级缓存(Caffeine L1 + Redis L2)
// └─→ 熔断(Resilience4j/Sentinel)
// └─→ 异步化(RabbitMQ/RocketMQ)
//
// 数据层:
// └─→ Redis Cluster: 6主6从 (每主60万QPS)
// └─→ MySQL分库分表: 4分库 (每库5万写QPS)
// └─→ Elasticsearch: 3主3从 (搜索集群)
// └─→ Kafka: 24Broker (消息队列)
//
// 治理层:
// └─→ Sentinel: 限流/熔断/热点探测
// └─→ SkyWalking: 全链路追踪
// └─→ Prometheus+Grafana: 监控告警
// └─→ Docker+K8S: 弹性伸缩