SpringBoot与ZooKeeper整合,实现智能停车计费系统

在多台服务器同时处理车辆进出记录的情况下,使用ZooKeeper的分布式锁可以确保同一辆车的计费操作在同一时间只能由一台服务器处理。这避免了并发问题,保证了计费的准确性和一致性。
首页 新闻资讯 行业资讯 SpringBoot与ZooKeeper整合,实现智能停车计费系统

智能停车计费系统通过分布式锁技术确保在多台服务器环境下,同一辆车的计费操作不会发生冲突,从而保证计费的准确性和一致性。

关键点

1. 高并发处理

  • 需求: 停车场可能有大量的车辆同时进出,尤其是在高峰时段。

  • 解决方案: 使用分布式架构和高效的数据库管理来处理高并发请求。

2. 数据一致性

  • 需求: 确保在多台服务器环境下,同一辆车的计费操作不会发生冲突。

  • 解决方案: 使用分布式锁(如ZooKeeper)来保证数据的一致性和准确性。

3. 实时性

  • 需求: 需要快速响应车辆的进出事件,并及时计算费用。

  • 解决方案: 优化算法和数据库查询,确保低延迟。

使用ZooKeeper的好处

1. 分布式锁

  • 保证数据一致性: 在多台服务器同时处理车辆进出记录的情况下,使用ZooKeeper的分布式锁可以确保同一辆车的计费操作在同一时间只能由一台服务器处理。这避免了并发问题,保证了计费的准确性和一致性。

  • 防止重复计费: 如果没有分布式锁,可能会出现同一辆车多次被不同服务器计费的情况,导致重复收费或计费错误。

2. 高可用性

  • 容错能力: ZooKeeper本身是一个高可用的服务,即使某一台ZooKeeper节点故障,其他节点仍然可以继续提供服务。这提高了整个系统的稳定性和可靠性。

  • 负载均衡: 分布式锁机制可以帮助均匀分配任务到不同的服务器上,提高系统的整体性能和响应速度。

3. 易于扩展

  • 动态增加服务器: 随着业务的增长,可以通过简单地增加更多的服务器来处理更多的请求。ZooKeeper可以轻松管理这些新增的服务器,并确保它们能够正确协同工作。

  • 灵活性: 添加新的功能或调整现有逻辑时,ZooKeeper的分布式特性使得这些更改更容易实现和部署。

4. 简化协调过程

  • 统一协调: ZooKeeper提供了一种集中式的协调机制,使得多个服务器之间的通信和同步变得简单和高效。不再需要复杂的自定义协议或手动协调逻辑。

  • 轻量级: ZooKeeper的设计目标是轻量级且高性能,适合在各种规模的应用中使用。

5. 监控和日志

  • 实时监控: ZooKeeper提供了丰富的监控工具和API,可以实时监控集群的状态和健康状况。

  • 审计日志: 可以通过ZooKeeper的日志功能跟踪所有对共享资源的操作,便于审计和故障排查。

代码实操

<!-- Spring Boot Starter Web for RESTful services --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Apache Curator for ZooKeeper interaction --><dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>${curator.version}</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>${curator.version}</version></dependency><!-- Lombok for reducing boilerplate code --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

application.properties

zookeeper.connect-string=localhost:2181

ZookeeperConfig.java

package com.example.parking.config;importorg.apache.curator.framework.CuratorFramework;importorg.apache.curator.framework.CuratorFrameworkFactory;importorg.apache.curator.retry.ExponentialBackoffRetry;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/**
 * 配置ZooKeeper连接。
 */@Configurationpublicclass ZookeeperConfig {@Value("${zookeeper.connect-string}")private String zkConnectString;/**
     * 创建并返回一个CuratorFramework实例。
     * @return CuratorFramework实例
     */@Bean(initMethod="start",destroyMethod="close")publicCuratorFramework curatorFramework(){returnCuratorFrameworkFactory.newClient(zkConnectString,new ExponentialBackoffRetry(1000,3));}
}

ParkingController.java

package com.example.parking.controller;importcom.example.parking.model.ParkingRequest;importcom.example.parking.service.ParkingService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.http.ResponseEntity;importorg.springframework.web.bind.annotation.*;/**
 * 提供HTTP接口用于车辆进入和离开停车场的操作。
 */@RestController@RequestMapping("/parking")publicclass ParkingController {@Autowiredprivate ParkingService parkingService;/**
     * 车辆进入停车场。
     * @param request 包含车辆ID的请求对象
     * @return 成功或失败的消息
     */@PostMapping("/enter")publicResponseEntity<String>enterParkingLot(@RequestBodyParkingRequest request){booleansuccess=parkingService.enterParkingLot(request.getCarId());if(success){returnResponseEntity.ok("Vehicle entered parking lot.");}else{returnResponseEntity.badRequest().body("Failed to enter parking lot.");}
    }/**
     * 车辆离开停车场。
     * @param request 包含车辆ID和停留时间的请求对象
     * @return 成功或失败的消息
     */@PostMapping("/leave")publicResponseEntity<String>leaveParkingLot(@RequestBodyParkingRequest request){
        long durationInMinutes=request.getDurationInMinutes();booleansuccess=parkingService.leaveParkingLot(request.getCarId(),durationInMinutes);if(success){returnResponseEntity.ok("Vehicle left parking lot. Fee calculated and stored.");}else{returnResponseEntity.badRequest().body("Failed to leave parking lot.");}
    }
}

ParkingDao.java

