Подтвердить что ты не робот

Управление количеством товаров в базе данных

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

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

Используемая структура ORM - это спящий режим

Пример кода

@Entity
@Table
class Product{
   @Column
   private int productCount;

   public void addProductToCart(){
     // decrements the product count by 1 & updates the database
   }

   public void removeTheProductFromTheCart(){
    // increments the product count by 1 & updates the database
   }

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

Также, если несколько пользователей пытаются добавить только один левый продукт в базу данных. Какую пользовательскую корзину продукт следует добавить?

Я немного исследовал этот

Возможные пути, которые я нашел, были

  • Создание класса Singleton для Product. Это обеспечит доступность всего одного экземпляра продукта во всем приложении.

  • Синхронизировать методы addProductToCart и removeTheProductFromTheCart. который позволит только одному потоку обновлять счетчик продуктов и обновлять db за раз.

  • Использование базы данных concurrency управляет применением некоторого уровня изоляции транзакции db, оптимистичной/пессимистической блокировки для productCount. Я использую mysql, уровень изоляции по умолчанию - REPEATABLE_READ.

Каким будет лучший подход к решению этого вопроса?

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

3. Использовать базу данных concurrency control

Почему?

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

  • В продуктах базы данных обычно есть много отказобезопасных механизмов, поэтому, если что-то пойдет не так, вы можете отслеживать, какие транзакции преуспели, а какие нет, и вы можете вернуться к определенному моменту времени. Java-программа, плавающая в памяти, не имеет этого из коробки, вам придется развивать это самостоятельно, если вы сделали 1 или 2. Spring, а Hibernate и другие подобные вещи, безусловно, лучше, чем ничего, кроме сравнения того, что они предлагают и что предлагает база данных с точки зрения восстановления после некоторой электронной катастрофы.

Ответ 3

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

Теория...

Services   a.k.a. "business logic", "business rules", "domain logic" etc.
 ^
DAOs       a.k.a. "Data Access Objects", "Data Access Layer", "repository" etc.
 ^
Entities   a.k.a. "model" - the ORM representation of the database structure
 ^
Database

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

Уровень DAO предоставляет основные операции CRUD, которые позволяют этим объектам сохраняться, извлекаться, объединяться и удаляться без необходимости знать контекст, в котором это делается. Это одно место, где синглтоны могут быть полезны для предотвращения повторного создания нескольких экземпляров, но использование одноэлементности не подразумевает безопасности потоков. Лично я бы рекомендовал использовать Spring для этого (Spring beans по умолчанию одиночные), но предположим, что это можно сделать вручную, если это необходимо.

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

На практике...

Следуя этому подходу, вы можете получить что-то вроде этого (много опущены для краткости):

@Entity
@Table
public class Product {
    @ManyToOne
    @JoinColumn
    private ShoppingCart shoppingCart;
}

@Entity
@Table
public class ShoppingCart {
    @OneToOne
    @JoinColumn
    private User user;

    @OneToMany(mappedBy = "shoppingCart")
    private Set<Product> products;
}

public class ShoppingCartDao { /* persist, merge, remove, findById etc. */ }

@Transactional
public class ProductService() {
    private ConcurrentMap<Integer, Integer> locks = 
        new ConcurrentHashMap<Integer, Integer>();

    public void addProductToCart(final int productId, final int userId) {
        ShoppingCart shoppingCart = shoppingCartDao.findByUserId(userId);                               
        Product product = productDao.findById(productId);
        synchronized(getCacheSyncObject(productId)) {
            if (product.shoppingCart == null) {
                product.setShoppingCart(shoppingCart);
            } else {
                throw new CustomException("Product already reserved.");
            }
        }
    }

    public void removeProductFromCart(final int productId, final int userId) {
        ShoppingCart shoppingCart = shoppingCartDao.findByUserId(userId);
        Product product = productDao.findById(productId);
        if (product.getShoppingCart() != shoppingCart) {
            throw new CustomException("Product not in specified user cart.");
        } else {
            product.setShoppingCart(null);
        }
    }

