文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

高效的并发管理:房间预订 API 的乐观锁和消息队列

2024-11-30 05:33

关注

我们将深入研究用于应对这些挑战的两种关键策略的复杂性:乐观锁定和消息队列。

想象一下您正在使用一个在线酒店预订平台,类似于 Booking.com 或 Expedia 等知名平台。以下是同步和异步流程如何发挥作用:

同步流程:

预订房间(同步):

创建房间实体

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


@Entity
public class Room {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String roomType;
    private boolean isAvailable;


    // getters and setters
}

创建房间存储库

import org.springframework.data.jpa.repository.JpaRepository;


public interface RoomRepository extends JpaRepository {
    Room findByRoomType(String roomType);
}

创建客房预订请求 DTO

import java.time.LocalDate;


public class RoomBookingRequest {
    private String roomType;
    private LocalDate checkInDate;
    private LocalDate checkOutDate;


    // getters and setters
}

创建客房预订响应 DTO

public class RoomBookingResponse {
    private String reservationNumber;


    // getters and setters
}

创建客房服务

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.util.UUID;


@Service
public class RoomService {


    @Autowired
    private RoomRepository roomRepository;


    public RoomBookingResponse bookRoom(RoomBookingRequest bookingRequest) {
        String roomType = bookingRequest.getRoomType();
        LocalDate checkInDate = bookingRequest.getCheckInDate();
        LocalDate checkOutDate = bookingRequest.getCheckOutDate();


        Room room = roomRepository.findByRoomType(roomType);


        if (room != null && room.isAvailable()) {
            // Add validation to check availability based on check-in and check-out dates here.


            // For simplicity, we'll assume the room is available.
            room.setAvailable(false);
            roomRepository.save(room);


            // Generate a reservation number (you can implement your logic here).
            String reservationNumber = generateReservationNumber();


            return new RoomBookingResponse(reservationNumber);
        } else {
            throw new RoomNotAvailableException();
        }
    }


    private String generateReservationNumber() {
        // Generate a unique reservation number (you can implement your logic here).
        return UUID.randomUUID().toString();
    }
}

创建房间控制器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/api/rooms")
public class RoomController {


    @Autowired
    private RoomService roomService;


    // Book a room
    @PostMapping("/book")
    public RoomBookingResponse bookRoom(@RequestBody RoomBookingRequest bookingRequest) {
        return roomService.bookRoom(bookingRequest);
    }
}

定义自定义异常

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;


@ResponseStatus(HttpStatus.BAD_REQUEST)
public class RoomNotAvailableException extends RuntimeException {
    public RoomNotAvailableException() {
        super("The requested room is not available.");
    }
}

测试API

您可以使用 Postman 或 cURL 等工具来测试您的 API。要预订房间,请http://localhost:8080/api/rooms/book使用包含房间类型、入住日期和退房日期的 JSON 正文发出 POST 请求:

{ 
  "roomType" :  "Standard" , 
  "checkInDate" :  "2023-10-01" , 
  "checkOutDate" :  "2023-10-05" 
}

如果房间可用,API 将返回带有预订编号的 JSON 响应。您可以根据您的课堂需求自定义预订逻辑和预订号码生成RoomService。

异步流程

当多个用户同时调用Booking API时

当多个并发呼叫在系统中搜索同一房间时,可能存在潜在的缺点和挑战:

竞争条件:当多个请求尝试同时预订同一房间时,可能会出现竞争条件。如果处理不当,这可能会导致超额预订,即系统允许的预订数量超过了可用房间的数量。

如何解决并发问题?

乐观锁定是一种数据库级技术,可防止多个用户同时尝试更新同一资源时发生数据冲突。

另一方面,消息队列是异步通信工具,可确保请求的有序、可靠处理,使其成为分布式系统中处理并发请求的理想选择。

方法一:实现消息队列响应并发请求

消息队列确保请求按照接收顺序进行处理,从而防止竞争条件和超量预订。

8. 在 中RoomBookingMessageConsumer,处理预订请求并生成预订号码后,您可以使用传统的 HTTP 客户端(例如RestTemplate、HttpClient)将确认响应直接发送到客户端的回调 URL 端点(该端点在请求中发送)。

执行:

创建客房预订请求和响应 DTO

import java.time.LocalDate;


public class RoomBookingRequest {
    private String roomType;
    private LocalDate checkInDate;
    private LocalDate checkOutDate;
    private String clientCallbackUrl; // Added to specify the client's callback URL


    // getters and setters
}


public class RoomBookingResponse {
    private String reservationNumber;


    // getters and setters
}

修改控制器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/api/rooms")
public class RoomController {


    @Autowired
    private RoomService roomService;


    @PostMapping("/book")
    public RoomBookingResponse bookRoom(@RequestBody RoomBookingRequest bookingRequest) {
        return roomService.bookRoom(bookingRequest);
    }
}

