APIçœå ³ïŒææè¯·æ±ç"第äžééš"
äœ çæžžæå¹³å°æ¯å€©å€çå åäžçº§è¯·æ±ïŒå®ä»¬æ¯æä¹æŸå°ç®çå°çïŒ
æ³è±¡äžäžïŒäœ äœåšäžäžªè¶ 倧åå°åºïŒæ¯å€©ææåäžäžçå¿«éãå€åã访客èŠè¿æ¥ã
åŠææ²¡æå€§éšä¿å®ïŒæ¯äžªäººéœèŠèªå·±æŸæ¥Œæ ãæ²éšã确讀身仜...
APIçœå ³ïŒå°±æ¯äœ ç³»ç»ç"å°åºå€§éšä¿å®"ââææè¯·æ±å¿ é¡»å ç»è¿å®ïŒæèœè¿å ¥å éšæå¡ã
äžã䞺ä»ä¹éèŠçœå ³ïŒ
没æ"ä¿å®"çæ¥å
åè®Ÿäœ çæžžæå¹³å°æè¿äºæå¡ïŒ
- ç©å®¶æå¡ïŒç»åœã泚åãèµæïŒ
- å åŒæå¡ïŒæ¯ä»ã订åïŒ
- æžžææå¡ïŒå¹é ãææïŒ
- 瀟亀æå¡ïŒå¥œåãè倩ïŒ
å¬èµ·æ¥è¿å¥œïŒäœåœæå¡æ°éä»10䞪åæ100䞪ïŒé®é¢å°±æ¥äºïŒ
ðš 客æ·ç«¯åŽ©æºïŒèŠç»Žæ€100䞪æå¡å°åïŒé 眮管çå°ç±
ðš 讀è¯éå€ïŒæ¯äžªæå¡éœèŠå®ç°ç»åœæ ¡éªïŒä»£ç éå€äžéŸä»¥ç»äž
ðš å®å šé£é©ïŒæææå¡éœæŽé²åšå ¬çœïŒæ»å»é¢å·šå€§
ðš è·šåé®é¢ïŒäžåååãäžååè®®ïŒå端ç¯çæ¥é
ðš åè®®äžäžèŽïŒæäºæå¡çš RESTïŒæäºçš gRPCïŒæäºçš WebSocketïŒå®¢æ·ç«¯èŠéé å€ç§åè®®
ðš çæ¬ç®¡çæ··ä¹±ïŒæå¡å级åïŒå®¢æ·ç«¯äžç¥é该è°åªäžªçæ¬
æ"ä¿å®"ä¹å
客æ·ç«¯åªéèŠç¥éçœå ³å°åïŒå©äžç亀ç»çœå ³å»è·¯ç±ã
客æ·ç«¯ â çœå
³ â å
éšæå¡
å°±åå¿«éååªéèŠæå 裹亀ç»å°åºä¿å®ïŒä¿å®èŽèŽ£éå°å ·äœç楌æ ã
â ç»äžå ¥å£ - äžäžªå°åïŒè·¯ç±å°æææå¡
â ç»äžè®€è¯ - ç»åœæ ¡éªåªåäžæ¬¡
â å®å šé犻 - å éšæå¡äžæŽé²
â è·šåå€ç - çœå ³ç»äžå€ç CORS
â åè®®èœ¬æ¢ - å±èœåç«¯ææ¯å·®åŒ
â æµéç®¡æ§ - éæµãçæãé级äžç«åŒæå®
ç宿¡äŸïŒææžžæå¹³å°ççœå ³æŒè¿
客æ·ç«¯çŽè¿åæå¡ IP
é®é¢ïŒé
çœ®æ··ä¹±ïŒæ¯æ¬¡æå¡è¿ç§»éœèŠæ¹å®¢æ·ç«¯
Nginx é
眮 location è§å
é®é¢ïŒéŽæé»èŸæ£èœåæå¡ïŒéæµèœå匱
èªç gateway æå¡
ææïŒç»äžè®€è¯ãçµæŽ»éæµãç°åºŠååž
è¿äžªæŒè¿è¿çšïŒæ¯å€§å€æ°å ¬åžççå®åç §ã
[é åŸå»ºè®®ïŒæçœå ³ vs æ çœå ³çæ¶æå¯¹æ¯åŸïŒå·ŠèŸ¹æ¯æ··ä¹±ç客æ·ç«¯çŽè¿å€æå¡ïŒå³èŸ¹æ¯æŽæŽç客æ·ç«¯âçœå ³âæå¡]
äºãçœå ³çå å€§æ žå¿è莣
ä¿å®çå·¥äœå¯äžåªæ¯åŒéšå ³éšïŒçœå ³ä¹äžæ ·ã
ð¯ 1. è·¯ç±ïŒRoutingïŒ
æ ¹æ® URL è·¯åŸãHTTP æ¹æ³ã请æ±å€Žçä¿¡æ¯ïŒå³å®è¯·æ±å»åªã
/api/player/* â ç©å®¶æå¡
/api/pay/* â å
åŒæå¡
/api/game/* â æžžææå¡
è·¯ç±çå®ç°åç
routes:
- id: player-service
uri: lb://player-service
predicates:
- Path=/api/player/**
- Method=GET,POST
filters:
- StripPrefix=1
- id: pay-service
uri: lb://pay-service
predicates:
- Path=/api/pay/**
- Header=X-Request-Id, \d+
- è·¯åŸå¹é ïŒåçŒå¹é ã粟确å¹é ãæ£åå¹é
- æ¹æ³å¹é ïŒGETãPOSTãPUTãDELETE ç
- Header å¹é ïŒæ ¹æ®è¯·æ±å€Žå€æ
- åæ°å¹é ïŒQuery åæ°æ¡ä»¶
æºèœè·¯ç±çç¥
routes:
- id: player-v1
uri: lb://player-service-v1
predicates:
- Path=/v1/api/player/**
- id: player-v2
uri: lb://player-service-v2
predicates:
- Path=/v2/api/player/**
routes:
- id: player-canary
uri: lb://player-service-v2
predicates:
- Path=/api/player/**
- Header=X-User-Group, canary
routes:
- id: player-stable
uri: lb://player-service-v1
predicates:
- Path=/api/player/**
- Weight=group1, 90
- id: player-canary
uri: lb://player-service-v2
predicates:
- Path=/api/player/**
- Weight=group1, 10
è·¯ç±æ§èœäŒå
- çº¿æ§æ«æïŒO(n)ïŒæ¯æ¬¡è¯·æ±éåææè§å
- åçŒæ ïŒTrieïŒïŒO(m)ïŒm æ¯è·¯åŸé¿åºŠ
- ååžçŽ¢åŒïŒO(1)ïŒäœåªæ¯æç²Ÿç¡®å¹é
- è·¯ç±è§åæ°é < 1000ïŒçº¿æ§æ«æå€çš
- è·¯ç±è§åæ°é > 1000ïŒäœ¿çšåçŒæ äŒå
- é«é¢æ¥å£ïŒåç¬æåïŒäŒå å¹é
ð 2. éŽæïŒAuthenticationïŒ
éŽæç屿¬¡
第äžå±ïŒäœ æ¯è°ïŒïŒèº«ä»œè®€è¯ïŒ
第äºå±ïŒäœ èœåä»ä¹ïŒïŒæéæ ¡éªïŒ
第äžå±ïŒäœ èœè®¿é®å€ä¹
ïŒïŒäŒè¯ç®¡çïŒ
JWT éŽæå®æŽæµçš
1. 客æ·ç«¯ç»åœ â è®€è¯æå¡
2. è®€è¯æå¡éªè¯èŽŠå·å¯ç
3. è®€è¯æå¡çæ JWT Token
4. 客æ·ç«¯ä¿å TokenïŒLocalStorage / CookieïŒ
5. åç»è¯·æ±æºåžŠ TokenïŒAuthorization: Bearer xxxïŒ
6. çœå
³éªè¯ Token æææ§
7. çœå
³æåçšæ·ä¿¡æ¯æ³šå
¥è¯·æ±å€Ž
8. 请æ±èœ¬åå°äžæžžæå¡
çœå ³éŽæè¿æ»€åšå®ç°
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 1. æ£æ¥ Token æ¯åŠååš
if (StringUtils.isEmpty(token)) {
return onError(exchange, "æªç»åœ", 401);
}
// 2. éªè¯ Token æææ§
try {
Claims claims = JwtUtil.parseToken(token.replace("Bearer ", ""));
// 3. æ£æ¥æ¯åŠè¿æ
if (claims.getExpiration().before(new Date())) {
return onError(exchange, "Token å·²è¿æ", 401);
}
// 4. æåçšæ·ä¿¡æ¯æ³šå
¥è¯·æ±å€Ž
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-User-Id", claims.get("userId", String.class))
.header("X-User-Name", claims.get("userName", String.class))
.build();
return chain.filter(exchange.mutate().request(request).build());
} catch (Exception e) {
return onError(exchange, "Token æ æ", 401);
}
}
@Override
public int getOrder() {
return -100; // äŒå
级æé«
}
}
æéæ ¡éªïŒRBAC æš¡åïŒ
@Component
public class PermissionFilter implements GlobalFilter, Ordered {
@Autowired
private PermissionService permissionService;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
// æ¥è¯¢çšæ·æ¯åŠæè¯¥æ¥å£çè®¿é®æé
if (!permissionService.hasPermission(userId, path, method)) {
return onError(exchange, "æ è®¿é®æé", 403);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -90; // éŽæä¹åæ§è¡
}
}
çœååæºå¶
æäºæ¥å£äžéèŠéŽæïŒåŠç»åœã泚åãå¥åº·æ£æ¥ïŒïŒ
public class WhiteListConfig {
private static final Set<String> WHITE_LIST = Set.of(
"/api/auth/login",
"/api/auth/register",
"/api/health",
"/api/public/**"
);
public static boolean isWhiteList(String path) {
return WHITE_LIST.stream().anyMatch(path::startsWith);
}
}
ðŠ 3. éæµïŒRate LimitingïŒ
é«å¹¶ååºæ¯äžïŒåŠæäžéæµïŒäžäžªæ¶æçšæ·å°±èœæçäœ çæå¡ã
䞺ä»ä¹éèŠéæµïŒ
åºæ¯1ïŒé»äºä¿éïŒæ£åžžçšæ·æ¶å
¥ â ç³»ç»è¿èœœåŽ©æº
åºæ¯2ïŒæ¶æçšæ·å·æ¥å£ â æ¶èæå¡åšèµæº
åºæ¯3ïŒç¬è«å€§éæåæ°æ® â åœ±åæ£åžžçšæ·
åºæ¯4ïŒæäžªæå¡ååºæ
¢ â 请æ±å 积ïŒéªåŽ©
åç§ç»å žéæµç®æ³
public class FixedWindowRateLimiter {
private int limit = 100; // éå¶æ¬¡æ°
private long windowSize = 60000; // çªå£å€§å°ïŒæ¯«ç§ïŒ
private AtomicInteger count = new AtomicInteger(0);
private long windowStart = System.currentTimeMillis();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// çªå£è¿æïŒé眮计æ°
if (now - windowStart >= windowSize) {
windowStart = now;
count.set(0);
}
// 倿æ¯åŠè¶
è¿éå¶
if (count.get() >= limit) {
return false;
}
count.incrementAndGet();
return true;
}
}
çªå£1ïŒ0-1åéïŒïŒç¬¬59ç§æ¥äº100äžªè¯·æ± â
çªå£2ïŒ1-2åéïŒïŒç¬¬1ç§åæ¥äº100äžªè¯·æ± â
å®é
äžåš2ç§å
å€çäº200䞪请æ±ïŒè¶
åºéå¶ïŒ
public class SlidingWindowRateLimiter {
private int limit = 100;
private long windowSize = 60000;
private LinkedList<Long> timestamps = new LinkedList<>();
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// ç§»é€çªå£å€çæ¶éŽæ³
while (!timestamps.isEmpty() && timestamps.getFirst() < now - windowSize) {
timestamps.removeFirst();
}
// 倿åœåçªå£å
çè¯·æ±æ°
if (timestamps.size() >= limit) {
return false;
}
timestamps.addLast(now);
return true;
}
}
public class TokenBucketRateLimiter {
private int capacity = 100; // 桶容é
private int rate = 10; // 什ççæéçïŒäžª/ç§ïŒ
private AtomicInteger tokens = new AtomicInteger(capacity);
private long lastRefillTime = System.currentTimeMillis();
public synchronized boolean tryAcquire() {
// è¡¥å
什ç
refillTokens();
// å°è¯è·å什ç
if (tokens.get() > 0) {
tokens.decrementAndGet();
return true;
}
return false;
}
private void refillTokens() {
long now = System.currentTimeMillis();
long elapsed = now - lastRefillTime;
int tokensToAdd = (int) (elapsed * rate / 1000);
if (tokensToAdd > 0) {
tokens.set(Math.min(capacity, tokens.get() + tokensToAdd));
lastRefillTime = now;
}
}
}
public class LeakyBucketRateLimiter {
private int capacity = 100; // 桶容é
private int rate = 10; // æŒæ°ŽéçïŒäžª/ç§ïŒ
private AtomicInteger water = new AtomicInteger(0);
private long lastLeakTime = System.currentTimeMillis();
public synchronized boolean tryAcquire() {
// æŒæ°Ž
leak();
// å€ææ¡¶æ¯åŠå·²æ»¡
if (water.get() >= capacity) {
return false;
}
water.incrementAndGet();
return true;
}
private void leak() {
long now = System.currentTimeMillis();
long elapsed = now - lastLeakTime;
int waterToLeak = (int) (elapsed * rate / 1000);
if (waterToLeak > 0) {
water.set(Math.max(0, water.get() - waterToLeak));
lastLeakTime = now;
}
}
}
éæµç»ŽåºŠ
# æçšæ·éæµ
rate-limit:
type: USER
limit: 1000/minute
# æ IP éæµ
rate-limit:
type: IP
limit: 100/minute
# ææ¥å£éæµ
rate-limit:
type: API
path: /api/pay/**
limit: 500/minute
# ç»åéæµ
rate-limit:
type: COMPOSITE
rules:
- type: USER
limit: 1000/minute
- type: IP
limit: 100/minute
ååžåŒéæµ
åæºéæµåªèœä¿æ€æ¬æºïŒååžåŒç¯å¢éèŠéäžåŒéæµïŒ
-- rate_limit.lua
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- ç§»é€çªå£å€çè®°åœ
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- è·ååœåçªå£å
çè¯·æ±æ°
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('EXPIRE', key, window / 1000)
return 1
else
return 0
end
ð¥ 4. çæïŒCircuit BreakerïŒ
çæåšçäžç§ç¶æ
[请æ±å€±èŽ¥ç < éåŒ]
â
â
âââââââââââŽââââââââââ
â â
â â
ââââââââââ [倱莥ç>éåŒ] ââââââââââ
â å
³é â ââââââââââââââ â æåŒ â
ââââââââââ ââââââââââ
â â
â â [è¶
æ¶å]
â â
â ââââââââââ
âââââââââââââââââââ â ååŒ â
[è¯·æ±æå] ââââââââââ
- å ³éïŒClosedïŒïŒæ£åžžèœ¬å请æ±ïŒç»è®¡å€±èŽ¥ç
- æåŒïŒOpenïŒïŒçŽæ¥è¿åé误ïŒäžå蜬åïŒå¯åšè¶ æ¶è®¡æ¶åš
- ååŒïŒHalf-OpenïŒïŒè¯æ¢æ§æŸè¡éšå请æ±ïŒæ£æµæ¯åŠæ¢å€
Resilience4j çæåšé 眮
resilience4j:
circuitbreaker:
instances:
playerService:
slidingWindowSize: 100 # æ»åšçªå£å€§å°
failureRateThreshold: 50 # 倱莥çéåŒïŒ%ïŒ
waitDurationInOpenState: 30s # æåŒç¶æçåŸ
æ¶éŽ
permittedNumberOfCallsInHalfOpenState: 10 # ååŒç¶æå
讞çè¯·æ±æ°
slowCallDurationThreshold: 2s # æ
¢è°çšéåŒ
slowCallRateThreshold: 60 # æ
¢è°çšçéåŒïŒ%ïŒ
çæåšä»£ç 瀺äŸ
@RestController
public class GameController {
@CircuitBreaker(name = "playerService", fallbackMethod = "getPlayerFallback")
@GetMapping("/api/player/{id}")
public Player getPlayer(@PathVariable String id) {
return playerService.getPlayer(id);
}
// çæéçº§æ¹æ³
public Player getPlayerFallback(String id, Exception e) {
// è¿åçŒåæ°æ®æé»è®€åŒ
return Player.builder()
.id(id)
.name("æªç¥ç©å®¶")
.level(1)
.build();
}
}
ð 5. æ¥å¿äžçæ§ïŒLogging & MonitoringïŒ
è¯·æ±æ¥å¿ç»æ
{
"traceId": "abc123",
"timestamp": "2026-03-01T10:30:00Z",
"method": "POST",
"path": "/api/pay/order",
"userId": "user_001",
"ip": "192.168.1.100",
"userAgent": "GameClient/1.0",
"requestSize": 256,
"responseSize": 128,
"statusCode": 200,
"latency": 45,
"upstreamService": "pay-service",
"upstreamLatency": 38
}
å šéŸè·¯è¿œèžª
客æ·ç«¯è¯·æ± â çœå
³ïŒçæ TraceIdïŒâ æå¡A â æå¡B â æ°æ®åº
â
âââ ææèç¹å
±äº«åäžäžª TraceId
@Component
public class TraceFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String traceId = exchange.getRequest().getHeaders().getFirst("X-Trace-Id");
// åŠææ²¡æ TraceIdïŒçæäžäžª
if (StringUtils.isEmpty(traceId)) {
traceId = UUID.randomUUID().toString().replace("-", "");
}
// 泚å
¥å°è¯·æ±å€Ž
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-Trace-Id", traceId)
.build();
// è®°åœæ¥å¿
exchange.getAttributes().put("startTime", System.currentTimeMillis());
return chain.filter(exchange.mutate().request(request).build())
.doFinally(signalType -> {
long latency = System.currentTimeMillis()
- exchange.getAttribute("startTime");
log.info("traceId={}, path={}, latency={}ms",
traceId, request.getPath(), latency);
});
}
}
çæ§ææ
# Prometheus ææ ç€ºäŸ
http_requests_total{method="GET", path="/api/player", status="200"} 12345
http_request_duration_seconds{method="GET", path="/api/player", quantile="0.99"} 0.045
gateway_active_connections 1024
gateway_circuit_breaker_state{service="player"} "closed"
ð 6. å议蜬æ¢ïŒProtocol TranslationïŒ
HTTP â gRPC 蜬æ¢
spring:
cloud:
gateway:
routes:
- id: grpc-service
uri: grpc://localhost:9090
predicates:
- Path=/api/grpc/**
filters:
- name: GrpcToHttp
WebSocket 代ç
spring:
cloud:
gateway:
routes:
- id: websocket-service
uri: ws://localhost:8081
predicates:
- Path=/ws/**
请æ±/ååºèœ¬æ¢
@Component
public class RequestTransformFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 蜬æ¢è¯·æ±äœ
return exchange.getRequest().getBody()
.map(dataBuffer -> {
// JSON åæ®µæ å°ãæ ŒåŒèœ¬æ¢ç
return transformBody(dataBuffer);
})
.then(chain.filter(exchange));
}
}
[é åŸå»ºè®®ïŒçœå ³æ žå¿è莣çå äžªåŸæ ç»æçåŸç€ºïŒæ¯äžªåŸæ 代衚äžäžªåèœ]
äžãé«å¯çšè®Ÿè®¡ïŒçœå ³äžèœæ
çœå ³æ¯ææè¯·æ±çå¿ ç»ä¹è·¯ïŒäžæŠææïŒæŽäžªç³»ç»å°±ç«çªäºã
1. æ ç¶æè®Ÿè®¡
â éè¯¯åæ³ïŒ
çœå
³A å€ççšæ·1çäŒè¯
çœå
³B å€ççšæ·2çäŒè¯
â çœå
³A æäºïŒçšæ·1 çäŒè¯äž¢å€±
â
æ£ç¡®åæ³ïŒ
äŒè¯ç¶æååšåš Redis
ä»»äœçœå
³å®äŸéœèœå€çä»»äœçšæ·ç请æ±
â çœå
³A æäºïŒçœå
³B æ¥ç®¡ïŒæ æç¥
2. å€å®äŸéšçœ² + èŽèœœåè¡¡
âââââââââââââââ
â èŽèœœåè¡¡åš â
â (Nginx/LB) â
ââââââââ¬âââââââ
â
âââââââââââââââââŒââââââââââââââââ
â â â
â â â
ââââââââââ ââââââââââ ââââââââââ
â çœå
³-1 â â çœå
³-2 â â çœå
³-3 â
ââââââââââ ââââââââââ ââââââââââ
# Nginx å¥åº·æ£æ¥
upstream gateway {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 max_fails=3 fail_timeout=30s;
}
3. çæé级
@GetMapping("/api/player/{id}")
@CircuitBreaker(name = "playerService", fallbackMethod = "getPlayerFromCache")
public Player getPlayer(@PathVariable String id) {
return playerService.getPlayer(id);
}
// éçº§æ¹æ¡1ïŒä»çŒå读å
public Player getPlayerFromCache(String id, Exception e) {
return cacheService.getPlayer(id);
}
// éçº§æ¹æ¡2ïŒè¿åé»è®€åŒ
public Player getPlayerDefault(String id, Exception e) {
return Player.defaultPlayer();
}
4. éæµä¿æ€
# å
šå±éæµ
rate-limit:
global: 100000/second
# å IP éæµ
rate-limit:
ip: 100/second
# çç¹æ¥å£éæµ
rate-limit:
api:
/api/login: 1000/second
/api/pay: 500/second
5. 倿ºæ¿éšçœ²
âââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â DNS æºèœè§£æ â
âââââââââââââââââââââââââ¬ââââââââââââââââââââââââââââââ
â
ââââââââââââââââŒâââââââââââââââ
â â â
â â â
ââââââââââââ ââââââââââââ ââââââââââââ
â åäº¬æºæ¿ â â äžæµ·æºæ¿ â â å¹¿å·æºæ¿ â
â çœå
³é矀 â â çœå
³é矀 â â çœå
³é矀 â
ââââââââââââ ââââââââââââ ââââââââââââ
6. ç°åºŠååž
# ç°åºŠååžé
眮
routes:
- id: gateway-v1
uri: lb://gateway-v1
predicates:
- Weight=gateway, 90
- id: gateway-v2
uri: lb://gateway-v2
predicates:
- Weight=gateway, 10
åãçœå ³æ¶æè®Ÿè®¡ïŒåæ¥ vs åŒæ¥
忥çœå ³ïŒäŒ ç»æš¡åŒïŒ
è¯·æ± â 线çšé»å¡çåŸ
â ååºè¿å â 线çšéæŸ
请æ±1 â Thread-1 â çåŸ
â ååº
请æ±2 â Thread-2 â çåŸ
â ååº
请æ±3 â Thread-3 â çåŸ
â ååº
...
请æ±1000 â Thread-1000 â çåŸ
â ååº
- å®ç°ç®åïŒæäºçè§£
- è°è¯æ¹äŸ¿ïŒå æ æž æ°
- é«å¹¶åæ¶çº¿çšæ°ççž
- å åæ¶èå€§ïŒæ¯äžªçº¿çšçºŠ 1MB æ 空éŽïŒ
- 线çšåæ¢åŒé倧
- äžéåé¿è¿æ¥åºæ¯
- 请æ±é < 5000 QPS
- å éšæå¡çœå ³
- å¿«éåååŒå
åŒæ¥çœå ³ïŒç°ä»£æš¡åŒïŒ
è¯·æ± â æ³šååè° â 线çšéæŸ â ååºå°èŸŸæ¶åè°å€ç
âââââââââââââââââââââââââââââââââââââââ
â Event Loop Thread â
â âââââââ âââââââ âââââââ âââââââ â
â â请æ±1â â请æ±2â â请æ±3â â请æ±4â â
â ââââ¬âââ ââââ¬âââ ââââ¬âââ ââââ¬âââ â
â â â â â â
â â â â â â
â âââââââ âââââââ âââââââ âââââââ â
â âåè°1â âåè°2â âåè°3â âåè°4â â
â âââââââ âââââââ âââââââ âââââââ â
âââââââââââââââââââââââââââââââââââââââ
@Component
public class AsyncHandler {
public Mono<String> handleRequest(ServerRequest request) {
return Mono.fromCallable(() -> {
// åŒæ¥å€ç
return doSomething();
}).subscribeOn(Schedulers.boundedElastic());
}
}
- é«å¹¶åå奜ïŒå°é线çšå€ç倧é请æ±
- èµæºæ¶èäœ
- éå IO å¯éååºæ¯
- ååºåŒçŒçšæš¡å
- 代ç å€æåºŠå¢å
- è°è¯å°éŸïŒåŒæ¥å æ ïŒ
- åŠä¹ æ²çº¿é¡å³
- 请æ±é > 10000 QPS
- å ¬çœ API çœå ³
- é«å¹¶åãäœå»¶è¿èŠæ±
æ§èœå¯¹æ¯
| ææ | 忥çœå ³ | åŒæ¥çœå ³ |
|---|---|---|
| QPS | ~5000 | ~50000+ |
| å»¶è¿ïŒP99ïŒ | 50ms | 10ms |
| çº¿çšæ° | 200+ | 8-16 |
| å åå çš | 2GB+ | 500MB |
| CPU å©çšç | 60% | 85% |
æžžæè¡äžæä¹éïŒ
[é åŸå»ºè®®ïŒåæ¥ vs åŒæ¥çœå ³ç对æ¯åŸïŒå·ŠäŸ§æ¯é»å¡çåŸ ç线çšïŒå³äŸ§æ¯äºä»¶åŸªç¯çéé»å¡æš¡å]
äºãæžžæè¡äžççœå ³ææ
æžžæè¡äžå¯¹çœå ³æç¬ç¹çèŠæ±ïŒè¿è¶ æ®éç Web åºçšã
ð® ææ1ïŒè¶ é«å¹¶å
åŒæå 5 åéïŒ1000 QPS
åŒæç¬éŽïŒ100000 QPSïŒ100åïŒ
åŒæå 10 åéïŒ50000 QPS
åŒæå 1 å°æ¶ïŒ20000 QPS
# åŒ¹æ§æ©å®¹é
眮
autoscaling:
minReplicas: 5
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: 10000
@Scheduled(cron = "0 55 9 * * ?") // æ¯å€© 9:55 æ§è¡
public void preheatBeforeServerOpen() {
// åŒæå 5 åé颿©å®¹
kubernetesClient.scaleDeployment("gateway", 50);
// é¢çè¿æ¥æ±
connectionPool.warmUp();
// é¢å 蜜çç¹æ°æ®
cacheService.preloadHotData();
}
# åŒæäžçšéæµçç¥
rate-limit:
login:
type: QUEUE # æéæš¡åŒ
limit: 10000/second
queueSize: 100000
queueTimeout: 30s # æéè¶
æ¶
fallback:
message: "æå¡åšç«çïŒè¯·çšåéè¯"
retryAfter: 10
â¡ ææ2ïŒè¶ äœå»¶è¿
æ»å»¶è¿ = 客æ·ç«¯çœç» + çœå
³å€ç + æå¡å€ç + æ°æ®åº + è¿å
100ms = 30ms + 5ms + 30ms + 20ms + 15ms
- 蟹çŒè®¡ç®ïŒçœå ³äžæ²å°çŠ»ç©å®¶æè¿çèç¹
äŒ ç»æ¶æïŒ
ç©å®¶ïŒå京ïŒâ äžå¿æºæ¿ïŒäžæµ·ïŒâ æå¡
å»¶è¿ïŒ30ms
èŸ¹çŒæ¶æïŒ
ç©å®¶ïŒå京ïŒâ 蟹çŒèç¹ïŒå京ïŒâ æå¡
å»¶è¿ïŒ5ms
- åè®®äŒåïŒUDP ä»£æ¿ TCP
TCPïŒäžæ¬¡æ¡æ + æ¥å¡æ§å¶ + éäŒ
å»¶è¿ïŒ50-100ms
UDPïŒæ è¿æ¥ + æ æ¥å¡æ§å¶
å»¶è¿ïŒ10-20ms
- è¿æ¥å€çšïŒé¿è¿æ¥åå°æ¡æåŒé
// è¿æ¥æ± é
眮
HttpClient client = HttpClient.create()
.keepAlive(true)
.maxConnections(10000)
.responseTimeout(Duration.ofSeconds(5));
- é¶æ·èŽææ¯
// Netty é¶æ·èŽ
ByteBuf buf = Unpooled.wrappedBuffer(data);
// çŽæ¥æäœå
åïŒé¿å
æ°æ®æ·èŽ
ð ææ3ïŒé¿è¿æ¥ç®¡ç
| ç¹æ§ | HTTP çœå ³ | æžžæçœå ³ |
|---|---|---|
| åè®® | HTTP/HTTPS | TCP/UDP/WebSocket |
| è¿æ¥ | çè¿æ¥ | é¿è¿æ¥ |
| ç¶æ | æ ç¶æ | æç¶æ |
| æšé | äžæ¯æ | æ¯æ |
| å žå QPS | 10000 | 100000+ |
âââââââââââââââââââââââââââââââââââââââââââââââ
â æžžæçœå
³ â
âââââââââââââââââââââââââââââââââââââââââââââââ€
â âââââââââââ âââââââââââ âââââââââââ â
â â è¿æ¥ç®¡ç â â äŒè¯ç®¡ç â â æ¶æ¯è·¯ç± â â
â âââââââââââ âââââââââââ âââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââ€
â âââââââââââ âââââââââââ âââââââââââ â
â â å议解æ â â æ¶æ¯éå â â èŽèœœåè¡¡ â â
â âââââââââââ âââââââââââ âââââââââââ â
âââââââââââââââââââââââââââââââââââââââââââââââ
public class ReconnectHandler {
public void onReconnect(String playerId, String oldSessionId, String newSessionId) {
// 1. éªè¯ç©å®¶èº«ä»œ
if (!authService.validatePlayer(playerId)) {
return;
}
// 2. æ¢å€äŒè¯ç¶æ
Session session = sessionService.getSession(oldSessionId);
if (session != null && !session.isExpired()) {
// 3. è¿ç§»å°æ°è¿æ¥
sessionService.migrateSession(oldSessionId, newSessionId);
// 4. æšéæçº¿æéŽçæ¶æ¯
List<Message> missedMessages = messageQueue.getUnsentMessages(playerId);
pushService.pushMessages(newSessionId, missedMessages);
}
}
}
ð¥ ææ4ïŒDDoS 鲿€
1. SYN FloodïŒèå°œè¿æ¥æ±
2. UDP FloodïŒåžŠå®œæ»å»
3. HTTP FloodïŒåºçšå±æ»å»
4. CC æ»å»ïŒæš¡ææ£åžžçšæ·
ââââââââââââââââââââââââââââââââââââââââââââââââ
â DDoS 鲿€æ¶æ â
ââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â æ»å»æµé â
â â â
â â â
â ââââââââââââ ââââââââââââ â
â â é«é² IP â â â æµéæž
æŽ â â
â ââââââââââââ ââââââââââââ â
â â â
â â â
â ââââââââââââ â
â â çœå
³å± â â
â â (éæµ/é»åå)â â
â ââââââââââââ â
â â â
â â â
â æ£åžžæµé â
ââââââââââââââââââââââââââââââââââââââââââââââââ
@Component
public class DDoSFilter implements GlobalFilter {
private LoadingCache<String, AtomicInteger> requestCounts = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build(new CacheLoader<String, AtomicInteger>() {
@Override
public AtomicInteger load(String key) {
return new AtomicInteger(0);
}
});
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String ip = getClientIp(exchange);
try {
AtomicInteger count = requestCounts.get(ip);
if (count.incrementAndGet() > 100) { // æ¯ç§è¶
è¿ 100 次请æ±
// å å
¥é»åå
blacklistService.add(ip, 3600); // å°çŠ 1 å°æ¶
return onError(exchange, "请æ±è¿äºé¢ç¹", 429);
}
} catch (Exception e) {
log.error("éæµåŒåžž", e);
}
return chain.filter(exchange);
}
}
[é åŸå»ºè®®ïŒæžžæè¡äžçœå ³ææçå象éåŸïŒæ¯äžªè±¡é代衚äžäžªææ]
å ãçœå ³äžæå¡çœæ Œçå ³ç³»
çæ¡æ¯ïŒéèŠïŒå®ä»¬è§£å³äžåå±é¢çé®é¢ã
API çœå ³ vs Service Mesh
| 绎床 | API çœå ³ | Service Mesh |
|---|---|---|
| äœçœ® | ç³»ç»èŸ¹çïŒåååïŒ | æå¡ä¹éŽïŒäžè¥¿åïŒ |
| è莣 | æ¥å ¥ã讀è¯ãéæµ | æå¡åç°ãèŽèœœåè¡¡ãçæ |
| 对象 | å€éšè¯·æ± | å éšè°çš |
| éšçœ² | ç¬ç«é矀 | Sidecar æš¡åŒ |
| åè®® | HTTP/HTTPS | å€åè®®æ¯æ |
| å¯è§æµæ§ | 请æ±çº§ | è°çšéŸçº§ |
- API çœå ³ = å°åºå€§éšä¿å®ïŒç®¡è¿åºïŒ
- Service Mesh = 楌æ å éšç®¡å®¶ïŒç®¡å éšæµèœ¬ïŒ
å®ä»¬åŠäœé åïŒ
å€éšè¯·æ± â APIçœå
³ïŒè®€è¯ãéæµïŒâ Service MeshïŒæå¡åç°ãèŽèœœåè¡¡ïŒâ ç®æ æå¡
âââââââââââââââââââââââââââââââââââââââ
â API çœå
³ â
â (讀è¯ãéæµãè·¯ç±ãå议蜬æ¢) â
âââââââââââââââââââ¬ââââââââââââââââââââ
â
âââââââââââââââââââŽââââââââââââââââââââ
â â
âââââââââŽââââââââ ââââââââââŽâââââââââ
â æå¡ A â â æå¡ B â
â âââââââââââââ â â âââââââââââââââ â
â â äžå¡é»èŸ â â â â äžå¡é»èŸ â â
â âââââââââââââ â â âââââââââââââââ â
â âââââââââââââ â Service Mesh â âââââââââââââââ â
â â Sidecar â ââââââââââââââââââââºâ â Sidecar â â
â âââââââââââââ â (äžè¥¿åæµé) â âââââââââââââââ â
âââââââââââââââââ âââââââââââââââââââ
- API çœå ³ å€çææå€éšæµéïŒå讀è¯ãéæµãè·¯ç±
- Service Mesh 管çå éšæå¡éŽè°çšïŒåçæãéè¯ã远螪
- 䞀è é åïŒåœ¢æå®æŽçæµéæ²»çäœç³»
æžžæè¡äžçå®è·µ
- HTTP API çœå ³ïŒå€çç»åœãå åŒãé 眮ç HTTP 请æ±
- æžžæçœå ³ïŒå€çæžžæå ç宿¶éä¿¡ïŒTCP/UDPïŒ
- Service MeshïŒç®¡çå端埮æå¡éŽçè°çš
[é åŸå»ºè®®ïŒAPIçœå ³ + Service Mesh çå屿¶æåŸïŒå±ç€ºååååäžè¥¿åæµéçåå·¥]
äžãäž»æµçœå ³ææ¯éå
åŒæºçœå ³å¯¹æ¯
| çœå ³ | è¯èš | ç¹ç¹ | éçšåºæ¯ |
|---|---|---|---|
| Nginx | C | 髿§èœãé 眮ç®å | åå代çãéæèµæº |
| èªç Gateway | Go/Java | äžå¡æ·±åºŠå®å¶ãçµæŽ»æ©å± | æ ¹æ®äžå¡éæ±å®å¶ |
| Kong | Lua | æä»¶äž°å¯ã管ççé¢ | äŒäžçº§ API 管ç |
| APISIX | Lua | äºåçãåšæé 眮 | Kubernetes ç¯å¢ |
| Envoy | C++ | 髿§èœãå¯è§æµæ§ | Service Mesh æ°æ®é¢ |
| Traefik | Go | èªåšæå¡åç° | 容åšåç¯å¢ |
éå建议
æšè: æ ¹æ®äžå¡éæ±éæ©
çç±:
- èªç çœå
³ïŒäžå¡æ·±åºŠå®å¶ïŒçµæŽ»æ©å±
- KongïŒæä»¶äž°å¯ãäŒäžçº§ç®¡ç
- APISIXïŒäºåçãåšæé
眮
- EnvoyïŒé«æ§èœãService Mesh æ°æ®é¢
æšè: APISIX æ Traefik
çç±:
- åçæ¯æ Kubernetes Ingress
- åšæé
çœ®ïŒæ ééå¯
- äžäºåççæéæè¯å¥œ
æšè: Nginx + Lua æ Envoy
çç±:
- C/C++ å®ç°ïŒæ§èœæèŽ
- æ¯æåäžçº§å¹¶å
- å
åå çšäœ
æšè: Kong
çç±:
- å®åç管ççé¢
- äž°å¯çæä»¶åžåº
- äŒäžçæ¯ææŽå¥œ
å «ãæ»ç»ïŒçœå ³è®Ÿè®¡çå ³é®èŠç¹
讟计äžäžªå¥œç API çœå ³ïŒè®°äœè¿äºååïŒ
â èŠç¹1ïŒè莣åäž
çœå ³åªå"éšå«"该åçäºïŒäžèŠæäžå¡é»èŸå¡è¿çœå ³ã
â é误ïŒåšçœå
³éå订å计ç®ãåºåæ£å
â
æ£ç¡®ïŒçœå
³åªå讀è¯ãéæµãè·¯ç±ïŒäžå¡é»èŸäº€ç»äžæžžæå¡
â èŠç¹2ïŒé«å¯çšäŒå
çœå ³æ¯åç¹ïŒå¿ é¡»åå°ïŒæ ç¶æã坿°Žå¹³æ©å±ãæéçº§æ¹æ¡ã
â
æ ç¶æè®Ÿè®¡ïŒäŒè¯ååšåš Redis
â
å€å®äŸéšçœ²ïŒè³å° 3 䞪å®äŸ
â
å¥åº·æ£æ¥ïŒèªåšå逿
éèç¹
â
çæé级ïŒäžæžžæ
éæ¶å¿«éè¿å
â èŠç¹3ïŒæ§èœææ
çœå ³æ¯ææè¯·æ±çå¿ ç»ä¹è·¯ïŒä»»äœå»¶è¿éœäŒè¢«æŸå€§ã
â
åŒæ¥éé»å¡ïŒäœ¿çš Reactor æš¡å
â
è¿æ¥å€çšïŒåå°æ¡æåŒé
â
çŒåçç¹ïŒåå°äžæžžåå
â
åè®®äŒåïŒHTTP/2ãåçŒ©äŒ èŸ
â èŠç¹4ïŒå®å šç¬¬äž
çœå ³æ¯å®å šç第äžéé²çº¿ïŒè®€è¯ãéæµã鲿€äžäžªéœäžèœå°ã
â
ç»äžè®€è¯ïŒJWTãOAuth2ãAPI Key
â
éæµä¿æ€ïŒé²æ¢æ¶æè¯·æ±
â
DDoS 鲿€ïŒæµéæž
æŽãé»åå
â
ææä¿¡æ¯è±æïŒæ¥å¿äžéèå¯ç ãToken
â èŠç¹5ïŒå¯è§æµæ§
æ¥å¿ãçæ§ã远螪ïŒäžè 猺äžäžå¯ïŒåŠååºé®é¢å°±æ¯çåæžè±¡ã
â
ç»æåæ¥å¿ïŒJSON æ ŒåŒïŒäŸ¿äºæ£çŽ¢
â
ææ çæ§ïŒQPSãå»¶è¿ãé误ç
â
éŸè·¯è¿œèžªïŒå
šéŸè·¯ TraceId
â
åèŠæºå¶ïŒåŒåžžèªåšéç¥
ååšæå
API çœå ³ç䌌ç®åïŒå®åæ¯ç³»ç»æ¶æäžæå ³é®çç»ä»¶ä¹äžã
éšåŒåŸå€ªå€§ïŒä»ä¹äººéœèœè¿æ¥ïŒäžå®å šã
éšåŒåŸå€ªå°ïŒäžäž»è¿åºéœå µïŒäœéªå·®ã
åé¡Ÿæ¬æïŒæä»¬èŠçäºïŒ
- çœå ³çä»·åŒïŒç»äžå ¥å£ãç®åæ¶æãæåå®å š
- å å€§æ žå¿è莣ïŒè·¯ç±ãéŽæãéæµãçæãæ¥å¿ãå议蜬æ¢
- é«å¯çšè®Ÿè®¡ïŒæ ç¶æãå€å®äŸãçæé级ã倿ºæ¿
- 忥 vs åŒæ¥ïŒéåäŸæ®åæ§èœå¯¹æ¯
- æžžæè¡äžææïŒè¶ é«å¹¶åãè¶ äœå»¶è¿ãé¿è¿æ¥ãDDoS 鲿€
- äžæå¡çœæ Œé åïŒååå + äžè¥¿åæµéæ²»ç
- ææ¯éåïŒæ ¹æ®åºæ¯éæ©åéççœå ³
äžäžç¯ïŒæä»¬æ¥èè埮æå¡æ¶æäžç"身仜è¯ç³»ç»"ââæå¡æ³šåäžåç°ã
- 第1ç¯ïŒä»åäœå°åŸ®æå¡ïŒæžžæå¹³å°çæ¶ææŒè¿ä¹è·¯
- 第2ç¯ïŒåŸ®æå¡æåïŒæä¹åæäžäŒåå°æïŒ
ð¬ è¯è®º (0)