Как бы вы сбалансировали нагрузку на несколько * динамических * серверных экземпляров Google App Engine? - PullRequest
2 голосов
/ 20 ноября 2011

Я относительно новичок в StackOverflow и не уверен, что это подходящее место, чтобы задать вопрос по дизайну.Сайт подсказывает мне «Вопрос, который вы задаете, кажется субъективным и, вероятно, будет закрыт» .Возможно, это следует спросить на programmers.stackexchange.com .Пожалуйста, дайте мне знать.

В любом случае .. Один из проектов, над которым я работаю, это онлайн-опрос.Это мой первый крупный коммерческий проект на GAE .

Мне нужен ваш совет о том, как собирать статистику и эффективно записывать ее в хранилище данных, не разоряя меня.Начальные требования:

  • После того, как пользователь завершит опрос, клиент отправит список пар [ID (int) + PercentHit (double)].В этом списке показано, насколько близкие ответы этого пользователя соответствуют предварительно заданным ответам эталонных ответчиков (которые идентифицируются по идентификаторам).Я называю их «целевыми идентификаторами».
  • Создатель опроса хочет видеть агрегированный% для данных идентификаторов за последний час, конкретный период или с начала опроса.
  • Некоторые опросы могут иметьтысячи целевых / эталонных ответчиков.

Итак, я создал сущность

public class HitsStatsDO implements Serializable
{
    @Id
    transient private Long id;
    transient private Long version = (long) 0;

    transient private Long startDate;

    @Parent transient private Key parent;   // fake parent which contains target id
    @Transient int targetId;

    private double avgPercent;
    private long hitCount;
}

Но написание HitsStatsDO для каждой цели от каждого пользователя даст много данных.Например, у меня был опрос с 3000 мишенями, на который ответили ~ 4 миллиона человек в течение одной недели, и 300K человек приняли участие в опросе в первый день.Даже если мы предположим, что они отвечали на него равномерно в течение 24 часов, это дало бы нам ~ 1040 записей в секунду.Очевидно, что он достигает лимита одновременной записи в Datastore.

Я решил собрать данные за один час и сохранить их, поэтому в HitsStatsDO есть avgPercent и hitCount.Экземпляры GAE не имеют состояния, поэтому мне пришлось использовать динамический бэкэнд-экземпляр .

Там у меня есть что-то вроде этого:

// Contains stats for one hour
private class Shard
{
    ReadWriteLock lock = new ReentrantReadWriteLock();
    Map<Integer, HitsStatsDO> map = new HashMap<Integer, HitsStatsDO>(); // Key is target ID

    public void saveToDatastore();
    public void updateStats(Long startDate, Map<Integer, Double> hits);
}

и карта с осколком для текущего часа и предыдущегочас (который не остается здесь надолго)

private HashMap<Long, Shard> shards = new HashMap<Long, Shard>();   // Key is HitsStatsDO.startDate

Так, один раз в час я сбрасываю осколок за предыдущий час в хранилище данных.

Плюс у меня есть class LifetimeStats, который хранит Map<Integer, HitsStatsDO>в memcached, где map-key является идентификатором цели.

Также в моем методе back-end shutdown hook Я сбрасываю статистику за незавершенный час в хранилище данных.

Существует только одна серьезная проблемаздесь - У меня есть только ОДИН бэкэнд-экземпляр :) Возникают следующие вопросы, по которым я хотел бы услышать ваше мнение:

  • Можно ли это сделать без использования бэкэнд-экземпляра?
  • Что, если одного экземпляра недостаточно?
  • Как разделить данные между несколькими динамическими внутренними экземплярами?Это трудно, потому что я не знаю, сколько у меня есть, потому что Google создает новый при увеличении нагрузки.
  • Я знаю, что могу запустить точное количество резидентных бэкэндов.А сколько?2, 5, 10?Что делать, если у меня нет нагрузки на неделю.Постоянно работать с 10 экземплярами бэкэнда слишком дорого.
  • Что мне делать с данными от клиентов, когда бэкэнд-экземпляр не работает / перезапускается?

Следует отметить, что я не могуизменить клиента оченьВ настоящее время это JavaScript, встроенный в веб-страницы клиентов.Я могу каким-то образом изменить RPC, но архитектурно я не могу заменить клиента формами Google Docs, например.

Большое спасибо заранее за ваши мысли.

Ответы [ 3 ]

1 голос
/ 20 ноября 2011

Разработчики не должны уклоняться от интеграции автономных ресурсов, сайтов Google и API данных Google с gae.

