基于环形队列和令牌桶实现的限流模块

概述

在分布式服务架构下,比如微服务架构,一般需要构建一个独立的gateway模块。gateway模块的主要作用包括流量控制,规则路由,负载均衡,鉴权,熔断等等;而gateway一般是stateless的,因此可自由弹性伸缩集群规模,以应对实际的访问场景。
本文主要介绍通过golang实现限流模块的一种方案,其关键点主要有两个:

  • 环形队列(ring queue)
  • 令牌桶算法(token bucket)

基于环形队列(ring queue)

type Queue struct {
    mtx      sync.RWMutex
    head     int64
    tail     int64
    capacity int64
    elemcnt  int64
    data     []interface{}
}

// 将一个元素推入队列n次
func func (q *Queue) AtomEnqueue(d interface{}, n int64) bool {
}

// 根据自定义方法的判定结果,将符合条件的元素推出队列
func (q *Queue) DequeueBy(fc func(interface{}) bool) []interface{
}

环形队列在限流中的作用是,提供一个状态保存空间,用以判断当前访问频率是否已达到限制值。

在使用这种队列之前,尝试过用time.Tick()时钟方法来实现令牌的定期发放,容易实现但是性能是不高的。因为据说在频率超过1000rps时, 不建议用time.Tick, 具体可以参考这里controlling throughput with rate.limiter

另外如果用到time.Tick(), 一般就需要新开goroutine来进行定期执行了。虽然并没有消耗太多性能,但能避免总是好的。而且如果限流是要实现到针对ip,或者user级别的粒度,那开销就很难忽略了。

而环形队列的一个好处是内存从初始化后就基本是固定的,队列里的每一个元素存放的是每个请求的状态(当前示例里存的是访问时间戳),在请求到达时查询流量状态的同时,进行队列元素的更新(有别于time.Tick的定期性,这个是依靠请求到达来触发的,也就不需要依赖到其他系统资源)。另一个好处就是访问速度快,也没有过多的内存拷贝。其中要注意的是解决并发竞争的问题,用读写锁是其中一种解决方法。

基于令牌桶(token bucket)算法

type TokenBucket struct {
    mutex    sync.Mutex
    capacity int64
    interval time.Duration
    tsQ      *Queue
}

// 获取token, 成功返回true, 失败返回false及需要等待的时间
func (tb *TokenBucket) Take(n int64) (bool, time.Duration) {
}

// 获取token, 成功返回true,失败则等待直至超时
func (tb *TokenBucket) Wait(n int64, t time.Duration) bool {
}

常用的限流算法有两种: 漏桶算法, 令牌桶算法。网上有更详细的算法描述,这里不展开讲了。

我这里采取的是类似令牌桶的算法,原算法是每隔一段时间往桶里放一个token,有请求来时就拿走token,没有token时请求就等待(Wait)或直接返回失败(Take);这里的实现有所区别:

  • 首先是实现中并不是放token, 而是记录每个到达请求的时间戳,环形队列中有空闲的元素才能记录成功,否则等待或返回失败,
  • 另外触发时机也不一样,不是基于时钟的周期触发,而是依靠请求到达来触发

源码地址及测试用例

github项目地址

未严格验证,欢迎各位大大帮忙甩bug。其中测试用例参考如下:

$ go test
[1 2 3 4 5 6]
[1 2 3 4 5 6 7 8]
[3 4 5 6 7 8 9 10]
[9 10 3 4 5 6 7 8]
[10 11 12 13 14 15 16 17]
[10 11 12 13 14]
[15 16 17]
[]
[9 10 haha 15 3 6]
[9 10]
take-n:1 time-used:3.327341023s suc-cnt:4000 fail-cnt:9996000
take-n:2 time-used:3.334162956s suc-cnt:2000 fail-cnt:9998000
take-n:1 time-used:1.257582083s suc-cnt:1499 fail-cnt:1001
take-n:1 time-used:2.250956732s suc-cnt:2500 fail-cnt:0
take-n:2 time-used:1.254886958s suc-cnt:999 fail-cnt:1501
take-n:2 time-used:4.251330391s suc-cnt:2499 fail-cnt:1
take-n:2 time-used:2.251117604s suc-cnt:2500 fail-cnt:0
PASS
ok      github.com/moxiaomomo/token-bucket23.931s

慕课网同步链接:https://www.imooc.com/article/31788

