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