Я смотрю на поддержку веб-сокетов AWS API Gateway, о которой было объявлено относительно недавно -

https://aws.amazon.com/blogs/compute/announcing-websocket-apis-in-amazon-api-gateway/

У них есть образец сервера чата -

https://github.com/aws-samples/simple-websockets-chat-app/blob/master/sendmessage/app.js

Который у меня работает, очень красиво.

Если вы отправляете сообщение, sendmessage Lambda передает это сообщение всем подключенным пользователям с помощью следующего:

// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

const AWS = require('aws-sdk');

const ddb = new AWS.DynamoDB.DocumentClient({ apiVersion: '2012-08-10' });

const { TABLE_NAME } = process.env;

exports.handler = async (event, context) => {
  let connectionData;

  try {
    connectionData = await ddb.scan({ TableName: TABLE_NAME, ProjectionExpression: 'connectionId' }).promise();
  } catch (e) {
    return { statusCode: 500, body: e.stack };
  }

  const apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: '2018-11-29',
    endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
  });

  const postData = JSON.parse(event.body).data;

  const postCalls = connectionData.Items.map(async ({ connectionId }) => {
    try {
      await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
    } catch (e) {
      if (e.statusCode === 410) {
        console.log(`Found stale connection, deleting ${connectionId}`);
        await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
      } else {
        throw e;
      }
    }
  });

  try {
    await Promise.all(postCalls);
  } catch (e) {
    return { statusCode: 500, body: e.stack };
  }

  return { statusCode: 200, body: 'Data sent.' };
};

Теперь, к сожалению (к счастью ??), у меня есть фон Python / Erlang, а не Javascript / nodejs. Итак, я могу видеть, что некоторые из них делают, а именно перебирают соединения в таблице DynamoDB и отправляют ответ каждому. Он также выглядит так, как будто он работает асинхронно, благодаря использованию ключевых слов async и await, которые, как я полагаю, являются обещаниями. Но я не могу быть уверен , что это работает асинхронно, и это меня беспокоит ... если у меня есть одна лямбда, которая выполняет итерацию по большому количеству соединений и выполняет синхронные вызовы, это не сработает. .

Итак - особенно в отношении этой части кода -

 const postCalls = connectionData.Items.map(async ({ connectionId }) => {
    try {
      await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
    } catch (e) {
      if (e.statusCode === 410) {
        console.log(`Found stale connection, deleting ${connectionId}`);
        await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
      } else {
        throw e;
      }
    }
  });

Могу ли я быть уверен, что это отправляет postData для всех подключений полностью асинхронным образом? Должен ли я беспокоиться о том, что одна лямбда может отправить сообщения тысячам клиентов?

2
Justin 10 Окт 2019 в 15:46

1 ответ

Лучший ответ

Это не зря называется простым -websockets-chat-app :)

Читая код, он делает именно то, что вас беспокоит. Только один экземпляр лямбда отправит сообщение для всех подключений.

Но это чат, обычно бывает, что у вас тысячи пользователей?

Также похоже, что он работает асинхронно с использованием ключевых слов async и await, которые, я думаю, являются обещаниями.

Да, он работает асинхронно, но лямбда будет выполняться до тех пор, пока все сообщения не будут отправлены.


Около

  await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();

Это оказывает очень небольшое давление на саму лямбду, выполняя только удаленный вызов за раз. И будучи асинхронным, он не ждет ответа, а продолжает отправлять все больше и больше.

(примечание: в этом случае я бы использовал forEach вместо map)

Решение для чата с абсурдно высоким трафиком:

  • В DispatcherLambda разделите сканирование из TABLE_NAME на несколько фрагментов, отправьте их в очередь SQS и подпишите на него Lambda. Таким образом, будет несколько контейнеров, выполняющих этот код (в зависимости от выбранной вами степени детализации).
7
Horatiu Jeflea 10 Окт 2019 в 12:59