【问题标题】:How to handle concurrency issue on a service method without an optimistic or pessimistic lock?如何在没有乐观或悲观锁的情况下处理服务方法的并发问题?
【发布时间】:2021-11-19 07:07:20
【问题描述】:

我正在开发酒店和预订微服务,用户可以在其中为特定酒店预订特定入住和退房日期的房间。酒店可以包含许多相同类型的房间。 (我使用的是 spring-boot、spring-data-jpa 和 oracle 数据库)

当多个用户预订同一个酒店房间时,我想处理并发问题(考虑到房间有限,并非所有用户都能成功预订)。

我不会存储有关该房型在特定入住和退房日期有多少房间可用的任何信息,因为这样做会使检查房间的可用性变得复杂。因此,我没有任何可以添加 @Version 注释并使用乐观锁的实体。所以我采取的方法是,在预订被保存在数据库中之前,我会检查我的服务方法,如果那个房间在那个入住和退房日期是可用的。

我实现该服务方法(如下所示)的方式是获取该房间的所有即将到来的预订,这些预订与新用户(想要预订该房间)给出的入住和退房日期相冲突。如果房间可用,则预订将持续存在,否则将为用户抛出异常。下面是服务代码(变量名应该是不言自明的):

@Transactional
public Booking findRoomsAvailibilityAndSave(Booking bookingInfoFromUser) {
        Booking persistedBooking = null;
        boolean isAllRoomsAvailable = true;
        int hotelId = bookingInfoFromUser.getHotelId();
        LocalDate checkInDate = bookingInfoFromUser.getCheckInDate();
        LocalDate checkoutDate = bookingInfoFromUser.getCheckoutDate();
        Set<RoomBookingDetails> newBookingRoomsInfo = bookingInfoFromUser.getRoomBookingDetails();
        Map<Integer, Integer> roomTotalRooms = new HashMap<>();
        Map<Integer, Integer> roomTotalRoomsBooked = new HashMap<>();

        List<BookingSummary> existingBookings = bookingRepository
                .findByHotelIdAndCheckInDateBeforeAndCheckoutDateAfter(hotelId, checkInDate, checkoutDate);

        RestTemplate template = new RestTemplate();
        ResponseEntity<Object> hotelEntity = template.getForEntity("http://localhost:8383/hotel/" + hotelId,
                Object.class);
        Object hotelObj = hotelEntity.getBody();
        ObjectMapper mapper = new ObjectMapper();
        JsonNode hotelNode = mapper.convertValue(hotelObj, JsonNode.class);

        hotelNode.withArray("hotelRooms")
                .forEach(roomNode -> roomTotalRooms.put(roomNode.get("id").asInt(), roomNode.get("noRooms").asInt()));
        
        existingBookings.forEach(existingBooking -> {
            existingBooking.getRoomBookingDetails().forEach(bookedRoom -> {
                if (roomTotalRoomsBooked.containsKey(bookedRoom.getHotelRoomId())) {
                    Integer existingKey = bookedRoom.getHotelRoomId();
                    Integer updateValue = roomTotalRoomsBooked.get(existingKey) + bookedRoom.getNoRooms();
                    roomTotalRoomsBooked.put(existingKey, updateValue);
                } else {
                    roomTotalRoomsBooked.put(bookedRoom.getHotelRoomId(), bookedRoom.getNoRooms());
                }
            });
        });

        for (RoomBookingDetails newRoom : newBookingRoomsInfo) {
            if (!((roomTotalRooms.get(newRoom.getHotelRoomId())
                    - roomTotalRoomsBooked.get(newRoom.getHotelRoomId())) >= newRoom.getNoRooms())) {
                isAllRoomsAvailable = false;
            }
        }

        if (isAllRoomsAvailable) {
            persistedBooking = bookingService.save(persistedBooking);
        } else {
            throw new OptimisticLockException();
        }

        return persistedBooking;
    }

我想要的是,如果两个用户同时尝试预订唯一可用的房间,那么只有一个用户应该能够成功。我不想锁定此服务,但让两个用户都尝试预订房间。但是,由于我展示的这种服务方法的检查,一个会失败。正如我所解释的,我无法进行乐观锁定,因为我没有包含特定入住和退房日期的可用房间信息的实体,我将在预订后更新。我检查可用性的唯一方法是获取特定房间类型的总房间数,并将其与该入住和退房日期已预订的房间总数进行比较。

到目前为止,我看到的所有解决方案都谈到了使用乐观方法进行处理,其中版本将针对我正在更新的行进行更改(但我没有在此处更新任何行)。我见过的另一种方法是悲观地锁定我正在更新的行(但正如我所说,我没有更新任何行)。

有没有办法解决这个并发问题,我让两个用户都尝试预订房间(一种乐观的方法),但服务方法中一个用户的可用性检查失败并为他们抛出异常。

如果我遗漏了任何信息,请随时告诉我,感谢您的帮助。

【问题讨论】:

    标签: java spring oracle spring-boot spring-data-jpa


    【解决方案1】:

    由于您没有对象/行来创建锁定,因此您需要创建一个。例如,您可以在Room 表中添加lockbooking-in-progress 列。

    完整的预订比这样工作:

    • 如果当前为false,则将booking-in-progress设置为true,否则预订失败。
    • 提交。
    • 进行预订。
    • booking-in-progress 设置为false
    • 提交。

    您可能想要一份清理长期存在的锁的工作。如果您的应用程序在创建锁后崩溃,就会发生这种情况。

    当然,您可以通过以roomday 作为主键的表来创建更细粒度的锁。在这种情况下,您不会更新任何内容,而只需插入一行。主键将阻止并发写入。

    【讨论】:

    • 有道理。甚至没有像这样处理这个问题。我认为您提供的第一个选项更有意义,因为您也可以使用版本控制来实现该解决方案。我通过创建一张桌子来使用细粒度锁的问题是我有很多房间适合一种类型。因此,如果我的客户在 9 月 29 日至 9 月 31 日期间预订了 5 间单一房型的房间,其他客户仍然可以在这些特定日期预订该房型的剩余可用房间。所以,我认为这种方法行不通。我仍然需要从该表中获取数据以检查可用性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-22
    相关资源
    最近更新 更多