创建客房预订服务(生产者)

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;


@Service
public class RoomService {


    @Autowired
    private RoomRepository roomRepository;


    @Autowired
    private RabbitTemplate rabbitTemplate;


    private RestTemplate restTemplate = new RestTemplate();


    public RoomBookingResponse bookRoom(RoomBookingRequest bookingRequest) {
        String roomType = bookingRequest.getRoomType();


        // Send the booking request to the message queue
        rabbitTemplate.convertAndSend("room-booking-exchange", "room-booking", bookingRequest);


        return new RoomBookingResponse("Booking request sent. Please wait for confirmation.");
    }


    // This method sends the response to the client's callback URL
    public void sendResponseToClient(RoomBookingResponse response, String clientCallbackUrl) {
        ResponseEntity result = restTemplate.postForEntity(clientCallbackUrl, response, Void.class);
        if (result.getStatusCode().is2xxSuccessful()) {
            // Handle a successful response sent to the client
        } else {
            // Handle the case when the response to the client failed
        }
    }
}

创建消息消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component
public class RoomBookingMessageConsumer {


    @Autowired
    private RoomService roomService;


    @RabbitListener(queues = "room-booking-queue")
    public void processBookingRequest(RoomBookingRequest bookingRequest) {
        // Process the booking request
        RoomBookingResponse response = processBookingLogic(bookingRequest);


        // Send the confirmation response to the client's callback URL
        roomService.sendResponseToClient(response, bookingRequest.getClientCallbackUrl());
    }


    private RoomBookingResponse processBookingLogic(RoomBookingRequest bookingRequest) {
        // Implement your booking logic here, e.g., checking room availability and generating a reservation number
        // Update room availability in the database
        // Send a response message to confirm the booking or indicate unavailability


        // For simplicity, we'll assume the room is available and generate a reservation number.
        String reservationNumber = generateReservationNumber();


        return new RoomBookingResponse(reservationNumber);
    }


    private String generateReservationNumber() {
        // Generate a unique reservation number (you can implement your logic here).
        return "RES-" + System.currentTimeMillis();
    }
}

方法二:实现乐观锁来处理并发请求

您可以修改代码以使用同步方法和 JPA 乐观锁定。

步骤1:修改Room实体:@Version向实体添加一个字段Room以启用乐观锁定:

import javax.persistence.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


@Entity
public class Room {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String roomType;
    private boolean isAvailable;
    
    @Version
    private Long version;


    // getters and setters
}
步骤2:修改客房服务对每个房间使用ReentrantLock来同步访问房间预订操作
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


@Service
public class RoomService {


    @Autowired
    private RoomRepository roomRepository;


    private final ConcurrentHashMap roomLocks = new ConcurrentHashMap<>();


    public RoomBookingResponse bookRoom(RoomBookingRequest bookingRequest) {
        String roomType = bookingRequest.getRoomType();
        LocalDate checkInDate = bookingRequest.getCheckInDate();
        LocalDate checkOutDate = bookingRequest.getCheckOutDate();


        Room room = roomRepository.findByRoomType(roomType);


        if (room != null) {
            Lock roomLock = roomLocks.computeIfAbsent(room.getId(), id -> new ReentrantLock());


            roomLock.lock();
            try {
                if (room.isAvailable()) {
                    // Add validation to check availability based on check-in and check-out dates here.


                    // For simplicity, we'll assume the room is available.
                    room.setAvailable(false);
                    roomRepository.save(room);


                    // Generate a reservation number (you can implement your logic here).
                    String reservationNumber = generateReservationNumber();


                    return new RoomBookingResponse(reservationNumber);
                }
            } finally {
                roomLock.unlock();
            }
        }


        throw new RoomNotAvailableException();
    }


    private String generateReservationNumber() {
        // Generate a unique reservation number (you can implement your logic here).
        return UUID.randomUUID().toString();
    }
}

详细工作原理:

并发请求&ConcurrentHashMap:当同一房间收到多个并发预订请求时,它们可能同时到达并可能导致竞争条件。的引入ConcurrentHashMap确保每个房间都有自己的锁。这ConcurrentHashMap是一个线程安全的映射,可以由多个线程同时安全地访问。

通过锁定并发更新房间可用性:如果两个线程同时尝试预订同一个房间,则只有其中一个线程会使用 成功获取锁roomLock.lock(),而另一个线程将暂时阻塞,直到第一个线程释放锁。

释放锁以供其他线程更新:一旦线程获取了锁并成功修改了房间的可用性,它就会使用 释放锁roomLock.unlock(),从而允许其他线程继续预订其他房间。

乐观锁防止数据库级别的竞争条件:在代码中,实体中的字段启用数据库级别的乐观锁。更新房间时,JPA 在允许更新之前会根据实体中的版本字段检查数据库中的版本字段。@VersionRoom

来源:HELLO程序员内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