关键资源总是有限的,也就意味着处理能力也有限,所以当面对大量业务时,为了保障自己能够有序的提供服务最经济的做法就是限制同一时间处理的事务数。比如银行的工作人员,一个工作人员同时只能为一个客户服务,来多了根本处理不了,不光是一种浪费而且有可以造成混乱的局面导致工作人员无法工作。
越上层的服务器处理的事务越轻,应付请求的能力也越强,也就意味着同一请求越上层处理时间越短。为了有效的保护下层服务器,就需要对发送给下层的请求量做限流,在下层服务器可接受的范围内。否则就可能会出现下层服务器资源耗尽而无法正常提供服务的情况。
如果在服务端做限流,无论有多少个客户端,总的提供能力是固定的(感谢@ xuanbg提出的评论,指出服务端也可以对客户端做精准的判断,后续我再想想实现方案),所以不会因为客户端数量过多而导致资源不足,因为处理不过来的请求会被阻塞等待获取资源。
缺点
缺点也比较明显,由于服务提供者整体设置了最大限流数,此时所有的客户端共享同一份限流数据,那么有可能导致有的服务能分配到资源有些服务请求分配不到资源导致无法请求的情况。
客户端限流解决上服务端限流提到的问题,它能保证每个客户端都能得到响应。但是从其它方面考虑,必须针对不同的客户端做不同的限流策略:
缺点
限流
这里指的限流是指每秒从客户端提交到服务端的请求数量。
过滤器机制可参考:简易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://github.com/jiangmin168168/jim-framework
文中代码是依赖上述项目的,如果有不明白的可下载源码
来源: http://www.cnblogs.com/ASPNET2008/p/7712974.html