Тест-интервью: как спроектировать систему шипов?

интервью задняя часть
Тест-интервью: как спроектировать систему шипов?

предисловие

Как спроектировать шиповую систему с высокой степенью параллелизма? Это часто задаваемый вопрос на собеседовании. Этот вопрос кажется простым, но вода внутри очень глубокая Он исследует различные знания от внешнего интерфейса до внутреннего в сценариях с высоким параллелизмом.

Шипы обычно появляются в торговом центре促销活动В , указано определенное количество (например: 10) товаров (например, мобильных телефонов), и большому количеству пользователей разрешено участвовать в деятельности по очень низкой цене (например, 0,1 юаня), но только очень небольшое количество пользователей может успешно купить. Большинство продавцов в этом виде деятельности не зарабатывают, грубо говоря, ищут уловки для саморекламы.

Хотя seckill — это просто рекламная деятельность, технические требования не низкие. Ниже приводится сводка из 9 деталей, на которые необходимо обратить внимание при разработке системы seckill.

图片

1 Мгновенный высокий параллелизм

обычно в秒杀时间点(Например: 12:00) Количество одновременных пользователей внезапно увеличилось всего несколько минут назад, и когда будет достигнуто время мгновенного уничтожения, количество одновременных пользователей достигнет своего пика.

Однако, поскольку этот тип активности представляет собой сцену, в которой большое количество пользователей захватывает небольшое количество товаров, это неизбежно произойдет.狼多肉少Следовательно, на самом деле подавляющее большинство пользователей не сможет убить, и только очень небольшое количество пользователей сможет добиться успеха.

В обычных условиях большинство пользователей получат напоминание о том, что товар распродан, после получения напоминания они, скорее всего, не останутся на активной странице, в результате чего количество одновременных пользователей резко упадет. Следовательно, продолжительность этого пика на самом деле очень мала, что вызовет мгновенный высокий параллелизм.Давайте воспользуемся картинкой, чтобы интуитивно почувствовать изменение трафика:

图片

С таким временным сценарием с высоким параллелизмом трудно справиться традиционным системам, и нам необходимо разработать совершенно новую систему. Вы можете начать со следующих аспектов:

  1. статическая страница

  2. CDN-ускорение

  3. тайник

  4. mq асинхронная обработка

  5. Ограничение

  6. Распределенная блокировка

2. Статическая страница

Активная страница является первой точкой входа для пользовательского трафика, поэтому это место с наибольшим параллелизмом.

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

图片Большая часть содержимого страницы активности фиксирована, например: название продукта, описание продукта, изображения и т. д. Чтобы уменьшить количество ненужных запросов на стороне сервера, обычно静态化иметь дело с. Регулярные операции, такие как просмотр продуктов пользователями, не будут запрашиваться на сервере. Доступ к серверу разрешается только по истечении времени seckill, когда пользователь активно нажимает кнопку seckill.

图片Это отфильтровывает большинство недействительных запросов.

Но сделать страницу статической недостаточно, потому что пользователи распределены по всей стране, кто-то в Пекине, кто-то в Чэнду, кто-то в Шэньчжэне, географическая разница очень велика, а скорость сети разные.

Как пользователи могут попасть на страницу мероприятия как можно быстрее?

Для этого требуется использование CDN, ее полное название Content Delivery Network, то есть сеть распространения контента.

图片Позвольте пользователям получать желаемый контент поблизости, уменьшите перегрузку сети и улучшите скорость отклика пользователя и количество попаданий.

3 кнопка блокировки

Большинство пользователей боятся упустить秒杀时间点, обычно заходят на страницу мероприятия заранее. видел в это время秒杀按钮Оно неактивно и не кликабельно. Только в момент времени seckill кнопка seckill автоматически загорится и станет доступной для нажатия.

Но в это время многим пользователям не терпится, постоянно обновляя страницу, они стремятся как можно быстрее увидеть загорание кнопки seckill.

Как вы уже знаете, страница активности статична. Так как же нам управлять кнопкой seckill на статической странице, чтобы она загоралась только во время seckill?

Правильно, используйте управление файлами js.

Из соображений производительности статические файлы ресурсов, такие как css, js и изображения, обычно заранее кэшируются в CDN, чтобы пользователи могли получить доступ к странице мгновенного уничтожения поблизости.

Увидев это, некоторые умные друзья могут спросить: как обновляются файлы 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;

Добавьте в конце sql:stock > 0, чтобы не было условий перепроданности.

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

6.2 Redis вычитает инвентарь

редисincrЭтот метод является атомарным и может использоваться для вычета запасов. Псевдокод выглядит следующим образом:

 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. Сначала определите, добавил ли пользователь продукт, и если уже добавил, верните -1 напрямую.

  2. Запросите инвентарь, если инвентарь меньше или равен 0, он напрямую вернет 0, указывая на то, что инвентаря недостаточно.

  3. Если инвентаря достаточно, инвентарь будет вычтен, а затем запись об этом шипе будет сохранена. Затем верните 1, что означает успех.

Подсчитано, что многие мелкие партнеры будут писать код таким образом в начале. Но если вы хорошенько об этом подумаете, то обнаружите, что с этим кодом что-то не так.

В чем проблема?

Если есть несколько запросов на запрос инвентаризации одновременно с высокой степенью параллелизма, все они в это время больше 0. Из-за непринципиальных операций запроса инвентаря и обновления инвентаря возникнет ситуация, когда инвентарь будет отрицательным, т.е.库存超卖.

Конечно, кто-то может сказать, добавьтеsynchronizedне решить проблему?

