通过CloudFlare Worker搭建负载均衡服务器

in #cn8 months ago

Cloudflare Worker 是和 Amazon Lambda, Google Function 类似的无服务器 Serverless 技术。我们可以写一些代码(JS)部署到 CloudFlare 的网络节点中。这项技术的好处是我们并不需要去维护服务器(减少运维成本),而且通过Serverless技术很容易就可以把程序跑在成千上万的节点上 (较强的可扩展性)。

负载均衡服务器(Load Balancer)用于把用户的请求重新分配(Route)到提供真正服务的源服务器(Worker). 我们可以通过负载均衡来实现水平扩展(Horizontal Scaling). 当然如果负载均衡只有一台服务器,也是会有单点故障的 (Single Point of Failure).

如果通过CloudFlare Worker来搭建负载均衡,这样我们的负载均衡服务器会被自动部署到成千上万的CloudFlare节点中 - 可靠性 Durability 和可用性 Availability 就很靠谱了。

通过CloudFlare Worker搭建的分布式负载均衡服务器


设置分布式负载平衡器的成本是可以承受的。 CloudFlare工作者有一个免费计划-每天为您提供10万个API调用,每个API请求的最大CPU时间为10毫秒。 对于付费计划-每月报价为1000万个请求,最长50ms CPU时间。

例如,让我们首先定义分布式负载均衡器后面的源服务器列表。源服务器也就是真正干活的节点。

let nodes = [
  "https://api.justyy.com",
  "https://api.steemyy.com"
];

我们需要一个 Promise.any 实现方法 - 在 CloudFlare Worker 里 并不支持 Promise.any 但我们可以定义如下:

function reverse(promise) {
    return new Promise((resolve, reject) => Promise.resolve(promise).then(reject, resolve));
}

function promiseAny(iterable) {
    return reverse(Promise.all([...iterable].map(reverse)));
};

Promise.race 是返回第一个被实现或者拒绝的 Promise 而相反 Promise.any 是返回第一个被实现的 Promise. 所以用在这里就是获得响应最快(成功)的源服务器。

async function contactServer(server) {
  return new Promise((resolve, reject) => {
    fetch(server, {
      method: "GET"
    }).then(response => {
      resolve({
        "server": server,
      });
    }).catch(function(error) {
      reject(error);
    });
  });
}

我们还可以加于改进,为每个源服务器添加一个返回当前负载的API - 这样一来,我们就可以选出负载最小的那个源服务器了。

处理 跨域 CORs 和头部数据 Headers


以下JS代码用于处理跨域和相应的Headers。

function handleOptions(request) {
  // Make sure the necesssary headers are present
  // for this to be a valid pre-flight request
  if (
    request.headers.get('Origin') !== null &&
    request.headers.get('Access-Control-Request-Method') !== null &&
    request.headers.get('Access-Control-Request-Headers') !== null
  ) {
    // Handle CORS pre-flight request.
    // If you want to check the requested method + headers
    // you can do that here.
    return new Response(null, {
      headers: corsHeaders,
    })
  } else {
    // Handle standard OPTIONS request.
    // If you want to allow other HTTP Methods, you can do that here.
    return new Response(null, {
      headers: {
        Allow: 'GET, HEAD, POST, OPTIONS',
      },
    })
  }
}

addEventListener('fetch', event => {
  const request = event.request;
  const method = request.method.toUpperCase();
  if (method === 'OPTIONS') {
    // Handle CORS preflight requests
    event.respondWith(handleOptions(request))
  } else if (
    method === 'GET' ||
    method === 'HEAD' ||
    method === 'POST'
  ) {
    // Handle requests to the API server
    event.respondWith(handleRequest(request))
  } else {
    event.respondWith(
      new Response(null, {
        status: 405,
        statusText: 'Method Not Allowed',
      }),
    )
  }
});

转发请求


负载均衡最重要的部分就是转发请求。一旦我们知道哪个(最快)服务器应满足当前请求。 然后,我们需要将请求转发到原始服务器,并在获得结果后将其转发回用户。 以下是分别转发GET和POST请求的两个功能-您可能希望添加其他请求,例如PUT,PATCH,DELETE等。

