我正在制作一个座位预订模块,用户可以检查座位是否可用于考试,如果是,他可以进入付款页面,然后输入卡片详细信息并付款。现在我想要的是,当打开支付页面时,座位可用值应该在一段时间内减少1(直到会话到期或用户导航到其他页面)。
如果我使用数据库事务为创建操作(MVC)执行此操作,那么将无法触发控制器的操作(In grails)并保存数据库更改而不会有任何回滚的可能性。
使用db获取座位锁是否可行,或者我应该使用一些cron作业来检查它。请建议。
答案 0 :(得分:0)
我假设考试永远不会被超额预订。换句话说, exactness 比并发更重要。在这种情况下,悲观锁定策略是最好的选择。 注意:默认情况下,Grails使用乐观锁定。
您可以使用数据库悲观锁定来协助分配席位,但就其本身而言,数据库锁定不适合业务事务锁定。正如您所暗示的,当事务提交时会释放锁定。所以你可以做的是在数据库悲观锁定之上实现你的业务事务锁定。
我建议使用服务分配/取消分配席位,以便所有锁定代码都在一个地方完成。
import groovy.time.TimeDuration
class ExamService {
TimeDuration allocationExpiration = new TimeDuration(0, 30, 0, 0) // 30 minutes
SeatsAllocation allocateSeats(long examId, long userId, int numberOfSeats) {
def exam = Exam.lock(examId)
if(exam.seatsAvailable >= numberOfSeats) {
exam.seatsAvailable = exam.seatsAvailable - numberOfSeats
exam.save()
return new SeatsAllocation(
exam: exam,
user: User.get(userId),
numberOfSeats: numberOfSeats,
purchased: false,
expiration: new Date() + allocationExpiration).save()
} else {
return null
}
}
}
在此示例中,当用户决定购买一定数量的座位但尚未实际购买时,会调用allocateSeats()
。在交易的这个阶段,SeatsAllocation
代表用户购买的意图。分配有一个到期日,必须在购买的确切时刻进行检查。如果分配已过期,则应禁止购买。购买代码未显示,但基本上确保将SeatsAllocation.purchased
设置为true
。
这是您提到的cron / Quartz工作的好地方。该作业将检索已过期的SeatsAllocation
,使座位可用,最后删除分配。在下面的示例中,我将代码作为服务的一部分,Quartz作业可以调用它。
import groovy.time.TimeDuration
class ExamService {
...
def deallocateExpiredSeats() {
SeatsAllocation.where {
purchased == false
expiration < new Date()
}.list([lock: true])
.each {
it.exam.seatsAvailable = it.exam.seatsAvailable + it.numberOfSeats
it.exam.save()
it.delete()
}
}
}
请注意,使用预定作业会导致SeatsAllocation
到期与可用座位之间出现延迟的问题。如果作业运行得太频繁,它将与allocateSeats()
中使用的悲观锁定冲突,从而进一步降低并发性。另一方面,如果工作太频繁,那么技术上可用的座位不能分配给潜在客户,因为他们会被SeatsAllocation
人质挟持。您可以根据应用的负载选择频率。