方罗交接
亚马逊交接
广州同步店铺框架搭建
QPS-Starter 集成文档
刊登规则引擎技术方案
MQ异常补偿方案
负责项目总目录
广州侧同步商品平台
本文档使用 MrDoc 发布
-
+
首页
QPS-Starter 集成文档
## 一. 简介 QPS-Starter组件是用来限制访问接口频率工具, 方便公司同学们在项目当中直接使用,避免重复造轮子情况。该组件主要是提供了固定窗口(计数器), 滑动窗口, 令牌桶三种限流算法。同时在达成限流条件时还提供了重试,兜底策略,抛异常等三种处理方式。 **源码地址:** http://git.mabangerp.com:2280/mc/mc-biz-base-modules/-/tree/develop **PS:其中固定窗口,滑动窗口底层是针对于==店铺==级别维度,而令牌桶底层则是针对于==相同业务key==请求级别维度。整个项目要在SpringCloud环境下运行!!!!,需要配合陈俊安写的==@ShopTokenAop注解==使用!!!!** ## 二. POM.XML配置 ```xml <dependency> <groupId>com.mabang</groupId> <artifactId>biz-mps-qps-starter</artifactId> <version>1.0-SNAPSHOT</version> </dependency> ``` ## 三. YAML配置 ```yaml mps: qps: enable: true redisTemplateBean: defaultTemplete ``` **同时因为该工具底层使用了Redis,Redisson,Cache,因此还需要配置Redis,Redisson和Cache** ```yaml mps: #QPS组件配置 qps: enable: true redisTemplateBean: defaultTemplete mdc: #业务Cache cache: enable: true session: redis-template-bean: defaultTemplete shop-token: redis-template-bean: defaultTemplete #Redis redis: enable: true auto-write: true primary: defaultTemplete servers: defaultTemplete: host: redis-orderdetail.mabangerp.com port: 6379 password: ${password} timeout: 10000 database: 2 #分布式锁 redisson: enable: true single-address: redis-orderdetail.mabangerp.com:6379 password: ${password} database: 1 timeout: 10000 ``` ## 四.介绍@QPSThreshold注解 ```java public @interface QPSThreshold { /** * 业务key 业务用户自定义key 规范:${PlatFormName}:${BusinessType}:${Key} 例如NEWEGG:PUBLISH:ITEM_BASIC_INFO_REPORT required */ String key(); /** * 默认过期时间 1分钟(适用于滑动窗口, 计数器) */ long validTime() default 1L; /** * 默认单位分钟(==令牌桶模式下切记不要改成秒或毫秒,否则大量线程会占用CPU时间片,导致业务线程阻塞超时==) */ TimeUnit timeUnit() default TimeUnit.MINUTES; /** * 总数:建议总数是接口阈值80-90% (所有模式) required */ long total(); /** * 每次加入令牌数量(默认5个) */ long addTokenRatePer() default 5L; /** * 多少时间一次加入令牌数量(默认1分钟) */ long per() default 1; /** * 回调方法 */ String callbackMethod() default "defaultCallbackMethod"; /** * 重试间隔时间 单位:毫秒 */ long retryTimeInterval() default 1000L; /** * 触发 QPS 后的方案 */ FallBack fallback() default FallBack.RETRY; /** * 限流方案 默认计数器 * PS: 计数器和滑动窗口是针对于店铺限流, 令牌桶针对于全局限流 */ Strategy strategy() default Strategy.COUNTER; } ``` ## 五.使用说明 #### 1.简单使用(默认固定窗口计数器模式) ```java @RequestMapping("/qps") @RestController public class QpsController { // 必须要配合@ShopTokenAop注解使用(任意Bean都可以, 不仅仅只是在Controller) @ShopTokenAop @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}) @RequestMapping("/testQps") public Boolean testQps(String i) { return Boolean.TRUE; } } ``` 或者在其它接口上 ```java @Service public class TestServiceImpl implements TestService { @ShopTokenAop @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}) @Override public void test() { } } ``` #### 2.滑动窗口使用介绍 ```java @RequestMapping("/qps") @RestController public class QpsController { // 只需要把策略切换成Strategy.SLIDING_WINDOW即可 @ShopTokenAop @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}, callbackMethod = "test1", strategy = Strategy.SLIDING_WINDOW) @RequestMapping("/testQps") public Boolean testQps(String i) { return Boolean.TRUE; } /** * 限流兜底函数(ThresholdContext thresholdContext这个参数是必加) * * @param i 参数跟主函数一致 * @param thresholdContext 回调兜底上下文,里面包含gapValidTime距离有效时间 */ private void test1(String i, ThresholdContext thresholdContext) { } } ``` #### 3.令牌桶使用介绍 ```java @RequestMapping("/qps") @RestController public class QpsController { // 相同令牌桶配置要完全一致!否则只会默认取其中一个配置,另一个不会生效 @ShopTokenAop @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}, callbackMethod = "test1", strategy = Strategy.TOKEN_BUCKET) @RequestMapping("/testQps") public Boolean testQps(String i) { return Boolean.TRUE; } // 相同令牌桶配置要完全一致!否则只会默认取其中一个配置,另一个不会生效 @ShopTokenAop @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}, callbackMethod = "test1", strategy = Strategy.TOKEN_BUCKET) @RequestMapping("/testQps1") public Boolean testQps1(String i) { return Boolean.TRUE; } /** * 限流兜底函数(ThresholdContext thresholdContext这个参数是必加) * * @param i 参数跟主函数一致 * @param thresholdContext 回调兜底上下文,里面包含gapValidTime距离有效时间 */ private void test1(String i, ThresholdContext thresholdContext) { } } ``` #### 4.兜底回调介绍 **第一种是目标类内部定义兜底函数(针对于各自不同实现兜底回调实现, 优先级高于下述全局统一)** ```java @RequestMapping("/qps") @RestController public class QpsController { // 必须要配合@ShopTokenAop注解使用 @ShopTokenAop // 自定义回调方法"test1" @QPSThreshold(key = ${PLATFORM:BusinessType:Key}, total = ${total:总数:建议总数是接口阈值80-90%}, callbackMethod = "test1") @RequestMapping("/testQps") public Boolean testQps(String i) { return Boolean.TRUE; } /** * 限流兜底函数(ThresholdContext thresholdContext这个参数是必加) * * @param i 参数跟主函数一致 * @param thresholdContext 回调兜底上下文,里面包含gapValidTime距离有效时间 */ private void test1(String i, ThresholdContext thresholdContext) { } } ``` **第二种是实现接口来做兜底回调(针对于全局统一)** ```java // 接口兜底回调实现类一定要注入Spring上下文!!!!!!!! @Component public class FallBackHandler implements CallBackInterface { @Override public void fallback(ThresholdContext thresholdContext) { // 获取参数值 thresholdContext.getJoinPoint().getArgs(); // 获取方法标签等 MethodSignature method = (MethodSignature) thresholdContext.getJoinPoint().getSignature(); // 其它的参考 ProceedingJoinPoint API } } ``` **PS: 因为平台接口调用次数和工具接口限流次数会有不一致情况。因此作者建议还是在业务层面上处理Too Many Request情况** ## 六.介绍ThresholdContext ```java @Builder @Data public class ThresholdContext { /** * 注解对象 */ private QPSThreshold qpsThreshold; /** * Redis操作类 */ private RedisTemplate<String, Object> redisTemplate; /** * Redission 操作类 */ private final RedissonClient redissonClient; /** * key */ private String redisKey; /** * 目标函数切点 */ private ProceedingJoinPoint joinPoint; /** * method 目标函数 */ private Method method; /** * 距离有效时间差:毫秒(令牌桶模式不生效) */ private long gapValidTime; /** * 剩余次数(固定窗口模式有效) */ private long limit; } ``` ## 七.动态刷新(现仅适用于亚马逊!!!) #### 1.因为亚马逊平台的特殊性,因此改功能是专门为亚马逊平台订制研发功能。主要目的是通过亚马逊接口响应Header头部信息返回的每秒使用数量来达到动态刷新令牌数功能 ```java /** * 动态刷新的QPS策略必须用Strategy.SLIDING_WINDOW_PER */ @Override @QPSThreshold(key = "AMAZON:PUBLISH_QPS:CREATE_FEED_DOCUMENT", total = 15, fallback = FallBack.CALLBACK, strategy = Strategy.SLIDING_WINDOW_PER, addTokenRatePer = 0.0083, per = 1, timeUnit = TimeUnit.SECONDS) public AmazonResult<CreateFeedDocumentResult> createFeedDocument(ProcessContext processContext, AmazonBaseRequestBo<CreateFeedDocumentBo> createFeedDocumentBo, String appKey, String appSecret) { } ``` #### 2.在接口响应处把响应头部里的阈值传进QpsUtil工具类, 这样内部会*自动根据header头部*返回的阈值来做动态变更 ```java // 接口处把响应头header's map传进来, 并且在内部获取头部key-value设置 QpsUtil.refreshBucketPerByMinute(headerJsonObj.getInnerMap(), (map -> { RefreshBucketPerObject obj = new RefreshBucketPerObject(); // 1秒钟增加的令牌阈值数 obj.setAddTokenRatePer(map.get("x-amzn-RateLimit-Limit")); obj.setPer(1L); obj.setTimeUnit(TimeUnit.SECONDS); return obj; })); ``` #### 3.上一步骤做的动态变更会根据用户请求是否携带Header头MB_REDIS_KEY来作为唯一键生效,如果没有携带MB_REDIS_KEY唯一键, 那么就会自己组装merchantId + site的方式来做唯一键。最后*每次动态变更都需要做清除,否则只会对第一次生效* 举例: 1) 假设用户请求头携带MB_REDIS_KEY 为 "12345678",那么在做请求限流和动态变更时都会把这个"12345678" 拼接到Redis里面做唯一key, 当要重新做动态变更时必须使用下面方法来清除以后才能做下一次动态变更 ```java QpsUtil.clean("${key}", ${MB_REDIS_KEY}); ``` 2) 假设用户请求头没有携带MB_REDIS_KEY,那么在做请求限流和动态变更时会根据店铺自身的merchantId + site的方式 拼接到Redis里面做唯一key, 当要重新做动态变更时必须使用下面方法来清除以后才能做下一次动态变更 ```java QpsUtil.clean("${key}", ${merchantId}, ${site}); ``` 3) 最简单的方法, 不用上面两种。直接重启项目即可 **为什么要这样设定?** 因为99.99%概率同一个平台接口reponse header头返回的速率值是相同的,防止同一个接口每一次都要和Redis做一次网络IO。如果平台接口QPS限制变了,那么只需要重启一次项目即可, 或者不用重启的情况下单独使用SDK内部接口去清理某一个${key} + ${merchanId} + ${site} 或 ${key} + ${MB_REDIS_KEY}的标记 ## 八.Q&A #### 1.滑动窗口算法是不是均摊次数? 不是有效时间内均摊次数,它其实也是固定窗口一个变种,只不过把一个窗口恢复拆分成多个窗口恢复罢了。而非均摊 #### 2.为什么报获取锁超时? 在测试过程中如果在兜底回调函数进入Debug断点模式超过一定时长 (Redisson lockWatchDogTimeOut默认是30s),那么在多线程场景下去tryLock相同Redis Key会造成内部TimeOutException导致异常信息 ```java java.util.concurrent.CancellationException at java.util.concurrent.CompletableFuture.cancel(CompletableFuture.java:2276) at org.redisson.RedissonLock.tryLock(RedissonLock.java:235) ``` 这其实是Redisson 内部看门狗机制,**忽略**这个异常信息即可 #### 3.为什么total总数阈值在80-90% ? 其实是减少调用接口失败的一个阈值限制。比如说接口本来调用次数是20次,然后在加QPS的时候 已经消费了4次了,这个时候QPS这边Redis没有任何调用记录,就意味着当你再调用16次时接口那边满了但是QPS这边其实才16次,然后再继续调用接口时QPS没有拦住,调用方报Too Many Request。 这个时候是没办法进入QPS注解里设定的拦截兜底函数。除非在业务代码里面自己重新写一套重试业务逻辑(不是兜底函数)
thread
2023年8月24日 10:18
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码