前面我们了解了如何利用Nginx做网关层限流,这一小节我们学习一个稍微复杂一点的分布式限流手段,利用Redis+Lua实现服务端限流。
架构思考:Why Redis
同学们一定有个疑问,这么多中间件,为什么我们选择用Redis。这就好比孙悟空选兵器,十八般兵器都不得心应手,唯有那东海龙宫的定海神针是最佳选择。
- 性能 前面的章节中大家已经系统学习了Redis,作为缓存组件,如果不采用持久化方案的话,Redis的大部分操作都是纯内存操作,性能十分优异
- 线程安全 只用单线程承接网络请求(其他模块仍然多线程),天然具有线程安全的特性,而且对原子性操作的支持非常到位
限流服务不仅需要承接超高QPS,还需要保证限流逻辑的执行层面具备线程安全的特性。利用Redis的这些天然特性做限流,既能保证线程安全,也能保持良好的性能。
基于Redis限流的架构模式
我们先来设想下Redis限流的工作模式,在一个限流场景中通常主要有三个角色:
- 限流请求 需要被限流的对象
- 限流规则 定义一段程序或者脚本,当请求到来的时候执行
- 存储介质 用来存储限流信息的地方,比如令牌个数或者是访问请求的计数
思考题 在上面这个流程中,有一个需要大家思考的问题,那就是“限流逻辑”这块应该放到哪个地方。是放到服务入口里执行?还是放到Redis里执行?
我们假定这个服务入口就是我们的Java程序,在Java中执行一段限流逻辑并不会带来多大的性能压力,但是!我们有没有考虑过Redis的感受?在一个限流逻辑里,我们往往需要发起多个Redis查询和修改指令,比如获取令牌这样一步,这里面就涉及到查询令牌、发放令牌等等步骤,这些步骤会发起多个Redis的请求指令,造成更多的网络开销。不光如此,我们还要保证这些操作是线程安全的,如此一来,在程序中就会涉及到资源锁定等复杂操作。
那我们有没有一个更好的方案来规避这些问题呢?答案是肯定的,我们可以做这么一个假设,有这么一种脚本,它可以嵌入到Redis中去,那么对我们的系统来说就不用在一次限流控制中发送多次Redis访问命令,减少了大量不必要的网络开销,减轻了Redis的压力。让我们进一步YY一下,假如Redis在执行这个脚本逻辑的过程中,是当做一个原子操作来做的,也就是说,脚本开始到结束的时间段内Redis都只盯着这个脚本执行,不会插入其他的命令,那岂不是线程安全的问题也解决了?放眼天下,可有此等脚本语言的存在?!
有!今天正好有一款产品,不要钱免费送(开源),你没听错,只要998,Lua脚本语言搬回家!
Lua脚本
Lua是一个很小巧精致的语言,它的诞生(1993年)甚至比JDK 1.0还要早。Lua是由标准的C语言编写的,它的源码部分不过2万多行C代码,甚至一个完整的Lua解释器也就200k的大小。
Lua往大了说是一个新的编程语言,往小了说就是一个脚本语言。对于有编程经验的同学,拿到一个Lua脚本大体上就能把业务逻辑猜的八九不离十了。我非常不建议同学们花大工夫去学习Lua的技术细节,Lua就像Shell脚本一样,尽管简单,但是细节之处还是相当繁琐的。
在分布式限流的章节中,我们以理解架构模式为主,大家只要明白为什么我们使用Lua和Redis,这样做有什么架构上的考量,这就足够了。在稍后的小节里,通过下面三个环节,带大家去理解Redis + Lua的分布式限流方案:
- Lua的基本用法和介绍,有哪些优点
- Lua脚本如何嵌入Redis中执行
- 开发一个基于Redis + Lua的限流工具,在业务层通过自定义注解的形式为方法提供限流功能
小结
这一节我们了解了Redis + Lua限流方案的大致背景,接下来我们循序渐进,先从Lua开始讲起,一步步实现整个限流方案。
学习Tips:大家在学习一门技术的时候要有主线和支线之分,且听我讲个故事。当我还在读书的时候,我周围很多同学是这样学技术的,买一本比砖头还厚的书(在十多年前纸质书籍还是主流),从第一页开始像推土机一样往后看,一个细节都不放过。事无巨细并不是一种好的学习方法,这就像很多女生喜欢用五颜六色的荧光笔在课本上划重点,划来划去一本书全部都是重点。这样学习效率高吗?我想未必!
我认为在学习中应该有明确的目标,跟着主线剧情走。从我自己来说,我在05年左右接触Java,在学习Java之初我的目标就是做一个桌面版的华容道游戏,这个小游戏就是我的学习主线。在了解了Java基础语法之后,就针对性的学习这个小项目所需要用到的知识点。每当需要实现一个功能的时候,就去学习对应章节的知识,用小项目驱动整个学习过程。理论和实践同时开展的效果就是,对这些知识你会有更深的理解。