<think>好的,我现在要帮用户解答Java后端面试的复习攻略。首先,我得想想用户的需求是什么。用户可能是准备找工作的应届生或者有一定经验的开发者,想要系统性地复习Java后端相关的知识,应对面试。用户可能希望得到一个全面的复习路线,涵盖重点知识点常见面试题。 首先,我需要确定Java后端面试通常考察哪些内容。一般来说,包括Java基础、数据结构与算法、数据库、框架(如Spring)、系统设计、分布式系统、计算机网络、操作系统、项目经验、行为面试题等。此外,代码能力实战经验也很重要。 接下来要考虑用户的背景。如果是应届生,可能更侧重基础知识算法;如果有经验,可能更关注系统设计、分布式高并发。但用户没有说明,所以应该覆盖全面,同时突出重点。 然后,需要组织内容的结构。分模块来讲解,每个模块列出重点知识点常见问题,这样用户复习起来有条理。比如分Java基础、JVM、并发编程、数据库、框架、系统设计等模块。 每个模块里需要具体的关键点。例如Java基础中的集合类、多线程、IO/NIO等;JVM的内存模型、垃圾回收;并发编程的锁机制、线程池;数据库的索引、事务隔离级别;Spring的IoC、AOP、事务管理等。 还要注意结合实际面试题,比如HashMap的实现原理,ConcurrentHashMap的线程安全机制,MySQL的索引优化,Redis的持久化方式,分布式事务解决方案等。这些是高频问题,需要详细解释。 系统设计部分可能包括如何设计一个秒杀系统,分布式锁的实现,CAP理论等。这部分需要用户有一定的实战经验,或者了解常见的设计模式。 另外,代码能力部分要提醒用户多刷LeetCode,特别是数组、链表、二叉树、动态规划等题目。面试中可能会有手写代码的环节,所以熟练度很重要。 项目经验方面,用户需要准备自己参与过的项目,能够清晰描述项目架构、遇到的挑战解决方案。最好有技术难点优化经验,这能展示实际能力。 行为面试题如职业规划、优缺点、团队协作等问题也不能忽视,虽然技术为主,但综合能力同样重要。 最后,需要提醒用户复习时注意理解原理,而不仅仅是记忆,面试官喜欢深入追问。同时建议模拟面试,查漏补缺,保持良好心态。 可能用户没有提到的深层需求是希望高效复习,抓住重点,避免浪费时间在不重要的内容上。因此,攻略需要简洁明了,突出重点,提供明确的复习方向学习资源推荐。 总结下来,回答的结构应该是分模块列出重点,每个模块给出关键知识点常见问题,最后补充复习建议。这样用户可以根据自己的情况有针对性地准备,提高面试成功率。</think>以下是Java后端面试的系统复习攻略(按优先级排序),附高频考点复习建议: --- ### 一、Java核心基础(必须吃透) 1. **集合框架** - HashMap底层原理(数组+链表/红黑树)、ConcurrentHashMap分段锁/CAS机制 - ArrayList vs LinkedList时间复杂度对比 - 快速失败(fail-fast)安全失败(fail-safe)区别 *高频题:HashMap扩容机制?为什么线程不安全?* 2. **多线程与并发** - synchronized底层原理(对象头、锁升级过程) - volatile关键字与内存可见性 - ThreadLocal原理及内存泄漏问题 - 线程池参数(corePoolSize、workQueue等)与执行流程 *高频题:如何用ReentrantLock实现公平锁?* 3. **JVM** - 内存模型(堆、栈、方法区) - 垃圾回收算法(CMS/G1/ZGC对比) - 类加载机制与双亲委派 *高频题:Full GC触发条件?如何排查OOM问题?* --- ### 二、数据库(MySQL+Redis重点) 1. **MySQL** - 索引原理(B+树 vs Hash) - 事务隔离级别与MVCC实现 - 慢查询优化(EXPLAIN执行计划) *高频题:为什么用自增主键?如何解决幻读?* 2. **Redis** - 持久化机制(RDB/AOF混合持久化) - 缓存穿透/雪崩/击穿解决方案 - 分布式锁实现(Redlock算法) *高频题:Redis为什么快?集群数据如何分片?* --- ### 三、框架体系(Spring全家桶) 1. **Spring Framework** - IOC容器启动流程(Bean生命周期) - AOP实现原理(动态代理/CGLIB) - 声明式事务传播机制 *高频题:循环依赖如何解决?* 2. **Spring Boot** - 自动配置原理(@EnableAutoConfiguration) - Starter组件设计思想 - 监控端点(Actuator)使用 3. **Spring Cloud** - 服务注册发现(Eureka/Nacos) - 熔断降级(Hystrix/Sentinel) - 网关路由(Zuul/Gateway) --- ### 四、系统设计(社招重点) 1. **分布式系统** - CAP定理与BASE理论 - 分布式事务解决方案(Seata/TCC) - 分库分表策略(ShardingSphere) 2. **高并发场景** - 秒杀系统设计(库存扣减方案) - 限流算法(令牌桶/漏桶) - 消息队列应用(Kafka顺序消费) --- ### 五、编码能力(必须手写) 1. **LeetCode高频题** - 链表:反转链表、环形链表检测 - 二叉树:层序遍历、最近公共祖先 - DP:最长递增子序列、背包问题 *建议:每天保持3道中等难度练习* --- ### 六、项目复盘(必考) 1. **STAR法则描述项目** - 突出技术难点(如:如何解决分布式锁失效问题) - 性能优化指标(QPS从1000提升到5000) - 体现架构设计能力(微服务拆分策略) --- ### 复习建议 1. **优先级策略**:基础 > 数据库 > 框架 > 系统设计 2. **实战演练**:本地搭建Redis集群/实现简易RPC框架 3. **模拟面试**:使用《Java面试突击手册》自测 4. **避坑指南**:避免死记硬背,重点理解设计思想 *附资源推荐:* - 书籍:《Java并发编程实战》《凤凰架构》 - 视频:尚硅谷JUC/黑马Spring源码 - 网站:JavaGuide、小林coding图解系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值