Java项目如何实现限流?

01、为什么AUSTIN项目需要限流

众所周知,服务器能处理的请求数是有限的,如果请求量特别大,我们就可能需要做限流。

限流处理的姿势:要么就让请求等待,要么就把请求给扔了

image.png
image.png

从系统架构来看,我们的统一处理入口在austin-api接入层上,austin-api接入层做完简单的参数校验以及参数拼接后,就将请求转发到消息队列上了

image.png
image.png

按正常来说,因为接了消息队列且接入层没有什么耗时的操作,那对外的接口压力不大的。

没错的,austin要接入限流也并不是在austin-api接入层上做,是在austin-handler消息处理下发层。austin-handler消息处理下发层我们是用线程池去隔离不同的消息渠道不同的消息类型

image.png
image.png

在系统本身上其实没有性能相关的问题,但我们下发的渠道可能就需要我们去控制调用的速率

腾讯云短信默认限制3000次/秒调用下发接口

image.png
image.png

钉钉渠道对应用消息和群机器人消息都有接口调用的限制

image.png
image.png

....

在保证下发速度的前提下,为了让业务方所下发的消息其用户能正常接收到和下游渠道的稳定性,我们需要给某些渠道进行限流

于是在这个背景下,我目前定义了两种限流策略:

1、按照请求数限流

2、按照下发用户数限流

image.png
image.png

02、如何实现限流?

想要实现限流,摆在我们面前有两个选择:

1、单机限流

2、分布式限流

咋一看,线上不可能只部署一台机器去发送整个公司的消息推送的,我们的系统应用在线上环境绝对是集群部署的,那肯定就需要上分布式限流了,对吧?

但实际上分布式限流实现并不简单,目前分布式限流的方案一般借助两个中间件

1、Redis

2、Sentinel

我们可能会用Redis的setnx/incrby+expire命令(从而实现计数器、令牌桶限流)/zset数据结构(从而实现滑动窗口限流)

Redis实现的限流想要比较准确,无论是哪种方式,都要依靠lua脚本

而Sentinel支持单机限流和分布式限流,Sentinel分布式限流需要部署Token服务器

对于分布式限流而言,不管用哪一种方案,使用的成本和技术挑战都是比较大的。

如果用单机限流的话,那就简单得多了,省事直接用Guava包下的RateLimiter就完了。缺点就在于:它只能控制单机的限流,如果发生了服务器扩容和缩容,它是感知不到的

有的人就给出了方案:那我用Zookeeper监听服务器的数量不就好了吗。理论上确实是这样的:每台机器限流值=限流总值/服务器数量

不过这又要去依赖Zookeeper,Zookeeper集群本身也有一堆状态相关的问题。

我是怎么实现的?单机限流一把梭

03、代码设计

从上面的描述可以看到,austin的限流我是要做在具体渠道上的,根据现有的代码设计我要的就是在各个的Handler上写上限流的代码。

我本身就设计了BaseHandler抽象类作为模板方法设计模式,此次限流的设计我的是:

1、将flowControl定义为abstract抽象方法,子类渠道去实现限流的代码

2、子类在初始化的时候定义限流参数,BaseHandler父类根据限流参数统一实现限流的逻辑

我选择了第二种方式,主要是我认为对于各个渠道而言,只是限流值是不同的,限流的逻辑应该都是一样的,没必要在每个子类上实现类似的逻辑。

image.png
image.png

而限流的逻辑就比较简单了,主要就使用RateLimit提供的易用API实现

image.png
image.png

没错,限流值的大小我是配置在apollo分布式配置中心的。假设以后真的要扩缩容了,那到时候提前把分布式配置中心的值给改掉,也能解决一部分的问题。

image.png
image.png

04、总结

扯了半天,原来就用了Guava包的RateLimit实现了单机限流,就这么简单,只是把限流值配置在分布式配置中心上而已。

很多时候,设计简单的代码可能实现并不完美,并不智能,并不优雅,但它付出的代价往往是最小的。

虽说如此,如果大家想要了解Redis+lua实现的同学可以fetch下austin最新的代码,就我写文章这段时间里,已经有老哥提了pull request用Redis+lua实现了滑动窗口去重的功能了,本质上是一样的。我已经merge到master分支了。

austin消息推送平台项目源码Gitee链接:gitee.com/austin

austin消息推送平台项目源码GitHub链接:github.com/austin

全部评论

相关推荐

不愿透露姓名的神秘牛友
07-11 15:08
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
06-11 13:34
offe从四面八方来:我真的没时间陪你闹了
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
06-21 11:33
昨天是学校最后一场招聘会,鼠鼠去参加了,全场只有一个招聘java的岗位,上来先做一份笔试题,做完后他拿张纸对答案,然后开始问简历上的问题,深圳小厂,6-8k(题目如下),后面还有两轮面试。然后我就在招聘现场逛呀逛,看到有公司招聘电商运营,给的比上年的小厂还多,鼠鼠就去了解了下,然后hr跟鼠鼠要了份简历,虽然我的简历上面全是求职Java开发相关的内容,但是hr还是鼓励我说没关系,她帮我把简历给老板看看,下周一会给我通知。招聘会结束后鼠鼠想了一段时间,也和朋友聊了聊,发现我可能是不太适合这个方向,然后就跟爸爸说回家了给我发条微信,我有些话想跟他说说。晚上爸爸到家了,跟我发了条微信,我立马跑出图书馆跟他打起了电话,这个通话长达一个小时,主要是跟爸爸坦白说我不想找这行了,是你的儿子太没用了,想试试其他行业。然后爸爸也跟我说了很多,说他从来没有希望我毕业后就赚大钱的想法,找不到就回家去,回家了再慢慢找,实在找不到就跟他干(帮别人装修房子,个体户),他也知道工作不好找,让我不要那么焦虑,然后就是聊一些家常琐事。对于后面的求职者呢我有点建议想提一下,就是如果招实习的时间或者秋招开始,而你的简历又很差的情况下,不要说等做好项目填充完简历之后再投,那样就太晚了,建议先把熟悉的项目写上简历,然后边投边面边完善,求职是一个人进步的过程,本来就比别人慢,等到一切都准备好后再投岂不是黄花菜都凉了。时间够的话还是建议敲一遍代码,因为那样能让你加深一下对项目的理解,上面那些说法只是针对时间不够的情况。当然,这些建议可能没啥用,因为我只是一个loser,这些全是建立在我理想的情况下,有没有用还需其他人现身说法。上篇帖子没想到学校被人认了出来,为了不丢脸只能匿名处理了。
KPLACE:找研发类或技术类,主要还是要1.多投 2.多做准备,很多方面都要做准备 3.要有心理准备,投累了就休息一两天,再继续,要相信自己能找到
投递58到家等公司7个岗位
点赞 评论 收藏
分享
评论
点赞
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务