微服务网关(八)限流中间件网关集成与标准库rate限速器的使用和源码分析

微服务网关(八)限流中间件网关集成与time/rate限速器的使用和源码分析

限流原理

限流的意义

高并发系统的三大利器:

  • 缓存

    ​ 提升系统访问速度和增大处理容量,为相应业务增加缓存

  • 降级

    ​ 当服务器压力剧增时,根据业务逻辑选择降级,以此释放服务器资源,保证主要业务正常运行

  • 限流

    ​ 通过对并发限速,以达到拒绝服务、排队等待、降级等的处理

限流分类

漏桶限流

​ 每次请求时计算桶流量,当流量超过阈值时,则触发降级请求

微服务网关(八)限流中间件网关集成与标准库rate限速器的使用和源码分析_第1张图片

令牌桶限流

​ 每次请求时从令牌桶取令牌,取不到则触发降级请求

微服务网关(八)限流中间件网关集成与标准库rate限速器的使用和源码分析_第2张图片

time/rate限速器使用

​ time/rate是Golang 标准库中自带的限流算法,是基于 Token Bucket(令牌桶) 实现的

  • 创建方法

    rate.NewLimiter(limit,burst)
    limit表示每秒产生的token数,burst表示令牌桶中最多存储的token数

  • 使用方法(这是三个方法都会去取token)

    • Allow: 判断当前是否可以取到token。没有 token 返回 false,如果有token就会消耗 1 个 token 返回 true
    • Wait: 阻塞等待,直到取到token
    • Reserve: 返回等待时间,再去取token
func Test_RateLimiter(t *testing.T) {
	l := rate.NewLimiter(1, 5)
	log.Println(l.Limit(), l.Burst())
	for i := 0; i < 100; i++ {
		//阻塞等待直到,取到一个token
		log.Println("before Wait")
		c, _ := context.WithTimeout(context.Background(), time.Second*2)
		if err := l.Wait(c); err != nil {
			log.Println("limiter wait err:" + err.Error())
		}
		log.Println("after Wait")

		//返回需要等待多久才有新的token,这样就可以等待指定时间执行任务
		r := l.Reserve()
		log.Println("reserve Delay:", r.Delay())

		//判断当前是否可以取到token
		a := l.Allow()
		log.Println("Allow:", a)
		log.Println("------------------------")
	}
}

前五次都可以成功取得token,第六次则因超过token上限,所以获取失败

微服务网关(八)限流中间件网关集成与标准库rate限速器的使用和源码分析_第3张图片

time/rate源码分析

若是固定地向令牌桶中放入令牌,用户从桶中获取令牌,这么做固然是令牌桶的一种实现方式,但是效率过低,不仅需要多维护一个放入令牌的操作和一个令牌桶,还浪费了一些不必要的内存

所以在Golang的timer/rate中,并没有采用这种方式,而是采用了lazyload方式,知道每次消费之前,才根据时间差更新Token数目,而且也不使用令牌桶的方式来存放token,而是仅仅通过计数的方式

概要过程:

  1. 计算上次请求和当前请求的时间差
  2. 计算时间差内生成的token数+旧token数
  3. 如果token为负,计算等待时间
  4. token为正,则请求后token-1

结构体分析

type Limiter struct {
	limit Limit		//每秒token数
	burst int		//令牌桶的最大数

	mu     sync.Mutex
	tokens float64	//令牌数
	// last is the last time the limiter's tokens field was updated
	last time.Time	//最后一次令牌更新时间
	// lastEvent is the latest time of a rate-limited event (past or future)
	lastEvent time.Time//最后一次限速时间
}

三种使用方法

在内部实现中,三种消费方式最终都调用了reserveN 函数来生成和消费token

因此,核心代码,直接来吧

reserveN方法的实现源码 看看一次请求中消费令牌的过程

func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
	lim.mu.Lock()
    //没有设置limit就直接返回
	if lim.limit == Inf {
		lim.mu.Unlock()
		return Reservation{
			ok:        true,
			lim:       lim,
			tokens:    n,
			timeToAct: now,
		}
	}
	//根据时间推移 计算桶中的数目返回限流的结果
    //该方法用于计算 由于时间推移而导致的限流结果
	now, last, tokens := lim.advance(now)

这里暂停一下,进去看看advance方法的实现

func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
   last := lim.last
    // 如果now比last还早,则last使用更早的时间
   if now.Before(last) {
      last = now
   }

   //1、时间容错处理,暂时不看 
   //当last是过期数据时,应避免delta德尔塔δ 移除
   maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
   elapsed := now.Sub(last)
   if elapsed > maxElapsed {
      elapsed = maxElapsed
   }

   //2、计算时间差内可以生成的token数量	delta德尔塔δ
   delta := lim.limit.tokensFromDuration(elapsed)
    //将之前的token数与时间差内的token数求和
   tokens := lim.tokens + delta
    //最大令牌数限制
   if burst := float64(lim.burst); tokens > burst {
      tokens = burst
   }
   return now, last, tokens
}