    /** @See http://stackoverflow.com/questions/659915#659939 */
    private Object getCacheSyncObject(final Integer id) {
      locks.putIfAbsent(id, id);
      return locks.get(id);
    }       
}

Ответ 4

Правильный способ сделать это - использовать блокировки базы данных, поскольку он предназначен для этой работы. И если вы используете спящий режим, это довольно просто с LockRequest:

Session session = sessionFactory.openSession()
Transaction transaction;
boolean productTaken = false;

try {
    transaction = session.beginTransaction();
    Product product = session.get(Product.class, id);
    if (product == null)
        throw ...

    Session.LockRequest lockRequest = session.buildLockRequest(LockOptions.UPGRADE);
    lockRequest.lock(product);
    productTaken = product.take();
    if (productTaken) {
        session.update(product);
        transaction.commit();
    }
} finally {
    if (transaction != null && transaction.isActive())
        transaction.rollback();
    session.close();
}

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

Ответ 5

Позвольте оценить три варианта.

1.Создание одноэлементного класса для Product. Это обеспечит доступность всего одного экземпляра продукта во всем приложении.

Единственный экземпляр для продукта - это хорошо. Но если вы предлагаете продукт типа Mobile с количеством 20, все равно вы должны increment количество товаров (статическая переменная) на addProductToCart и decrement количество товаров на removeTheProductFromTheCart. Тем не менее вам нужно синхронизировать доступ к этому изменяемому счету или обновить базу данных и прочитать количество продуктов.

2. Синхронизируйте метод addProductToCart и removeTheProductFromTheCart. который позволит только одному потоку обновлять счетчик продуктов и обновлять db за раз.

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

3. Используйте базу данных concurrency, чтобы применить некоторый уровень изоляции транзакций db, оптимистичную/пессимистичную блокировку для productCount. Я использую mysql, уровень изоляции по умолчанию - REPEATABLE_READ.

Отложить согласованность с базой данных вместо приложения. Но вы должны использовать READ COMMITTED для уровня изоляции вместо REPEATABLE_READ

Посмотрите на статью

ПРОЧИТАЙТЕ КОМИТЕТ

A somewhat Oracle-like isolation level with respect to consistent (nonlocking) reads: Каждое последовательное чтение, даже в пределах одной транзакции, устанавливает и считывает собственный свежий снимок.

Ответ 6

Как я понял, тележки также сохраняются в db? И в качестве конечного результата тоже покупали продукты.

продукт:

[{id:1, count:100}]

cart:

[{user_id:1, product_id:1}, {user_id:2, product_id:1}]

купил:

[{user_id:3, product_id:1}]

Затем вы можете получить счетчик для продукта

select count as total from product where id = 1
select count(*) as in_cart from cart where product_id = 1
select count(*) as total_bought from bought where product_id = 1

теперь вы можете показать окончательный счет

int count = total - (in_cart + total_bought);

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

Если количество вашего продукта меняется ежедневно, я имею в виду, что вчера у вас было 100 продуктов и было продано 20, сегодня поступило 50 продуктов, поэтому у вас должно быть 150 - 20 проданных графов, что составляет 130. Чтобы иметь хорошие отчеты, вы можете ежедневно производить подсчет продукта. как

 [
  {id:1, count:100, newly_arrived: 0, date: 23/02/2016}, 
  {id:1, count:80, newly_arrived: 50, date: 24/02/2016}
 ]

тогда ваши запросы будут меняться, как

select count+newly_arrived as total from product where id = 1 and date = today
select count(*) as in_cart from cart where product_id = 1 and date = today
select count(*) as total_bought from bought where product_id = 1 and date = today

для этого вам нужно только вставлять новые данные о продукте в полночь 00:00:00, а когда ваши новые продукты поступят утром, вы можете обновлять new_arrived без вмешательства каких-либо операций вставки или удаления на телегах или купленных таблицах. и вы можете иметь подробные отчеты легко без сложных запросов к запросу:))

Ответ 7

Если вам нужно использовать реляционную базу данных (а не хранилище значений ключей, например,). Я бы очень рекомендовал сделать это как можно ближе к хранилищу, чтобы избежать блокировок и конфликтов и получить максимальную производительность, насколько это возможно.

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

В конце вам нужны утверждения типа

UPDATE product SET productCount = productCount + 1 WHERE id = ?

Для выполнения транзакции может использоваться простой оператор JDBC или метод репозитория JPA с аннотацией @Query.

Чтобы сделать Hibernate осведомленным об изменениях, вы можете использовать Session.refresh() для продукта после такой операции.

Ответ 8

Лучший способ сделать это в веб-приложении электронной коммерции, используя Очередь сообщений. Вы можете использовать RabbitMQ,

  • Создайте очередь на rabbitMQ и сохраните все запросы здесь, чтобы уменьшить количество продуктов

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

Ответ 9

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

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

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