相信各位对redis肯定是不陌生的,一个高吞吐量的内存型结构存储数据库。可用用于很多业务场景,能够有效的解决很多复杂的并发问题,分布式问题。

下面粘一下中文官网介绍:

关于解决对象共享问题,很多方式,通过一般的关系型数据库就可以(mysql),但是相较而言,mysql关系型数据库和nosql数据库,两者读写效率也是不一样的,一个在硬盘上工作,一个在内存上工作,此就是差距;频繁的IO操作,大大降低了CPU性能;redis采用cache。完全不一样的性能。

用一组数据对比:

redis读写能力为2W/s

mysql读能力5K/s、写能力为3K/s

从数据角度来说,基本上是碾压性的。

可想,对于redis来说,很方便适用于各种高频读写内存的场景。然而,对于单机版的应用,我们直接将变量、对象放在主机的物理内存上就可以了,也不需要用到这种中间件。但是对于分布式的环境下。我们无法实现内存共享。必然是需要借助于redis这个中间件技术的。才可以实现多节点下的内存数据共享。

然后,对于我之前写的那篇文章可以知道,jvm中是存在原子性操作的方法的,cas

那么,即使在分布式环境下我们更需要考虑这个情况了。毕竟多节点,并发数就翻了几倍了。

so,我们强大的redis也考虑到这个了。

于是有了Redisson框架

可以这么讲,jdk中的juc包提供的是单机版的并发业务。那么Redisson基本是基于juc实现的分布式的业务

此处直通车Redisson官方文档

我们可以看到很多基于分布式的封装

很多分布式锁的实现,基本满足了所有业务场景

是的,我们今天讲的就是以他的实现分布式限流方案讲解一下,以代码的形式,顺便讲讲如何使用,并且简单原理实现。

1.相关依赖

org.redisson

redisson

3.11.4

2.限流器(RateLimiter)谷歌的guava实现的,也很强大

但是今天的主要的是

RRateLimiter

一个演化版的限流器,是的,多了一个R,它支持在分布式环境下,现在调用方的请求频率,可以实现不同Redisson实例下的多线程限流,也适用于相同实例Redisson下的多线程限流。是非公平性阻塞。

提供一下RRateLimiter 的接口文档

我们可以发现,基本上就是四个方法名,其余是重载

先分析一下第一个方法 acquire()

从此RateLimiter处获取许可,直到获得一个许可为止都将阻塞。

可以知道,是一个阻塞限流,直到获取到令牌。那我们可以对其分析一下源代码

可以看到基本上是给予了一个默认值1,一个许可证的数量

那么从public RFutrueacquireAsync(long permits);分析一下

(1)先创建一个异步计算对象promise

(2)再调用方法tryAcquireAsync(permits, -1, null);

(3)然后将其结果,通过RFutrue返回

那么顺藤摸瓜,我们再分析一下方法:

public RFuturetryAcquireAsync(long permits, long timeout, TimeUnit unit) ;

如果有兴趣的童鞋可以看看源码,该对象,基本上所有的方法都是最终指向了上方法,只是参数,我们都封装好了。便于直接调用。

分析一下上述方法,主要做了一个什么事情呢?

就是将我们设定的超时时间统一转换为毫秒值,如果是-1,则不转换,直接为-1.

然后再定义一个异步任务,传递到

tryAcquireAsync(permits, promise, timeoutInMillis);

接下来就是重头戏了

一个私有方法:

(1)先记录进入该方法的起始时间 s

(2)进入下方法(执行一个lua脚本)

可想而知,这个就是redis获取令牌的命令,其中会判断是否超出获取许可数量。然后将其获取结果放回。返回的结果就是一个

Long类型数据,那么我们再通过异步计算,判断其返回结果是否为成功?是否等于NULL?

(3)判断e是否不等于null

如果不等于null,则代表获取到许可证,则立即返回

并将结果放在promise中,

如果等于null,则继续下面流程

(4)判断delay是否等于null

判断是否延迟,如果delay等于null,则将其异步标记为成功,也立即返回

(5)判断超时时间是否为-1

如果为-1,则进入递归任务,再获取一次许可,直至退出递归

(6)如果不等于-1,那说明存在确切的超时时间,那么将判断当前消耗的时间是否大于当前任务设定的超时时间

如果已经大于,则将立即返回,并标记结果失败

(7)再判断当前剩余的超时时间,是否小于延迟时间(delay)

(7.1)小于:则结束,并标记失败

(7.2)大于:则判断当前过去时间是否小于等于剩余超时时间,如果小于等于,则返回,标记失败,否则,则进入下个递归,并且传入剩余超时时间为最新超时时间。

3.如何实现分布式限流?

上代码!!

官方文档

基于Redis的分布式限流器(RateLimiter)可以用来在分布式环境下现在请求方的调用频率。既适用于不同Redisson实例下的多线程限流,也适用于相同Redisson实例下的多线程限流。该算法不保证公平性。除了同步接口外,还提供了异步(Async)、反射式(Reactive)和RxJava2标准的接口。

RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter");

// 初始化

// 最大流速 = 每1秒钟产生10个令牌

rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);

CountDownLatch latch = new CountDownLatch(2);

limiter.acquire(3);

// ...

Thread t = new Thread(() -> {

limiter.acquire(2);

// ...

});

aop实现

(1)监听注解

package cn.changemax.config.annotation;

import cn.changemax.enums.LimitTypeEnum;

import org.redisson.api.RateType;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* @author WangJi

* @Description cm限流注解

* @Date 2020/7/29 11:34

*/

@Target(ElementType.METHOD)

//方法上声明

@Retention(RetentionPolicy.RUNTIME)

public @interface CmLimit {

/**

* 资源名称,用于描述接口功能

*/

String name() default "";

/**

* 限制访问次数(单位时间内产生的令牌数)

*/

int count();

/**

* 时间间隔,单位秒

*/

int period();

/**

* 资源 key

*/

String key() default "";

/**

* 限制类型(ip/方法名)

*/

LimitTypeEnum limitType() default LimitTypeEnum.CUSTOMER;

/**

* RRateLimiter 速度类型

* OVERALL, //所有客户端加总限流

* PER_CLIENT; //每个客户端单独计算流量

* @return

*/

RateType mode() default RateType.PER_CLIENT;

}

(2)切点实现

package cn.changemax.config.aop;

import cn.changemax.commons.base.BaseAspectSupport;

import cn.changemax.config.annotation.CmLimit;

import cn.changemax.exception.ChangeMaxException;

import cn.changemax.utils.HttpContextUtil;

import cn.changemax.utils.IpInfoUtil;

import cn.changemax.utils.StringUtils;

import lombok.RequiredArgsConstructor;

import lombok.extern.slf4j.Slf4j;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.redisson.api.RRateLimiter;

import org.redisson.api.RateIntervalUnit;

import org.redisson.api.RedissonClient;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;

import java.util.concurrent.TimeUnit;

/**

* @author WangJi

* @Description redsson分布式限流器

*

* 技术参考文档:

* RRateLimiter rateLimiter = redisson.getRateLimiter("myRateLimiter");

* // 初始化

* // 最大流速 = 每10秒钟产生1个令牌

* rateLimiter.trySetRate(RateType.OVERALL, 1, 10, RateIntervalUnit.SECONDS);

* //需要1个令牌

* if(rateLimiter.tryAcquire(1)){

* //TODO:Do something

* }

*

*

* 高并发系统三把利器用于保护系统:缓存、降级和限流

* *缓存:缓存的目的就是提升系统的访问速度和增大系统处理容量

* *降级:降级是当服务出现问题或者影响到核心流程的时候,需要暂时屏蔽掉,待高峰或者问题解决后再打开

* *限流:限流的目的是通过对并发访问、请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或者等待、降级等处理方案。

*

* @Date 2020/7/29 11:00

*/

@Slf4j

@Aspect

@Component

@RequiredArgsConstructor

public class CmLimitAspect extends BaseAspectSupport {

private static final String CM_LIMIT_KEY_HEAD = "limit";

@Autowired

private RedissonClient redisson;

@Pointcut("@annotation(cn.changemax.config.annotation.CmLimit)")

public void pointcut() {

}

@Around("pointcut()")

public Object around(ProceedingJoinPoint point) throws Throwable {

HttpServletRequest request = HttpContextUtil.getHttpServletRequest();

Method method = resolveMethod(point);

CmLimit limit = method.getAnnotation(CmLimit.class);

String ip = IpInfoUtil.getIpAddr(request);

String key;

switch (limit.limitType()) {

case IP:

// ip类型

key = ip;

break;

case CUSTOMER:

//传统类型,采用注解提供key

key = limit.key();

break;

default:

//默认采用方法名

key = StringUtils.upperCase(method.getName());

}

// ImmutableListkeys = ImmutableList.of(StringUtils.join(CM_LIMIT_KEY_HEAD, limit.prefix(), ":", ip, key));

//生成key

final String ofRateLimiter = StringUtils.generateRedisKey(CM_LIMIT_KEY_HEAD, ip, key);

RRateLimiter rateLimiter = redisson.getRateLimiter(ofRateLimiter);

//设置访问速率,var2为访问数,var4为单位时间,var6为时间单

//每10秒产生1个令牌 总体限流

//创建令牌桶数据模型

rateLimiter.trySetRate(limit.mode(), limit.count(), limit.period(), RateIntervalUnit.SECONDS);

// permits 允许获得的许可数量 (如果获取失败,返回false) 1秒内不能获取到1个令牌,则返回,不阻塞

// 尝试访问数据,占数据计算值var1,设置等待时间var3

// acquire() 默认如下参数 如果超时时间为-1,则永不超时,则将线程阻塞,直至令牌补充

// 此处采用3秒超时方式,服务降级

if (!rateLimiter.tryAcquire(1, 2, TimeUnit.SECONDS)) {

log.error("IP【{}】访问接口【{}】超出频率限制,限制规则为[限流模式:{}; 限流数量:{}; 限流时间间隔:{};]",

ip, method.getName(), limit.mode().toString(), limit.count(), limit.period());

throw new ChangeMaxException("接口访问超出频率限制,请稍后重试");

}

return point.proceed();

}

}

(3)限流类型

package cn.changemax.enums;

/**

* @author WangJi

* @Description 限流类型

* @Date 2020/6/11 17:43

*/

public enum LimitTypeEnum {

/**

* 传统类型

*/

CUSTOMER,

/**

* 根据 IP地址限制

*/

IP

}

整个流程走完了,其中有个附加的知识点,了解一下:

CommandAsyncExecutor commandExecutor

RPromisepromise = new RedissonPromise();

分析第二个对象,我们可以看到上边源码中,都是返回成功,并且结果要么就是true,要么就是false,是因为我们再上层声明了结果为Boolean,可以理解为带有返回值的异步任务。

分析一下:Interface RPromise

所有的方法如下

刚刚源码中一直用到trySuccess,此文,我们了解一下这个方法即可

true当且仅当成功将这一未来标记为成功。否则,false因为此未来已被标记为成功或失败。并通知所有监听者

那么就说到这里,如果全文有什么错误的地方,积极欢迎各位指出错误,帮助大家一起成长,谢谢。

JAVA中useDrlimiter方法_今天来讲讲分布式环境下,怎么达到对象共享,以及实现原子性(atomic),以Redis中的Redisson为例(实现分布式锁、分布式限流等)...相关推荐

  1. java list sublist方法_(转)Java 中 List.subList() 方法的使用陷阱

    原文:http://blog.csdn.net/cleverGump/article/details/51105235 前言 本文原先发表在我的 iteye博客: http://clevergump. ...

  2. java 调用祖父方法_在Java中调用祖父母方法:您不能

    java 调用祖父方法 在文章保护的重点中,我详细介绍了"受保护"如何扩展"包私有"访问. 我在那儿写道: 你能做的是 覆盖子类中的方法或 使用关键字super ...

  3. java中gettext方法_深入理解Java中方法的参数传递机制

    形参和实参 我们知道,在Java中定义方法时,是可以定义参数的,比如: public static void main(String[] args){ } 这里的args就是一个字符串数组类型的参数. ...

  4. java的lookup方法_深入理解Spring中的Lookup(方法注入)

    前言 本文主要给大家介绍了关于Spring中Lookup(方法注入)的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在使用Spring时,可能会遇到这种情况:一个单例的Be ...

  5. java resume过时方法_面试官没想到,一个 Java 线程生命周期,我可以扯半小时

    面试官:你不是精通 Java 并发吗?从基础的 Java 线程生命周期开始讲讲吧. 好的,面试官.吧啦啦啦... 如果要说 Java 线程的生命周期的话,那我觉得就要先说说操作系统的线程生命周期 因为 ...

  6. java resume过时方法_学点开发|关于Java多线程用法解析

    在进行学习之前,我们先来了解下,什么是Java多线程: 多线程是实现并发机制的一种有效手段.进程和线程一样,都是实现并发的一个基本单位.为了让大家更清晰读懂关于Java多线程用法,由以下几点入手学,帮 ...

  7. kotlin调用类中的方法_一种轻松的方法来测试Kotlin中令人沮丧的静态方法调用

    kotlin调用类中的方法 by Oleksii Fedorov 通过Oleksii Fedorov 一种轻松的方法来测试Kotlin中令人沮丧的静态方法调用 (A stress-free way t ...

  8. java序列化的方法_【Java常见序列化与反序列方法总结】

    人和电脑在很多方面都是十分相似的,大脑可以看成电脑主机,五官/身体等表面器官就是显示器.鼠标等外设.这篇文章就是想把计算机跟人做类比YY一下序列化和反序列化的机制.用途. 如果你是初学者,心里肯定会问 ...

  9. java dll 调用方法_关于Java调用dll的方法 | 学步园

    Java语言本身具有跨平台性,如果通过Java调用DLL的技术方便易用,使用Java开发前台界面可以更快速,也能带来跨平台性. Java调用C/C   写好的DLL库时,由于基本数据类型不同.使用字节 ...

最新文章

  1. 微软算法100题58 从尾到头输出链表(java)
  2. android 显示单位 像素
  3. RabbitMQ批量确认发布
  4. 常用LINQ关键字用法汇总
  5. 前端学习(2597):按钮控制操作
  6. AVR单片机计算器C语言源程序,AVR单片机简单计算器的Proteus仿真实现+源码
  7. C++中set按降序排序
  8. 计算机网络项目化实训教程,计算机网络项目实训教程
  9. 面向对象初调用:foolish 电梯
  10. 阅读 嘀嗒嘀嗒 微信公共号
  11. HDU 5090 Game with Pearls (贪心)
  12. IEEE 754——计算机中浮点数的表示方法
  13. 9.判断回文数(力扣leetcode) 博主可答疑该问题
  14. WireShark抓包后数据分析
  15. 人工智能自动修复图片,模糊图片秒变高清
  16. Drug Discov. Today | 药物发现中的先进机器学习技术
  17. cpu功耗排行_intel酷睿10代CPU处理器功耗表
  18. Unity烘焙时UV Overlap的解决办法
  19. APP 合规讲堂 - 收集使用个人信息的目的、方式、范围发生变化时,是否以适当方式通知用户(五)
  20. ES6最通俗易懂的超重点保姆级笔记!女朋友看了都流泪的学习秘籍!没有一句废话,全部都是满满干货!

热门文章

  1. 雷柏v500se和v500pro的区别 哪个更值得入手
  2. 教育在线系统开发学习(二)
  3. word中更改共几页的数值大小
  4. Caffe图片特征提取(Python/C++)
  5. 事缓则圆,人缓则安,语迟则贵
  6. 案例1 分时显示不同图片,显示不同的问候语
  7. 接口和抽象类(超详细)
  8. MATLAB | 全网唯一 MATLAB双向弦图(有向弦图)绘制
  9. 管理者十句最不应该说的话
  10. 获取nacos配置中心文件值_09-Nacos配置中心(读取配置文件)