利用redis做次数限制的小结
2017-12-03 00:00
253 查看
有一些需要限制次数的场景,比如api调用次数限制、在一段时间内只能使用几次的限制,在几秒内、几分钟时间内只能使用几次的限制。简单的实现可以把需要做限制的次数放在redis中,利用redis的特点进行限制。这里只是对本人的一些用法做个简单的小结。
key的构造是一个前缀+对应的时间格式。比如要求是每秒的限制,时间格式就设置为yyyyMMddHHmmss,如果是每1分钟的限制,时间格式就设置为yyyyMMddHHmm。如果是每5秒的限制呢?类似每秒的限制,但是设置时间点的时候,需要再计算,自己定义一个规则,比如取0秒、5秒、10秒,[0,5)秒取0,[5,10)取5,可以[0,5)秒取2,[5,10)取7,总之就是定义规则,判断时间点对应的区间,取区间的代表值,构造最后的key。最后等待key失效的策略清理过期的key。
关于这个需求,脑子中第一种想到的方法就是利用keys 操作来实现。首先是构造key,直接一个前缀+时间戳,对应的值设置过期时间为1分钟,这样keys 前缀就可以得到1分钟内有效的个数。这种做法很简单,但是效率不高,如果遇到redis集群的情况,效率更低。曾经遇到过一次因为并发高了,用keys 获取数据导致cpu 100%的情况。
之后在实际项目的测试环境中,redis使用了cluster,估计是环境的什么配置有问题,用keys 操作的时候,得不到想要的结果。想到redis中还有list这种数据结构,有种预感,应该可以用list实现这种限制,于是看了api,发现llen 还有lrange,突然脑洞一开,想到可以利用这几个命令来实现。通过lpush,把最久的数据放在最右边,通过lrange获取前N个数据,通过ltrim删除过期的数据,于是用list来实现的限制就完成了。没有对应的key时,lpush 并且设置过期时间。设置新的值,再更新一下key的有效期。代码中没做事务,就是简单的实现,有需要再简单加个事务实现就行。
以上就是一些个人的用法总结,遇到其他的再继续完善。
1.在单位时间内只能使用N次的限制
常见于api调用次数限制,时间可以是1秒、1分钟、1小时,1天。其他规则的时间限制,需要自定义。这种的用法相对简单,直接用incr方法就可以实现。这里有个小细节,是先用get方法来获取key的值判断是否达到上限,还是直接用incr的返回值?这里我选择直接获取incr的值,因为先做get判断,之后再做incr操作,如果遇到并发,可能会造成脏读,当然也可以放在事务中实现。key的构造是一个前缀+对应的时间格式。比如要求是每秒的限制,时间格式就设置为yyyyMMddHHmmss,如果是每1分钟的限制,时间格式就设置为yyyyMMddHHmm。如果是每5秒的限制呢?类似每秒的限制,但是设置时间点的时候,需要再计算,自己定义一个规则,比如取0秒、5秒、10秒,[0,5)秒取0,[5,10)取5,可以[0,5)秒取2,[5,10)取7,总之就是定义规则,判断时间点对应的区间,取区间的代表值,构造最后的key。最后等待key失效的策略清理过期的key。
/** * * @param key * @param limitSeconds key有效期 * @param limitTimes 限制次数 * @return -1表示超过限制 */ public Long incr(final String key, final int limitSeconds, final int limitTimes) { if(StringUtils.isEmpty(key)) { return 0L; } Long ret = 1L; if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().increment(key, 1); redisTemplate.expire(key, limitSeconds, TimeUnit.SECONDS); return ret; } ret = redisTemplate.opsForValue().increment(key, 1); return ret > limitTimes ? -1L : ret; } //调用,省略各种设置,每秒10次的限制 // RedisTest test = new RedisTest(); // if(test.incr("test_20171203170252", 5, 10) < 0){ // key 设置5秒过期 // System.out.println("超过限制"); // }
2.在最近单位时间内只能使用N次的限制
举个例子,最近1分钟内要求限制N次。关于这个需求,脑子中第一种想到的方法就是利用keys 操作来实现。首先是构造key,直接一个前缀+时间戳,对应的值设置过期时间为1分钟,这样keys 前缀就可以得到1分钟内有效的个数。这种做法很简单,但是效率不高,如果遇到redis集群的情况,效率更低。曾经遇到过一次因为并发高了,用keys 获取数据导致cpu 100%的情况。
public Long incr2(final String key, final int limitSeconds, final int limitTimes) { if(StringUtils.isEmpty(key)) { return 0L; } long ret = -1L; if ((ret = redisTemplate.keys(key + "*").size()) >= limitTimes) { return -1L; } final String k = key + System.currentTimeMillis(); redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.setEx(k.getBytes(), limitSeconds, "1".getBytes()); return null; } }); return ret + 1; }
之后在实际项目的测试环境中,redis使用了cluster,估计是环境的什么配置有问题,用keys 操作的时候,得不到想要的结果。想到redis中还有list这种数据结构,有种预感,应该可以用list实现这种限制,于是看了api,发现llen 还有lrange,突然脑洞一开,想到可以利用这几个命令来实现。通过lpush,把最久的数据放在最右边,通过lrange获取前N个数据,通过ltrim删除过期的数据,于是用list来实现的限制就完成了。没有对应的key时,lpush 并且设置过期时间。设置新的值,再更新一下key的有效期。代码中没做事务,就是简单的实现,有需要再简单加个事务实现就行。
public Long incr3(final String key, final int limitSeconds, final int limitTimes) { if(StringUtils.isEmpty(key)) { return 0L; } long ret = -1L; long time = System.currentTimeMillis(); final String timestamp = "" + time; if (!redisTemplate.hasKey(key)) { redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.lPush(key.getBytes(), timestamp.getBytes()); redisConnection.expire(key.getBytes(), limitSeconds); return null; } }); return 1L; } List<String> list = redisTemplate.opsForList().range(key, 0, limitTimes); int t = 0; // 倒序遍历,查找最后一个没过期的下标 for (int i = list.size() - 1; i >= 0; i--) { if (Long.parseLong(list.get(i)) > time - limitSeconds * 1000) { t = i; break; } } // 清除过期的list值 redisTemplate.opsForList().trim(key, 0, t); if (t + 1 < limitTimes) { redisTemplate.execute(new RedisCallback<Object>() { public Object doInRedis(RedisConnection redisConnection) throws DataAccessException { redisConnection.lPush(key.getBytes(), timestamp.getBytes()); redisConnection.expire(key.getBytes(), limitSeconds); return null; } }); ret = t + 2; } else { ret = -1L; } return ret; }
以上就是一些个人的用法总结,遇到其他的再继续完善。
相关文章推荐
- javaee利用redis限制短信发送次数
- 利用Redis实现限制一个用户只能在一个地点登陆
- Lua在Redis中的应用—分布式锁,限制访问次数
- win7利用组策略设置电脑开机登录密码次数限制技巧图解
- PHP实现redis限制单ip、单用户的访问次数功能
- PHP中Yii2框架用redis实现限制接口访问次数
- 利用正则表达式限制网页表单里的文本框输入内容小结
- 利用Redis实现亿级别用户登录统计(活跃度以及登录次数统计)
- 利用pam限制linux登录次数
- 利用正则表达式限制网页表单里的文本框输入内容小结
- 使用redis进行用户接口访问时间次数限制
- [SpringMVC+redis]自定义aop注解实现控制器访问次数限制
- 【Java】SpringMVC项目利用Shiro设置固定时间内密码登录重试次数限制
- 使用memcache或redis限制某个用户或者某ip用户一段时间内最大投票次数
- Redis限制在规定时间范围内登陆错误次数限制
- redis 限制次数
- 利用Redis实现亿级别用户登录统计(活跃度以及登录次数统计)
- PHP结合redis 限制用户登入 密码错误次数(redis实例)
- PHP实现redis限制单ip、单用户的访问次数功能示例
- 利用IP地址限制单位时间内投票次数