文章目录
-
- 技术实现
- Redis(计数器)
- Spring Cloud Gateway限流
- Sentinel
- 参考
- 参考代码
需求
1.每日配额:新增每日配额信息,申请通过后默认分配50万次每日配额数据;
2.并发配额:新增并发配额信息,申请通过后默认分配500次/分钟并发配额数据;
需求分析
对于开放平台来说,有一个功能是必须要有的,那就是API的流控。
对于每一个接入开放平台的应用,都会分配一个Appkey,这个Appkey下面会关联你申请了哪些API。然后接入的API有些是不限量,可以一直调用。有些是需要申请调用包,比如一天10万次的调用量。
除了总量的限制,还有对频率的限制。比如每秒每个API调用频率的限制,每秒每个Appkey调用频率的限制。
要实现这些流控的功能,最好的方式就是已经有一个流量治理平台,里面提供了限流的功能。像开放平台这种还不属于普通的系统限流,有点偏业务限流了,因为有具体的数量指标。
技术选型
1.MySQL+Redis(计数器)去实现。 存在数据一致性的问题。 强依赖redis影响性能
2.Spring Cloud Gateway原生限流(令牌桶算法-redis)
3.Sentinel
流量整形技术方案
1.若调用方配额设置为无限制,则不执行网关限流策略;
2.基于spring-gateway默认redis+lua脚本技术方案实施;(一秒为单位,不支持小数)
3.网关层通过zookeeper接收到管理端配额配置信息后,为每个调用方涉及的资源生成流量令牌桶;
4.每个经过网关的请求,根据请求方信息+资源信息判断内存限流配置信息,若无限流配置则直接放行;
5.若存在配置信息,则基于流量桶进行流量管理,超额则返回预设错误码及报错信息;
技术实现
Redis(计数器)
@Component
public class FlowLimitFilter implements GlobalFilter, Ordered {
@Autowired
private AsyncTaskService asyncTaskService;
@Autowired
private CommonService commonService;//获取应用信息
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String appId = request.getHeaders().getFirst(APPID);//获取应用id
String requesttimemills = request.getHeaders().getFirst(REQUESTTIMEMILLS);//请求时间
try{
if(!StringUtils.isEmpty(appId)){
//获取应用信息
AppInfo appInfo = commonService.getAppInfoByAppId(appId);
//时间转化
String dateStr = DateUtil.timestampToTimeStr(requesttimemills);
String minu = dateStr.substring(0,12);
String day = dateStr.substring(0,8);
//1、累计调用次数
Future futureMinu = asyncTaskService.appMinuCounter("gateway:"+appId+":"+minu,"req");
Future futureDay = asyncTaskService.appDayCounter("gateway:"+appId+":"+day,"req");
//2、限流判断
if(appInfo!=null && appInfo.getMinuFlag()!=0){
// 限流开关打开才会进行判断
long minuNum = (long)futureMinu.get();
if(minuNum>appInfo.getMinuQuota()){
return commonService.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS,"当前请求数量超过并发配额",exchange.getResponse());
}
}
if(appInfo!=null && appInfo.getFreqFlag()!=0){
long dayNum = (long)futureDay.get();
if(dayNum>appInfo.getAppFreq()){
return commonService.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS,"当前请求数量超过当日配额",exchange.getResponse());
}
}
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
return chain.filter(exchange);//放行请求
}
@Override
public int getOrder() {
return 0;
}
}
@Service
@Slf4j
public class AsyncTaskService {
@Autowired
private RedisUtils redisUtils;
/**
* 请求计数器
*/
@Async
public void reqNumCounter(String appId, String requesttimemills, String hkey){
try{
String dateStr = DateUtil.timestampToTimeStr(requesttimemills);
String minu = dateStr.substring(0,12);
String day = dateStr.substring(0,8);
String minuKey = appId+":"+minu;
String dayKey = appId+":"+day;
long dayCount = redisUtils.hincr(dayKey,hkey);
long minuCount = redisUtils.hincr(minuKey,hkey);
if("req".equals(hkey)&&dayCount==1){
redisUtils.expire(dayKey,26,TimeUnit.HOURS);
}
if("req".equals(hkey)&&minuCount==1){
redisUtils.expire(minuKey,2,TimeUnit.HOURS);
}
log.info("应用:{} 状态:{} redis计数+1",appId,hkey);
}catch (Exception e){
log.error("redis计数失败:"+e.getMessage(),e);
}
}
@Async
public Future<Long> appMinuCounter(String key, String hkey){
long count = 0;
try{
count = redisUtils.hincr(key,hkey);
if("req".equals(hkey)&&count==1){
redisUtils.expire(key,2,TimeUnit.HOURS);
}
}catch (Exception e){
log.error("redis计数失败(分钟):"+e.getMessage(),e);
}
return new AsyncResult<Long>(count);
}
@Async
public Future<Long> appDayCounter(String key, String hkey){
long count = 0;
try{
count = redisUtils.hincr(key,hkey);
if("req".equals(hkey)&&count==1){
redisUtils.expire(key,26,TimeUnit.HOURS);
}
}catch (Exception e){
log.error("redis计数失败(日):"+e.getMessage(),e);
}
return new AsyncResult<Long>(count);
}
}
Spring Cloud Gateway限流
Sentinel
参考
推荐阅读
开放平台的限流通常都是怎么实现的?
—redis操作
https://blog.csdn.net/lydms/article/details/105224210
https://blog.csdn.net/sdrfengmi/article/details/103693212
—RedisRateLimiter源码解析
https://blog.csdn.net/weixin_42073629/article/details/106934827
–Gateway 网关限流
https://juejin.cn/post/7005060165892309022#heading-10
https://juejin.cn/post/7012076662955180068#heading-16
https://blog.csdn.net/weixin_42073629/article/details/106934827
https://www.cnblogs.com/yinjihuan/p/10514534.html?ivk_sa=1024320u
https://www.cnblogs.com/qianwei/p/10127700.html
参考代码
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.0.1.RELEASE</version>
</dependency>
@Component
public class RedisUtils {
private final static Long LOCK_EXP = 15l;
@Autowired
private RedisTemplate redisTemplate;
public Map hgetall(String key) {
return redisTemplate.opsForHash().entries(key);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public void setex(String key, Object value,long seconds ,TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, seconds, timeUnit);
}
public Long incr(String key) {
return redisTemplate.opsForValue().increment(key,1);
}
public Long hincr(String key,String hkey) {
return redisTemplate.opsForHash().increment(key,hkey,1);
}
public void expire(String key,long timeout, TimeUnit unit) {
redisTemplate.expire(key,timeout,unit);
}
public void del(String key) {
redisTemplate.delete(key);
}
public void lock(String key) {
setex(key, LOCK_EXP, 0,TimeUnit.SECONDS);
}
public void unlock(String key) {
del(key);
}
public boolean sismember(String key, String member) {
return redisTemplate.opsForSet().isMember(key, member);
}
}
@Configuration
public class RedisConfig {
@Resource
private LettuceConnectionFactory lettuceConnectionFactory;
/**
* RedisTemplate配置
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
lettuceConnectionFactory.setValidateConnection(false);
lettuceConnectionFactory.setShareNativeConnection(false);
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)