Скорректированный код выглядит следующим образом:

   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. Сначала определите, добавил ли пользователь продукт, и если уже добавил, верните -1 напрямую.

  2. Вычтите инвентарь и определите, меньше ли возвращенное значение 0. Если оно меньше 0, он сразу вернет 0, указывая на то, что инвентаря недостаточно.

  3. Если возвращенное значение больше или равно 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. Сначала определите, существует ли идентификатор продукта, и вернитесь напрямую, если он не существует.

  2. Получите инвентарь с идентификатором продукта, и если будет установлено, что инвентарь равен -1, он будет возвращен напрямую, что указывает на то, что инвентарь не ограничен.

  3. Если запас больше 0, вычтите запас.

  4. Если инвентарь равен 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 только один раз в секунду.图片Но если это сервер, то за одну секунду могут быть запрошены тысячи интерфейсов.图片Этот разрыв слишком очевиден, если не будет ограничений, то большая часть товаров может быть захвачена машинами, а не обычными пользователями, что немного несправедливо.

Поэтому нам необходимо выявить эти незаконные запросы и ввести некоторые ограничения. Итак, что нам теперь делать с этими незаконными запросами?

Существует два широко используемых метода ограничения тока:

  1. Текущее ограничение на основе nginx

  2. Текущее ограничение на основе Redis

9.1 Ограничение тока для одного и того же пользователя

Чтобы пользователь не запрашивал интерфейс слишком часто, вы можете только ограничить пользователя.图片Ограничьте один и тот же идентификатор пользователя, например, интерфейс можно запрашивать только 5 раз в минуту.

9.2 Ограничить ток одним и тем же ip

Иногда недостаточно ограничить поток определенного пользователя, некоторые специалисты умеют имитировать запросы нескольких пользователей, и такой nginx не распознается.

В это время вам нужно добавить ту же функцию ограничения тока IP.图片Ограничьте один и тот же ip, например, интерфейс можно запрашивать только 5 раз в минуту.

Однако такой метод ограничения тока может привести к непредумышленному убийству. Например, выходной IP-адрес одной и той же компании или интернет-кафе один и тот же. Если есть несколько обычных пользователей, которые одновременно инициируют запросы, некоторые пользователи могут быть ограничены.

9.3 Ограничение тока на интерфейсах

Не думайте, что все будет хорошо, если вы ограничите пользователей и IP-адреса, некоторые специалисты могут даже использовать прокси для смены IP-адресов каждый раз, когда они запрашивают.

В это время вы можете ограничить общее количество запросов к интерфейсу.图片В сценариях с высокой степенью параллелизма это ограничение необходимо для стабильности системы. Однако может случиться так, что количество незаконных запросов достигнет верхнего предела запросов для этого интерфейса, что помешает другим обычным пользователям получить доступ к этому интерфейсу. Кажется, немного больше, чем стоит.

9.4 Добавить проверочный код

По сравнению с тремя вышеперечисленными способами способ добавления проверочного кода может быть более точным, а также может ограничивать частоту доступа пользователей, но преимущество в том, что не будет непредумышленного убийства.

图片Как правило, перед отправкой запроса пользователям необходимо ввести код подтверждения. После того, как пользователь инициирует запрос, сервер проверит правильность кода подтверждения. Следующий шаг разрешен только в том случае, если он правильный, в противном случае он вернется напрямую, и будет предложено ввести ошибку кода подтверждения.

Кроме того, проверочный код, как правило, является одноразовым, и один и тот же проверочный код можно использовать только один раз, а повторное использование не допускается.

Обычные проверочные коды, поскольку генерируемые числа или шаблоны относительно просты, могут быть взломаны. Преимущество в том, что скорость генерации относительно высока, а недостаток в том, что существуют риски безопасности.

Существует также код подтверждения, который называется:移动滑块, который относительно медленно генерируется, но относительно безопасен и в настоящее время является первым выбором крупных интернет-компаний.

9.5 Повышение бизнес-порога

Хотя вышеупомянутый проверочный код может ограничивать незаконные запросы пользователей, он в некоторой степени повлияет на работу пользователей. Прежде чем пользователь нажмет кнопку seckill, он должен сначала ввести проверочный код.Этот процесс немного громоздкий.Не должен ли процесс функции seckill быть максимально простым?

На самом деле иногда для достижения определенной цели не обязательно техническими средствами, но и деловыми средствами.

Когда 12306 только запустился, люди по всей стране одновременно спешили за билетами на поезд, из-за большого количества параллелизма система часто зависала. Позже, после рефакторинга и оптимизации, цикл покупки был увеличен, и билеты на поезд можно было купить за 20 дней, а билеты на поезд можно было купить на час в 9:00, 10, 11 и 12:00. После корректировки бизнеса (конечно, технических корректировок тоже много) ранее централизованные запросы рассыпаются, что сразу уменьшает количество одновременных пользователей.

Возвращаясь сюда, мы подняли бизнес-порог, например, только участники могут участвовать в действиях seckill, а обычные зарегистрированные пользователи не имеют разрешения. Кроме того, в мероприятии могут участвовать только обычные пользователи с уровнем 3 или выше.

Просто немного повысив порог, даже скальперы беспомощны: они не могут тратить дополнительные деньги на пополнение членов, чтобы участвовать в пиковой активности, верно?

Последнее слово (пожалуйста, обратите внимание, не портите меня зря)

Если эта статья оказалась для вас полезной или познавательной, отсканируйте QR-код и обратите внимание, ваша поддержка — самая большая мотивация для меня продолжать писать.

Попросите в один клик три ссылки: лайк, вперед и смотреть.

Следите за официальной учетной записью: [Су Сан сказал о технологии], ответьте в официальной учетной записи: интервью, артефакты кода, руководства по разработке, управление временем имеют большие преимущества для поклонников, и ответьте: присоединяйтесь к группе, вы можете общаться и учиться со многими старшими из BAT. производители .