修改负载均衡器以优先考虑具有最低响应延迟的后端

时间:2018-10-16 18:58:45

标签: javascript node.js typescript load-balancing

我正在尝试修改此负载平衡器,以使响应延迟最低的后端优先。我对JavaScript非常熟悉,但对TypeScript却一点也不熟悉。这就是写的内容。

任何想法或帮助将不胜感激。

 /**
 * A fetch function load balancer. Distributes requests to a set of backends; attempts to 
 * send requests to most recently healthy backends using a 2 random (pick two healthiest, 
 * randomize which gets requests).
 * 
 * If all backends are healthy, tries to evenly distribute requests as much as possible.
 * 
 * When backends return server errors (500-599) it retries idempotent requests
 *  until it gets a good response, or all backends have been tried.
 * 
 * @param backends fetch functions for each backend to balance accross
 * @returns a function that behaves just like fetch, with a `.backends` property for 
 * retrieving backend stats.
 */
    export default function balancer(backends: FetchFn[]) {
    const tracked = backends.map((h) => {
    if (typeof h !== "function") {
      throw Error("Backend must be a fetch like function")
    }
    return <Backend>{
      proxy: h,
      requestCount: 0,
      scoredRequestCount: 0,
      statuses: Array<number>(10),
      lastError: 0,
      healthScore: 1,
      errorCount: 0
    }
    })

    const fn = async function fetchBalancer(req: RequestInfo, init?: RequestInit 
    | undefined): Promise<Response> {
    if (typeof req === "string") {
      req = new Request(req)
    }
    const attempted = new Set<Backend>()
    while (attempted.size < tracked.length) {
      let backend: Backend | null = null
      const [backendA, backendB] = chooseBackends(tracked, attempted)

      if (!backendA) {
        return new Response("No backend available", { status: 502 })
      }
      if (!backendB) {
        backend = backendA
      } else {
        // randomize between 2 good candidates
        backend = (Math.floor(Math.random() * 2) == 0) ? backendA : backendB
      }

      const promise = backend.proxy(req, init)
      if (backend.scoredRequestCount != backend.requestCount) {
        // fixup score
        // this should be relatively concurrent with the fetch promise
        score(backend)
      }
      backend.requestCount += 1
      attempted.add(backend)

      let resp: Response
      try {
        resp = await promise
      } catch (e) {
        resp = proxyError
      }
      if (backend.statuses.length < 10) {
        backend.statuses.push(resp.status)
      } else {
        backend.statuses[(backend.requestCount - 1) % backend.statuses.length] = 
       resp.status
      }

      if (resp.status >= 500 && resp.status < 600) {
        backend.lastError = Date.now()
        // always recompute score on errors
        score(backend)

        // clear out response to trigger retry
        if (canRetry(req, resp)) {
          continue
        }
      }

      return resp
      }

      return proxyError
      }

       return Object.assign(fn, { backends: tracked })
      }
      const proxyError = new Response("couldn't connect to origin", { status: 
      502 })
      export interface FetchFn {
      (req: RequestInfo, init?: RequestInit | undefined): Promise<Response>
       }

/**
 * Represents a backend with health and statistics.
 */
      export interface Backend {
      proxy: (req: RequestInfo, init?: RequestInit | undefined) => 
      Promise<Response>,
      requestCount: 0,
      scoredRequestCount: 0,
      statuses: number[],
      lastError: number,
      healthScore: number,
      errorCount: 0
      }
// compute a backend health score with time + status codes
      function score(backend: Backend, errorBasis?: number) {
      if (typeof errorBasis !== "number" && !errorBasis) errorBasis = Date.now()

      const timeSinceError = (errorBasis - backend.lastError)
      const statuses = backend.statuses
      const timeWeight = (backend.lastError === 0 && 0) ||
      ((timeSinceError < 1000) && 1) ||
      ((timeSinceError < 3000) && 0.8) ||
      ((timeSinceError < 5000) && 0.3) ||
      ((timeSinceError < 10000) && 0.1) ||
      0;
      if (statuses.length == 0) return 0
      let requests = 0
      let errors = 0
      for (let i = 0; i < statuses.length; i++) {
      const status = statuses[i]
      if (status && !isNaN(status)) {
      requests += 1
      if (status >= 500 && status < 600) {
        errors += 1
      }
      }
      }
      const score = (1 - (timeWeight * (errors / requests)))
      backend.healthScore = score
      backend.scoredRequestCount = backend.requestCount
      return score
      }
      function canRetry(req: Request, resp: Response) {
      if (resp && resp.status < 500) return false // don't retry normal boring 
      errors or success
      if (req.method == "GET" || req.method == "HEAD") return true
      return false
      }

      function chooseBackends(backends: Backend[], attempted?: Set<Backend>) {
      let b1: Backend | undefined
      let b2: Backend | undefined
      for (let i = 0; i < backends.length; i++) {
      const b = backends[i]
      if (attempted && attempted.has(b)) continue;

      if (!b1) {
      b1 = b
      continue
      }
      if (!b2) {
      b2 = b
      continue
      }

      const old1 = b1
      b1 = bestBackend(b, b1)

      if (old1 != b1) {
      // b1 got replaced, make sure it's not better
      b2 = bestBackend(old1, b2)
      } else {
      b2 = bestBackend(b, b2)
      }
      }

      return [b1, b2]
      }

      function bestBackend(b1: Backend, b2: Backend) {
      if (
      b1.healthScore > b2.healthScore ||
      (b1.healthScore == b2.healthScore && b1.requestCount < b2.requestCount)
      ) {
      return b1
      }
      return b2
      }

      export const _internal = {
      chooseBackends,
      score
      }

0 个答案:

没有答案