async function forwardRequestGET(apiURL) {
  return new Promise((resolve, reject) => {
    fetch(apiURL, {
      method: "GET",   
      headers: {
        'Content-Type': 'application/json'
      },
      redirect: "follow"
    }).then(response => {
      resolve(response.text());
    }).catch(function(error) {
      reject(error);
    });
  });
}

async function forwardRequestPOST(apiURL, body) {
  return new Promise((resolve, reject) => {
    fetch(apiURL, {
      method: "POST",   
      redirect: "follow",
      headers: {
      'Content-Type': 'application/json'
      },      
      body: body
    }).then(response => {
      resolve(response.text());
    }).catch(function(error) {
      reject(error);
    });
  });
}

使用CloudFlare Worker的负载均衡器服务器


以下是CloudFlare负载均衡服务器的主要实现部分。该脚本将在CloudFlare分布缘网络节点上运行。

/**
 * Respond to the request
 * @param {Request} request
 */
async function handleRequest(request) {
  const country = request.headers.get('cf-ipcountry');
  const servers = [];
  for (const server of nodes) {
    servers.push(contactServer(server));
  }
  const load = await promiseAny(servers);    
  const forwardedURL = load['server'];
  const method = request.method.toUpperCase();
  let result;
  let res;
  let version = "";
  try {
    version = await getVersion(load['server']);
  } catch (e) {
    version = JSON.stringify(e);
  }
  try {    
    if (method === "POST") {
      const body = await request.text();    
      result = await forwardRequestPOST(forwardedURL, body);
    } else if (method === "GET") {
      result = await forwardRequestGET(forwardedURL);
    } else {
      res = new Response(null, {
        status: 405,
        statusText: 'Method Not Allowed',
      });
      res.headers.set('Access-Control-Allow-Origin', '*');
      res.headers.set('Cache-Control', 'max-age=3600');    
      res.headers.set("Origin", load['server']);
      res.headers.set("Country", country);
      return res;
    }
    res = new Response(result, {status: 200});    
    res.headers.set('Content-Type', 'application/json');
    res.headers.set('Access-Control-Allow-Origin', '*');
    res.headers.set('Cache-Control', 'max-age=3');
    res.headers.set("Origin", load['server']);
    res.headers.set("Version", version);
    res.headers.set("Country", country);
  } catch (e) {
    res = new Response(JSON.stringify(result), {status: 500});
    res.headers.set('Content-Type', 'application/json');
    res.headers.set('Access-Control-Allow-Origin', '*');
    res.headers.set('Cache-Control', 'max-age=3');    
    res.headers.set("Origin", load['server']);
    res.headers.set("Version", version);
    res.headers.set("Country", country);
    res.headers.set("Error", JSON.stringify(e));
  }
  return res;
}

我们还可以在负载均衡服务器返回数据前添加一些Headers数据,比如我们可以 获得源服务器版本 信息。

async function getVersion(server) {
  return new Promise((resolve, reject) => {
    fetch(server, {
      method: "POST",
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({"id":0,"jsonrpc":"2.0","method":"call","params":["login_api","get_version",[]]})
    }).then(response => {
      resolve(response.text());
    }).catch(function(error) {
      reject(error);
    });
  });
}

通过实现这种分布式负载均衡器 (Distributed Load Balancer) 节点,我们以低成本提高了可用性 avaibability 和耐用性 durability ,因为由于无服务器技术 serverless ,我们实际上不需要维护(监控升级和安全补丁)服务器。 同时,我们正在通过CloudFlare工作程序将请求转发到“最快的”原始服务器,该节点在地理位置上会接近用户(拥有低延迟)。

中文: 通过CloudFlare Worker搭建负载均衡服务器
英文: Tutorial: How to Set Up a API Load Balancer by Using CloudFlare Worker?

Coin Marketplace

STEEM 1.22
TRX 0.16
JST 0.176
TRX 0.16
STEEM 1.22
JST 0.176
SBD 9.11