关键资源老是有限的,也就意味着处理能力也有限,因此当面对大量业务时,为了保障本身可以有序的提供服务最经济的作法就是限制同一时间处理的事务数。好比银行的工做人员,一个工做人员同时只能为一个客户服务,来多了根本处理不了,不光是一种浪费并且有能够形成混乱的局面致使工做人员没法工做。html
越上层的服务器处理的事务越轻,应付请求的能力也越强,也就意味着同一请求越上层处理时间越短。为了有效的保护下层服务器,就须要对发送给下层的请求量作限流,在下层服务器可接受的范围内。不然就可能会出现下层服务器资源耗尽而没法正常提供服务的状况。git
若是在服务端作限流,不管有多少个客户端,总的提供能力是固定的(感谢@ xuanbg提出的评论,指出服务端也能够对客户端作精准的判断,后续我再想一想实现方案),因此不会由于客户端数量过多而致使资源不足,由于处理不过来的请求会被阻塞等待获取资源。github
缺点服务器
缺点也比较明显,因为服务提供者总体设置了最大限流数,此时全部的客户端共享同一份限流数据,那么有可能致使有的服务能分配到资源有些服务请求分配不到资源致使没法请求的状况。网络
客户端限流解决上服务端限流提到的问题,它能保证每一个客户端都能获得响应。可是从其它方面考虑,必须针对不一样的客户端作不一样的限流策略:并发
缺点框架
若是客户端的数量不固定,那么有可能致使客户端数量过多形成大量请求打到服务端致使处理不了的结果,因此须要严格监控客户端的调用状况。ide
配置复杂,须要针对每一个客户端作相对精准的判断函数
限流ui
这里指的限流是指每秒从客户端提交到服务端的请求数量。
过滤器机制可参考:简易RPC框架-过滤器机制
public @interface RpcReference { boolean isSync() default true; /** * 客户端最大并发数 * @return */ int maxExecutesCount() default 10; }
须要修改RpcProxy类,构造函数中增长服务引用注解参数,而后在invoke方法中从服务引用注解中获取限流参数传递给request对象。
public RpcProxy(Class<T> clazz,ReferenceConfig referenceConfig,RpcReference reference) { this.clazz = clazz; this.referenceConfig=referenceConfig; this.reference=reference; this.isSync=reference.isSync(); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... if (this.reference != null) { request.setMaxExecutesCount(this.reference.maxExecutesCount()); } ... }
public interface RpcInvocation { ... int getMaxExecutesCount(); }
从request对象中获取限流参数,传递给RpcInvocation对象。
public RpcInvocation buildRpcInvocation(RpcRequest request){ RpcInvocation rpcInvocation=new RpcInvocation() { ... @Override public int getMaxExecutesCount() { return request.getMaxExecutesCount(); } }; return rpcInvocation; }
按接口分配令牌管理器,令牌管理器存储在map中共享。若是未初始化则进行令牌管理器的初始化,若是已经初始化则直接申请令牌。
static class AccessLimitManager{ private final static Object lock=new Object(); private final static Map<String,RateLimiter> rateLimiterMap= Maps.newHashMap(); public static void acquire(RpcInvocation invocation){ if(!rateLimiterMap.containsKey(invocation.getClassName())) { synchronized (lock) { if(!rateLimiterMap.containsKey(invocation.getClassName())) { final RateLimiter rateLimiter = RateLimiter.create(invocation.getMaxExecutesCount()); rateLimiterMap.put(invocation.getClassName(), rateLimiter); } } } else { RateLimiter rateLimiter=rateLimiterMap.get(invocation.getClassName()); rateLimiter.acquire(); } } }
将invocation参数传递给acquire方法。
public Object invoke(RpcInvoker invoker, RpcInvocation invocation) { logger.info("before acquire,"+new Date()); AccessLimitManager.acquire(invocation); Object rpcResponse=invoker.invoke(invocation); logger.info("after acquire,"+new Date()); return rpcResponse; }
这里配置每秒一个请求
@RpcReference(maxExecutesCount = 1) private ProductService productService;
以下图所示,每次请求相隔了一秒,达到了限流请求的目的。
以上只支持客户端接口级别的限流配置,能够再单首创建一个方法级的注解来配置相关参数。
服务端限流尽管有它的缺点,但为了更好的保护服务提供者,须要结合多种业务场景来配合客户端限流一块儿完善,取长补短共同发挥做用。
https://www.cnblogs.com/clds/p/5850086.html
https://github.com/jiangmin168168/jim-framework
文中代码是依赖上述项目的,若是有不明白的可下载源码