简单限流器实现

这是阿里一面的一道设计题,限制一个用户一秒只能访问100次。

1. 先设计一个限制器接口,具体实现尚不考虑,便于后期扩展。

/**
 * <p>描述: 限制器顶级接口 </p>
 * @date 2020-9-24 15:48
 * @since jdk1.8
 */
public interface Limiter<T> {
    boolean isAllow(T t);
}

2.用什么限制?

一般情况只有任何只要可以代表用户信息的都可以作为限制的凭证,如用户id,当然可以是整个用户信息,这样就可根据用户信息进行个性化的限制策略,比如VIP用户不用限制,或者放宽限制,RMB还是最重要的。这里就使用用户id限制(String),当然Long类型用户id也一样。

先定一下流量限制器,后面再写实现


/**
 * <p>描述: [流量限制器] </p>
 *
 * @author <a href="mailto: [email protected]" rel="nofollow">yangzengqiang</a>
 * @version v5.0
 * @date 2020-9-24 15:50
 * @since jdk1.8
 */
public class FlowLimiter  implements  Limiter<String>{
    @Override
    public boolean isAllow(String s) {
        return false;
    }
}


3.如何限制?

我的思路是使用一个容器,来统计访问的次数。当然这个容器必须在多线程下可以正常的统。这个想法一看缺少没有啥问题,确实他也的确没啥问题在非分布式的情况下,当然在分布式的情况下就要使用分布式下,多机线程安全的容器来解决这个问题了。
3.1 非分布式版本

思路:使用一个线程安全的集合类来统计访问的次数,每个用户都需要统计,比较好的数据结构就是map,K为用户id,V为用户的访问记录,每次访问的时候对访问记录统计分析,符合放行,不符合拦截。线程安全的Map这里使用的是ConcurrentHashMap,而统计的List使用的是PriorityBlockingQueue来统计访问时间,原因是多线程下并不一定是最先访问的就是第一入队,使用PriorityBlockingQueue进行排序,取得之前的访问时间。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>描述: [流量限制器] </p>
 *
 * @author <a href="mailto: [email protected]" rel="nofollow">yangzengqiang</a>
 * @version v5.0
 * @date 2020-9-24 15:50
 * @since jdk1.8
 */
public class FlowLimiter implements Limiter<String> {
    
    
    //内部使用 不向外开标,避免引用逃逸
    private static ConcurrentHashMap<String, PriorityBlockingQueue<Long>> counter = new ConcurrentHashMap<>();
    private final ReentrantLock lock = new ReentrantLock();

    public boolean isAllow(String clientId) {
    
    
        //上锁初始化 统计list
        lock.lock();
        try {
    
    
            //避免 虚假唤醒
            while (counter.get(clientId) == null) {
    
    
                counter.put(clientId, new PriorityBlockingQueue<Long>());
            }
        } finally {
    
    
            lock.unlock();

        }
        long currentTimeMillis = System.currentTimeMillis();
        PriorityBlockingQueue<Long> priorityQueue = counter.get(clientId);
        if (priorityQueue.size() >= 100) {
    
    
            if (currentTimeMillis - priorityQueue.peek() <= 1000) {
    
    
                priorityQueue.offer(currentTimeMillis);
                if (priorityQueue.size() > 100) {
    
    
                    priorityQueue.poll();
                }
                return false;// 一百次少于一秒
            } else {
    
    
                priorityQueue.offer(currentTimeMillis);
                if (priorityQueue.size() > 100) {
    
    
                    priorityQueue.poll();
                }
                // 一百次多于一秒删除第一个重新统计
                return true;
            }
        }

        priorityQueue.offer(currentTimeMillis);

        return true;

    }

    public static void main(String[] args) {
    
    
        FlowLimiter limiter = new FlowLimiter();
        List<Thread> list = new ArrayList<>();
        long start = System.currentTimeMillis();
        //一百零一个线程,访问
        for (int i = 0; i < 101; i++) {
    
    
            Thread thread = new Thread(() -> {
    
    
                if (!limiter.isAllow("yzq")) {
    
    
                    System.out.println(false);
                }

            });
            list.add(thread);
            thread.start();
        }
        //等待结束
        list.forEach(e -> {
    
    
            try {
    
    
                e.join();
            } catch (InterruptedException ex) {
    
    
                ex.printStackTrace();
            }
        });
        long end = System.currentTimeMillis();
        System.out.println("cost:" + (end - start)+"ms");
    }

}

3.2 分布式版本

思路:分布式版本,将会使用redis,而且大概率要写LUA脚本,由于只是听说过,没使用过,就大体讲下思路。

使用redis 主要是它天生单线程,无线程安全问题。

LUA脚本伪代码
//获取用户id
boolean allow(string userId,Long time,Long limitTime){
int size=zcard userId

if(size<100){
	zadd userId time time
	return false
}else{
	while(size>100){
	zremrangebyrank  userId 0 0 //删除头部
	}
    //开始时间
       int startTime=zrange userId 0 0;
    if(time-startTime >limitTime)){
      return false;
        }else{
    return true;
    }

}

猜你喜欢

转载自blog.csdn.net/qq_39165528/article/details/108972778