QPS 的限流算法网上已经有很多了,一般都用令牌桶+Redis(分布式集群的 redis,也可以在 redis 失效时结合单实例限流来做),能达到一个较为精确的限流效果,在文章里就不再详细介绍原理(不过要考虑到各个 redis 命令可能执行失败的情况的)
关于并发数的限流,一个最简单的做法是用 redis 存一个计数器,然后每次连接开始 +1,连接结束 -1。这样可能会出现的问题是,在 -1 的时候,由于 redis 服务不可用导致没成功怎么办?总不能多次重试去阻塞连接关闭吧。
一个解决方案是,开一个消息队列(在 go 里直接用 channel),-1 失败以后记录到队列里,然后另起一个线程去消费队列(一个优化是可以合并相同 key 的 -1),反复去重试 -1。
但这样做的问题存在一个问题,由于 connections 这个 key 在 redis 里没有加 TTL,万一服务挂了,消息队列中的 -1 没消费完,那么 connections 这个 key 就是脏数据了。
一个比较简单的解决方法是,给 connections 设置一个比较大的 TTL(比如 1 周),但这样又会出现一个问题,要是 connections 置 0 了,但消息队列里仍然还有没消费完的 -1,那么 connections 数值不就变成负数了?
一个比较简单的解决方法就是,给 connections 加一个时间戳,比如 connections_2020_09_11 就可以了。这样做仍然会有一个问题,一个用户在 TTL 的前一瞬间建立起了一堆连接(达到上限),然后在 TTL 生效 connections 置为 0 后,就又能瞬间建立起一堆连接,这样会造成连接堆积,超过限制,没有达到限流效果。
一个比较简单的解决方法就是,我也不会了,去死吧233。
这玩意没有办法解决,之前有考虑过消息队列可持久化,但是,一个 redis 都能挂,你 kafka 难道挂的几率不是更大吗?持久化干啥...
所以啊,就内存开个消息队列起一个 goroutine 多次消费 -1 直到成功就是目前最可取的办法啦
1 条评论