package com.example.parking.dao;importcom.example.parking.entity.ParkingRecord;importorg.springframework.stereotype.Repository;importjava.util.HashMap;importjava.util.Map;/**
 * 数据访问对象,负责存储和检索停车记录。
 */@Repositorypublicclass ParkingDao {

    privatefinal Map<String,ParkingRecord>records=new HashMap<>();/**
     * 记录车辆进入停车场的时间。
     * @param carId 车辆ID
     * @return 是否成功记录
     */publicbooleansaveEntry(String carId){if(!records.containsKey(carId)){
            records.put(carId,new ParkingRecord(carId));returntrue;}
        returnfalse;}/**
     * 获取指定车辆的停车记录。
     * @param carId 车辆ID
     * @return 停车记录
     */publicParkingRecord getRecord(String carId){returnrecords.get(carId);}/**
     * 移除指定车辆的停车记录。
     * @param carId 车辆ID
     * @return 是否成功移除
     */publicbooleanremoveRecord(String carId){returnrecords.remove(carId)!=null;}
}

ParkingRecord.java

package com.example.parking.entity;importjava.time.LocalDateTime;/**
 * 实体类,表示一辆车的停车记录。
 */publicclass ParkingRecord {

    private String carId;private LocalDateTime entryTime;/**
     * 构造函数,初始化停车记录。
     * @param carId 车辆ID
     */publicParkingRecord(String carId){
        this.carId=carId;this.entryTime=LocalDateTime.now();}/**
     * 获取车辆ID。
     * @return 车辆ID
     */publicString getCarId(){returncarId;}/**
     * 获取车辆进入停车场的时间。
     * @return 进入时间
     */publicLocalDateTime getEntryTime(){returnentryTime;}
}

ParkingRequest.java

package com.example.parking.model;

/**
 * 请求模型,包含车辆ID和停留时间。
 */
publicclass ParkingRequest {

    private String carId;
    privatelong durationInMinutes;

    // Getters and Setters
    /**
     * 获取车辆ID。
     * @return 车辆ID
     */
    public String getCarId() {
        return carId;
    }

    /**
     * 设置车辆ID。
     * @param carId 车辆ID
     */
    public void setCarId(String carId) {
        this.carId = carId;
    }

    /**
     * 获取停留时间(分钟)。
     * @return 停留时间
     */
    public long getDurationInMinutes() {
        return durationInMinutes;
    }

    /**
     * 设置停留时间(分钟)。
     * @param durationInMinutes 停留时间
     */
    public void setDurationInMinutes(long durationInMinutes) {
        this.durationInMinutes = durationInMinutes;
    }
}

ParkingService.java

package com.example.parking.service;/**
 * 接口定义了进入和离开停车场的方法。
 */publicinterface ParkingService {/**
     * 车辆进入停车场。
     * @param carId 车辆ID
     * @return 是否成功进入
     */booleanenterParkingLot(String carId);/**
     * 车辆离开停车场。
     * @param carId 车辆ID
     * @param durationInMinutes 停留时间(分钟)
     * @return 是否成功离开
     */booleanleaveParkingLot(String carId,long durationInMinutes);}

ParkingServiceImpl.java

package com.example.parking.service.impl;importcom.example.parking.dao.ParkingDao;importcom.example.parking.entity.ParkingRecord;importcom.example.parking.service.ParkingService;importorg.apache.curator.framework.CuratorFramework;importorg.apache.curator.framework.recipes.locks.InterProcessMutex;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/**
 * 实现了ParkingService接口的具体逻辑,并使用ZooKeeper的互斥锁来保证同一辆车的计费操作不会被并发执行。
 */@Servicepublicclass ParkingServiceImpl implements ParkingService {

    privatefinal CuratorFramework client;privatefinal ParkingDao parkingDao;@AutowiredpublicParkingServiceImpl(CuratorFramework client,ParkingDao parkingDao){
        this.client=client;this.parkingDao=parkingDao;}/**
     * 车辆进入停车场。
     * @param carId 车辆ID
     * @return 是否成功进入
     */@OverridepublicbooleanenterParkingLot(String carId){returnparkingDao.saveEntry(carId);}/**
     * 车辆离开停车场。
     * @param carId 车辆ID
     * @param durationInMinutes 停留时间(分钟)
     * @return 是否成功离开
     */@OverridepublicbooleanleaveParkingLot(String carId,long durationInMinutes){
        InterProcessMutexlock=new InterProcessMutex(client,"/locks/"+carId);try {if(lock.acquire(10,TimeUnit.SECONDS)){
                try {if(!enterParkingLot(carId)){
                        returnfalse;}

                    ParkingRecord record=parkingDao.getRecord(carId);doublefee=calculateFee(record,durationInMinutes);System.out.println("Calculated fee for "+carId+": $"+fee);// Logic to store the fee in a database or other storage systemparkingDao.removeRecord(carId);returntrue;} finally {lock.release();}
            }else{
                System.err.println("Could not acquire lock for "+carId);returnfalse;}
        } catch(Exception e){
            e.printStackTrace();returnfalse;}
    }/**
     * 计算停车费用。
     * @param record 停车记录
     * @param durationInMinutes 停留时间(分钟)
     * @return 停车费用
     */privatedoublecalculateFee(ParkingRecord record,long durationInMinutes){doubleratePerMinute=0.5;returndurationInMinutes*ratePerMinute;}
}

ParkingApplication.java

package com.example.parking;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;/**
 * Spring Boot应用的主类。
 */@SpringBootApplicationpublicclass ParkingApplication {publicstatic void main(String[]args){
        SpringApplication.run(ParkingApplication.class,args);}
}

测试结果

确保ZooKeeper服务器正在运行。

车辆进入停车场

http://localhost:8080/parking/enter{"carId":"A123"}
  • 响应:

Vehicle entered parking lot.

车辆离开停车场

http://localhost:8080/parking/leave{"carId":"A123","durationInMinutes":60}
  • 响应:

Vehicleleftparking lot.Fee calculatedandstored.
  • 控制台输出:

Calculated feeforA123: $30.0