基于redis的zset,支持高并发的时间滑动窗口计数器实现

关于计数器的核心实现参考自如下:

https://github.com/halilduygulu/redis-sliding-window-counter

 

课题需求是需要对可能产生高并发的接口进行计数管理,如10秒内最大允许访问100次,超出次数拒绝。

 

基本上有3种实现方式

  1. 指定时限,如每天0点开始,每10秒开放一定次数。计时窗口固定,过期清空

  2. 采用时间滑动窗口,每秒(毫秒)计时窗口滑动,重新启动计数

  3. 采用令牌方式,每秒产生10枚令牌,只有拿到令牌的允许访问,否则排队

 

这里是实现的第二种方式,为了支持分布式,采用了redis,同时利用redis的特性zset可以简洁高效的实现滑动窗口。

完整代码如下:

package slidingWindowCounter;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;

/**
 * 根据时间滑动窗口计数
 */
public class SlidingWindowCounter {

	private static SlidingWindowCounter instance = null;
	private JedisPool jedisPool = null;
	/*
	 * 单位时间访问次数限制
	 */
	private static long maxcount = 100;
	/*
	 * 时间间隔
	 */
	private static int windowInSecond = 10;

	private SlidingWindowCounter() {
	}

	/**
	 * 获取实例
	 */
	public static SlidingWindowCounter getInstance() {
		if (instance == null) {
			synchronized (SlidingWindowCounter.class) {
				if (instance == null) {
					instance = new SlidingWindowCounter();
				}
			}
		}

		return instance;
	}

	/**
	 * 初始化连接池
	 * 
	 * @param host
	 * @param port
	 * @throws Exception
	 */
	public void configure(String host, int port) throws Exception {
		jedisPool = new JedisPool(host, port);
	}

	/**
	 * 初始化连接池
	 * 
	 * @param pool
	 * @throws Exception
	 */
	public void configure(JedisPool pool) throws Exception {
		jedisPool = pool;
	}

	/**
	 * 增长访问数
	 * 
	 * @param key
	 */
	public void increment(String key) {
		long currentMs = System.currentTimeMillis();
		long maxScoreMs = currentMs - windowInSecond * 1000;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			Transaction redis = jedis.multi();
			// 按分数清除过期成员
			redis.zremrangeByScore(key, 0, maxScoreMs);
			redis.zadd(key, currentMs, currentMs + "-" + Math.random());
			redis.expire(key, windowInSecond);
			redis.exec();
		} finally {
			if (jedis != null)
				jedis.close();
		}
	}

	/**
	 * 获取当前key中的有效访问数
	 * 
	 * @param key
	 * @return
	 */
	public long getCount(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			// 按key统计集合中的有效数量
			return jedis.zcard(key);
		} finally {
			if (jedis != null)
				jedis.close();
		}
	}

	/**
	 * 获取key的剩余有效时间
	 * 
	 * @param key
	 * @return
	 */
	public long getLastTime(String key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			return jedis.ttl(key);
		} finally {
			if (jedis != null)
				jedis.close();
		}
	}

	/**
	 * 根据时间段/访问次数判断当前访问是否允许
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public boolean access(String key) throws Exception {
		if (getCount(key) < maxcount) {
			increment(key);
			System.out.println(key + ":" + getLastTime(key) + ",result:true");
			return true;
		}
		if (getCount(key) > maxcount) {
			throw new Exception(key + "超过了" + maxcount);
		}
		System.out.println(key + ":" + getLastTime(key) + ",result:false");
		return false;
	}
}

 

你可能感兴趣的:(服务端框架,java,redis,时间滑动窗口计数器)