开发者社区> 问答> 正文

[@wangccsy][¥20]如何设计高并发下的限流架构保证应用的可用性?

问题来自Java技术沙龙的潘建成
Java技术沙龙报名链接:https://yq.aliyun.com/promotion/796

展开
收起
李博 bluemind 2018-12-10 18:15:03 2121 0
1 条回答
写回答
取消 提交回答
  • 前一个帐号wangccsy@126.com不知道怎么的就成了企业帐号,改不成个人。所以重新注册了一个个人帐号。老程序员。精通JAVA,C#,数据库,对软件开发过程和流程熟悉。考取系统分析师,项目管理师和系统架构设计师等软件资格考试认证。愿意和大家一起前进。

    系统在设计之初就会有一个预估容量,长时间超过系统能承受的TPS/QPS阈值,系统可能会被压垮,最终导致整个服务不够用。为了避免这种情况,我们就需要对接口请求进行限流。

    限流的目的是通过对并发访问请求进行限速或者一个时间窗口内的的请求数量进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待。

    常见的限流模式有控制并发和控制速率,一个是限制并发的数量,一个是限制并发访问的速率,另外还可以限制单位时间窗口内的请求数量。
    控制并发数量

    属于一种较常见的限流手段,在实际应用中可以通过信号量机制(如Java中的Semaphore)来实现。
    举个例子,我们对外提供一个服务接口,允许最大并发数为10,代码实现如下:

    public class DubboService {    private final Semaphore permit = new Semaphore(10, true);    public void process(){        try{
                permit.acquire();            //业务逻辑处理
     
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                permit.release();
            }
        }
    }
    

    在代码中,虽然有30个线程在执行,但是只允许10个并发的执行。Semaphore的构造方法Semaphore(int permits) 接受一个整型的数字,表示可用的许可证数量。Semaphore(10)表示允许10个线程获取许可证,也就是最大并发数是10。Semaphore的用法也很简单,首先线程使用Semaphore的acquire()获取一个许可证,使用完之后调用release()归还许可证,还可以用tryAcquire()方法尝试获取许可证。
    控制访问速率

    在我们的工程实践中,常见的是使用令牌桶算法来实现这种模式,其他如漏桶算法也可以实现控制速率,但在我们的工程实践中使用不多,这里不做介绍,读者请自行了解。

    在Wikipedia上,令牌桶算法是这么描述的:

    每过1/r秒桶中增加一个令牌。
    
    桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃。
    
    当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包。
    
    如果桶中可用令牌小于n,则该数据包将被缓存或丢弃。
    

    令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率。

    在我们的工程实践中,通常使用Guava中的Ratelimiter来实现控制速率,如我们不希望每秒的任务提交超过2个:

    //速率是每秒两个许可final RateLimiter rateLimiter = RateLimiter.create(2.0);
     
    void submitTasks(List tasks, Executor executor) {    for (Runnable task : tasks) {
            rateLimiter.acquire(); // 也许需要等待
            executor.execute(task);
        }
    }
    

    控制单位时间窗口内请求数

    某些场景下,我们想限制某个接口或服务 每秒/每分钟/每天 的请求次数或调用次数。例如限制服务每秒的调用次数为50,实现如下:

    import com.google.common.cache.CacheBuilder;import com.google.common.cache.CacheLoader;import com.google.common.cache.LoadingCache;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicLong;
     
        private LoadingCache<Long, AtomicLong> counter =
                CacheBuilder.newBuilder()                    .expireAfterWrite(2, TimeUnit.SECONDS)                    .build(new CacheLoader<Long, AtomicLong>() {
                            @Override
                            public AtomicLong load(Long seconds) throws Exception {
                                return new AtomicLong(0);
                            }
                        });
     
        public static long permit = 50;
     
        public ResponseEntity getData() throws ExecutionException {
     
            //得到当前秒
            long currentSeconds = System.currentTimeMillis() / 1000;
            if(counter.get(currentSeconds).incrementAndGet() > permit) {
                return ResponseEntity.builder().code(404).msg("访问速率过快").build();
            }
            //业务处理
     
        }
    
    2019-07-17 23:19:32
    赞同 展开评论 打赏
问答排行榜
最热
最新

相关电子书

更多
徐雷-Java为王,互联网高并发架构设计与选型之路6.0 立即下载
Redis 的高并发实战:抢购系统 立即下载
MySQL高并发场景实战 立即下载