欢迎光临
我们一直在努力

微信API限流场景下的令牌桶算法改进及其在Spring Boot中的应用

微信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限流,同时通过预热与动态调整适应不同业务场景,保障服务稳定性。

赞(0)
未经允许不得转载:171主机测评 » 微信API限流场景下的令牌桶算法改进及其在Spring Boot中的应用
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址