Вы можете настроить сайт Google, который приведет к форме вашего опроса.

Ориентируйтесь на респондентовбудет вводить их ответы в вашу форму, и сайты Google собирают их в одну электронную таблицу документов Google.

Затем вы используете автономную систему (не gae), которая периодически обращается к этой «электронной таблице» через данные API Google /ежечасно загружать данные.

Документы Google предоставят вам время ввода данных, а дизайн вашей формы должен позволять респондентам индексировать.Таким образом, вы сможете загружать только сегменты «электронных таблиц».

Вам необходимо ознакомиться с OAuth и, возможно, федеративным пользователем входа в систему Google / openid.

Выможно изучить интеграцию имени пользователя респондента с вашей формой.

На самом деле вам, возможно, даже не придется использовать gae.

Вы должны иметь возможность использовать API сайтов Google для обновления своих страниц, обновлениястатистика, которая будет отображаться, чтобы переключить форму на новую электронную таблицу.

А затем использовать gae только для создания пользовательских страниц.

В качестве альтернативы, если у вас слишком большая близость для gae, выМожно использовать его для создания страниц опроса, а затем использовать данные API для сохранения результатов в Google Docs, но использовать собственные автономные ресурсы для выполнения статистических вычислений.

0 голосов
/ 05 декабря 2011

Мой сервис запущен, и я хочу поделиться тем, как я его реализовал.

Поэтому вместо того, чтобы собирать данные в памяти одного экземпляра бэкэнда в течение часа, я решил собирать их в несколько динамических экземпляров бэкэнда и обновлять шард для текущего часа в Datastore каждые 10 минут для каждого экземпляра. Класс Shard остается прежним, за исключением saveToDatastore(), где я теперь обновляю HitsStatsDOs в цикле транзакций, чтобы убедиться, что он обновляется, даже если другой бэкэнд-экземпляр изменяет шард в данный момент.

Чтобы действительно быстро получить HitsStatsDO, я решил поместить целевой идентификатор в фальшивый родительский ключ и метку времени, если этот жесткий первичный идентификатор такой, как этот

public class HitsStatsDO implements Serializable
{
    @Id
    transient private Long id;  // always equals to "startDate"
    @Unindexed
    transient private Long version = (long) 0;

    transient private Long startDate;

    @Parent
    transient private Key targetIdKey;   // fake parent which contains target id

    @Unindexed
    private double avgPercent;
    @Unindexed
    private long hitCount;

    public Key<HitsStatsDO> createKey()
    {
        return new Key<HitsStatsDO>(targetIdKey, HitsStatsDO.class, startDate);
    }

    public HitsStatsDO(Long startDate, long targetId)
    {
        this.id = this.startDate = startDate;
        this.targetIdKey = new Key(Long.class, targetId);
    }
}

Для этого объекта требуется только 2 записи. Количество записей никогда не превышает ([количество серверных экземпляров] * 2 * 6) в час, что неплохо. Также я могу предварительно создать ключи в своем коде и выполнить пакетную загрузку из хранилища данных.

Аналогичным образом я изменил HitsStatsTotalDO, который содержит статистику с начала опроса. Похоже, это

public class HitsStatsTotalDO implements Serializable
{
    @Id
    private Long targetId;
    @Unindexed
    transient private Long version = (long) 0;

    @Unindexed
    private double avgPercent;
    @Unindexed
    private long hitCount;
}

То же самое - 2 записи для сохранения / обновления.

Сервис запущен 3 дня назад. Максимальная нагрузка до сих пор составляла 230 QPS. Я использую динамические экземпляры типа B1. В конфигурации я установил максимум 4 экземпляра на данный момент, но, к моему удовольствию, GAE никогда не создавал более одного экземпляра. И, что удивительно, у меня еще не было исключений параллелизма.

Дайте мне знать, если у вас есть какие-либо вопросы или вы думаете, что я что-то пропустил.

И спасибо всем за помощь. StackOverflow - действительно классное сообщество.

0 голосов
/ 21 ноября 2011

Единственная причина, по которой вы сталкиваетесь с ограничениями одновременной записи в описываемой вами модели, заключается в том, что вы делаете все экземпляры дочерними объектами одного и того же родителя. Это необходимо только в том случае, если вам нужны объекты в одной и той же группе объектов для транзакционных целей, что здесь не так. Удалите родительское свойство и сохраните все объекты как объекты верхнего уровня, и вам больше не придется беспокоиться о частоте обновления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...