两种设计方案轮询等待模式、回调模式
方案一:轮询等待模式
java
/**
* 轮询等待模式实现
*
* 流程:
* 1. Activity开始执行
* 2. 循环调用速率服务获取令牌
* 3. 获取到令牌后执行实际逻辑
* 4. 完成Activity
*/
public class PollingRateLimitedActivity {
public String sendMessage(Message message) {
long startTime = System.currentTimeMillis();
boolean acquired = false;
// 轮询获取令牌(带超时)
while (!acquired && (System.currentTimeMillis() - startTime) < TIMEOUT_MS) {
try {
// 调用外部速率服务
acquired = rateLimitService.tryAcquire("send_message", 1);
if (!acquired) {
// 发送心跳,避免Activity超时
Activity.getExecutionContext().heartbeat("waiting_for_rate_limit");
// 等待100ms后重试
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ActivityFailureException("Rate limit wait interrupted");
}
}
if (!acquired) {
throw new ActivityFailureException("Rate limit timeout");
}
// 执行实际逻辑
return doSendMessage(message);
}
}
方案二:回调模式
java
/**
* 回调模式实现
*
* 流程:
* 1. Activity调用速率服务注册回调
* 2. Activity暂停(异步完成)
* 3. 速率服务控制速率,令牌可用时回调
* 4. Temporal Worker接收回调,恢复Activity执行
*/
public class CallbackRateLimitedActivity {
public String sendMessage(Message message) {
// 获取Activity上下文
ActivityExecutionContext context = Activity.getExecutionContext();
String activityId = context.getInfo().getActivityId();
String taskToken = context.getTaskToken();
// 1. 向速率服务注册回调
rateLimitService.registerCallback(
activityId,
taskToken,
"send_message",
1,
callbackUrl
);
// 2. 告诉Temporal这个Activity将异步完成
context.doNotCompleteOnReturn();
// 3. 返回中间结果(可选)
return "WAITING_FOR_RATE_LIMIT";
}
}
/**
* 回调处理端点
*/
@RestController
public class RateLimitCallbackController {
@PostMapping("/callback/rate-limit")
public void handleRateLimitCallback(@RequestBody CallbackRequest request) {
// 使用Temporal Client恢复Activity执行
try {
// 方式1:通过TaskToken直接完成Activity
completionClient.complete(request.getTaskToken(), request.getResult());
// 方式2:通过ActivityId发送信号
// workflowClient.signalActivityById(
// request.getActivityId(),
// "rateLimitGranted",
// request.getResult()
// );
} catch (Exception e) {
log.error("Failed to handle rate limit callback", e);
}
}
}
两种方案对比分析
架构复杂度对比
| 维度 | 轮询等待模式 | 回调模式 |
|---|---|---|
| 架构复杂度 | 低 | 高 |
| 依赖组件 | 少(只需速率服务) | 多(需要回调端点、消息队列、Temporal Client) |
| 状态管理 | 无状态 | 需要管理回调状态 |
| 容错处理 | 简单重试 | 复杂(需要保证回调可靠性) |
性能对比
| 维度 | 轮询等待模式 | 回调模式 |
|---|---|---|
| Worker线程占用 | 高(长时间阻塞) | 低(立即释放) |
| 网络开销 | 高(频繁轮询) | 低(一次注册+一次回调) |
| 内存消耗 | 较高(每个Activity占用线程栈) | 较低(只保存回调信息) |
| 可扩展性 | 差(受限于Worker线程数) | 好(Worker可处理更多并发) |
可靠性对比
| 维度 | 轮询等待模式 | 回调模式 |
|---|---|---|
| 超时处理 | 简单(Activity自带超时) | 复杂(需要额外超时机制) |
| 失败恢复 | 简单重试 | 需要补偿机制 |
| 消息丢失 | 无(同步请求) | 可能(异步回调可能丢失) |
| 幂等性 | 容易实现 | 需要额外设计 |
运维复杂度对比
| 维度 | 轮询等待模式 | 回调模式 |
|---|---|---|
| 监控 | 简单(Temporal原生支持) | 复杂(需要跨服务追踪) |
| 调试 | 简单(线性执行) | 复杂(分布式追踪) |
| 日志聚合 | 简单 | 复杂 |
| 部署 | 简单 | 复杂(多组件协调) |
详细技术实现对比
轮询等待模式的实现细节
java
/**
* 轮询等待模式的优缺点分析
*/
public class PollingPatternAnalysis {
// 优点:
// 1. 实现简单直观
// 2. 代码逻辑线性,易于理解和调试
// 3. 无需额外的回调基础设施
// 4. 利用Temporal原生的重试和超时机制
// 5. 状态完全由Temporal管理
// 缺点:
// 1. Worker线程阻塞,影响吞吐量
// 2. 网络开销大(频繁轮询)
// 3. 不适合长时间等待的场景
// 4. 可能触发Temporal的心跳超时
// 5. 难以支持大规模并发
/**
* 优化后的轮询实现
*/
public String optimizedPolling(Message message) {
ActivityExecutionContext context = Activity.getExecutionContext();
// 使用指数退避减少轮询频率
int maxRetries = 10;
long baseDelay = 100; // 100ms
long maxDelay = 5000; // 5s
for (int attempt = 0; attempt < maxRetries; attempt++) {
// 发送心跳
context.heartbeat("rate_limit_attempt_" + attempt);
// 尝试获取令牌
if (rateLimitService.tryAcquire("send_message", 1)) {
return doSendMessage(message);
}
// 计算退避时间
long delay = Math.min(
baseDelay * (long) Math.pow(2, attempt),
maxDelay
);
// 等待(可被心跳中断)
waitWithInterruptibleSleep(delay, context);
}
throw new ActivityFailureException("Rate limit retries exhausted");
}
}
轮训等待模式-使用外部速率限制服务的详细设计
架构概览
┌─────────────────────────────────────────────────────────────┐
│ Temporal 集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Worker1 │ │ Worker2 │ │ Worker3 │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └─────────────┼────────────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ Activity │ │
│ │ 实现类 │ │
│ └─────┬─────┘ │
│ │ │
└─────────────────────┼────────────────────────────────────────┘
│
调用速率限制服务
│
┌───────────▼───────────┐
│ 外部速率限制服务 │
│ (Redis/Consul/等) │
└───────────────────────┘
1. 选择合适的外部服务
Redis(推荐)
java
// 优点:
// 1. 性能极高(10万+ QPS)
// 2. 支持原子操作
// 3. 丰富的限流算法实现
// 4. 集群和持久化支持
Consul / Etcd
java
// 优点:
// 1. 强一致性
// 2. 服务发现集成
// 3. 分布式锁支持
专用限流服务(如:Sentinel, Envoy)
java
// 优点:
// 1. 功能完善
// 2. 监控集成
// 3. 动态配置
2. 核心设计模式
模式一:令牌桶模式(Token Bucket)
java
/**
* 基于Redis的分布式令牌桶实现
*/
public class RedisTokenBucketRateLimiter {
private final JedisPool jedisPool;
private final String rateLimitKey;
private final int maxTokens;
private final int refillRate; // tokens per second
public RedisTokenBucketRateLimiter(String serviceName, int maxTokens, int refillRate) {
this.jedisPool = new JedisPool("localhost", 6379);
this.rateLimitKey = "rate_limit:" + serviceName;
this.maxTokens = maxTokens;
this.refillRate = refillRate;
}
public boolean tryAcquire(int tokens) {
try (Jedis jedis = jedisPool.getResource()) {
// Lua脚本保证原子性
String luaScript = """
local key = KEYS[1]
local tokensRequested = tonumber(ARGV[1])
local maxTokens = tonumber(ARGV[2])
local refillRate = tonumber(ARGV[3])
local now = tonumber(ARGV[4])
-- 获取当前桶状态
local bucket = redis.call('HMGET', key, 'tokens', 'lastRefill')
local currentTokens = maxTokens
local lastRefill = now
if bucket[1] then
currentTokens = tonumber(bucket[1])
lastRefill = tonumber(bucket[2])
else
-- 初始化桶
redis.call('HMSET', key, 'tokens', maxTokens, 'lastRefill', now)
redis.call('EXPIRE', key, 3600)
end
-- 计算应补充的令牌数
local timePassed = now - lastRefill
local tokensToAdd = math.floor(timePassed * refillRate)
local newTokens = math.min(currentTokens + tokensToAdd, maxTokens)
-- 检查是否有足够令牌
if newTokens >= tokensRequested then
newTokens = newTokens - tokensRequested
redis.call('HMSET', key, 'tokens', newTokens, 'lastRefill', now)
return 1 -- 成功
else
return 0 -- 失败
end
""";
long now = System.currentTimeMillis() / 1000;
Long result = (Long) jedis.eval(
luaScript,
Collections.singletonList(rateLimitKey),
Arrays.asList(
String.valueOf(tokens),
String.valueOf(maxTokens),
String.valueOf(refillRate),
String.valueOf(now)
)
);
return result == 1;
}
}
public void waitUntilAcquired(int tokens) {
while (!tryAcquire(tokens)) {
try {
Thread.sleep(100); // 等待100ms后重试
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Rate limit wait interrupted", e);
}
}
}
}
模式二:滑动窗口计数器
java
/**
* 基于Redis的滑动窗口限流
*/
public class RedisSlidingWindowRateLimiter {
private final JedisPool jedisPool;
private final String windowKey;
private final int maxRequests;
private final long windowSizeInMillis;
public RedisSlidingWindowRateLimiter(String serviceName, int maxRequests, Duration windowSize) {
this.jedisPool = new JedisPool("localhost", 6379);
this.windowKey = "sliding_window:" + serviceName;
this.maxRequests = maxRequests;
this.windowSizeInMillis = windowSize.toMillis();
}
public boolean tryAcquire() {
try (Jedis jedis = jedisPool.getResource()) {
long now = System.currentTimeMillis();
long windowStart = now - windowSizeInMillis;
// 清理过期请求并统计
String luaScript = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local windowStart = tonumber(ARGV[2])
local maxRequests = tonumber(ARGV[3])
local windowSize = tonumber(ARGV[4])
-- 清理窗口之前的记录
redis.call('ZREMRANGEBYSCORE', key, 0, windowStart)
-- 获取当前窗口内的请求数
local currentCount = redis.call('ZCOUNT', key, windowStart, now)
if currentCount < maxRequests then
-- 添加新请求
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, math.ceil(windowSize / 1000) * 2)
return 1
else
return 0
end
""";
Long result = (Long) jedis.eval(
luaScript,
Collections.singletonList(windowKey),
Arrays.asList(
String.valueOf(now),
String.valueOf(windowStart),
String.valueOf(maxRequests),
String.valueOf(windowSizeInMillis)
)
);
return result == 1;
}
}
}
3. Temporal Activity 集成方案
方案A:Activity拦截器模式
java
/**
* 全局速率限制拦截器
*/
public class DistributedRateLimitInterceptor implements ActivityInboundInterceptor {
private final RateLimitService rateLimitService;
private final Map<String, RateLimitConfig> activityConfigs;
public DistributedRateLimitInterceptor() {
this.rateLimitService = new RedisRateLimitService();
this.activityConfigs = loadRateLimitConfigs();
}
@Override
public ActivityOutput execute(ActivityInput input) {
String activityType = input.getActivityType();
RateLimitConfig config = activityConfigs.get(activityType);
if (config != null && config.isEnabled()) {
String limitKey = buildLimitKey(activityType, input);
// 等待获取执行许可
boolean acquired = rateLimitService.acquire(
limitKey,
config.getMaxRequests(),
config.getWindow()
);
if (!acquired && config.isBlocking()) {
// 阻塞等待
rateLimitService.waitUntilAcquired(
limitKey,
config.getMaxRequests(),
config.getWindow(),
config.getTimeout()
);
} else if (!acquired) {
// 快速失败
throw new ActivityFailureException(
"Rate limit exceeded for activity: " + activityType,
"RATE_LIMIT_EXCEEDED"
);
}
}
return delegate.execute(input);
}
private String buildLimitKey(String activityType, ActivityInput input) {
// 可根据业务需求构建不同的限流key
// 例如:按用户限流、按商户限流、全局限流等
return String.format("rate:%s:%s",
activityType,
extractTenantId(input)
);
}
}
方案B:Activity装饰器模式
java
/**
* 速率限制Activity装饰器
*/
public class RateLimitedSendMessageActivity implements SendMessageActivity {
private final SendMessageActivity delegate;
private final DistributedRateLimiter rateLimiter;
private final ActivityExecutionContext context;
public RateLimitedSendMessageActivity(
SendMessageActivity delegate,
DistributedRateLimiter rateLimiter
) {
this.delegate = delegate;
this.rateLimiter = rateLimiter;
this.context = Activity.getExecutionContext();
}
@Override
public String sendMessage(Message message) {
String rateLimitKey = String.format("send_message:%s", message.getReceiver());
// 带心跳的等待
return withHeartbeat(() -> {
rateLimiter.acquireWithWait(rateLimitKey, 1, Duration.ofSeconds(1));
return delegate.sendMessage(message);
});
}
private <T> T withHeartbeat(Callable<T> task) {
ScheduledExecutorService heartbeatExecutor =
Executors.newSingleThreadScheduledExecutor();
try {
// 开始发送心跳
ScheduledFuture<?> heartbeatFuture = heartbeatExecutor.scheduleAtFixedRate(
() -> context.heartbeat("rate_limit_waiting"),
0, // 立即开始
10, // 每10秒
TimeUnit.SECONDS
);
try {
return task.call();
} finally {
heartbeatFuture.cancel(true);
}
} catch (Exception e) {
throw Activity.wrap(e);
} finally {
heartbeatExecutor.shutdown();
}
}
}
4. 配置管理
动态配置类
java
@Configuration
public class RateLimitConfiguration {
@Value("${rate-limit.redis.host:localhost}")
private String redisHost;
@Value("${rate-limit.redis.port:6379}")
private int redisPort;
@Bean
public DistributedRateLimiter distributedRateLimiter() {
Config redisConfig = new Config();
redisConfig.useSingleServer()
.setAddress(String.format("redis://%s:%d", redisHost, redisPort))
.setConnectionPoolSize(10)
.setConnectTimeout(3000);
RedissonClient redisson = Redisson.create(redisConfig);
return new RedissonDistributedRateLimiter(redisson);
}
@Bean
public Map<String, RateLimitConfig> activityRateLimits() {
Map<String, RateLimitConfig> configs = new HashMap<>();
// SendMessageActivity: 每秒1次,按接收者限流
configs.put("SendMessageActivity", RateLimitConfig.builder()
.maxRequests(1)
.window(Duration.ofSeconds(1))
.strategy(RateLimitStrategy.PER_USER)
.blocking(true)
.timeout(Duration.ofMinutes(5))
.build());
// ProcessPaymentActivity: 每分钟100次,全局限流
configs.put("ProcessPaymentActivity", RateLimitConfig.builder()
.maxRequests(100)
.window(Duration.ofMinutes(1))
.strategy(RateLimitStrategy.GLOBAL)
.blocking(false)
.build());
return configs;
}
}
配置热更新
java
@Component
public class DynamicRateLimitManager {
private final ConfigRepository configRepository;
private final Map<String, RateLimitConfig> currentConfigs;
private final ScheduledExecutorService updater;
public DynamicRateLimitManager(ConfigRepository configRepository) {
this.configRepository = configRepository;
this.currentConfigs = new ConcurrentHashMap<>();
this.updater = Executors.newSingleThreadScheduledExecutor();
// 每30秒检查配置更新
updater.scheduleAtFixedRate(this::refreshConfigs, 0, 30, TimeUnit.SECONDS);
}
private void refreshConfigs() {
Map<String, RateLimitConfig> newConfigs =
configRepository.loadRateLimitConfigs();
// 比较并应用变更
newConfigs.forEach((activityType, newConfig) -> {
RateLimitConfig oldConfig = currentConfigs.get(activityType);
if (!newConfig.equals(oldConfig)) {
applyConfigChange(activityType, newConfig);
currentConfigs.put(activityType, newConfig);
}
});
}
}
5. 监控和告警
java
@Component
public class RateLimitMetricsCollector {
private final MeterRegistry meterRegistry;
private final Map<String, DistributionSummary> metrics;
public void recordRateLimitEvent(String activityType, String key,
boolean success, long waitTime) {
// 记录指标
meterRegistry.counter("rate_limit_requests_total",
"activity", activityType,
"key", key,
"success", String.valueOf(success)
).increment();
if (!success) {
meterRegistry.counter("rate_limit_rejections_total",
"activity", activityType,
"key", key
).increment();
}
meterRegistry.timer("rate_limit_wait_time",
"activity", activityType
).record(waitTime, TimeUnit.MILLISECONDS);
// 记录到日志用于调试
log.debug("Rate limit event: activity={}, key={}, success={}, wait={}ms",
activityType, key, success, waitTime);
}
@EventListener
public void onRateLimitViolation(RateLimitViolationEvent event) {
// 触发告警
alertService.sendAlert(Alert.builder()
.level(AlertLevel.WARNING)
.title("Rate Limit Violation")
.message(String.format(
"Activity %s exceeded rate limit for key %s",
event.getActivityType(),
event.getLimitKey()
))
.build());
}
}
6. 故障处理和降级
java
public class FallbackRateLimiter implements DistributedRateLimiter {
private final DistributedRateLimiter primary;
private final LocalRateLimiter fallback;
private final CircuitBreaker circuitBreaker;
private volatile boolean externalServiceAvailable = true;
@Override
public boolean tryAcquire(String key, int tokens) {
if (externalServiceAvailable) {
try {
boolean result = circuitBreaker.executeSupplier(() ->
primary.tryAcquire(key, tokens));
return result;
} catch (Exception e) {
// 外部服务故障,切换到本地限流
log.warn("External rate limit service failed, falling back to local", e);
externalServiceAvailable = false;
// 启动健康检查
scheduleHealthCheck();
return fallback.tryAcquire(key, tokens);
}
} else {
return fallback.tryAcquire(key, tokens);
}
}
private void scheduleHealthCheck() {
ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
try {
if (primary.healthCheck()) {
externalServiceAvailable = true;
scheduler.shutdown();
log.info("External rate limit service recovered");
}
} catch (Exception e) {
// 继续使用本地限流
}
}, 10, 10, TimeUnit.SECONDS);
}
}
7. 最佳实践建议
-
分级限流策略:
java// 多级限流:全局 → 服务 → 用户 → 操作 String[] limitKeys = { "global:send_message", "service:notification_service", "user:" + userId, "operation:send_email" }; -
预热机制:
java// 系统启动时缓慢增加限流阈值 RateLimiter rateLimiter = RateLimiter.create(10.0, 5, TimeUnit.MINUTES); -
动态配额调整:
java// 根据系统负载自动调整限流阈值 if (systemLoad > 0.8) { // 降低限流阈值 adjustRateLimit("send_message", -20); } -
限流响应头:
java// 返回剩余请求数和重置时间 public class RateLimitResponse { private boolean allowed; private int remaining; private long resetAfter; // seconds private int limit; }
部署架构
┌─────────────────┐
│ Temporal │
│ Activities │
└────────┬────────┘
│
▼
┌─────────────────────────────────┐
│ Rate Limit Proxy │
│ (可选:Envoy/Sidecar模式) │
└─────────────────────────────────┘
│ │
┌───────▼────┐ ┌───────▼─────┐
│ Redis │ │ Sentinel │
│ Cluster │ │ Dashboard │
└────────────┘ └─────────────┘
这种设计提供了高可用、可观测、可动态调整的分布式速率控制方案,能够满足生产环境的需求。
回调模式的实现细节
java
/**
* 回调模式的优缺点分析
*/
public class CallbackPatternAnalysis {
// 优点:
// 1. Worker线程立即释放,提高吞吐量
// 2. 适合长时间等待的场景
// 3. 支持大规模并发
// 4. 减少网络调用
// 5. 可扩展性强
// 缺点:
// 1. 架构复杂,引入多个组件
// 2. 需要保证回调的可靠性
// 3. 需要处理异步状态管理
// 4. 调试和追踪困难
// 5. 可能引入分布式事务问题
/**
* 完整的回调模式实现架构
*/
@Component
public class CallbackRateLimitSystem {
// 1. 回调注册表(Redis或数据库)
private final CallbackRegistry callbackRegistry;
// 2. 消息队列(用于可靠回调)
private final MessageQueue messageQueue;
// 3. Temporal客户端
private final TemporalClient temporalClient;
// 4. 超时处理器
private final TimeoutHandler timeoutHandler;
/**
* Activity注册回调
*/
public void registerCallback(String activityId, String taskToken,
String rateKey, int tokens) {
// 生成回调ID
String callbackId = UUID.randomUUID().toString();
// 保存回调信息
CallbackInfo info = new CallbackInfo(
callbackId,
activityId,
taskToken,
rateKey,
tokens,
System.currentTimeMillis()
);
callbackRegistry.save(info);
// 将请求放入速率队列
messageQueue.enqueue(rateKey, callbackId);
// 启动超时检查
timeoutHandler.scheduleTimeoutCheck(
callbackId,
Duration.ofMinutes(5)
);
}
/**
* 速率服务处理逻辑
*/
@Scheduled(fixedRate = 1000) // 每秒处理一次
public void processRateLimitQueue() {
for (String rateKey : getAllRateKeys()) {
// 检查速率限制
if (canProcess(rateKey)) {
// 从队列中取出下一个回调ID
String callbackId = messageQueue.dequeue(rateKey);
if (callbackId != null) {
// 获取回调信息
CallbackInfo info = callbackRegistry.get(callbackId);
// 回调Temporal
callbackToTemporal(info);
// 清理回调记录
callbackRegistry.delete(callbackId);
}
}
}
}
/**
* 回调Temporal Worker
*/
private void callbackToTemporal(CallbackInfo info) {
try {
// 方式1:直接完成Activity
temporalClient.completeActivity(
info.getTaskToken(),
new ActivityResult("RATE_LIMIT_GRANTED")
);
// 方式2:发送信号
// temporalClient.signalActivity(
// info.getActivityId(),
// "rateLimitGranted",
// new RateLimitGrantedEvent()
// );
} catch (ActivityNotFoundException e) {
// Activity可能已超时或取消
log.warn("Activity not found, cleaning up callback", e);
callbackRegistry.delete(info.getCallbackId());
}
}
/**
* 超时处理
*/
public void handleTimeout(String callbackId) {
CallbackInfo info = callbackRegistry.get(callbackId);
if (info != null) {
// 尝试失败Activity
try {
temporalClient.failActivity(
info.getTaskToken(),
new ActivityFailureException("Rate limit timeout")
);
} catch (Exception e) {
log.error("Failed to timeout activity", e);
}
// 清理
callbackRegistry.delete(callbackId);
}
}
}
}
建议与推荐方案
基于场景的推荐
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 低并发,短等待(<5秒) | 轮询等待模式 | 实现简单,无额外复杂度 |
| 高并发,短等待 | 轮询+优化 | 使用退避算法,减少轮询 |
| 长等待(>30秒) | 回调模式 | 避免线程长期阻塞 |
| 大规模分布式 | 回调模式 | 支持水平扩展 |
| 关键业务,需高可靠 | 轮询等待模式 | 同步处理,可靠性高 |
| 资源受限环境 | 轮询等待模式 | 无需额外基础设施 |
混合方案(推荐)
java
/**
* 智能混合方案:根据等待时间动态选择模式
*/
public class SmartRateLimitStrategy {
private static final long POLLING_THRESHOLD = 5000; // 5秒
private static final long HYBRID_THRESHOLD = 30000; // 30秒
public String executeWithSmartRateLimit(Message message) {
// 1. 先尝试快速获取(轮询模式)
RateLimitResult quickResult = tryQuickAcquisition();
if (quickResult.isSuccess()) {
return doSendMessage(message);
}
// 2. 根据预估等待时间选择策略
long estimatedWait = estimateWaitTime();
if (estimatedWait <= POLLING_THRESHOLD) {
// 短等待:使用优化轮询
return executeWithOptimizedPolling(message);
} else if (estimatedWait <= HYBRID_THRESHOLD) {
// 中等等待:使用带心跳的轮询
return executeWithHeartbeatPolling(message, estimatedWait);
} else {
// 长等待:切换到回调模式
return executeWithCallback(message);
}
}
/**
* 预估等待时间算法
*/
private long estimateWaitTime() {
// 基于:
// 1. 当前队列长度
// 2. 历史等待时间统计
// 3. 系统负载
// 4. 时间因素(高峰/低谷)
int queueSize = rateLimitService.getQueueSize();
long avgWaitTime = getHistoricalAverageWait();
double loadFactor = getSystemLoad();
return (long) (queueSize * avgWaitTime * loadFactor);
}
}
决策矩阵
技术决策表
| 决策因子 | 权重 | 轮询等待模式得分 | 回调模式得分 |
|---|---|---|---|
| 实现复杂度 | 30% | 9/10 | 4/10 |
| 性能影响 | 25% | 5/10 | 9/10 |
| 可靠性 | 20% | 8/10 | 6/10 |
| 可维护性 | 15% | 8/10 | 5/10 |
| 扩展性 | 10% | 4/10 | 9/10 |
| 加权总分 | 100% | 7.25/10 | 6.35/10 |
业务决策表
| 业务需求 | 轮询等待模式 | 回调模式 |
|---|---|---|
| 快速上线 | ✅ 优 | ❌ 差 |
| 团队熟悉度 | ✅ 优 | ❌ 差 |
| 运维能力 | ✅ 优 | ❌ 差 |
| 未来扩展 | ❌ 差 | ✅ 优 |
| SLA要求 | ✅ 优 | ❌ 中 |
| 成本控制 | ✅ 优 | ❌ 差 |
最终建议
我的推荐:采用优化后的轮询等待模式
理由:
- 实现简单,风险低:对于每秒1次的速率控制,轮询模式完全足够
- 维护成本低:不需要额外的回调基础设施
- 可靠性高:同步处理,无消息丢失风险
- 符合Temporal设计哲学:Temporal本身就是为处理长时间运行任务设计的,Activity可以适当阻塞
优化建议:
java
/**
* 最终推荐的优化实现
*/
@Slf4j
public class RecommendedRateLimitedActivity {
private static final long MAX_WAIT_TIME = 30000; // 最大等待30秒
private static final long HEARTBEAT_INTERVAL = 10000; // 心跳间隔10秒
@Override
public String sendMessage(Message message) {
ActivityExecutionContext context = Activity.getExecutionContext();
long startTime = System.currentTimeMillis();
try {
// 尝试立即获取
if (rateLimitService.tryAcquireImmediate("send_message", 1)) {
return doSendMessage(message);
}
// 启动心跳线程
ScheduledExecutorService heartbeatExecutor =
Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> heartbeatFuture = heartbeatExecutor.scheduleAtFixedRate(
() -> {
try {
context.heartbeat("waiting_for_rate_limit");
} catch (Exception e) {
log.warn("Heartbeat failed", e);
}
},
0, // 立即开始
HEARTBEAT_INTERVAL,
TimeUnit.MILLISECONDS
);
try {
// 使用退避算法等待
int attempt = 0;
long baseDelay = 100; // 100ms
while (System.currentTimeMillis() - startTime < MAX_WAIT_TIME) {
long delay = Math.min(
baseDelay * (long) Math.pow(2, attempt),
5000 // 最大5秒
);
// 等待
Thread.sleep(delay);
// 尝试获取
if (rateLimitService.tryAcquire("send_message", 1)) {
return doSendMessage(message);
}
attempt++;
}
// 超时
throw new ActivityFailureException(
"Rate limit timeout after " + MAX_WAIT_TIME + "ms",
"RATE_LIMIT_TIMEOUT"
);
} finally {
// 清理心跳
heartbeatFuture.cancel(true);
heartbeatExecutor.shutdown();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ActivityFailureException("Rate limit wait interrupted", e);
}
}
}
何时考虑切换到回调模式:
- 当并发数超过1000:Worker线程可能成为瓶颈
- 当平均等待时间超过10秒:长时间阻塞不合理
- 当需要支持优先级队列:回调模式支持更复杂的调度
- 当有跨地域部署需求:回调模式更适合分布式场景
分阶段实施建议:
阶段1(现在) :实现优化后的轮询等待模式
阶段2(3个月后) :监控系统性能,评估是否需要优化
阶段3(6个月后) :根据业务增长,考虑实现混合方案
阶段4(1年后):如果需要,部分迁移到回调模式
结论:如果你的需求(每秒1次速率控制),轮询等待模式是更合适的选择。它简单、可靠、易于维护,完全能满足你的需求。只有当业务量大幅增长或需求变更时,才需要考虑更复杂的回调模式。
轮询等待模式并发限制的深度分析与优化方案
一、为什么轮询等待模式并发低?
1.1 线程资源瓶颈
java
/**
* Temporal Worker 线程模型
*
* 问题根源:每个Activity执行都需要占用一个Worker线程
* 轮询等待期间线程被阻塞,无法处理其他任务
*/
public class WorkerThreadModel {
// 典型的Temporal Worker配置
Worker worker = factory.newWorker("task-queue",
WorkerOptions.newBuilder()
.setMaxConcurrentActivityExecutionSize(50) // 最大并发Activity数
.build());
/**
* 问题演示:
* 假设有100个Activity需要执行
* 每个Activity需要等待速率控制服务(平均等待2秒)
*
* 计算:
* - Worker线程数:50
* - Activity总数:100
* - 每个Activity执行时间:0.1秒(实际业务)+ 2秒(等待)
*
* 理论执行时间:
* 第一波:50个Activity同时开始,等待2.1秒完成
* 第二波:剩余50个Activity开始,再等2.1秒完成
* 总时间:~4.2秒
*
* 如果是理想的非阻塞模式:
* - 实际业务时间:100 * 0.1 = 10秒
* - 等待时间可以重叠执行
* - 总时间:~2.1秒
*/
}
1.2 系统资源浪费
java
/**
* 资源浪费分析
*/
public class ResourceWasteAnalysis {
// 资源类型 | 轮询等待模式 | 理想模式
// ---------|-------------|-----------
// CPU | 高(频繁轮询) | 低(事件驱动)
// 内存 | 高(线程栈占用) | 低
// 网络 | 高(频繁请求) | 低
// 线程数 | 高(1:1占用) | 低(线程复用)
/**
* 具体分析:
* 1. 线程栈内存:每个Java线程需要约1MB栈内存
* 50个线程 = 50MB(仅栈内存)
*
* 2. CPU上下文切换:
* - 线程从运行态切换到等待态
* - 调度器需要选择下一个线程
* - 频繁切换导致CPU缓存失效
*
* 3. 网络连接消耗:
* - 每个轮询请求都建立TCP连接
* - SSL握手开销(如果有)
* - 序列化/反序列化成本
*/
private void calculateResourceUsage() {
int concurrentActivities = 1000;
long waitTimePerActivity = 2000; // 2秒
long pollInterval = 100; // 100ms轮询一次
// 网络请求次数
long pollRequests = (waitTimePerActivity / pollInterval) * concurrentActivities;
// 2000/100 * 1000 = 20 * 1000 = 20,000次请求!
// 线程等待时间总和
long totalWaitTime = waitTimePerActivity * concurrentActivities;
// 2000 * 1000 = 2,000,000毫秒 = 2000秒的线程等待时间
}
}
1.3 可扩展性差的根本原因
java
/**
* 扩展性瓶颈分析
*/
public class ScalabilityBottleneck {
/**
* 水平扩展限制:
*
* 1. Worker扩展有限:
* - 每个Worker有最大线程数限制(通常50-100)
* - 增加Worker会增加资源消耗
* - 业务逻辑可能限制跨Worker状态共享
*
* 2. 速率服务压力:
* - 所有Worker都轮询同一个速率服务
* - 高并发时速率服务成为瓶颈
* - 需要复杂的分片和负载均衡
*
* 3. 协调复杂度:
* - 跨多个Worker的速率控制需要协调
* - 全局速率限制实现困难
*/
/**
* 并发增长时的性能衰减曲线
*
* 并发数 | 吞吐量 | 延迟 | 资源使用率
* --------|---------|---------|-----------
* 10 | 高 | 低 | 20%
* 50 | 中 | 中 | 60%
* 100 | 低 | 高 | 90%
* 200 | 很低 | 很高 | 100% (队列积压)
*/
/**
* 排队理论分析(Little's Law)
* L = λW
* L: 系统中平均请求数
* λ: 到达率
* W: 平均等待时间
*
* 问题:轮询模式下W增长,导致L增长
* 最终系统进入恶性循环
*/
}
二、提高并发性的架构设计
2.1 架构演进路径
阶段1:简单轮询(当前)
缺点:低并发,高资源消耗
阶段2:智能轮询 + 本地缓存
改进:减少轮询频率,使用本地令牌缓存
阶段3:异步事件驱动
改进:Activity不阻塞,事件通知恢复
阶段4:流式处理 + 背压控制
改进:基于流的速率控制,自动背压
2.2 方案一:智能轮询 + 本地缓存
java
/**
* 智能轮询模式:减少等待,提高并发
*/
@Slf4j
public class SmartPollingRateLimiter {
private final DistributedRateLimitService remoteService;
private final LocalTokenCache localCache;
private final RateLimitPredictor predictor;
private final ScheduledExecutorService scheduler;
// 每个Activity实例的令牌缓存
private final Map<String, TokenReservation> reservations = new ConcurrentHashMap<>();
/**
* 核心改进点:
* 1. 批量预取令牌
* 2. 本地令牌缓存
* 3. 智能调度
* 4. 异步刷新
*/
/**
* 改进后的Activity执行逻辑
*/
public String sendMessageWithSmartPolling(Message message) {
String activityId = Activity.getExecutionContext().getInfo().getActivityId();
// 1. 首先检查本地缓存
TokenReservation reservation = reservations.get(activityId);
if (reservation != null && reservation.isValid()) {
// 使用缓存的令牌
return doSendMessage(message);
}
// 2. 智能预取:根据预测需求预取多个令牌
int predictedNeed = predictor.predictTokenNeed(activityId);
List<Token> tokens = remoteService.reserveTokens(
"send_message",
predictedNeed, // 批量预取
Duration.ofSeconds(30) // 预订30秒内使用
);
// 3. 存入本地缓存
reservation = new TokenReservation(tokens);
reservations.put(activityId, reservation);
// 4. 异步刷新令牌(不阻塞当前线程)
scheduleTokenRefresh(activityId, reservation);
// 5. 使用令牌执行
reservation.useToken();
return doSendMessage(message);
}
/**
* 令牌缓存管理
*/
private static class TokenReservation {
private final Queue<Token> tokens;
private final long expiryTime;
private volatile boolean refreshing = false;
public TokenReservation(List<Token> tokens) {
this.tokens = new ConcurrentLinkedQueue<>(tokens);
this.expiryTime = System.currentTimeMillis() + 30000; // 30秒有效期
}
public boolean isValid() {
return !tokens.isEmpty() && System.currentTimeMillis() < expiryTime;
}
public void useToken() {
tokens.poll(); // 使用一个令牌
}
public int remainingTokens() {
return tokens.size();
}
}
/**
* 预测器:智能预测令牌需求
*/
public class RateLimitPredictor {
private final Map<String, UsagePattern> patterns = new ConcurrentHashMap<>();
public int predictTokenNeed(String activityId) {
UsagePattern pattern = patterns.get(activityId);
if (pattern == null) {
// 初始预测:基于历史平均值
return 5; // 预取5个令牌
}
// 基于滑动窗口预测
return pattern.predictNextWindow();
}
private static class UsagePattern {
private final Deque<Integer> history = new ArrayDeque<>();
private final int windowSize = 10;
public void recordUsage(int tokensUsed) {
history.addLast(tokensUsed);
if (history.size() > windowSize) {
history.removeFirst();
}
}
public int predictNextWindow() {
if (history.isEmpty()) return 1;
// 简单移动平均
double avg = history.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(1.0);
// 加上20%的缓冲
return (int) Math.ceil(avg * 1.2);
}
}
}
/**
* 异步刷新机制:不阻塞Activity执行
*/
private void scheduleTokenRefresh(String activityId, TokenReservation reservation) {
// 当令牌剩余量低于阈值时触发刷新
scheduler.schedule(() -> {
if (reservation.remainingTokens() < 2 && !reservation.refreshing) {
reservation.refreshing = true;
try {
// 异步预取更多令牌
int needMore = predictor.predictTokenNeed(activityId);
List<Token> newTokens = remoteService.reserveTokens(
"send_message",
needMore,
Duration.ofSeconds(30)
);
// 合并到现有缓存
reservation.tokens.addAll(newTokens);
} finally {
reservation.refreshing = false;
}
}
}, 100, TimeUnit.MILLISECONDS); // 100ms后检查
}
}
2.3 方案二:异步事件驱动架构
java
/**
* 异步事件驱动架构:彻底解决并发瓶颈
*/
public class AsyncEventDrivenArchitecture {
/**
* 架构组件:
* 1. RateLimitEventBus: 事件总线
* 2. ActivitySuspensionService: Activity挂起/恢复服务
* 3. TokenStreamProcessor: 令牌流处理器
* 4. PriorityQueueManager: 优先级队列管理器
*/
/**
* 执行流程:
* 1. Activity注册到事件总线,然后挂起
* 2. 速率服务处理令牌,发出事件
* 3. 事件总线分发令牌事件
* 4. 对应的Activity被唤醒执行
*/
}
/**
* 事件总线实现
*/
@Component
public class RateLimitEventBus {
private final Map<String, List<EventListener>> listeners = new ConcurrentHashMap<>();
private final ExecutorService eventExecutor;
private final EventQueue eventQueue;
public RateLimitEventBus() {
// 使用Disruptor或高性能队列
this.eventQueue = new DisruptorEventQueue(1024);
this.eventExecutor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() * 2
);
startEventProcessing();
}
/**
* Activity注册监听器
*/
public void register(String activityId, EventListener listener) {
listeners.computeIfAbsent(activityId, k -> new CopyOnWriteArrayList<>())
.add(listener);
}
/**
* 发布令牌可用事件
*/
public void publishTokenAvailable(String rateKey, String consumerId) {
TokenEvent event = new TokenEvent(rateKey, consumerId, System.currentTimeMillis());
eventQueue.publish(event);
}
private void startEventProcessing() {
// 事件处理线程
new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
TokenEvent event = eventQueue.take();
// 异步处理事件
eventExecutor.submit(() -> {
List<EventListener> activityListeners = listeners.get(event.getConsumerId());
if (activityListeners != null) {
activityListeners.forEach(listener ->
listener.onTokenAvailable(event)
);
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "event-bus-processor").start();
}
}
/**
* Activity挂起服务
*/
@Component
public class ActivitySuspensionService {
private final TemporalClient temporalClient;
private final CompletableFutureStore futureStore;
/**
* 挂起Activity,返回Future
*/
public CompletableFuture<String> suspendActivity(String activityId, String taskToken) {
CompletableFuture<String> future = new CompletableFuture<>();
// 存储Future,等待事件触发
futureStore.store(activityId, future);
// 异步完成Activity当前执行
temporalClient.completeActivity(taskToken, "SUSPENDED");
return future;
}
/**
* 恢复Activity执行
*/
public void resumeActivity(String activityId, String result) {
CompletableFuture<String> future = futureStore.get(activityId);
if (future != null) {
future.complete(result);
futureStore.remove(activityId);
// 触发新的Activity执行
triggerResumedActivity(activityId, result);
}
}
private void triggerResumedActivity(String activityId, String result) {
// 通过信号或新的Activity执行来继续工作流
// 这取决于工作流设计
}
}
/**
* 流式令牌处理器
*/
@Component
public class TokenStreamProcessor {
private final Map<String, TokenBucket> buckets = new ConcurrentHashMap<>();
private final PriorityQueue<RateLimitRequest> priorityQueue;
private final ScheduledExecutorService tokenGenerator;
public TokenStreamProcessor() {
this.priorityQueue = new PriorityQueue<>(
Comparator.comparingInt(RateLimitRequest::getPriority)
.thenComparing(RateLimitRequest::getRequestTime)
);
this.tokenGenerator = Executors.newScheduledThreadPool(1);
startTokenGeneration();
}
/**
* 流式处理请求
*/
public void processStream(RateLimitRequest request) {
// 将请求加入优先级队列
priorityQueue.offer(request);
// 立即尝试处理
tryProcessQueue();
}
private void tryProcessQueue() {
while (!priorityQueue.isEmpty()) {
RateLimitRequest request = priorityQueue.peek();
TokenBucket bucket = buckets.get(request.getRateKey());
if (bucket != null && bucket.tryConsume(1)) {
// 令牌可用,从队列移除并处理
priorityQueue.poll();
// 触发事件通知Activity
eventBus.publishTokenAvailable(
request.getRateKey(),
request.getActivityId()
);
} else {
// 令牌不足,等待下次生成
break;
}
}
}
private void startTokenGeneration() {
// 按固定速率生成令牌
tokenGenerator.scheduleAtFixedRate(() -> {
buckets.values().forEach(TokenBucket::refill);
tryProcessQueue(); // 令牌生成后尝试处理队列
}, 0, 100, TimeUnit.MILLISECONDS); // 每100ms生成一次
}
}
2.4 方案三:响应式流 + 背压控制
java
/**
* 响应式流架构:最高效的并发控制
*
* 使用Reactor或RxJava实现响应式流
* 自动背压控制,防止过载
*/
@Slf4j
public class ReactiveRateLimitSystem {
private final Flux<RateLimitRequest> requestStream;
private final Sinks.Many<RateLimitRequest> requestSink;
private final Map<String, TokenBucket> buckets;
private final Scheduler scheduler;
public ReactiveRateLimitSystem() {
// 创建响应式流
this.requestSink = Sinks.many().multicast().directBestEffort();
this.requestStream = requestSink.asFlux();
this.buckets = new ConcurrentHashMap<>();
this.scheduler = Schedulers.newBoundedElastic(
50, // 最大线程数
1000, // 最大任务队列
"rate-limit-processor"
);
initializeProcessingPipeline();
}
/**
* 初始化处理流水线
*/
private void initializeProcessingPipeline() {
requestStream
// 1. 分组:按速率键分组
.groupBy(RateLimitRequest::getRateKey)
// 2. 背压控制:每组独立控制
.flatMap(group -> group
.onBackpressureBuffer(1000, // 缓冲大小
BufferOverflowStrategy.DROP_OLDEST)
// 3. 窗口化:按时间窗口处理
.window(Duration.ofMillis(100))
// 4. 并发处理:每个窗口内并发处理
.flatMap(window -> window
.parallel()
.runOn(Schedulers.parallel())
.map(this::tryAcquireToken)
.sequential()
)
// 5. 限流:控制处理速率
.limitRate(1000) // 每秒最多1000个
)
// 6. 处理结果
.subscribe(
this::handleProcessedRequest,
this::handleError,
() -> log.info("Stream completed")
);
}
/**
* 提交请求到流
*/
public Mono<String> submitRequest(RateLimitRequest request) {
return Mono.create(sink -> {
// 创建可取消的请求
CancellableRequest cancellableRequest =
new CancellableRequest(request, sink);
// 将请求推送到流
try {
requestSink.tryEmitNext(cancellableRequest);
// 设置超时
cancellableRequest.setTimeout(Duration.ofSeconds(30));
} catch (Exception e) {
sink.error(e);
}
});
}
/**
* 尝试获取令牌(非阻塞)
*/
private ProcessedRequest tryAcquireToken(RateLimitRequest request) {
TokenBucket bucket = buckets.computeIfAbsent(
request.getRateKey(),
k -> new TokenBucket(1.0) // 每秒1个令牌
);
boolean acquired = bucket.tryConsume(1);
return new ProcessedRequest(request, acquired, System.currentTimeMillis());
}
/**
* 处理已处理的请求
*/
private void handleProcessedRequest(ProcessedRequest processed) {
if (processed.isAcquired()) {
// 令牌获取成功,通知Activity
eventBus.publishTokenAvailable(
processed.getRequest().getRateKey(),
processed.getRequest().getActivityId()
);
} else {
// 重新排队(带延迟)
scheduleRetry(processed.getRequest());
}
}
/**
* 智能重试调度
*/
private void scheduleRetry(RateLimitRequest request) {
// 计算重试延迟(指数退避)
long delay = calculateRetryDelay(request.getRetryCount());
Mono.delay(Duration.ofMillis(delay))
.subscribeOn(scheduler)
.subscribe(tick -> {
request.incrementRetryCount();
requestSink.tryEmitNext(request);
});
}
/**
* 背压状态监控
*/
@Component
public class BackpressureMonitor {
private final MeterRegistry meterRegistry;
private final Map<String, BackpressureStats> stats = new ConcurrentHashMap<>();
@Scheduled(fixedRate = 5000)
public void monitorBackpressure() {
requestSink.asFlux()
.onBackpressureBuffer()
.doOnNext(request -> {
// 监控每个速率键的队列深度
String rateKey = request.getRateKey();
BackpressureStats stat = stats.computeIfAbsent(
rateKey, k -> new BackpressureStats()
);
stat.recordRequest();
// 发布指标
meterRegistry.gauge("rate.limit.queue.depth",
Tags.of("rateKey", rateKey),
stat.getQueueDepth()
);
// 动态调整:如果队列过深,增加处理速率
if (stat.getQueueDepth() > 1000) {
adjustProcessingRate(rateKey, stat.getQueueDepth());
}
});
}
private void adjustProcessingRate(String rateKey, int queueDepth) {
// 动态调整令牌生成速率
TokenBucket bucket = buckets.get(rateKey);
if (bucket != null) {
// 临时增加令牌生成速率
double currentRate = bucket.getRate();
double newRate = Math.min(currentRate * 1.5, 1000.0); // 不超过1000/秒
bucket.setRate(newRate);
// 30秒后恢复
scheduler.schedule(() ->
bucket.setRate(currentRate),
30, TimeUnit.SECONDS
);
}
}
}
}
三、扩展性设计模式
3.1 分片模式(Sharding)
java
/**
* 分片模式:水平扩展的核心
*/
public class RateLimitSharding {
/**
* 分片策略:
* 1. 基于Key哈希分片
* 2. 基于范围分片
* 3. 基于地理位置分片
* 4. 动态分片
*/
/**
* 一致性哈希分片实现
*/
public class ConsistentHashSharding {
private final SortedMap<Integer, ShardNode> circle = new TreeMap<>();
private final int numberOfReplicas = 100; // 虚拟节点数
public void addNode(ShardNode node) {
for (int i = 0; i < numberOfReplicas; i++) {
int hash = hash(node.getAddress() + ":" + i);
circle.put(hash, node);
}
}
public ShardNode getNode(String key) {
if (circle.isEmpty()) return null;
int hash = hash(key);
// 找到第一个大于等于hash的节点
SortedMap<Integer, ShardNode> tailMap = circle.tailMap(hash);
Integer nodeHash = tailMap.isEmpty() ?
circle.firstKey() : tailMap.firstKey();
return circle.get(nodeHash);
}
public String getShardKey(String originalKey) {
ShardNode node = getNode(originalKey);
return node != null ? node.getShardKey(originalKey) : originalKey;
}
}
/**
* 分片感知的速率限制器
*/
public class ShardedRateLimiter {
private final ConsistentHashSharding sharding;
private final Map<String, DistributedRateLimiter> shardLimiters;
public boolean tryAcquire(String originalKey, int tokens) {
// 1. 计算分片键
String shardKey = sharding.getShardKey(originalKey);
// 2. 获取分片对应的限流器
DistributedRateLimiter limiter = shardLimiters.get(shardKey);
// 3. 在分片内执行限流
return limiter.tryAcquire(originalKey, tokens);
}
/**
* 动态重分片(无停机扩容)
*/
public void rebalance(Set<ShardNode> newNodes) {
// 1. 暂停新请求
pauseRequests();
// 2. 迁移数据
migrateData(newNodes);
// 3. 更新路由
updateRouting(newNodes);
// 4. 恢复请求
resumeRequests();
}
}
}
3.2 多层缓存架构
java
/**
* 多层缓存:减少远程调用
*
* L1: 本地内存缓存(毫秒级)
* L2: 本地Redis缓存(微秒级)
* L3: 远程分布式缓存(毫秒级)
* L4: 持久化存储(秒级)
*/
public class MultiLevelCacheRateLimiter {
private final Cache<String, TokenBucket> l1Cache; // Caffeine
private final RedisTemplate<String, byte[]> l2Cache; // Redis
private final HazelcastInstance l3Cache; // 分布式缓存
private final DatabaseRepository l4Storage; // 数据库
/**
* 缓存读取策略
*/
public TokenBucket getTokenBucket(String key) {
// 1. 检查L1缓存
TokenBucket bucket = l1Cache.getIfPresent(key);
if (bucket != null) {
return bucket;
}
// 2. 检查L2缓存
byte[] l2Data = l2Cache.opsForValue().get(key);
if (l2Data != null) {
bucket = deserialize(l2Data);
l1Cache.put(key, bucket);
return bucket;
}
// 3. 检查L3缓存
bucket = l3Cache.getMap("rate_buckets").get(key);
if (bucket != null) {
// 填充L2和L1缓存
l2Cache.opsForValue().set(key, serialize(bucket), 1, TimeUnit.MINUTES);
l1Cache.put(key, bucket);
return bucket;
}
// 4. 从数据库加载
bucket = l4Storage.loadTokenBucket(key);
if (bucket != null) {
// 填充所有缓存层
l3Cache.getMap("rate_buckets").put(key, bucket);
l2Cache.opsForValue().set(key, serialize(bucket), 5, TimeUnit.MINUTES);
l1Cache.put(key, bucket);
}
return bucket;
}
/**
* 缓存更新策略
*/
public void updateTokenBucket(String key, TokenBucket bucket) {
// 1. 更新数据库(最终一致性)
l4Storage.saveTokenBucketAsync(key, bucket);
// 2. 更新L3缓存
l3Cache.getMap("rate_buckets").putAsync(key, bucket);
// 3. 更新L2缓存
l2Cache.opsForValue().set(key, serialize(bucket), 5, TimeUnit.MINUTES);
// 4. 使L1缓存失效
l1Cache.invalidate(key);
}
}
3.3 自适应限流算法
java
/**
* 自适应限流:根据系统负载动态调整
*/
public class AdaptiveRateLimiting {
/**
* 基于PID控制器的自适应限流
*/
public class PIDRateController {
private double Kp = 1.0; // 比例系数
private double Ki = 0.1; // 积分系数
private double Kd = 0.01; // 微分系数
private double lastError = 0;
private double integral = 0;
public double calculateRate(double targetLoad, double currentLoad) {
// 计算误差
double error = targetLoad - currentLoad;
// PID计算
double proportional = Kp * error;
integral += Ki * error;
double derivative = Kd * (error - lastError);
// 更新状态
lastError = error;
// 计算新速率
double adjustment = proportional + integral + derivative;
// 应用限制
return Math.max(0.1, Math.min(1000.0, adjustment));
}
}
/**
* 基于机器学习的智能限流
*/
public class MLRateLimiter {
private final Model model;
private final FeatureExtractor featureExtractor;
public double predictOptimalRate(String context) {
// 提取特征
Features features = featureExtractor.extract(context);
// 模型预测
return model.predict(features);
}
public void trainModel(List<TrainingExample> examples) {
// 在线学习:根据系统表现调整模型
model.update(examples);
}
}
/**
* 系统负载监控
*/
public class SystemLoadMonitor {
@Scheduled(fixedRate = 1000)
public void monitorAndAdjust() {
// 监控关键指标
double cpuUsage = getCpuUsage();
double memoryUsage = getMemoryUsage();
double networkLatency = getNetworkLatency();
double queueDepth = getQueueDepth();
// 计算综合负载分数
double loadScore = calculateLoadScore(
cpuUsage, memoryUsage, networkLatency, queueDepth
);
// 根据负载调整限流策略
if (loadScore > 0.8) {
// 高负载:降低限流阈值
adjustRateLimits(-0.2); // 降低20%
} else if (loadScore < 0.3) {
// 低负载:提高限流阈值
adjustRateLimits(+0.1); // 提高10%
}
}
}
}
四、未来扩展路线图
4.1 技术演进路径
yaml
版本1.0(当前):
架构: 轮询等待模式
并发: 100-1000 TPS
扩展性: 垂直扩展(增加Worker)
可靠性: 中等
复杂度: 低
版本2.0(6个月):
架构: 智能轮询 + 本地缓存
并发: 1000-10000 TPS
扩展性: 有限水平扩展
可靠性: 高
复杂度: 中
版本3.0(12个月):
架构: 异步事件驱动
并发: 10000-100000 TPS
扩展性: 良好水平扩展
可靠性: 很高
复杂度: 高
版本4.0(18个月):
架构: 响应式流 + 智能分片
并发: 100000+ TPS
扩展性: 弹性自动扩展
可靠性: 极高
复杂度: 很高
4.2 容量规划建议
java
/**
* 容量规划工具
*/
public class CapacityPlanner {
/**
* 基于业务的容量规划
*/
public CapacityPlan planForBusiness(double growthRate, int months) {
// 当前指标
double currentTPS = 1000;
double currentLatency = 100; // ms
// 预测未来需求
double predictedTPS = currentTPS * Math.pow(1 + growthRate, months);
// 计算所需资源
return calculateRequiredResources(predictedTPS, currentLatency);
}
/**
* 资源需求计算
*/
private CapacityPlan calculateRequiredResources(double tps, double targetLatency) {
// 根据排队理论计算
CapacityPlan plan = new CapacityPlan();
// Worker数量计算
plan.setWorkersNeeded((int) Math.ceil(tps / 1000)); // 每个Worker处理1000 TPS
// 内存计算
plan.setMemoryGB(plan.getWorkersNeeded() * 4); // 每个Worker 4GB
// CPU计算
plan.setCpuCores(plan.getWorkersNeeded() * 2); // 每个Worker 2核
// 存储计算
plan.setStorageGB((int) (tps * 0.1 * 24 * 30)); // 每月存储
return plan;
}
}
五、总结与建议
5.1 当前优化建议
-
短期(立即实施):
- 使用指数退避减少轮询频率
- 实现本地令牌缓存
- 添加智能预取机制
-
中期(3个月内):
- 引入异步事件处理
- 实现基本的分片策略
- 添加多层缓存支持
-
长期(6个月后):
- 迁移到响应式架构
- 实现智能自适应限流
- 构建弹性扩展能力
5.2 关键指标监控
java
// 必须监控的指标
public enum CriticalMetrics {
WORKER_THREAD_UTILIZATION, // Worker线程使用率
ACTIVITY_WAIT_TIME, // Activity平均等待时间
RATE_LIMIT_QUEUE_DEPTH, // 限流队列深度
TOKEN_ACQUISITION_LATENCY, // 令牌获取延迟
ERROR_RATE, // 错误率
SYSTEM_LOAD_SCORE, // 系统负载分数
CACHE_HIT_RATIO, // 缓存命中率
NETWORK_IO_OVERHEAD // 网络IO开销
}
5.3 决策框架
当面临扩展性问题时,使用以下决策树:
是否并发超过1000 TPS?
├── 否 → 保持轮询模式,简单优化
└── 是 → 评估等待时间
├── 平均等待 < 1秒 → 使用智能轮询 + 缓存
├── 平均等待 1-10秒 → 使用异步事件驱动
└── 平均等待 > 10秒 → 使用响应式流架构
最终建议:从简单的轮询模式开始,但设计时考虑到未来演进。实现模块化的架构,方便后续逐步升级到更高级的模式。监控关键指标,当系统达到瓶颈时,按照规划好的演进路径进行升级。