Анализ исходного кода Jseckill
1. Общая архитектура
Система развёрнута по схеме, представленной на рисунке:
Статические ресурсы сайта секунды-килла, такие как статические веб-страницы, ссылки на JavaScript, CSS, изображения, аудио и видео, размещаются на CDN (сети распространения контента).
Если компания хочет снизить затраты, статические ресурсы можно развернуть на nginx. Благодаря высокой производительности nginx при обслуживании статических ресурсов, можно максимально увеличить скорость доступа к этим ресурсам.
Через nginx осуществляется обратный прокси-сервер, который предоставляет доступ только к порту 80. Кроме того, nginx обеспечивает балансировку нагрузки, распределяя её между несколькими узлами jseckill-backend. Стратегия балансировки нагрузки основана на распределении нагрузки в соответствии с производительностью каждого приложения.
Для MySQL используется репликация Master-Slave, которая позволяет разделить чтение и запись, повышая тем самым способность базы данных обрабатывать большое количество одновременных запросов.
2. Экспозиция секунд-килл интерфейса
Цель экспозиции секунд-килл интерфейсов — предоставить доступ к md5-кодам товаров только после начала периода секунды-килла. После окончания этого периода интерфейс больше не возвращает md5-коды.
Данные, предоставляемые через секунд-килл интерфейсы, являются «горячими» данными, которые не изменяются (за исключением количества товаров), и хранятся в Redis, базе данных в памяти.
Используется неблокирующий мультиплексор epool, который работает быстрее, чем операции с диском или базой данных. Код можно найти в методе SeckillServiceImpl.java public Exposer exportSeckillUrl(long seckillId).
Перед сохранением в Redis данные сериализуются в двоичный код с помощью фреймворка Protostuff.
Исходный код:
@Override
public Exposer exportSeckillUrl(long seckillId) {
// Оптимизация точки: кеширование оптимизации: на основе тайм-аута для поддержания согласованности
//1. Доступ к Redis
Seckill seckill = redisDAO.getSeckill(seckillId);
if (seckill == null) {
//2. Доступ к базе данных
seckill = seckillDAO.queryById(seckillId);
if (seckill == null) {
return new Exposer(false, seckillId);
} else {
//3. Сохранение в Redis
redisDAO.putSeckill(seckill);
}
}
Date startTime = seckill.getStartTime();
Date endTime = seckill.getEndTime();
// Текущее время системы
Date nowTime = new Date();
if (nowTime.getTime() < startTime.getTime()
|| nowTime.getTime() > endTime.getTime()) {
return new Exposer(false, seckillId, nowTime.getTime(), startTime.getTime(),
endTime.getTime());
}
// Преобразование процесса конкретной строки, необратимое
String md5 = getMD5(seckillId);
return new Exposer(true, md5, seckillId);
}
3. Обработка секунды-килла на стороне сервера
3.1 Ограничение потока на Java-сервере
Ограничение потока реализуется с использованием RateLimiter из Google guava. Например, допускается только 10 человек в секунду для входа в процесс секунды-килла (возможно, это приведёт к отклонению 90% запросов пользователей, а отклоненные запросы будут возвращать сообщение «К сожалению, вы не успели»).
Доступ к LimitServiceImpl.java:
package com.liushaoming.jseckill.backend.service.impl;
import com.google.common.util.concurrent.RateLimiter;
import com.liushaoming.jseckill.backend.service.AccessLimitService;
import org.springframework.stereotype.Service;
/**
* Ограничение перед секундой-киллом.
* Используется RateLimiter от Google guava
*/
@Service
public class AccessLimitServiceImpl implements AccessLimitService {
/**
* Каждые 10 токенов выдаются в секунду, и только запросы, получившие токены, могут войти в процесс секунды-килла
*/
private RateLimiter seckillRateLimiter = RateLimiter.create(10);
/**
* Попытка получить токен
* @return
*/
@Override
public boolean tryAcquireSeckill() {
return seckillRateLimiter.tryAcquire();
}
}
Использование ограничения потока, SeckillServiceImpl.java
@Override
@Transactional
/**
* Выполнение секунды-килла
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException {
if (accessLimitService.tryAcquireSeckill()) { // Если ограничение потока не применяется, выполнить обработку секунды-килла
return updateStock(seckillId, userPhone, md5);
} else { // Если применено ограничение потока, напрямую вызвать исключение ограничения доступа
logger.info("--->ACCESS_LIMITED-->seckillId={},userPhone={}", seckillId, userPhone);
throw new SeckillException(SeckillStateEnum.ACCESS_LIMIT);
}
}
3.2 Выполнение секунды-килла в Redis
Процесс секунды-килла представлен на диаграмме:
Диаграмма Step1: сначала проходит через Nginx балансировщик нагрузки и маршрутизацию.
Запрос обрабатывается программой jseckill. При большом количестве одновременных запросов Google guava RateLimiter ограничивает поток. Часть запросов пользователей отклоняется.
Redis проверяет, была ли секунда-килл уже выполнена. Если нет, сообщение с именем пользователя (здесь это номер телефона) и seckillId отправляется в RabbitMQ, делая запрос последовательной обработкой. Немедленно возвращается статус «В очереди» клиенту, который отображает «В очереди...» на клиенте.
Сервер прослушивает сообщения в RabbitMQ и обрабатывает их одно за другим. После анализа сообщения запрос отправляется в Redis для уменьшения значения на 1 (команда decr). Затем вручную подтверждается очередь.
Если уменьшение значения в Redis успешно, номер телефона пользователя, успешно выполнившего секунду-килл, записывается в Redis.
3.3 Уменьшение количества товаров после оплаты
Код можно найти в SeckillServiceImpl.java. Принцип заключается в том, что в методе public SeckillExecution executeSeckill (long seckillId, long userPhone, String md5), сначала выполняется insertSuccessKilled (), а затем reduceNumber (). Сначала в базу данных записываются данные о выполнении секунды-килла, а затем уменьшается количество товаров. Таким образом, блокировка строк применяется только на этапе уменьшения количества товаров, что повышает производительность операций с базой данных при одновременном выполнении. В запросе скорее всего текст технической направленности из области разработки и тестирования программного обеспечения. Основной язык текста запроса — китайский.
Перевод на русский язык:
@Override
@Transactional
/**
* Сначала вставляем запись о мгновенной распродаже, затем уменьшаем количество товаров на складе
*/
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5)
throws SeckillException, RepeatKillException, SeckillCloseException {
if (md5 == null || !md5.equals(getMD5(seckillId))) {
logger.info("Данные о мгновенной распродаже были перезаписаны!!!. seckillId={},userPhone={}", seckillId, userPhone);
throw new SeckillException("Данные о мгновенной распродаже были перезаписаны");
}
//Выполнение логики мгновенной распродажи: уменьшение количества товаров на складе + запись информации о покупке
Date nowTime = new Date();
try {
//Запись информации о мгновенной распродаже (запись информации о покупке)
int insertCount = successKilledDAO.insertSuccessKilled(seckillId, userPhone);
//Уникальность: seckillId,userPhone
if (insertCount <= 0) {
//Повторная мгновенная распродажа
logger.info("Мгновенная распродажа повторилась. seckillId={},userPhone={}", seckillId, userPhone);
throw new RepeatKillException("Мгновенная распродажа повторилась");
} else {
//Уменьшение количества товаров на складе, конкуренция за горячие товары
// reduceNumber - это операция обновления, которая активирует блокировку строки в таблице seckill
int updateCount = seckillDAO.reduceNumber(seckillId, nowTime);
if (updateCount <= 0) {
//Обновление записи не произошло, мгновенная распродажа завершена, откат
throw new SeckillCloseException("Мгновенная распродажа закрыта");
} else {
//Мгновенная распродажа прошла успешно commit
SuccessKilled payOrder = successKilledDAO.queryByIdWithSeckill(seckillId, userPhone);
logger.info("Успех мгновенной распродажи->>>. seckillId={},userPhone={}", seckillId, userPhone);
//Завершение транзакции, снятие блокировки строки в таблице seckill
return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, payOrder);
}
}
} catch (SeckillCloseException e1) {
throw e1;
} catch (RepeatKillException e2) {
throw e2;
} catch (Exception e) {
logger.error(e.getMessage(), e);
// Все исключения времени компиляции преобразуются в исключения времени выполнения
throw new SeckillException("Внутренняя ошибка мгновенной распродажи:" + e.getMessage());
}
}
#Конфигурация Rabbitmq
rabbitmq.address-list=192.168.20.3:5672,localhost:5672
rabbitmq.username=myname
rabbitmq.password=somepass
rabbitmq.publisher-confirms=true
rabbitmq.virtual-host=/vh_test
rabbitmq.queue=seckill
Конфигурация адреса кластера RabbitMQ выглядит следующим образом:
rabbitmq.address-list=192.168.20.3:5672,localhost:5672
Правило заключается в том, что каждый адрес должен быть в формате host:port, а несколько адресов серверов MQ должны быть разделены запятыми без лишних пробелов.
Принцип работы кластера: следующий метод может возвращать доступные адреса MQ на основе списка адресов. Если все они недоступны, то будет выброшено исключение.
com.rabbitmq.client.ConnectionFactory#newConnection(List<Address> addrs) throws IOException, TimeoutException {}
Код приложения см. в com.liushaoming.jseckill.backend.config.MQConfig
.
Фрагмент кода:
@Bean("mqConnectionSeckill")
public Connection mqConnectionSeckill(@Autowired MQConfigBean mqConfigBean) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
//Имя пользователя
factory.setUsername(username);
//Пароль
factory.setPassword(password);
//Путь виртуального хоста (аналогично имени базы данных)
factory.setVirtualHost(virtualHost);
//Возвращаем соединение
return factory.newConnection(mqConfigBean.getAddressList());
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )