全栈编程

Balance $ 2,317
Item Sold 1230
文章作者: 全栈编程@luboke.com
版权声明: 本文章为全栈编程go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!

在开发高并发系统时,限流的基本策略:缓存、降级和限流。用于保护系统。

那么何为限流呢?限流就是限制流量,就像你手机每月使用的上网流量是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)
}

 

 
文章作者: 全栈编程@luboke.com
版权声明: 本文章为全栈编程go语言体系课视频教程配套电子书,版权归 全栈编程@luboke.com所有,欢迎免费学习,转载必须注明出处!但禁止任何商业用途,否则将受到法律制裁!
copyright © 2020 全栈编程@luboke.com