在开发高并发系统时,限流的基本策略:缓存、降级和限流。用于保护系统。
那么何为限流呢?限流就是限制流量,就像你手机每月使用的上网流量是2个G的流量,用完了就没了。
缓存
缓存比较好理解,在大型高并发系统中,
像天猫这种大型的电商系统,在一秒钟的并发量可能会达到亿级或者数亿级的并发请求,那如果每一个请求都从数据库查询的话,那天猫估计得准备几百万台的数据库服务器供用户使用了,这种方案根本就不可取。
如果没有缓存,那我们的数据库随时可能会爆我们的服务器也会瞬间宕机。
缓存不仅是提升系统访问速度、提高并发访问的措施,同时也是对我们数据库的一种保护、并保护着我们的系统。
特别是在大型的项目当中涉及到对数据的“读”操作的时候,缓存的作用更是重要了,程序直接走缓存,而不走DB,带来的好处就是提供数据的查询效率,毕竟缓存是存储在内存当中的,对内存的操作是非常的快的。
针对于大型项目当中涉及到对数据的“写”操作的时候,缓存也同样占据着重要作用。比如需要向DB中写数据,那么可以在内存里面缓存一部分数据,当这些数据达到设定的大小时,一次性批量的写入到DB,这样通过缓存提升系统的吞吐量或者实现系统的保护措施。
降级
服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面进行相应策略性的降级,以此释放服务器资源以保证核心任务的正常运行。
那这里是什么意思?
降级一听这个词可能首先想到的是降低级别是吧,在计算机当中指的是根据不同的级别的业务或者页面,进行不同级别的处理。
降级的结果:
可以拒接服务(比如恶意请求的ip)
可以延迟服务(比如在天猫查看我的订单的时候,针对3个月前或者说半年前的订单暂时不提供服务)
可以随机服务(可以根据不同的区域进行随机服务,部分请求允许,部分请求拒绝)
以上所列出的服务范围:
可以砍掉某个功能(比如相对来说不重要的消息提醒或者是客服对话)
也可以砍掉某些模块(比如哪些功能模块暂时不用的或者使用的频率较低的可以砍掉)
总之服务降级需要根据不同的业务需求采用不同的降级策略。
降级的主要目的就是服务虽然有损但是总比没有好。
限流
限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。
从更精细的角度来讲,限流就是为了防止用户恶意刷新接口,因为我们的项目是部署在外部服务器并提供服务的,如果有恶意大并发的请求过来就会对系统的资源进行长时间的占用,这样就减弱了我们服务器对外提供服务的能力,同时也会浪费我们的带宽。
比如我们可以采取如下措施:
延迟处理
拒绝处理
部分拒绝处理等
限流的算法
常见的限流算法有:计数器、漏桶和令牌桶算法。
服务器限流:
一般我们的项目都是通过nginx进行转发的,比如我们的go语言web启动之后会绑定到我们服务器的某个端口上,然后nginx绑定域名进行转发到指定的端口上,即将服务绑定到我们的go语言web服务上。
计数器是最简单粗暴的算法。比如我们项目的某个服务最多只能每秒钟处理100个请求。我们可以设置一个1秒钟内访问的次数要小于等于100,如果超过就需要限流了。
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
var (
netWorkRequestLimit *NetWorkRequestLimitService
doNetWorkRequestLimitService *NetWorkRequestLimitService
)
func init() {
//在服务请求的时候, 我们会对当前计数器和阈值进行比较,只有未超过阈值时才进行服务:
doNetWorkRequestLimitService = DoNetWorkRequestLimitService(10 * time.Second, 5)
}
//使用计数器实现请求限流
//限流的要求是在指定的时间间隔内,server 最多只能服务指定数量的请求。实现的原理是我们启动一个计数器,每次服务请求会把计数器加一,同时到达指定的时间间隔后会把计数器清零
type NetWorkRequestLimitService struct {
RequestInterval time.Duration //当达到最大访问次数时,如果再次访问的间隔时间
MaxCount int //最大请求访问次数
LockRequest sync.Mutex //加锁
ReqCount int //当前请求访问的次数
ReqIp string //当前发起请求的Ip
}
func DoNetWorkRequestLimitService(interval time.Duration, maxCnt int) *NetWorkRequestLimitService {
netWorkRequestLimit = &NetWorkRequestLimitService{
RequestInterval: interval,
MaxCount: maxCnt,
//ReqIp:reqIp,//当前发起请求的Ip是计算出来的
}
go func() {
ticker := time.NewTicker(interval)
for {
//每指定的时间间隔检查一次是否达到最大请求数
<-ticker.C
if netWorkRequestLimit.ReqCount == netWorkRequestLimit.MaxCount {
netWorkRequestLimit.LockRequest.Lock()
fmt.Println("Reset Count...")
netWorkRequestLimit.ReqCount = 0
netWorkRequestLimit.LockRequest.Unlock()
}
}
}()
return netWorkRequestLimit
}
func (this *NetWorkRequestLimitService) ReqIncreased() {
this.LockRequest.Lock()
defer this.LockRequest.Unlock()
this.ReqCount += 1
}
func (this *NetWorkRequestLimitService) ReqIsAvailabled() bool {
this.LockRequest.Lock()
defer this.LockRequest.Unlock()
return this.ReqCount < this.MaxCount
}
func ServeHTTPW(w http.ResponseWriter, r *http.Request) {
//判断是否达到访问设置的最大数量
if doNetWorkRequestLimitService.ReqIsAvailabled() {
fmt.Println("doNetWorkRequestLimitService:",doNetWorkRequestLimitService)
//访问一次让请求计数加1
doNetWorkRequestLimitService.ReqIncreased()
fmt.Println(doNetWorkRequestLimitService.ReqCount)
fmt.Fprintln(w, fmt.Sprintf("当前请求:%d\n",doNetWorkRequestLimitService.ReqCount))
return
}
fmt.Println("Reach request limiting!")
fmt.Fprintln(w, "Reach request limit!")
}
func main() {
http.HandleFunc("/robotReq",ServeHTTPW)
http.ListenAndServe(":8080", nil)
}