parent
f8fd24ce32
commit
59a41de04e
@ -0,0 +1,67 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.filter; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiContext; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.RouteConfig; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.SopConstants; |
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException; |
||||||
|
import com.gitee.sop.gatewaycommon.limit.LimitManager; |
||||||
|
import com.gitee.sop.gatewaycommon.limit.LimitType; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; |
||||||
|
import com.gitee.sop.gatewaycommon.message.ErrorImpl; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ParamNames; |
||||||
|
import com.gitee.sop.gatewaycommon.util.RouteUtil; |
||||||
|
import com.gitee.sop.gatewaycommon.validate.Validator; |
||||||
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain; |
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.web.server.ServerWebExchange; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class LimitFilter implements GlobalFilter, Ordered { |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { |
||||||
|
ApiConfig apiConfig = ApiConfig.getInstance(); |
||||||
|
// 限流功能未开启,直接返回
|
||||||
|
if (!apiConfig.isOpenLimit()) { |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
Map<String, ?> apiParam = exchange.getAttribute(SopConstants.CACHE_API_PARAM); |
||||||
|
String routeId = apiParam.get(ParamNames.API_NAME).toString() + apiParam.get(ParamNames.VERSION_NAME); |
||||||
|
RouteConfigManager routeConfigManager = apiConfig.getRouteConfigManager(); |
||||||
|
RouteConfig routeConfig = routeConfigManager.get(routeId); |
||||||
|
if (routeConfig == null) { |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
// 某个路由限流功能未开启
|
||||||
|
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) { |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
byte limitType = routeConfig.getLimitType().byteValue(); |
||||||
|
LimitManager limitManager = ApiConfig.getInstance().getLimitManager(); |
||||||
|
if (limitType == LimitType.LEAKY_BUCKET.getType()) { |
||||||
|
boolean acquire = limitManager.acquire(routeConfig); |
||||||
|
if (!acquire) { |
||||||
|
throw new ApiException(new ErrorImpl(routeConfig.getLimitCode(), routeConfig.getLimitMsg())); |
||||||
|
} |
||||||
|
} else if (limitType == LimitType.TOKEN_BUCKET.getType()) { |
||||||
|
limitManager.acquireToken(routeConfig); |
||||||
|
} |
||||||
|
return chain.filter(exchange); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getOrder() { |
||||||
|
return Orders.LIMIT_ORDER; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.gateway.filter; |
||||||
|
|
||||||
|
import org.springframework.core.Ordered; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class Orders { |
||||||
|
/** 验证拦截器order */ |
||||||
|
public static final int VALIDATE_ORDER = Ordered.HIGHEST_PRECEDENCE + 1000; |
||||||
|
|
||||||
|
/** 验证拦截器order */ |
||||||
|
public static final int LIMIT_ORDER = VALIDATE_ORDER + 1; |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.limit; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.RouteConfig; |
||||||
|
import com.google.common.cache.LoadingCache; |
||||||
|
import lombok.extern.slf4j.Slf4j; |
||||||
|
|
||||||
|
import java.util.concurrent.ExecutionException; |
||||||
|
import java.util.concurrent.atomic.AtomicLong; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
@Slf4j |
||||||
|
public class DefaultLimitManager implements LimitManager { |
||||||
|
|
||||||
|
@Override |
||||||
|
public double acquireToken(RouteConfig routeConfig) { |
||||||
|
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
if (LimitType.LEAKY_BUCKET.getType() == routeConfig.getLimitType().byteValue()) { |
||||||
|
throw new IllegalStateException("漏桶策略无法调用此方法"); |
||||||
|
} |
||||||
|
return routeConfig.fetchRateLimiter().acquire(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean acquire(RouteConfig routeConfig) { |
||||||
|
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (LimitType.TOKEN_BUCKET.getType() == routeConfig.getLimitType().byteValue()) { |
||||||
|
throw new IllegalStateException("令牌桶策略无法调用此方法"); |
||||||
|
} |
||||||
|
int execCountPerSecond = routeConfig.getExecCountPerSecond(); |
||||||
|
long currentSeconds = System.currentTimeMillis() / 1000; |
||||||
|
try { |
||||||
|
LoadingCache<Long, AtomicLong> counter = routeConfig.getCounter(); |
||||||
|
// 被限流了
|
||||||
|
if (counter.get(currentSeconds).incrementAndGet() > execCountPerSecond) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} catch (ExecutionException e) { |
||||||
|
log.error("漏桶限流出错,routeConfig", routeConfig, e); |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.limit; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.RouteConfig; |
||||||
|
|
||||||
|
/** |
||||||
|
* 限流管理 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public interface LimitManager { |
||||||
|
|
||||||
|
/** |
||||||
|
* 从令牌桶中获取令牌,如果使用{@link LimitType#TOKEN_BUCKET |
||||||
|
* RateType.TOKEN_BUCKET}限流策略,则该方法生效 |
||||||
|
* |
||||||
|
* @param routeConfig 路由配置 |
||||||
|
* @return 返回耗时时间,秒 |
||||||
|
*/ |
||||||
|
double acquireToken(RouteConfig routeConfig); |
||||||
|
|
||||||
|
/** |
||||||
|
* 是否需要限流,如果使用{@link LimitType#LEAKY_BUCKET |
||||||
|
* RateType.LIMIT}限流策略,则该方法生效 |
||||||
|
* |
||||||
|
* @param routeConfig 路由配置 |
||||||
|
* @return 如果返回true,表示可以执行业务代码,返回false则需要限流 |
||||||
|
*/ |
||||||
|
boolean acquire(RouteConfig routeConfig); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.limit; |
||||||
|
|
||||||
|
/** |
||||||
|
* 限流策略 |
||||||
|
* |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public enum LimitType { |
||||||
|
/** |
||||||
|
* 漏桶策略。每秒处理固定数量的请求,超出请求返回错误信息。 |
||||||
|
*/ |
||||||
|
LEAKY_BUCKET(1), |
||||||
|
/** |
||||||
|
* 令牌桶策略,每秒放置固定数量的令牌数,不足的令牌数做等待处理,直到拿到令牌为止。 |
||||||
|
*/ |
||||||
|
TOKEN_BUCKET(2); |
||||||
|
|
||||||
|
private byte type; |
||||||
|
|
||||||
|
LimitType(int type) { |
||||||
|
this.type = (byte)type; |
||||||
|
} |
||||||
|
|
||||||
|
public byte getType() { |
||||||
|
return type; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
package com.gitee.sop.gatewaycommon.zuul.filter; |
||||||
|
|
||||||
|
import com.gitee.sop.gatewaycommon.bean.ApiConfig; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.RouteConfig; |
||||||
|
import com.gitee.sop.gatewaycommon.bean.TargetRoute; |
||||||
|
import com.gitee.sop.gatewaycommon.exception.ApiException; |
||||||
|
import com.gitee.sop.gatewaycommon.limit.LimitManager; |
||||||
|
import com.gitee.sop.gatewaycommon.limit.LimitType; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager; |
||||||
|
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext; |
||||||
|
import com.gitee.sop.gatewaycommon.message.ErrorImpl; |
||||||
|
import com.gitee.sop.gatewaycommon.param.ApiParam; |
||||||
|
import com.gitee.sop.gatewaycommon.zuul.ZuulContext; |
||||||
|
import com.netflix.zuul.context.RequestContext; |
||||||
|
import com.netflix.zuul.exception.ZuulException; |
||||||
|
|
||||||
|
/** |
||||||
|
* 限流拦截器 |
||||||
|
* @author tanghc |
||||||
|
*/ |
||||||
|
public class PreLimitFilter extends BaseZuulFilter { |
||||||
|
@Override |
||||||
|
protected FilterType getFilterType() { |
||||||
|
return FilterType.PRE; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected int getFilterOrder() { |
||||||
|
return PRE_LIMIT_FILTER_ORDER; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Object doRun(RequestContext requestContext) throws ZuulException { |
||||||
|
ApiConfig apiConfig = ApiConfig.getInstance(); |
||||||
|
// 限流功能未开启,直接返回
|
||||||
|
if (!apiConfig.isOpenLimit()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
ApiParam apiParam = ZuulContext.getApiParam(); |
||||||
|
String routeId = apiParam.getRouteId(); |
||||||
|
RouteConfigManager routeConfigManager = apiConfig.getRouteConfigManager(); |
||||||
|
RouteConfig routeConfig = routeConfigManager.get(routeId); |
||||||
|
if (routeConfig == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
// 某个路由限流功能未开启
|
||||||
|
if (routeConfig.getLimitStatus() == RouteConfig.LIMIT_STATUS_CLOSE) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
byte limitType = routeConfig.getLimitType().byteValue(); |
||||||
|
LimitManager limitManager = ApiConfig.getInstance().getLimitManager(); |
||||||
|
if (limitType == LimitType.LEAKY_BUCKET.getType()) { |
||||||
|
boolean acquire = limitManager.acquire(routeConfig); |
||||||
|
if (!acquire) { |
||||||
|
throw new ApiException(new ErrorImpl(routeConfig.getLimitCode(), routeConfig.getLimitMsg())); |
||||||
|
} |
||||||
|
} else if (limitType == LimitType.TOKEN_BUCKET.getType()) { |
||||||
|
limitManager.acquireToken(routeConfig); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
package com.gitee.sop; |
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.gitee.sop.alipay.AlipaySignature; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import java.text.SimpleDateFormat; |
||||||
|
import java.util.Date; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.CountDownLatch; |
||||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||||
|
|
||||||
|
/** |
||||||
|
* 限流测试 |
||||||
|
*/ |
||||||
|
public class LimitDemoPostTest extends TestBase { |
||||||
|
|
||||||
|
String url = "http://localhost:8081/api"; // zuul
|
||||||
|
String appId = "2019032617262200001"; |
||||||
|
// 支付宝私钥
|
||||||
|
String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testLimit() throws InterruptedException { |
||||||
|
int threadsCount = 10; // threadsCount个线程同时提交
|
||||||
|
final CountDownLatch countDownLatch = new CountDownLatch(1); |
||||||
|
final CountDownLatch count = new CountDownLatch(threadsCount); |
||||||
|
final AtomicInteger success = new AtomicInteger(); |
||||||
|
for (int i = 0; i < threadsCount; i++) { |
||||||
|
new Thread(new Runnable() { |
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
try { |
||||||
|
countDownLatch.await(); // 等在这里,执行countDownLatch.countDown();集体触发
|
||||||
|
// 业务方法
|
||||||
|
doBusiness(Thread.currentThread().getName()); |
||||||
|
success.incrementAndGet(); |
||||||
|
} catch (Exception e) { |
||||||
|
} finally { |
||||||
|
count.countDown(); |
||||||
|
} |
||||||
|
} |
||||||
|
}).start(); |
||||||
|
} |
||||||
|
countDownLatch.countDown(); |
||||||
|
count.await(); |
||||||
|
System.out.println("成功次数:" + success); |
||||||
|
} |
||||||
|
|
||||||
|
// 这个请求会路由到story服务
|
||||||
|
public void doBusiness(String threadName) throws Exception { |
||||||
|
|
||||||
|
// 公共请求参数
|
||||||
|
Map<String, String> params = new HashMap<String, String>(); |
||||||
|
params.put("app_id", appId); |
||||||
|
params.put("method", "alipay.story.get"); |
||||||
|
params.put("format", "json"); |
||||||
|
params.put("charset", "utf-8"); |
||||||
|
params.put("sign_type", "RSA2"); |
||||||
|
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); |
||||||
|
params.put("version", "1.2"); |
||||||
|
|
||||||
|
// 业务参数
|
||||||
|
Map<String, String> bizContent = new HashMap<>(); |
||||||
|
bizContent.put("id", "1"); |
||||||
|
bizContent.put("name", "葫芦娃"); |
||||||
|
|
||||||
|
params.put("biz_content", JSON.toJSONString(bizContent)); |
||||||
|
|
||||||
|
String content = AlipaySignature.getSignContent(params); |
||||||
|
String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); |
||||||
|
|
||||||
|
params.put("sign", sign); |
||||||
|
|
||||||
|
String responseData = post(url, params);// 发送请求
|
||||||
|
System.out.println(responseData); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue