分布式接口幂等性分布式限流(Guavanginx和lua限流
一、接口幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条,这就没有保证接口的幂等性。
幂等性的核心思想:通过唯一的业务单号保障幂等性,非并发的情况下,查询业务单号有没有操作过,没有则执行操作,并发情况下,这个操作过程需要加锁。1、Update操作的幂等性
1)根据唯一业务号去更新数据
通过版本号的方式,来控制update的操作的幂等性,用户查询出要修改的数据,系统将数据返回给页面,将数据版本号放入隐藏域,用户修改数据,点击提交,将版本号一同提交给后台,后台使用版本号作为更新条件updatesetversionversion1,xxx{xxx}whereidxxxandversion{version};2、使用Token机制,保证update、insert操作的幂等性
1)没有唯一业务号的update与insert操作
进入到注册页时,后台统一生成Token,返回前台隐藏域中,用户在页面点击提交时,将Token一同传入后台,使用Token获取分布式锁,完成Insert操作,执行成功后,不释放锁,等待过期自动释放。二、分布式限流1、分布式限流的几种维度
时间限流基于某段时间范围或者某个时间点,也就是我们常说的时间窗口,比如对每分钟、每秒钟的时间窗口做限定资源基于可用资源的限制,比如设定最大访问次数,或最高可用连接数
上面两个维度结合起来看,限流就是在某个时间窗口对资源访问做限制,比如设定每秒最多100个访问请求。但在真正的场景里,我们不止设置一种限流规则,而是会设置多个限流规则共同作用,主要的几种限流规则如下:
1)QPS和连接数控制
针对上图中的连接数和QPS(querypersecond)限流来说,我们可以设定IP维度的限流,也可以设置基于单个服务器的限流。在真实环境中通常会设置多个维度的限流规则,比如设定同一个IP每秒访问频率小于10,连接数小于5,再设定每台机器QPS最高1000,连接数最大保持200。
更进一步,我们可以把某个服务器组或整个机房的服务器当做一个整体,设置更highlevel的限流规则,这些所有限流规则都会共同作用于流量控制。
2)传输速率
对于传输速率大家都不会陌生,比如资源的下载速度。有的网站在这方面的限流逻辑做的更细致,比如普通注册用户下载速度为100ks,购买会员后是10Ms,这背后就是基于用户组或者用户标签的限流逻辑。
3)黑白名单
黑白名单是各个大型企业应用里很常见的限流和放行手段,而且黑白名单往往是动态变化的。举个例子,如果某个IP在一段时间的访问次数过于频繁,被系统识别为机器人用户或流量攻击,那么这个IP就会被加入到黑名单,从而限制其对系统资源的访问,这就是我们俗称的封IP。
我们平时见到的爬虫程序,比如说爬知乎上的美女图片,或者爬券商系统的股票分时信息,这类爬虫程序都必须实现更换IP的功能,以防被加入黑名单。有时我们还会发现公司的网络无法访问12306这类大型公共网站,这也是因为某些公司的出网IP是同一个地址,因此在访问量过高的情况下,这个IP地址就被对方系统识别,进而被添加到了黑名单。使用家庭宽带的同学们应该知道,大部分网络运营商都会将用户分配到不同出网IP段,或者时不时动态更换用户的IP地址。
白名单就更好理解了,相当于御赐金牌在身,可以自由穿梭在各种限流规则里,畅行无阻。比如某些电商公司会将超大卖家的账号加入白名单,因为这类卖家往往有自己的一套运维系统,需要对接公司的IT系统做大量的商品发布、补货等等操作。
4)分布式环境
所谓的分布式限流,其实道理很简单,一句话就可以解释清楚。分布式区别于单机限流的场景,它把整个分布式环境中所有服务器当做一个整体来考量。比如说针对IP的限流,我们限制了1个IP每秒最多10个访问,不管来自这个IP的请求落在了哪台机器上,只要是访问了集群中的服务节点,那么都会受到限流规则的制约。
从上面的例子不难看出,我们必须将限流信息保存在一个中心化的组件上,这样它就可以获取到集群中所有机器的访问状态,目前有两个比较主流的限流方案:
网关层限流将限流规则应用在所有流量的入口处
中间件限流将限流信息存储在分布式环境中某个中间件里(比如Redis缓存),每个组件都可以从这里获取到当前时刻的流量统计,从而决定是拒绝服务还是放行流量2、限流方案常用算法讲解1)令牌桶算法
TokenBucket令牌桶算法是目前应用最为广泛的限流算法,顾名思义,它有以下两个关键角色:令牌获取到令牌的Request才会被处理,其他Requests要么排队要么被直接丢弃桶用来装令牌的地方,所有Request都从这个桶里面获取令牌
令牌生成
这个流程涉及到令牌生成器和令牌桶,前面我们提到过令牌桶是一个装令牌的地方,既然是个桶那么必然有一个容量,也就是说令牌桶所能容纳的令牌数量是一个固定的数值。
对于令牌生成器来说,它会根据一个预定的速率向桶中添加令牌,比如我们可以配置让它以每秒100个请求的速率发放令牌,或者每分钟50个。注意这里的发放速度是匀速,也就是说这50个令牌并非是在每个时间窗口刚开始的时候一次性发放,而是会在这个时间窗口内匀速发放。
在令牌发放器就是一个水龙头,假如在下面接水的桶子满了,那么自然这个水(令牌)就流到了外面。在令牌发放过程中也一样,令牌桶的容量是有限的,如果当前已经放满了额定容量的令牌,那么新来的令牌就会被丢弃掉。
令牌获取
每个访问请求到来后,必须获取到一个令牌才能执行后面的逻辑。假如令牌的数量少,而访问请求较多的情况下,一部分请求自然无法获取到令牌,那么这个时候我们可以设置一个缓冲队列来暂存这些多余的令牌。
缓冲队列其实是一个可选的选项,并不是所有应用了令牌桶算法的程序都会实现队列。当有缓存队列存在的情况下,那些暂时没有获取到令牌的请求将被放到这个队列中排队,直到新的令牌产生后,再从队列头部拿出一个请求来匹配令牌。
当队列已满的情况下,这部分访问请求将被丢弃。在实际应用中我们还可以给这个队列加一系列的特效,比如设置队列中请求的存活时间,或者将队列改造为PriorityQueue,根据某种优先级排序,而不是先进先出。算法是死的,人是活的,先进的生产力来自于不断地创造,在技术领域尤其如此。2)漏桶算法
LeakyBucket
漏桶算法的前半段和令牌桶类似,但是操作的对象不同,令牌桶是将令牌放入桶里,而漏桶是将访问请求的数据包放到桶里。同样的是,如果桶满了,那么后面新来的数据包将被丢弃。
漏桶算法的后半程是有鲜明特色的,它永远只会以一个恒定的速率将数据包从桶内流出。打个比方,如果我设置了漏桶可以存放100个数据包,然后流出速度是1s一个,那么不管数据包以什么速率流入桶里,也不管桶里有多少数据包,漏桶能保证这些数据包永远以1s一个的恒定速度被处理。
漏桶vs令牌桶的区别
根据它们各自的特点不难看出来,这两种算法都有一个恒定的速率和不定的速率。令牌桶是以恒定速率创建令牌,但是访问请求获取令牌的速率不定,反正有多少令牌发多少,令牌没了就干等。而漏桶是以恒定的速率处理请求,但是这些请求流入桶的速率是不定的。
从这两个特点来说,漏洞的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不同,其特性可以预存一定量的令牌,因此在应对突发流量的时候可以在短时间内消耗所有令牌,其突发流量处理效率会比漏桶高,但是导向后台系统的压力也会相应增多。3、分布式限流的主流方案
这里主要讲nginx和lua的限流,gateway和hystrix放在后面springcloud中讲1)GuavaRateLimiter客户端限流
1。引入mavendependencygroupIdcom。google。guavagroupIdguavaartifactIdversion18。0versiondependency
2。编写ControllerRestControllerSlf4jpublicclassController{每秒钟可以创建两个令牌RateLimiterlimiterRateLimiter。create(2。0);非阻塞限流GetMapping(tryAcquire)publicStringtryAcquire(Integercount){count每次消耗的令牌if(limiter。tryAcquire(count)){log。info(成功,允许通过,速率为{},limiter。getRate());returnsuccess;}else{log。info(错误,不允许通过,速率为{},limiter。getRate());returnfail;}}限定时间的非阻塞限流GetMapping(tryAcquireWithTimeout)publicStringtryAcquireWithTimeout(Integercount,Integertimeout){count每次消耗的令牌timeout超时等待的时间if(limiter。tryAcquire(count,timeout,TimeUnit。SECONDS)){log。info(成功,允许通过,速率为{},limiter。getRate());returnsuccess;}else{log。info(错误,不允许通过,速率为{},limiter。getRate());returnfail;}}同步阻塞限流GetMapping(acquire)publicStringacquire(Integercount){limiter。acquire(count);log。info(成功,允许通过,速率为{},limiter。getRate());returnsuccess;}}2)基于Nginx的限流
1。iP限流
1。编写ControllerRestControllerSlf4jpublicclassController{nginx测试使用GetMapping(nginx)publicStringnginx(){log。info(Nginxsuccess);}}
2。修改host文件,添加一个网址域名127。0。0。1www。test。com
3。修改nginx,将步骤2中的域名,添加到路由规则当中
打开nginx的配置文件vimusrlocalnginxconfnginx。conf
添加一个服务根据IP地址限制速度1)binaryremoteaddrbinary目的是缩写内存占用,remoteaddr表示通过IP地址来限流2)zoneiplimit:20miplimit是一块内存区域(记录访问频率信息),20m是指这块内存区域的大小3)rate1rs每秒放行1个请求limitreqzonebinaryremoteaddrzoneiplimit:20mrate1rs;server{servernamewww。test。com;locationaccesslimit{proxypasshttp:127。0。0。1:8080;基于ip地址的限制1)zoneiplimit引用limitrepzone中的zone变量2)burst2设置一个大小为2的缓冲区域,当大量请求到来,请求数量超过限流频率时,将其放入缓冲区域3)nodelay缓冲区满了以后,直接返回503异常limitreqzoneiplimitburst2nodelay;}}
4。访问地址,测试是否限流
www。test。comaccesslimitnginx
2。多维度限流
1。修改nginx配置根据IP地址限制速度limitreqzonebinaryremoteaddrzoneiplimit:20mrate10rs;根据服务器级别做限流limitreqzoneservernamezoneserverlimit:10mrate1rs;根据ip地址的链接数量做限流limitconnzonebinaryremoteaddrzoneperip:20m;根据服务器的连接数做限流limitconnzoneservernamezoneperserver:20m;server{servernamewww。test。com;locationaccesslimit{proxypasshttp:127。0。0。1:8080;基于ip地址的限制limitreqzoneiplimitburst2nodelay;基于服务器级别做限流limitreqzoneserverlimitburst2nodelay;基于ip地址的链接数量做限流最多保持100个链接limitconnzoneperip100;基于服务器的连接数做限流最多保持100个链接limitconnzoneperserver1;配置request的异常返回504(默认为503)limitreqstatus504;limitconnstatus504;}locationdownload{前100m不限制速度limitrateaffer100m;限制速度为256klimitrate256k;}}3)基于RedisLua的分布式限流
1。Lua脚本
Lua是一个很小巧精致的语言,它的诞生(1993年)甚至比JDK1。0还要早。Lua是由标准的C语言编写的,它的源码部分不过2万多行C代码,甚至一个完整的Lua解释器也就200k的大小。
Lua往大了说是一个新的编程语言,往小了说就是一个脚本语言。对于有编程经验的同学,拿到一个Lua脚本大体上就能把业务逻辑猜的八九不离十了。
Redis内置了Lua解释器,执行过程保证原子性
2。Lua安装
安装Lua:
1。参考http:www。lua。orgftp教程,下载5。3。51版本,本地安装
如果你使用的是Mac,那建议用brew工具直接执行brewinstalllua就可以顺利安装,有关brew工具的安装可以参考https:brew。sh网站,使用brew安装后的目录在usrlocalCellarlua5。3。51
2。安装IDEA插件,在IDEAPreferences面板,Plugins,里面Browserepositories,在里面搜索lua,然后就选择同名插件lua。安装好后重启IDEA
3。配置LuaSDK的位置:IDEAFileProjectStructure,选择添加Lua,路径指向LuaSDK的bin文件夹
4。都配置好之后,在项目中右键创建Module,左侧栏选择lua,点下一步,选择lua的sdk,下一步,输入lua项目名,完成
3。编写helloluaprintHelloLua
4。编写模拟限流模拟限流用作限流的keylocalkeymykey限流的最大阈值locallimit2当前限流大小localcurrentLimit2是否超过限流标准ifcurrentLimit1limitthenprintrejectreturnfalseelseprintacceptreturntrueend
5。限流组件封装
1。添加mavendependencygroupIdorg。springframework。bootgroupIdspringbootstarterdataredisartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstarteraopartifactIddependencydependencygroupIdcom。google。guavagroupIdguavaartifactIdversion18。0versiondependency
2。添加Spring配置
不是重要内容就随便写点,主要就是把reids配置一下server。port8080spring。redis。database0spring。redis。hostlocalhostspring。redis。port6376
3。编写限流脚本
lua脚本放在resource目录下就可以了获取方法签名特征localmethodKeyKEYS〔1〕redis。log(redis。LOGDEBUG,keyis,methodKey)调用脚本传入的限流大小locallimittonumber(ARGV〔1〕)获取当前流量大小localcounttonumber(redis。call(get,methodKey)or0)是否超出限流值ifcount1limitthen拒绝访问returnfalseelse没有超过阈值设置当前访问数量1redis。call(INCRBY,methodKey,1)设置过期时间redis。call(EXPIRE,methodKey,1)放行returntrueend
4。使用springdataredis组件集成Lua和Redis
创建限流类ServiceSlf4jpublicclassAccessLimiter{AutowiredprivateStringRedisTemplatestringRedisTemplate;AutowiredprivateRedisScriptBooleanrateLimitLua;publicvoidlimitAccess(Stringkey,Integerlimit){booleanacquiredstringRedisTemplate。execute(rateLimitLua,lua脚本的真身Lists。newArrayList(key),lua脚本中的key列表limit。toString()lua脚本的value列表);if(!acquired){log。error(Youraccessisblocked,key{},key);thrownewRuntimeException(Youraccessisblocked);}}}
创建配置类ConfigurationpublicclassRedisConfiguration{publicRedisTemplateString,StringredisTemplate(RedisConnectionFactoryfactory){returnnewStringRedisTemplate(factory);}publicDefaultRedisScriptloadRedisScript(){DefaultRedisScriptredisScriptnewDefaultRedisScript();redisScript。setLocation(newClassPathResource(rateLimiter。lua));redisScript。setResultType(java。lang。Boolean。class);returnredisScript;}}
5。在Controller中添加测试方法验证限流效果RestControllerSlf4jpublicclassController{AutowiredprivateAccessLimiteraccessLimiter;GetMapping(test)publicStringtest(){accessLimiter。limitAccess(ratelimitertest,1);returnsuccess;}}
6。编写限流注解
1。新增注解Target({ElementType。METHOD})Retention(RetentionPolicy。RUNTIME)DocumentedpublicinterfaceAccessLimiterAop{intlimit();StringmethodKey()default;}
2。新增切面Slf4jAspectComponentpublicclassAccessLimiterAspect{AutowiredprivateAccessLimiteraccessLimiter;根据注解的位置,自己修改Pointcut(annotation(com。gyx。demo。annotation。AccessLimiter))publicvoidcut(){log。info(cut);}Before(cut())publicvoidbefore(JoinPointjoinPoint){获取方法签名,作为methodkeyMethodSignaturesignature(MethodSignature)joinPoint。getSignature();Methodmethodsignature。getMethod();AccessLimiterAopannotationmethod。getAnnotation(AccessLimiterAop。class);if(annotationnull){return;}Stringkeyannotation。methodKey();Integerlimitannotation。limit();如果没有设置methodKey,就自动添加一个if(StringUtils。isEmpty(key)){Class〔〕typemethod。getParameterType();keymethod。getName();if(type!null){StringparamTypesArrays。stream(type)。map(Class::getName)。collect(Collectors。joining(,));keyparamTypes;}}调用redisreturnaccessLimiter。limitAccess(key,limit);}}
3。在Controller中添加测试方法验证限流效果RestControllerSlf4jpublicclassController{AutowiredprivateAccessLimiteraccessLimiter;GetMapping(test)AccessLImiterAop(limit1)publicStringtest(){returnsuccess;}}感谢阅读,希望对你有所帮助:)
来源:blog。csdn。netqq34886352articledetails104694550
错过大芯片,可不能再错过小芯粒了近日,有消息称,由于3nm制程的N3工艺某预定大客户临时取消订单,台积电因而大砍供应链订单,涉及再生晶圆、关键耗材、设备等供应链领域。但台积电并不承认,而是回应称:N3制程进度……
这些短款薄外套挺不错轻松应对早晚气温差四月的天孩子的脸,说变就变,原本大好艳阳春光无敌的出游日子,说不定立马变下雨,每天睡醒睁眼都是阴雨潮湿的天气,也真是没脾气了。短款薄外套,早晚气温差距大,备一件还不错。文……
TFboys人气爆棚三小男神私服惹人爱(图)老大王俊凯mdash;mdash;穿衣主打酷帅风作为TFBoys成员中的老大王俊凯,在穿搭方面自然带种大哥哥的风范,平时多以酷帅的黑白系造型示人,各式黑色系字母印花T恤、……
冬季时尚长款毛呢大衣外套御寒保暖又显瘦冬天没有什么比得上一款长大衣穿得更加温暖了!小编今天带来的长款毛呢大衣外套,抗寒显瘦做暖美人。风格:气质时尚看点:韩国甜美毛呢大衣,很气质的大红色,穿起来很女人,宽……
个性背带裤减龄随性耍帅任你搭背带裤简单随意,随便套件上衣都能穿的很潮很有个性,最重要的是显年轻!减龄什么的完全没压力哦。下面小编推荐几个背带裤图片,看看有你喜欢的吗?【LOOK1】:随性的背带裤加上……
OPPO千元新机上手,颜值拍照表现太惊喜,1699元值了日前,OPPOA58正式上架开售,这次直接普及了大存储版本,售价1699元。原本以为只是普通的千元机,但实际真机上手之后,这款新机确实给我们带来了不少惊喜。千元机定位,但……
S12英雄联盟全球总决赛最新情况英雄联盟S12全球总决赛2022年10月8日随着秋季的到来,电竞圈最大的盛典英雄联盟S12全球总决赛开幕,来自世界各地的32个俱乐部战队向着象征世界英雄联盟项目最高的荣誉……
条顺颜正衣品佳金小妹的穿衣经(图)开衩裙如果你的衣柜里至今还未添置一条开衩裙就不能自称ITgirl啦。别问开衩裙有多火爆,看看它在各大时尚杂志网站上出现的频率就知道!短款的开衩裙能最大限度的展现你的……
夏季T恤的3个搭配要点nbsp穿T恤记住这三个要点夏天到了,清凉的夏装必需安排一波。要说夏天最流行穿什么?那必须还得是时尚百搭的T恤,T恤的实用性和百搭性是有目共睹的。谁的衣柜没几件不同款式的T恤呢?T恤设计简单,风格多……
京东方发布全新一代Q9发光器件及全新自研蓝钻像素排列首推超高频PWM调光技术,首度将OLED屏幕的调光频率提升至1920Hz超高水平,最高还可实现2160Hz超高频调光,可使移动终端在暗光环境下最大程度降低屏幕频闪对眼睛的潜在危……
基础款上衣怎么穿出高级感nbsp把基础单品穿出高级感才是真会普通人如果会穿,就很容易在人群中脱颖而出。在日常生活中,大多人都会选择比较基础的单品进行穿搭,在同等的条件下,想要和别人穿得不同,会穿就显得尤其重要了。很多基础款的单品虽……
夏天穿什么裤子清爽又时尚nbsp夏季选对裤子清爽感加倍生活中可能有很多女生不喜欢穿裙子,她们喜欢更加帅气舒适的穿搭,所以平时会更多的选择裤子进行穿搭。裤子穿着非常方便,不用担心走光,夏季多准备几条裤子非常实用。裤子有很多的种……