当Redis的increment遇上了高并发,结果让人...
前言
什么是increment?
Redis 的 INCR 命令将key中存储的数字值递增。如果key不存在,那么key的值会先被初始化为0,然后在执行 INCR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。本操作的值限制在 64 位(bit)有符号数字表示之内。 [1]
使用场景
- id自增生成,且满足并发要求
- 使用计数的特性来防止重复提交的请求
- 记录用户点击量的
- 并发请求场景进行限流
- …
实战
开发环境
- spring-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 Redis Documentation: INCR*/
@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 Redis Documentation: INCRBY*/
@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 Redis Documentation: INCRBYFLOAT*/
@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);
}//感兴趣的同学可以翻阅一下源码
业务需求
假设需要对用户生成二维码的接口调用次数进行限制,在某一时刻内只能调用多少次,超过次数后将不再走生成二维码逻辑而是直接提示用户“访问频繁” 或 “次数已达上限”。
代码实现
TestLimitController.java 只是简单的实现了如何通过计数器进行限流,没有实现具体的生成二维码逻辑
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 {@Autowiredprivate 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]来模拟用户并发场景调用生成二维码接口,假设:我们设定一个用户30秒内只能生成2次二维码,那么接口反馈的结果应该是达到2次之后应该提示用户“次数已达上限”;验证结果如下:
测试场景:jmeter模拟一个用户同一时刻开了10个线程来调用接口


jmeter测试结果树:


查看后台日志结果:只有2个线程生成成功

存储在Redis中用户访问getQRCode接口的标记

当然这里测试只是用了10个线程来模拟,测试结果也达到预期效果。但不能代表真实生产环境的效果,建议看完这篇文章的同学,如果是要上生产环境还是要加大测试力度,避免测试不充足导致上线之后出现问题。
总结
综上所述,在一些对高并发请求有限制的系统中,我们可以使用Redis的 increment 和 expire 来实现了对接口进行限流,当用户频繁请求时通过限制次数对后台系统进行保护,防止过大的流量冲击导致系统崩溃。
问题探讨
在使用Redis的 increment 和 expire实现高并发限流时会不会出现问题?
会出现哪些问题?为什么会出现这些问题?
出现的问题该如果解决或者怎样避免问题的发生?
Java技术栈中还有哪些是可以实现限流的?
哈哈,看到这里的同学一定是对技术精益求精的,不妨我们在评论区探讨一下
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
