【问题标题】:Synchronization in booking reservation application预订预订应用程序中的同步
【发布时间】:2015-07-09 08:12:17
【问题描述】:

对于经典的 Servlet -> Service -> DAO 模式的同步,我有一个非常根本的疑问。

假设我有一个可供多个并发用户访问的订票应用程序。没有两个用户最终会预订同一张票。当两个不同的用户尝试访问机票预订时,我认为会发生以下情况 -

  • Web 容器为这两个创建请求/响应对象 要求。将请求 url 映射到一个 servlet(比如 BookingServlet)。
  • 为每个请求创建线程以运行服务方法 BookingServlet。
  • 服务 -> doPost 方法创建一个 BookingService 对象并传递座位信息。
  • BookingService 对象执行一些业务逻辑并实例化 BookingDAO 对象连接数据库并进行更新。

现在,为了避免两个用户预订同一张票,必须在哪里进行同步?

是在 BookingService 还是在 BookingDAO?

但是,这是我的疑问,对于每个请求,我们实际上是在创建一个单独的 BookingService 和 BookingDAO 对象 - 这不会破坏将同步放在其中的目的吗?

【问题讨论】:

    标签: java multithreading concurrency dao synchronized


    【解决方案1】:

    有几种方法可以解决您的问题。

    1. 使BookingService 和/或BookingDAO 单例。通常不需要有更多的实例。然后就可以进行同步了。或者你可以在课堂上进行同步。例如。 synchronized(BookingService.class)

    仅当您只有一个应用程序实例正在运行时,此方法才有效。因此我会推荐第二种选择(假设您使用的是关系数据库)

    1. 使用您的数据库进行“同步”。假设您在数据库中的每个座位上都有“未预订”票,并且您希望能够为用户保留它,或者如果订单被取消(或未在给定时间内付款)则撤消此操作。然后您可以使用“乐观”的方法进行预订(伪 SQL):update ticket set reserved_by = 'CustomerA' where ticket_id = 23 and reserved_by = null 更新后您需要检查更新的行数。如果是1,则一切正常,如果是0,则表示该票已被他人预订,客户需要重新预订。

    【讨论】:

    • 我不太确定我是否会推荐这个,在 Java 中......你在使用数据库时需要保守,发出的每个请求......都是服务器上的费用(每个拉,请求很昂贵;因为连接池的内部工作)。因此,将整个同步策略基于您的数据库似乎不是一个好主意。
    • 在这种特殊情况下,我认为这是非常合适的解决方案。第一个数据库是为并发而构建的,连接池使数据库请求更有效。但主要是在这种情况下,您无论如何都想更新数据库以进行预订,因此您在好的情况下不要做任何额外的工作(即没有冲突)。我认为碰撞风险相当低——但这是悲观锁定和乐观锁定之间的传统权衡。
    • 是的,我同意数据库是为并发而构建的,因为每个请求都是原子的。我的评论是关于规模,如果您在同一时间段内有 3000 人订购门票,您的数据库可能会有一些冲突的事务(“All or Nothing”同步)。但是这么说,我认为无论如何都不能避免使用具有线性同步的数据库。我个人认为,数据库应该只在最后提交数据时使用(而不是之前)。
    • @Killrawr 谢谢。我也是这么想的。我正在使用 spring 来注入 dao 和服务。如果它们是单例范围的,那么我想我可以拥有预订方法的同步版本。我怀疑这是否是最佳实践,或者是否有更优雅的方法来处理这个用例。如果没有其他方法,乐观锁定将是最后的努力。
    • @The_301 看到我的回答,关于 UUID(和 ID 连接,这将为每个预订创建一个唯一的 ID)。 Good case study on UUID
    【解决方案2】:

    唯一标识符

    一个相对简单的解决方案是使用Java UUID。我们在一个项目中使用了这种方法,以确保人们不能同时创建相同的lobby

    import java.util.UUID;
    
    public class GenerateUUID {
    
      public static final void main(String... aArgs){
        //generate random UUIDs
        UUID idOne = UUID.randomUUID();
        UUID idTwo = UUID.randomUUID();
        log("UUID One: " + idOne);
        log("UUID Two: " + idTwo);
      }
    
      private static void log(Object aObject){
        System.out.println( String.valueOf(aObject) );
      }
    } 
    

    此外,如果您想降低一位用户获得与另一位用户相同的UUID 的可能性。您可以将用户的 Primary Key 连接到 UUID 的末尾。

    例如067e6162-3b6f-4ae2-a171-2470b63dff00变成067e6162-3b6f-4ae2-a171-2470b63dff00-23,假设每个用户包含不同的Primary Key,两个用户点击相同Token的概率非常低。

    1. 创建唯一的会话 ID,例如UUID sessionID = UUID.randomUUID();
    2. 开始交易
    3. 从数据库中删除与请求中的令牌匹配的所有令牌,例如
      从令牌中删除 TokenID =
    4. 获取受影响的行数
    5. 如果受影响的行数正好是 1,那么令牌是有效的。创建会话,例如
      插入会话值(sessionUUID、userID、loginTime、...)
    6. 提交事务

    source: finnw, java: race conditions - is there a way to make sure several lines of code will be executed together?

    互斥

    否则我相信,您需要开发一些具有互斥性的东西,以便双方(订票的人,不能同时订同一张票)。看Lamport's bakery algorithm

    Lamport 设想了一家面包店,其入口处有一台编号机,因此每位顾客都会获得一个唯一编号。随着顾客进入商店,人数增加一。全局计数器显示当前正在服务的客户数量。所有其他顾客必须排队等候,直到面包师完成为当前顾客服务并显示下一个数字。当顾客完成购物并处理了他或她的号码时,店员会增加号码,以便为下一位顾客提供服务。该客户必须从编号机中抽出另一个号码才能再次购物。

    例如,使用static LinkedList 跟踪今天订购的每个票号。

    【讨论】:

    • 谢谢,但可以详细说明生成唯一 ID 如何有助于同步并发预订。我们是否需要为每个请求生成一个 id 并根据请求的座位号将其存储在 DB 中,然后通过匹配 id 执行操作?
    • 您可以生成一个 ID,然后将有关预订的数据存储在 java 对象中,一旦确认预订......然后将所述数据移动到数据库中。使用 UUID 可以确保没有人错过,并且您没有任何竞争条件。如果您使用 0-100 作为预订系统的基础,则使用单数或双位数整数;您可能会在某个时候或另一个时候遇到比赛条件。
    • Failure Resilient Distributed Commit for Web Services Atomic Transactions 是一个使用 UUID (Universal Unique Identifier) 的好系统。
    • 好的,我有一个问题。请多多包涵。两个用户尝试预订同一个座位。两个请求进来。由于两个请求由 servlet 在不同的线程中处理,它们最终创建了单独的服务/dao 对象。他们不会反过来创建自己的 UUID 吗?我如何考虑与座位关联的 UUID?
    • 如果您使用static 变量,则数据将在两个线程中都可用。看this project,打开两个浏览器(IE和Chrome);创建一个大厅,您就会明白,静态数据可以从两个浏览器中查看。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-19
    • 2016-09-23
    • 1970-01-01
    相关资源
    最近更新 更多