前言
什么是increment?
Redis 的INCR 命令将key中存储的数字值递增。如果key不存在,那么key的值会先被初始化为0,然后在执行INhttps和http的区别CR操作。如https协议果值包含错误的类型,或字符串类型的值不能表测试智商示为数字,那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表接口测试面试题示之内。 [1]
使用场景
- id自增生成,且满足并发要求
- 使用计数的特性来防止重复提交的请求
- 记录用户点击量的
- 并发请求场景进行限流
- …
实战
开发环境
- sprspringing-boot 版本2.1.6.RELEASE
- spring-boot-starter-data-redis 版本2.1.6.RELEASE
源码部分
//接口 ValueOperations 代码
/**
* 将 {@code key} 下存储为字符串值的整数值加一。
*
* @param key must not be {@literal null}.
* @return {@literal null} when used in pipeline / transaction.
* @since 2.1
* @see <a href="http://redis.io/commands/incr">Redis Documentation: INCR</a>
*/
@Nullable
Long increment(K key);
/**
* {@code key} 下的字符串值按照 {@code delta} 的整数值来进行递增。
*
* @param key must not be {@literal null}.
* @param delta 递增值
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="http://redis.io/commands/incrby">Redis Documentation: INCRBY</a>
*/
@Nullable
Long increment(K key, long delta);
/**
* {@code key} 下的字符串值按照 {@code delta} 的浮点值来进行递增。
*
* @param key must not be {@literal null}.
* @param delta
* @return {@literal null} when used in pipeline / transaction.
* @see <a href="http://redis.io/commands/incrbyfloat">Redis Documentation: INCRBYFLOAT</a>
*/
@Nullable
Double increment(K key, double delta);
//class DefaultValueOperations实现
@Override
public Long increment(K key) {
byte[] rawKey = rawKey(key);
return execute(connection -> connection.incr(rawKey), true);
}
@Override
public Long increment(K key, long delta) {
byte[] rawKey = rawKey(key);
return execute(connection -> connection.incrBy(rawKey, delta), true);
}
@Override
public Double increment(K key, double delta) {
byte[] rawKey = rawKey(key);
return execute(connection -> connection.incrBy(rawKey, delta), true);
}
//感兴趣的同学可以翻阅一下源码
业务需求
假设需要对用户生成二维码的接口调用次数进行限制,在某一时刻内只能调用多少次测试你的自卑程度,超过次数后将不再走生成二维码逻辑而是直接提示用户“访问频繁” 或 “次数已达上接口限”。
代码实现
TestLimitContjava培训机构roller.java只是简测试工程师单的实现java环境变量配置了如何通过计数器进行限流,没有实现具体的生成二spring维码逻辑
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
public class TestLimitController {
@Autowired
private RedisTemplate<String, String> stringRedisTemplate;
@GetMapping(path = "/getqrcode")
public String getQRCode(String username, HttpServletResponse response) {
//需求:假设需要对用户生成二维码的接口调用次数进行限制,在某一时刻内只能调用多少次,超过次数后将不再走生成二维码逻辑而是直接提示用户“访问频繁” 或 “次数已达上限”。
//当前线程名称
String currentThreadName = Thread.currentThread().getName();
//根据用户名来标记用户访问接口次数
String key = username + "_getQRCode";
//阈值:用户调用接口次数上限
long threshold = 2;
//30秒
final long timeout = 30;
final TimeUnit unit = TimeUnit.SECONDS;
Long increment = stringRedisTemplate.opsForValue().increment(key);
log.info("{} 计数器的值:{}", currentThreadName, increment);
if (null != increment) {
if (increment > threshold) {
log.info("{} 次数已达上限", currentThreadName);
//方便jmeter测试时 查看结果树的状态
try {
response.sendError(HttpStatus.LOCKED.value(), "次数已达上限");
} catch (IOException e) {
e.printStackTrace();
}
return "次数已达上限";
} else {
if (1L == increment) {
//设置key过期时间,当key失效后 相当于次数限制归零
Boolean expire = stringRedisTemplate.expire(key, timeout, unit);
}
log.info("{} 二维码生成成功", currentThreadName);
return "二维码生成成功";
}
}
log.info("{} increment is null 二维码生成失败", currentThreadName);
//方便jmeter测试时 查看结果树的状态
try {
response.sendError(HttpStatus.LOCKED.value(), "increment is null 二维码生成失败");
} catch (IOException e) {
e.printStackTrace();
}
return "二维码生成失败";
}
}
代码实测
来折腾一下自己写的代码是否能经受住考验,我在这里使用的测试工具是jmeter[2]来模拟用户并发场景调用生成二维码接口,假设:我们设定https和http的区别一个用户30秒内只能生成2次二维码,那么接口反馈的结果应该是达到2次之后应该提示用户“次数已达上限”;验证结果如下:
测试场景:jmeter模拟一个用户同一时刻开了10个线程来调用接口
jmeter测试结果树:
查看后台日志结测试抑郁程度的问卷果:只有2个线程生成成功
存储在Redis中用户访问getQRC接口卡ode接口的标记
当然这里测试只是用了10个线程来模拟,spring测试结果也达到预期效果。但不能代表真实生产环境的效果,建议看java语言完这篇文章的同学,如javascript果是要上生产环境还是要加大测试力度,避免测试不充足导致上线之后出现问题。
总结
综上所述,在一些对高并发请求有限制的系统中,我们可以使用Redis的 increment 和 expire 来实现了对接口进行限流,当用户频繁请求时通过限制次数对后台系统进行保护,防止过大的流量冲击导致系统崩溃。
问题探讨
在使用Redis的 increment 和 expire实现高并发限流时会不会出现问题?
会出现哪些问题?为什么会出现这些问题?
出现的问题该如果解决或者怎样避免问题的发生?
Java技术栈中还有哪些是可以实现限流的?
哈哈,看到这里的同学一定是对技术精益求精的,不妨我们在评论区探讨一下