紧接着回到reserveN方法,获取了限流返回结果(now, last, tokens)之后


	//计算剩余token,因为每次请求当然要token减一啦
	tokens -= float64(n)

	// 当token不够时,计算等待时间
	var waitDuration time.Duration
	if tokens < 0 {
		waitDuration = lim.limit.durationFromTokens(-tokens)
	}

	// Decide result
	ok := n <= lim.burst && waitDuration <= maxFutureReserve

	// Prepare reservation
	r := Reservation{
		ok:    ok,
		lim:   lim,
		limit: lim.limit,
	}
	if ok {
		r.tokens = n
		r.timeToAct = now.Add(waitDuration)
	}

	// 更新状态 关锁返回即可
	if ok {
		lim.last = now
		lim.tokens = tokens
		lim.lastEvent = r.timeToAct
	} else {
		lim.last = last
	}

	lim.mu.Unlock()
	return r
}

网关集成限流

搞清楚了time/rate限速器使用和源码逻辑后,就可以看到我们的主线:微服务网关的中间件上了

拿HTTP的限流中间件举例:

http_flow_limit.go

  1. 从上下文中拿到服务信息
  2. 调用在public中封装好的限流器获取实例方法创建对应的服务端/客户端限流器实例limiter
  3. 从拿到的limiter中调用Allow方法消费即可
func HTTPFlowLimitMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      serviceInterface, ok := c.Get("service")
      if !ok {
         middleware.ResponseError(c, 2001, errors.New("service not fount"))
         c.Abort()
         return
      }
      serviceDetail := serviceInterface.(*dao.ServiceDetail)
      //服务端限流操作
      if serviceDetail.AccessControl.ServiceFlowLimit != 0 {
         //1、根据限流器创建返回的对应服务限流器实例
         serviceLimiter, err := public.FlowLimiterHandler.GetLimiter(
            public.FlowServicePrefix+serviceDetail.Info.ServiceName,
            float64(serviceDetail.AccessControl.ServiceFlowLimit),
         )
         if err != nil {
            middleware.ResponseError(c, 5001, err)
         }
         //2、对应服务限流器实例的请求消费令牌
         if !serviceLimiter.Allow() {
            middleware.ResponseError(c, 5002, errors.New(fmt.Sprintf("service flow limit %v", serviceDetail.AccessControl.ServiceFlowLimit)))
            c.Abort()
            return
         }
      }

      if serviceDetail.AccessControl.ClientIPFlowLimit != 0 {
         //客户端相同限流操作
         //1、创建限流器实例
         clientLimit, err := public.FlowLimiterHandler.GetLimiter(
            public.FlowServicePrefix+serviceDetail.Info.ServiceName+"_"+c.ClientIP(),
            float64(serviceDetail.AccessControl.ClientIPFlowLimit),
         )
         if err != nil {
            middleware.ResponseError(c, 5003, err)
            c.Abort()
            return
         }
         //2、请求消费令牌
         if !clientLimit.Allow() {
            middleware.ResponseError(c, 5002, errors.New(fmt.Sprintf("%v flow limit %v", c.ClientIP(), serviceDetail.AccessControl.ClientIPFlowLimit)))
            c.Abort()
            return
         }
      }
      c.Next()
   }
}

在中间件中获取限流器并消费令牌只有这么几步而已,接下来看public中封装好的限流器获取实例方法

flow_limit_handler.go

这里我们使用单例模式进行封装

具体单例模式的结构已经写了很多遍了,这里就不一一赘述了

直接看向核心方法GetLimiter()

看过上面的time/rate限速器使用和源码分析后,核心代码就很通俗易懂了

​ 根据服务信息中的qps,调用rate的构造方法**rate.NewLimiter(limit,burst)**即可得到限速器实例,limit是服务信息中的QPS,这里设置令牌桶的最大令牌数为QPS*3

然后!就没咯!!!就这么简单

var FlowLimiterHandler *FlowLimiter

type FlowLimiter struct {
   FlowLimiterMap   map[string]*FlowLimiterItem
   FlowLimiterSlice []*FlowLimiterItem
   Locker           sync.RWMutex
}

type FlowLimiterItem struct {
   ServiceName string
   Limiter     *rate.Limiter
}

func NewFlowLimiter() *FlowLimiter {
   return &FlowLimiter{
      FlowLimiterMap:   map[string]*FlowLimiterItem{},
      FlowLimiterSlice: []*FlowLimiterItem{},
      Locker:           sync.RWMutex{},
   }
}
func init() {
   FlowLimiterHandler = NewFlowLimiter()
}

func (counter *FlowLimiter) GetLimiter(serviceName string, qps float64) (*rate.Limiter, error) {
   for _, item := range counter.FlowLimiterSlice {
      if item.ServiceName == serviceName {
         return item.Limiter, nil
      }
   }

   //创建限流器(每秒令牌数,桶中最大令牌数)
   newLimiter := rate.NewLimiter(rate.Limit(qps), int(qps*3))
   item := &FlowLimiterItem{
      ServiceName: serviceName,
      Limiter:     newLimiter,
   }
   counter.FlowLimiterSlice = append(counter.FlowLimiterSlice, item)
   counter.Locker.Lock()
   defer counter.Locker.Unlock()
   counter.FlowLimiterMap[serviceName] = item
   return newLimiter, nil
}

你可能感兴趣的:(项目实践,微服务,中间件,golang,后端,http)