预订预订申请中的同步
Synchronization in booking reservation application
相对于经典的 Servlet -> 服务 -> DAO 模式,我对同步有一个非常根本的疑问。
假设我有一个由多个并发用户访问的订票应用程序。任何两个用户都不应该最终预订同一张机票。当两个不同的用户尝试访问机票预订时,我认为会发生以下情况 -
- Web 容器为两者创建 request/response 个对象
要求。将请求 url 映射到一个 servlet(比方说,BookingServlet)。
- 为每个请求创建线程,以便 运行 服务方法
BookingServlet 的。
- 服务 -> doPost 方法创建一个 BookingService 对象并传递座位信息。
- BookingService 对象执行一些业务逻辑并实例化
BookingDAO 对象连接数据库并进行更新。
现在,为了避免两个用户预订同一张机票,必须在哪里进行同步?
是在 BookingService 还是在 BookingDAO?
但是,这是我的疑问,对于每个请求,我们实际上是在创建一个单独的 BookingService 和 BookingDAO 对象 - 这不会破坏将同步放在那里的目的吗?
唯一标识符
一个相对简单的解决方案可以使用 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
的概率非常低。
- Create a unique session ID, e.g.
UUID sessionID = UUID.randomUUID();
- Begin transaction
- Delete any tokens from the DB that match the one in the request, e.g.
DELETE FROM Tokens WHERE TokenID = <requested-token>
- Get the affected row count
- If the affected row count is exactly 1, then the token was valid. Create a session, e.g.
INSERT INTO Session VALUES (sessionUUID, userID, loginTime, ...)
- Commit transaction
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 envisioned a bakery with a numbering machine at its entrance so each customer is given a unique number. Numbers increase by one as customers enter the store. A global counter displays the number of the customer that is currently being served. All other customers must wait in a queue until the baker finishes serving the current customer and the next number is displayed. When the customer is done shopping and has disposed of his or her number, the clerk increments the number, allowing the next customer to be served. That customer must draw another number from the numbering machine in order to shop again.
例如使用 static LinkedList
来跟踪今天订购的每个票号。
有多种方法可以解决您的问题。
- 使
BookingService
and/orBookingDAO
单例。通常不需要有更多的实例。然后你可以做同步。或者您可以在 class 上进行同步。例如。 synchronized(BookingService.class)
.
此方法仅在您只有一个应用程序实例时有效 运行。因此我会推荐第二个选项(假设您使用的是关系数据库)
- 使用您的数据库来完成 "synchronization"。假设您的数据库中每个座位都有 "unreserved" 票,并且您希望能够为用户保留它,或者如果订单被取消(或未在给定时间内支付)则撤消此操作。然后你可以使用"optimistic" 方法来做保留(伪SQL):
update ticket set reserved_by = 'CustomerA' where ticket_id = 23 and reserved_by = null
更新后你需要检查更新的行数。如果是1
,则一切正常,如果是0
,则表示该票已被他人预留,需要客户重新订位。
相对于经典的 Servlet -> 服务 -> DAO 模式,我对同步有一个非常根本的疑问。
假设我有一个由多个并发用户访问的订票应用程序。任何两个用户都不应该最终预订同一张机票。当两个不同的用户尝试访问机票预订时,我认为会发生以下情况 -
- Web 容器为两者创建 request/response 个对象 要求。将请求 url 映射到一个 servlet(比方说,BookingServlet)。
- 为每个请求创建线程,以便 运行 服务方法 BookingServlet 的。
- 服务 -> doPost 方法创建一个 BookingService 对象并传递座位信息。
- BookingService 对象执行一些业务逻辑并实例化 BookingDAO 对象连接数据库并进行更新。
现在,为了避免两个用户预订同一张机票,必须在哪里进行同步?
是在 BookingService 还是在 BookingDAO?
但是,这是我的疑问,对于每个请求,我们实际上是在创建一个单独的 BookingService 和 BookingDAO 对象 - 这不会破坏将同步放在那里的目的吗?
唯一标识符
一个相对简单的解决方案可以使用 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
的概率非常低。
- Create a unique session ID, e.g.
UUID sessionID = UUID.randomUUID();
- Begin transaction
- Delete any tokens from the DB that match the one in the request, e.g.
DELETE FROM Tokens WHERE TokenID = <requested-token>- Get the affected row count
- If the affected row count is exactly 1, then the token was valid. Create a session, e.g.
INSERT INTO Session VALUES (sessionUUID, userID, loginTime, ...)- Commit transaction
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 envisioned a bakery with a numbering machine at its entrance so each customer is given a unique number. Numbers increase by one as customers enter the store. A global counter displays the number of the customer that is currently being served. All other customers must wait in a queue until the baker finishes serving the current customer and the next number is displayed. When the customer is done shopping and has disposed of his or her number, the clerk increments the number, allowing the next customer to be served. That customer must draw another number from the numbering machine in order to shop again.
例如使用 static LinkedList
来跟踪今天订购的每个票号。
有多种方法可以解决您的问题。
- 使
BookingService
and/orBookingDAO
单例。通常不需要有更多的实例。然后你可以做同步。或者您可以在 class 上进行同步。例如。synchronized(BookingService.class)
.
此方法仅在您只有一个应用程序实例时有效 运行。因此我会推荐第二个选项(假设您使用的是关系数据库)
- 使用您的数据库来完成 "synchronization"。假设您的数据库中每个座位都有 "unreserved" 票,并且您希望能够为用户保留它,或者如果订单被取消(或未在给定时间内支付)则撤消此操作。然后你可以使用"optimistic" 方法来做保留(伪SQL):
update ticket set reserved_by = 'CustomerA' where ticket_id = 23 and reserved_by = null
更新后你需要检查更新的行数。如果是1
,则一切正常,如果是0
,则表示该票已被他人预留,需要客户重新订位。