написать впереди
Как спроектировать шиповую систему с высокой степенью параллелизма? Это часто задаваемый вопрос на собеседовании.Этот вопрос кажется простым, но вода внутри очень глубокая Он исследует различные знания от внешнего интерфейса до внутреннего в сценариях с высокой степенью параллелизма.
Удары молнии обычно появляются в рекламных акциях торговых центров с указанием определенного количества (например, 10) товаров (например, мобильных телефонов) по очень низкой цене (например, 0,1 юаня), что позволяет большому количеству пользователей участвовать в акциях. активность, но только очень небольшое количество Пользователь может совершить успешную покупку. Большинство продавцов в этом виде деятельности не зарабатывают, грубо говоря, ищут уловки для саморекламы.
Хотя seckill — это просто рекламная деятельность, технические требования не низкие. Ниже приводится сводка из 9 деталей, на которые необходимо обратить внимание при разработке системы seckill.Как только вы освоите их, вы сможете хорошо поболтать с интервьюером в будущем.
Эти 9 деталей кажутся довольно простыми, но это не так. Если вы хотите использовать их в умелом сочетании для достижения желаемого эффекта, вам все равно нужно глубоко изучить их. В этой статье мы кратко упомянем о них по отдельности. также есть специальные учебные материалы. Организовано, если какие-либо студенты заинтересованы, вы можете нажать прямоПримечания к исследованию дизайна системы с высоким параллелизмом
1 Мгновенный высокий параллелизм
Как правило, за несколько минут до времени seckill (например, в 12:00) параллелизм пользователей резко возрастает, а по истечении времени seckill параллелизм достигает своего пика.
Однако, поскольку этот тип деятельности представляет собой сценарий, в котором большое количество пользователей захватывает небольшое количество товаров, неизбежно будет больше волков и меньше мяса, поэтому на самом деле большинство пользователей не смогут убить за секунды, и только очень небольшое количество пользователей может добиться успеха.
В обычных условиях большинство пользователей получат напоминание о том, что товар распродан, после получения напоминания они, скорее всего, не останутся на активной странице, в результате чего количество одновременных пользователей резко упадет. Следовательно, продолжительность этого пика на самом деле очень мала, что вызовет мгновенный высокий параллелизм.Давайте воспользуемся картинкой, чтобы интуитивно почувствовать изменение трафика:
С таким временным сценарием с высоким параллелизмом трудно справиться традиционным системам, и нам необходимо разработать совершенно новую систему. Вы можете начать со следующих аспектов:
- статическая страница
- CDN-ускорение
- тайник
- mq асинхронная обработка
- Ограничение
- Распределенная блокировка
2. Статическая страница
Активная страница является первой точкой входа для пользовательского трафика, поэтому это место с наибольшим параллелизмом.
Если этот трафик может получить прямой доступ к серверу, я боюсь, что сервер напрямую зависнет, потому что он не выдержит такой большой нагрузки.
Большая часть содержимого страницы активности фиксирована, например: название продукта, описание продукта, изображения и т. д. Чтобы уменьшить количество ненужных запросов на стороне сервера, активная страница обычно обрабатывается статически. Регулярные операции, такие как просмотр продуктов пользователями, не будут запрашиваться на сервере. Доступ к серверу разрешается только по истечении времени seckill, когда пользователь активно нажимает кнопку seckill.
Это отфильтровывает большинство недействительных запросов.
Но сделать страницу статической недостаточно, потому что пользователи распределены по всей стране, кто-то в Пекине, кто-то в Чэнду, кто-то в Шэньчжэне, географическая разница очень велика, а скорость сети разные.
Как пользователи могут попасть на страницу мероприятия как можно быстрее?
Для этого требуется использование CDN, ее полное название Content Delivery Network, то есть сеть распространения контента.
Позвольте пользователям получать желаемый контент поблизости, уменьшите перегрузку сети и улучшите скорость отклика пользователя и количество попаданий.
3 кнопка блокировки
Большинство пользователей боятся пропустить время seckill и обычно заходят на страницу события заранее. Кнопка seckill, которую вы видите в это время, неактивна и не может быть нажата. Только в момент времени seckill кнопка seckill автоматически загорится и станет доступной для нажатия.
Но в это время многим пользователям не терпится, постоянно обновляя страницу, они стремятся как можно скорее увидеть загорание кнопки шипа.
Как вы уже знаете, страница активности статична. Так как же нам управлять кнопкой seckill на статической странице, чтобы она загоралась только во время seckill?
Правильно, используйте управление файлами js.
Из соображений производительности статические файлы ресурсов, такие как css, js и изображения, обычно заранее кэшируются в CDN, чтобы пользователи могли получить доступ к странице seckill поблизости.
Увидев это, некоторые умные друзья могут спросить: как обновляются файлы js в CDN?
Перед началом всплеска флаг js равен false, и есть еще один случайный параметр.
При запуске seckill система сгенерирует новый файл js, флаг в это время истинен, и для случайного параметра будет сгенерировано новое значение, а затем синхронизировано с CDN. Из-за этого случайного параметра CDN не будет кэшировать данные, и каждый раз из CDN можно будет получать последний js-код.
Кроме того, во фронтенд можно добавить еще и таймер для контроля, например, в течение 10 секунд разрешен только один запрос. Если пользователь нажмет кнопку seckill один раз, она станет неактивной в течение 10 секунд, и повторное нажатие будет невозможно. После истечения срока разрешено снова нажать кнопку.
4 Больше читайте и меньше пишите
В процессе seckill система, как правило, сначала проверяет, достаточно ли инвентаря, и если достаточно, то разрешает разместить заказ и записать базу данных. Если этого недостаточно, вернитесь непосредственно к проданному товару.
Поскольку большое количество пользователей захватывает небольшое количество товаров, только очень небольшое количество пользователей может успешно захватить его, поэтому, когда большинство пользователей находятся в пике, запасов фактически недостаточно, и система напрямую вернет, что товары есть. был схвачен.
Это очень типичный сценарий: больше читайте и меньше пишите.
При наличии сотен тысяч запросов и проверке базы данных на предмет достаточности кеша база данных может зависнуть. Поскольку ресурсы соединений базы данных очень ограничены, например: mysql, она не может поддерживать столько соединений одновременно.
Вместо этого используйте кеш, например Redis.
Даже если используется Redis, необходимо развернуть несколько узлов.
5 Проблемы с кешем
В обычных условиях нам необходимо сохранять информацию о продукте в Redis, которая включает в себя: идентификатор продукта, название продукта, атрибуты спецификации, инвентарь и другую информацию, и в базе данных должна быть соответствующая информация, ведь кеш не является полностью надежным.
Когда пользователь нажимает кнопку seckill и запрашивает интерфейс seckill, ему необходимо передать параметр идентификатора товара, а затем сервер должен проверить, является ли товар законным.
Общий процесс показан на следующем рисунке:
По идентификатору продукта сначала запросите продукт из кеша, и, если продукт существует, примите участие в seckill. Если его нет, нужно запросить товар из базы, если он есть, поместить информацию о товаре в кеш, а затем участвовать в ударе молнии. Если продукт не существует, он сразу вызовет отказ.
На первый взгляд этот процесс выглядит нормально, но если копнуть глубже, вы обнаружите некоторые проблемы.
5.1 Разбивка кеша
Например, когда продукт А убивают в первый раз, данных в кеше нет, а в базе данных есть. Хотя вышеприведенная логика заключается в том, что если данные найдены из базы данных, они помещаются в кеш.
Однако при высоком уровне параллелизма одновременно будет большое количество запросов, и все они убивают один и тот же продукт за считанные секунды.Эти запросы одновременно проверяют, нет ли данных в кеше, а затем обращаются к базе данных по адресу в то же время. Итог трагичен, БД может не выдержать нагрузки и зависнуть напрямую.
Как решить эту проблему?
Для этого требуется блокировка, предпочтительно распределенная блокировка.
Конечно, ввиду такой ситуации лучше всего прогреть кеш перед запуском проекта. То есть заранее синхронизируйте все товары с кешем, чтобы товары можно было получить в основном напрямую из кеша, и не возникало проблемы поломки кеша.
Вышеупомянутый шаг блокировки не нужен?
На поверхности это действительно не должно быть. Однако, если время истечения срока действия, установленное в кеше, неверно, срок действия кеша истекает раньше, или кеш случайно удален, то также может произойти сбой кеша, если он не ускорен.
По сути, блокировка здесь эквивалентна покупке страховки.
5.2 Проникновение в кэш
Если есть большое количество запросов на входящие идентификаторы элементов, которые не существуют в кеше и базе данных, эти запросы не будут каждый раз проходить через кеш, а напрямую обращаться к базе данных.
Так как блокировка была добавлена ранее, даже если количество параллелизма здесь велико, это не приведет к зависанию базы данных напрямую.
Но очевидно, что производительность обработки этих запросов не очень хорошая, есть ли лучшее решение?
Здесь на ум приходит фильтр Блума.
По идентификатору продукта система сначала проверяет, существует ли идентификатор из фильтра Блума.Если он существует, то позволяет запросить данные из кеша.Если он не существует, он напрямую возвращает отказ.
Хотя это решение может решить проблему проникновения в кеш, оно приведет к другому вопросу: как данные в фильтре Блума могут быть более согласованы с данными в кеше?
Это требует, чтобы, если данные в кеше обновляются, они должны были вовремя синхронизироваться с фильтром Блума. Если синхронизация данных не удалась, необходимо добавить механизм повторных попыток, и можно ли гарантировать согласованность данных в реальном времени между источниками данных?
Очевидно нет.
Поэтому фильтры Блума в основном используются в сценариях, где данные кэша редко обновляются.
Что делать, если кэшированные данные обновляются очень часто?
В это время необходимо кэшировать несуществующий идентификатор товара.
В следующий раз идет другой запрос id товара, и данные тоже можно найти из кеша, но данные специальные, указывающие на то, что товара не существует. Важно отметить, что тайм-аут для этой специальной настройки кэша должен быть как можно короче.
6 Проблемы с инвентарем
Проблема с инвентарем кажется простой, но что-то в ней все же есть.
Реальная сцена товаров мгновенного убийства не означает, что после вычета инвентаря все кончено.Если пользователь не совершит платеж в течение определенного периода времени, вычтенный инвентарь будет добавлен обратно.
Поэтому здесь вводится понятие удержания инвентаря.Основной процесс удержания инвентаря выглядит следующим образом:
В дополнение к упомянутым выше удержанию и возврату запасов, особое внимание следует уделить проблеме нехватки запасов и перепроданности запасов.
6.1 Инвентаризация базы данных
Использование базы данных для вычета запасов — самое простое решение.Предположим, что sql для вычета запасов выглядит следующим образом:
update product set stock=stock-1 where id=123;
Такой способ написания не является проблемой для вычета инвентаря, но как контролировать нехватку инвентаря и не допускать работы пользователей?
Для этого перед обновлением необходимо проверить, достаточно ли запасов.
Псевдокод выглядит следующим образом:
int stock = mapper.getStockById(123);
if(stock > 0) {
int count = mapper.updateStock(123);
if(count > 0) {
addOrder(123);
}
}
Вы нашли какие-либо проблемы с этим кодом?
Да, операции запроса и операции обновления не являются атомарными, что приведет к перепроданности запасов в параллельных сценариях.
Некоторые люди могут сказать, что с этим легко справиться, добавить блокировку или вы можете это сделать, например, используя ключевое слово synchronized.
Да, да, но производительность не достаточно хороша.
Есть и более изящное решение, а именно оптимистическая блокировка на основе базы данных, которая сэкономит один запрос к базе данных и, естественно, гарантирует атомарность операций с данными.
Просто немного подправьте sql выше:
update product set stock=stock-1 where id=product and stock > 0;
Добавьте: stock > 0 в конце sql, чтобы не было ситуации перепроданности.
Но требуется частый доступ к базе данных, а все мы знаем, что подключения к базе данных являются очень дорогими ресурсами. В сценариях с высокой степенью параллелизма могут возникать системные лавины. Более того, несколько запросов склонны конкурировать за блокировку строк одновременно, что приводит к взаимному ожиданию и взаимоблокировкам.
6.2 Redis вычитает инвентарь
Метод incr Redis является атомарным и может использоваться для вычета запасов. Псевдокод выглядит следующим образом:
boolean exist = redisClient.query(productId,userId);
if(exist) {
return -1;
}
int stock = redisClient.queryStock(productId);
if(stock <=0) {
return 0;
}
redisClient.incrby(productId, -1);
redisClient.add(productId,userId);
return 1;
Поток кода выглядит следующим образом:
- Сначала определите, добавил ли пользователь продукт, и если уже добавил, верните -1 напрямую.
- Запросите инвентарь, если инвентарь меньше или равен 0, он напрямую вернет 0, указывая на то, что инвентаря недостаточно.
- Если инвентаря достаточно, инвентарь будет вычтен, а затем запись об этом шипе будет сохранена. Затем верните 1, что означает успех.
Подсчитано, что многие мелкие партнеры будут писать код таким образом в начале. Но если вы задумаетесь, то обнаружите, что с этим кодом что-то не так.
В чем проблема?
Если есть несколько запросов на запрос инвентаризации одновременно с высокой степенью параллелизма, все они в это время больше 0. Из-за не принципиальных операций запроса инвентаря и обновления инвентаря будет ситуация, когда инвентарь отрицательный, то есть инвентарь перепродан.
Конечно, некоторые люди могут сказать, что добавление синхронизированного не решит проблему?
Скорректированный код выглядит следующим образом:
boolean exist = redisClient.query(productId,userId);
if(exist) {
return -1;
}
synchronized(this) {
int stock = redisClient.queryStock(productId);
if(stock <=0) {
return 0;
}
redisClient.incrby(productId, -1);
redisClient.add(productId,userId);
}
return 1;
Добавление synchronized действительно может решить проблему отрицательного инвентаря, но это приведет к резкому падению производительности интерфейса, а каждый запрос должен конкурировать за одну и ту же блокировку, что явно неразумно.
Для решения вышеуказанной проблемы код оптимизируется следующим образом:
boolean exist = redisClient.query(productId,userId);
if(exist) {
return -1;
}
if(redisClient.incrby(productId, -1)<0) {
return 0;
}
redisClient.add(productId,userId);
return 1;
Основной поток кода выглядит следующим образом:
- Сначала определите, добавил ли пользователь продукт, и если уже добавил, верните -1 напрямую.
- Вычтите инвентарь и определите, меньше ли возвращенное значение 0. Если оно меньше 0, он сразу вернет 0, указывая на то, что инвентаря недостаточно.
- Если возвращенное значение больше или равно 0 после вычета запасов, запись об этом всплеске будет сохранена. Затем верните 1, что означает успех.
На первый взгляд программа неплохая.
Однако при наличии нескольких запросов на вычет запасов одновременно в сценарии с высокой степенью параллелизма результат большинства запросов будет меньше 0 после операции incrby.
Несмотря на то, что запасы отрицательные, проблемы перепроданности не будет. Однако, поскольку запасы здесь предварительно уменьшены, если отрицательное значение слишком отрицательное, запасы будут неточными, если запасы будут возвращены позже.
Итак, есть ли лучшее решение?
6.3 lua скрипт для вычета инвентаря
Все мы знаем, что lua-скрипт может гарантировать атомарность, и он может прекрасно решить вышеуказанные проблемы при использовании вместе с Redis.
Сценарий lua имеет очень классический код:
StringBuilder lua = new StringBuilder();
lua.append("if (redis.call('exists', KEYS[1]) == 1) then");
lua.append(" local stock = tonumber(redis.call('get', KEYS[1]));");
lua.append(" if (stock == -1) then");
lua.append(" return 1;");
lua.append(" end;");
lua.append(" if (stock > 0) then");
lua.append(" redis.call('incrby', KEYS[1], -1);");
lua.append(" return stock;");
lua.append(" end;");
lua.append(" return 0;");
lua.append("end;");
lua.append("return -1;");
Основной поток кода выглядит следующим образом:
- Сначала определите, существует ли идентификатор продукта, и вернитесь напрямую, если он не существует.
- Получите инвентарь с идентификатором продукта, и если будет установлено, что инвентарь равен -1, он будет возвращен напрямую, что указывает на то, что инвентарь не ограничен.
- Если инвентарь больше 0, вычесть инвентарь.
- Если инвентарь равен 0, он возвращается напрямую, указывая на то, что инвентаря недостаточно.
7 распределенных замков
Как я уже говорил, при секкилле сначала нужно проверить, существует ли элемент в кеше, если его нет, то элемент будет проверен из базы данных. Если в базе данных, поместите элемент в кеш и верните. Если в базе данных нет, вернуть ошибку напрямую.
Только представьте, если в режиме high concurrency будет большое количество запросов на проверку товара, которого нет в кеше, и эти запросы будут отправляться напрямую в базу данных. База данных зависает напрямую, потому что не может выдержать нагрузки.
Итак, как решить эту проблему?
Это требует использования распределенных блокировок redis.
7.1 блокировка setNx
При использовании распределенной блокировки redis первое, что приходит на ум, — это команда setNx.
if (jedis.setnx(lockKey, val) == 1) {
jedis.expire(lockKey, timeout);
}
Использование этой команды действительно может добавить блокировку, но она отделена от времени ожидания, установленного позже, и не является атомарной операцией.
Если блокировка выполнена успешно, но установка времени ожидания не удалась, срок действия lockKey никогда не истечет. В сценариях с высокой степенью параллелизма эта проблема может привести к очень серьезным последствиям.
Итак, существует ли команда блокировки, гарантирующая атомарность?
7.2 установить замок
Используйте команду redis set, которая может указывать несколько параметров.
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
return false;
в:
- lockKey: идентификатор замка
- requestId: идентификатор запроса
- NX: Установите ключ, только если ключ не существует.
- PX: установите время истечения ключа в миллисекунды миллисекунды.
- expireTime: время истечения срока действия
Поскольку команда состоит только из одного шага, это атомарная операция.
7.3 Снять блокировку
Далее некоторые знакомые могут спросить: при блокировке, раз уж есть идентификация блокировки lockKey, то зачем нужно записывать requestId?
A: requestId используется при снятии блокировки.
if (jedis.get(lockKey).equals(requestId)) {
jedis.del(lockKey);
return true;
}
return false;
При снятии блокировки могут быть сняты только блокировки, добавленные вами, а блокировки, добавленные другими, не могут быть сняты.
Зачем использовать здесь requestId вместо userId?
A: Если используется userId, предполагается, что процесс запроса завершен и блокировка готова к удалению. В этот момент срок действия блокировки совпадения истекает, когда он истекает. И другой запрос, случайно использующий ту же блокировку userId, будет успешным. Когда в этот раз был сделан запрос на удаление блокировки, на самом деле была удалена чужая блокировка.
Конечно, этой проблемы также можно избежать, используя lua-скрипты:
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
Это гарантирует, что запрос о существовании блокировки и удаление блокировки являются атомарными операциями.
7.4 Спин-блокировки
Вышеприведенный метод блокировки кажется хорошим, но если вы хорошенько обдумаете, если 10 000 запросов одновременно конкурируют за блокировку, только один запрос может быть успешным, а остальные 9999 запросов потерпят неудачу.
В случае пикового сценария, в чем может быть проблема?
О: Из каждых 10 000 запросов 1 является успешным. После 10 000 запросов 1 удалось. И так до тех пор, пока не закончатся запасы. Это становится равномерно распределенным шипом, который отличается от того, что мы себе представляли.
Как решить эту проблему?
Ответ: используйте спиновые замки.
try {
Long start = System.currentTimeMillis();
while(true) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
long time = System.currentTimeMillis() - start;
if (time>=timeout) {
return false;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally{
unlock(lockKey,requestId);
}
return false;
В течение заданного времени, например 500 миллисекунд, spin продолжает пытаться заблокироваться и в случае успеха возвращается напрямую. Если это не удается, засните на 50 миллисекунд, а затем инициируйте новый раунд попыток. Если период тайм-аута достигнут, а блокировка не была успешно заблокирована, она сразу вернет ошибку.
7.5 redisson
В дополнение к вышеперечисленным проблемам при использовании распределенных блокировок Redis также возникают проблемы с конкуренцией блокировок, проблемы с продлением, проблемы с повторным входом в блокировку и проблемы с блокировкой нескольких экземпляров Redis.
Эти проблемы могут быть решены с помощью redisson.Из-за места есть возможность рассмотреть специальную тему для внедрения распределенных блокировок позже.
8 мкв асинхронной обработки
Все мы знаем, что в сценарии реального всплеска есть три основных процесса:
Среди этих трех основных процессов реальный параллелизм — это пиковая функция, а реальный параллелизм функций заказа и оплаты очень мал. Поэтому, когда мы проектируем систему seckill, необходимо отделить функции заказа и оплаты от основного процесса seckill, особенно функция заказа должна обрабатываться mq асинхронно. Платежная функция, такая как платеж Alipay, является асинхронной, что гарантируется самим бизнес-сценарием.
Поэтому процесс оформления заказа после seckill становится следующим:
Если вы используете mq, вам необходимо обратить внимание на следующие вопросы:
8.1 Проблема потери сообщений
seckill прошел успешно, при отправке сообщения заказа в mq может произойти сбой. Причин много, например: проблемы с сетью, зависания брокера, проблемы с диском сервера mq и т.д. В этих случаях сообщения могут быть потеряны.
Итак, как предотвратить потерю сообщений?
A: Добавьте форму отправки сообщения.
Прежде чем производитель отправит сообщение mq, сообщение сначала записывается в таблицу отправки сообщений, начальное состояние должно быть обработано, а затем отправляется сообщение mq. Когда потребитель получает сообщение, после обработки бизнес-логики он вызывает интерфейс производителя, чтобы изменить статус сообщения на обработанный.
Если производителю не удается отправить сообщение mq на сервер mq после записи сообщения в таблицу отправки сообщений, сообщение теряется.
В это время, как с этим бороться?
A: Используйте работу, чтобы увеличить количество повторных попыток.
Используйте задание для запроса ожидающих данных в таблице отправки сообщений через равные промежутки времени, а затем повторно отправьте сообщение mq.
8.2 Проблема повторного потребления
Первоначально, когда потребители потребляют сообщения, когда ack отвечает, если сеть истекает, они могут потреблять дубликаты сообщений. Однако, поскольку отправитель сообщения добавляет механизм повтора, вероятность того, что потребитель повторит сообщение, возрастает.
Итак, как решить проблему дублирования сообщений?
A: Добавьте таблицу обработки сообщений.
После того, как потребитель прочитает сообщение, он сначала определяет, существует ли сообщение в таблице обработки сообщений.Если оно существует, это означает повторное потребление и прямой возврат. Если его нет, разместите заказ, затем запишите сообщение в таблицу обработки сообщений, а затем вернитесь.
Ключевой момент: размещение заказа и запись таблицы обработки сообщений должны быть помещены в одну и ту же транзакцию, чтобы обеспечить атомарные операции.
8.3 Проблемы со спамом
На первый взгляд эта схема выглядит прекрасно, но если возникает ситуация, когда потребление сообщений не работает. Например, по некоторым причинам потребитель сообщений не может разместить заказ и не может вызвать интерфейс изменения состояния, поэтому задание будет продолжать повторять попытки отправки сообщений. В итоге генерируется много спама.
Итак, как решить эту проблему?
Каждый раз, когда задание повторяется, необходимо сначала определить, достигло ли максимальное количество раз, когда сообщение отправляется в таблице отправки сообщений, и если да, то вернуться напрямую. Если не достигнуто, увеличьте счетчик на 1 и отправьте сообщение.
Таким образом, в случае возникновения исключения будет сгенерировано лишь небольшое количество спама, и это не повлияет на обычный бизнес.
8.4 Проблема отсроченного потребления
При нормальных обстоятельствах, если пользователю удастся выполнить всплеск и оплата не будет завершена в течение 15 минут после размещения заказа, заказ будет автоматически отменен, а инвентарь будет возвращен.
Итак, как реализовать функцию автоматической отмены заказа, если оплата не будет произведена в течение 15 минут?
Наверное, первое, что приходит на ум, это работа, потому что она проще.
Но есть проблема с заданием, его нужно время от времени обрабатывать, а производительность в реальном времени не очень хорошая.
Есть ли лучшее решение?
О: Используйте отложенную очередь.
Мы все знаем, что RocketMQ поставляется с функцией очереди задержки.
При оформлении заказа производитель сообщений сначала сформирует заказ со статусом «ожидает оплаты», а затем отправит сообщение в очередь задержки. Когда время задержки достигнуто, после того, как потребитель сообщения прочитает сообщение, он запросит, находится ли статус заказа в ожидании оплаты. Если он ожидает оплаты, статус заказа будет обновлен до статуса отмены. Если он не находится в статусе ожидания оплаты, это означает, что заказ был оплачен, и он будет возвращен напрямую.
Еще одним ключевым моментом является то, что после того, как пользователь совершит платеж, статус заказа изменится на оплаченный.
9 Как ограничить ток
С помощью Lightning Deals, если нам повезет, мы сможем купить хорошие товары по очень низким ценам (эта вероятность сравнима с выигрышем в лотерейный билет).
Однако некоторые мастера будут не так честны, как мы, нажав кнопку seckill на странице seckill и расхватав товар. Они могут войти в систему, имитируя обычных пользователей на своих серверах, пропустить страницу seckill и напрямую вызвать интерфейс seckill.
Если мы сделаем это вручную, при нормальных обстоятельствах мы сможем нажимать кнопку seckill только один раз в секунду.
Но если это сервер, то за одну секунду могут быть запрошены тысячи интерфейсов.
Этот разрыв слишком очевиден, если не будет ограничений, то большая часть продуктов может быть захвачена машинами, а не обычными пользователями, что немного несправедливо.
Поэтому нам необходимо выявить эти незаконные запросы и ввести некоторые ограничения. Итак, что нам теперь делать с этими незаконными запросами?
Существует два широко используемых метода ограничения тока:
- Текущее ограничение на основе nginx
- Текущее ограничение на основе Redis
9.1 Ограничение тока для одного и того же пользователя
Чтобы пользователь не запрашивал интерфейс слишком часто, вы можете только ограничить пользователя.
Ограничьте один и тот же идентификатор пользователя, например, интерфейс можно запрашивать только 5 раз в минуту.
9.2 Ограничить ток одним и тем же ip
Иногда недостаточно ограничить поток определенного пользователя, некоторые мастера могут имитировать запросы нескольких пользователей, и такой nginx не может быть распознан.
В это время вам нужно добавить ту же функцию ограничения тока IP.
Ограничьте один и тот же ip, например, интерфейс можно запрашивать только 5 раз в минуту.
Однако такой метод ограничения тока может привести к непредумышленному убийству. Например, выходной IP-адрес одной и той же компании или интернет-кафе один и тот же. Если есть несколько обычных пользователей, которые одновременно инициируют запросы, некоторые пользователи могут быть ограничены.
9.3 Ограничение тока на интерфейсах
Не думайте, что все будет хорошо, если вы ограничите пользователей и IP-адреса, некоторые специалисты могут даже использовать прокси для смены IP-адресов каждый раз, когда они запрашивают.
В это время вы можете ограничить общее количество запросов к интерфейсу.
В сценариях с высокой степенью параллелизма это ограничение необходимо для стабильности системы. Однако может случиться так, что количество незаконных запросов достигнет верхнего предела запросов для этого интерфейса, что помешает другим обычным пользователям получить доступ к этому интерфейсу. Кажется, немного больше, чем стоит.
9.4 Добавить проверочный код
По сравнению с тремя вышеперечисленными способами способ добавления проверочного кода может быть более точным, а также может ограничивать частоту доступа пользователя, но преимущество в том, что не будет непредумышленного убийства.
Как правило, перед отправкой запроса пользователям необходимо ввести код подтверждения. После того, как пользователь инициирует запрос, сервер проверит правильность кода подтверждения. Следующий шаг разрешен только в том случае, если он правильный, в противном случае он вернется напрямую, и будет предложено ввести ошибку кода подтверждения.
Кроме того, проверочный код, как правило, является одноразовым, и один и тот же проверочный код можно использовать только один раз, а повторное использование не допускается.
Обычные проверочные коды могут быть взломаны, потому что сгенерированные числа или шаблоны относительно просты. Преимущество в том, что скорость генерации относительно высока, а недостаток в том, что существуют риски безопасности.
Существует также проверочный код под названием Mobile Slider, который генерируется медленнее, но более безопасен и в настоящее время является предпочтительным выбором крупных интернет-компаний.
9.5 Повышение бизнес-порога
Хотя вышеупомянутый проверочный код может ограничивать незаконные запросы пользователей, он в некоторой степени повлияет на работу пользователей. Прежде чем пользователь нажмет кнопку seckill, он должен сначала ввести проверочный код.Этот процесс немного громоздкий.Не должен ли процесс функции seckill быть максимально простым?
На самом деле иногда для достижения определенной цели не обязательно техническими средствами, но и деловыми средствами.
Когда 12306 только запустился, люди по всей стране одновременно спешили за билетами на поезд, из-за большого количества параллелизма система часто зависала. Позже, после рефакторинга и оптимизации, цикл покупки был увеличен, и билеты на поезд можно было купить за 20 дней, а билеты на поезд можно было купить на час в 9:00, 10, 11 и 12:00. После корректировки бизнеса (конечно, технических корректировок тоже много) ранее централизованные запросы рассыпаются, что сразу уменьшает количество одновременных пользователей.
Здесь мы подняли бизнес-порог, например, только участники могут участвовать в действиях seckill, а обычные зарегистрированные пользователи не имеют разрешения. Или только обычные пользователи с уровнем 3 и выше имеют право участвовать в этом событии.
Просто немного повысив порог, даже скальперы беспомощны: они не могут тратить дополнительные деньги на пополнение баланса для участия в пиковой активности, верно?
над.
Сегодняшний обмен приходит сюда первым, увидимся в следующий раз.