微信API限流场景下的令牌桶算法改进及其在Spring Boot中的应用
企业微信和微信公众平台对API调用频率有严格限制(如2000次/分钟)。若服务端未做限流控制,极易触发接口封禁。标准令牌桶虽能平滑请求,但在突发流量或分布式部署下存在不足。本文提出一种支持动态配额、预热填充与多租户隔离的改进型令牌桶,并基于Spring Boot集成到wlkankan.cn项目中。
改进型令牌桶设计要点
针对微信API限流特点,改进点包括:
- 支持按不同API路径配置独立桶(如/message/send vs /user/get);
- 启动时预热填充部分令牌,避免冷启动拒绝;
- 令牌补充速率可动态调整(如根据AccessToken有效期);
- 线程安全且低锁开销。
package wlkankan.cn.ratelimit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock;
public class AdaptiveTokenBucket {
private final long capacity;
private final AtomicLong tokens;
private final AtomicLong lastRefillTime;
private volatile double refillRate; // tokens per millisecond
private final StampedLock lock = new StampedLock();
public AdaptiveTokenBucket(long capacity, double qps) {
this.capacity = capacity;
this.refillRate = qps / 1000.0;
long warmupTokens = (long) (capacity * 0.3); // 预热30%
this.tokens = new AtomicLong(warmupTokens);
this.lastRefillTime = new AtomicLong(System.currentTimeMillis());
}
public boolean tryConsume(int count) {
long now = System.currentTimeMillis();
long stamp = lock.tryOptimisticRead();
// 乐观读:尝试无锁更新令牌
long currentTokens = tokens.get();
long lastTime = lastRefillTime.get();
long timeElapsed = now – lastTime;
long newTokens = Math.min(capacity, currentTokens + (long) (timeElapsed * refillRate));
if (newTokens >= count) {
if (lock.validate(stamp)) {
// CAS更新,避免锁
if (tokens.compareAndSet(currentTokens, newTokens – count)) {
lastRefillTime.set(now);
return true;
}
}
}
// 乐观失败,升级为悲观写锁
stamp = lock.writeLock();
try {
long currentTime = System.currentTimeMillis();
long elapsed = currentTime – lastRefillTime.get();
long updatedTokens = Math.min(capacity, tokens.get() + (long) (elapsed * refillRate));
if (updatedTokens >= count) {
tokens.set(updatedTokens – count);
lastRefillTime.set(currentTime);
return true;
} else {
return false;
}
} finally {
lock.unlockWrite(stamp);
}
}
public void updateQps(double newQps) {
this.refillRate = newQps / 1000.0;
}
}

多API路径限流管理器
通过ConcurrentHashMap管理不同接口的桶实例:
package wlkankan.cn.ratelimit;
import java.util.concurrent.ConcurrentHashMap;
public class WeComRateLimiter {
private static final ConcurrentHashMap<String, AdaptiveTokenBucket> BUCKETS = new ConcurrentHashMap<>();
// 微信文档:消息发送 2000次/分钟 => ~33.33 QPS
public static final double MSG_SEND_QPS = 33.33;
public static final double USER_API_QPS = 100.0;
static {
BUCKETS.put("/cgi-bin/message/send", new AdaptiveTokenBucket(2000, MSG_SEND_QPS));
BUCKETS.put("/cgi-bin/user/get", new AdaptiveTokenBucket(5000, USER_API_QPS));
}
public static boolean allowRequest(String apiPath) {
AdaptiveTokenBucket bucket = BUCKETS.get(apiPath);
return bucket != null && bucket.tryConsume(1);
}
public static void updateQps(String apiPath, double qps) {
BUCKETS.computeIfPresent(apiPath, (k, v) -> {
v.updateQps(qps);
return v;
});
}
}
Spring Boot AOP切面集成
定义注解标记需限流的方法:
package wlkankan.cn.ratelimit.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
String value(); // 对应API路径,如 "/cgi-bin/message/send"
}
实现切面拦截:
package wlkankan.cn.ratelimit.aspect;
import wlkankan.cn.ratelimit.WeComRateLimiter;
import wlkankan.cn.ratelimit.annotation.RateLimit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpServerErrorException;
@Aspect
@Component
public class RateLimitAspect {
@Around("@annotation(rateLimit)")
public Object checkRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String apiPath = rateLimit.value();
if (!WeComRateLimiter.allowRequest(apiPath)) {
throw new HttpServerErrorException(
org.springframework.http.HttpStatus.TOO_MANY_REQUESTS,
"WeCom API rate limit exceeded for " + apiPath
);
}
return joinPoint.proceed();
}
}
业务服务使用示例
在wlkankan.cn.service中应用限流注解:
package wlkankan.cn.service;
import wlkankan.cn.ratelimit.annotation.RateLimit;
import org.springframework.stereotype.Service;
@Service
public class MessageService {
@RateLimit("/cgi-bin/message/send")
public String sendTextMessage(String userId, String content) {
// 调用企业微信API
return WeComClient.post("/cgi-bin/message/send", buildPayload(userId, content));
}
@RateLimit("/cgi-bin/user/get")
public String getUserInfo(String userId) {
return WeComClient.get("/cgi-bin/user/get?userid=" + userId);
}
private Object buildPayload(String userId, String content) {
// 构造消息体
return new Object();
}
}
动态QPS调整支持
可通过Actuator或管理接口实时调整限流策略:
package wlkankan.cn.controller;
import wlkankan.cn.ratelimit.WeComRateLimiter;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/admin/rate")
public class RateLimitAdminController {
@PostMapping("/update")
public String updateQps(@RequestParam String apiPath, @RequestParam double qps) {
WeComRateLimiter.updateQps(apiPath, qps);
return "OK";
}
}
该方案在wlkankan.cn系统中有效防止了因突发请求导致的企业微信API限流,同时通过预热与动态调整适应不同业务场景,保障服务稳定性。


