SpringBoot與ZooKeeper整合,實現(xiàn)智能停車計費系統(tǒng)
作者:Java知識日歷
在多臺服務(wù)器同時處理車輛進出記錄的情況下,使用ZooKeeper的分布式鎖可以確保同一輛車的計費操作在同一時間只能由一臺服務(wù)器處理。這避免了并發(fā)問題,保證了計費的準確性和一致性。
智能停車計費系統(tǒng)通過分布式鎖技術(shù)確保在多臺服務(wù)器環(huán)境下,同一輛車的計費操作不會發(fā)生沖突,從而保證計費的準確性和一致性。
關(guān)鍵點
1. 高并發(fā)處理
- 需求: 停車場可能有大量的車輛同時進出,尤其是在高峰時段。
- 解決方案: 使用分布式架構(gòu)和高效的數(shù)據(jù)庫管理來處理高并發(fā)請求。
2. 數(shù)據(jù)一致性
- 需求: 確保在多臺服務(wù)器環(huán)境下,同一輛車的計費操作不會發(fā)生沖突。
- 解決方案: 使用分布式鎖(如ZooKeeper)來保證數(shù)據(jù)的一致性和準確性。
3. 實時性
- 需求: 需要快速響應(yīng)車輛的進出事件,并及時計算費用。
- 解決方案: 優(yōu)化算法和數(shù)據(jù)庫查詢,確保低延遲。
使用ZooKeeper的好處
1. 分布式鎖
- 保證數(shù)據(jù)一致性: 在多臺服務(wù)器同時處理車輛進出記錄的情況下,使用ZooKeeper的分布式鎖可以確保同一輛車的計費操作在同一時間只能由一臺服務(wù)器處理。這避免了并發(fā)問題,保證了計費的準確性和一致性。
- 防止重復(fù)計費: 如果沒有分布式鎖,可能會出現(xiàn)同一輛車多次被不同服務(wù)器計費的情況,導(dǎo)致重復(fù)收費或計費錯誤。
2. 高可用性
- 容錯能力: ZooKeeper本身是一個高可用的服務(wù),即使某一臺ZooKeeper節(jié)點故障,其他節(jié)點仍然可以繼續(xù)提供服務(wù)。這提高了整個系統(tǒng)的穩(wěn)定性和可靠性。
- 負載均衡: 分布式鎖機制可以幫助均勻分配任務(wù)到不同的服務(wù)器上,提高系統(tǒng)的整體性能和響應(yīng)速度。
3. 易于擴展
- 動態(tài)增加服務(wù)器: 隨著業(yè)務(wù)的增長,可以通過簡單地增加更多的服務(wù)器來處理更多的請求。ZooKeeper可以輕松管理這些新增的服務(wù)器,并確保它們能夠正確協(xié)同工作。
- 靈活性: 添加新的功能或調(diào)整現(xiàn)有邏輯時,ZooKeeper的分布式特性使得這些更改更容易實現(xiàn)和部署。
4. 簡化協(xié)調(diào)過程
- 統(tǒng)一協(xié)調(diào): ZooKeeper提供了一種集中式的協(xié)調(diào)機制,使得多個服務(wù)器之間的通信和同步變得簡單和高效。不再需要復(fù)雜的自定義協(xié)議或手動協(xié)調(diào)邏輯。
- 輕量級: ZooKeeper的設(shè)計目標是輕量級且高性能,適合在各種規(guī)模的應(yīng)用中使用。
5. 監(jiān)控和日志
- 實時監(jiān)控: ZooKeeper提供了豐富的監(jiān)控工具和API,可以實時監(jiān)控集群的狀態(tài)和健康狀況。
- 審計日志: 可以通過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;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配置ZooKeeper連接。
*/
@Configuration
publicclass ZookeeperConfig {
@Value("${zookeeper.connect-string}")
private String zkConnectString;
/**
* 創(chuàng)建并返回一個CuratorFramework實例。
* @return CuratorFramework實例
*/
@Bean(initMethod = "start", destroyMethod = "close")
public CuratorFramework curatorFramework() {
return CuratorFrameworkFactory.newClient(zkConnectString, new ExponentialBackoffRetry(1000, 3));
}
}
ParkingController.java
package com.example.parking.controller;
import com.example.parking.model.ParkingRequest;
import com.example.parking.service.ParkingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* 提供HTTP接口用于車輛進入和離開停車場的操作。
*/
@RestController
@RequestMapping("/parking")
publicclass ParkingController {
@Autowired
private ParkingService parkingService;
/**
* 車輛進入停車場。
* @param request 包含車輛ID的請求對象
* @return 成功或失敗的消息
*/
@PostMapping("/enter")
public ResponseEntity<String> enterParkingLot(@RequestBody ParkingRequest request) {
boolean success = parkingService.enterParkingLot(request.getCarId());
if (success) {
return ResponseEntity.ok("Vehicle entered parking lot.");
} else {
return ResponseEntity.badRequest().body("Failed to enter parking lot.");
}
}
/**
* 車輛離開停車場。
* @param request 包含車輛ID和停留時間的請求對象
* @return 成功或失敗的消息
*/
@PostMapping("/leave")
public ResponseEntity<String> leaveParkingLot(@RequestBody ParkingRequest request) {
long durationInMinutes = request.getDurationInMinutes();
boolean success = parkingService.leaveParkingLot(request.getCarId(), durationInMinutes);
if (success) {
return ResponseEntity.ok("Vehicle left parking lot. Fee calculated and stored.");
} else {
return ResponseEntity.badRequest().body("Failed to leave parking lot.");
}
}
}
ParkingDao.java
package com.example.parking.dao;
import com.example.parking.entity.ParkingRecord;
import org.springframework.stereotype.Repository;
import java.util.HashMap;
import java.util.Map;
/**
* 數(shù)據(jù)訪問對象,負責(zé)存儲和檢索停車記錄。
*/
@Repository
publicclass ParkingDao {
privatefinal Map<String, ParkingRecord> records = new HashMap<>();
/**
* 記錄車輛進入停車場的時間。
* @param carId 車輛ID
* @return 是否成功記錄
*/
public boolean saveEntry(String carId) {
if (!records.containsKey(carId)) {
records.put(carId, new ParkingRecord(carId));
returntrue;
}
returnfalse;
}
/**
* 獲取指定車輛的停車記錄。
* @param carId 車輛ID
* @return 停車記錄
*/
public ParkingRecord getRecord(String carId) {
return records.get(carId);
}
/**
* 移除指定車輛的停車記錄。
* @param carId 車輛ID
* @return 是否成功移除
*/
public boolean removeRecord(String carId) {
return records.remove(carId) != null;
}
}
ParkingRecord.java
package com.example.parking.entity;
import java.time.LocalDateTime;
/**
* 實體類,表示一輛車的停車記錄。
*/
publicclass ParkingRecord {
private String carId;
private LocalDateTime entryTime;
/**
* 構(gòu)造函數(shù),初始化停車記錄。
* @param carId 車輛ID
*/
public ParkingRecord(String carId) {
this.carId = carId;
this.entryTime = LocalDateTime.now();
}
/**
* 獲取車輛ID。
* @return 車輛ID
*/
public String getCarId() {
return carId;
}
/**
* 獲取車輛進入停車場的時間。
* @return 進入時間
*/
public LocalDateTime getEntryTime() {
return entryTime;
}
}
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;
}
/**
* 設(shè)置車輛ID。
* @param carId 車輛ID
*/
public void setCarId(String carId) {
this.carId = carId;
}
/**
* 獲取停留時間(分鐘)。
* @return 停留時間
*/
public long getDurationInMinutes() {
return durationInMinutes;
}
/**
* 設(shè)置停留時間(分鐘)。
* @param durationInMinutes 停留時間
*/
public void setDurationInMinutes(long durationInMinutes) {
this.durationInMinutes = durationInMinutes;
}
}
ParkingService.java
package com.example.parking.service;
/**
* 接口定義了進入和離開停車場的方法。
*/
public interface ParkingService {
/**
* 車輛進入停車場。
* @param carId 車輛ID
* @return 是否成功進入
*/
boolean enterParkingLot(String carId);
/**
* 車輛離開停車場。
* @param carId 車輛ID
* @param durationInMinutes 停留時間(分鐘)
* @return 是否成功離開
*/
boolean leaveParkingLot(String carId, long durationInMinutes);
}
ParkingServiceImpl.java
package com.example.parking.service.impl;
import com.example.parking.dao.ParkingDao;
import com.example.parking.entity.ParkingRecord;
import com.example.parking.service.ParkingService;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 實現(xiàn)了ParkingService接口的具體邏輯,并使用ZooKeeper的互斥鎖來保證同一輛車的計費操作不會被并發(fā)執(zhí)行。
*/
@Service
publicclass ParkingServiceImpl implements ParkingService {
privatefinal CuratorFramework client;
privatefinal ParkingDao parkingDao;
@Autowired
public ParkingServiceImpl(CuratorFramework client, ParkingDao parkingDao) {
this.client = client;
this.parkingDao = parkingDao;
}
/**
* 車輛進入停車場。
* @param carId 車輛ID
* @return 是否成功進入
*/
@Override
public boolean enterParkingLot(String carId) {
return parkingDao.saveEntry(carId);
}
/**
* 車輛離開停車場。
* @param carId 車輛ID
* @param durationInMinutes 停留時間(分鐘)
* @return 是否成功離開
*/
@Override
public boolean leaveParkingLot(String carId, long durationInMinutes) {
InterProcessMutex lock = new InterProcessMutex(client, "/locks/" + carId);
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
if (!enterParkingLot(carId)) {
returnfalse;
}
ParkingRecord record = parkingDao.getRecord(carId);
double fee = calculateFee(record, durationInMinutes);
System.out.println("Calculated fee for " + carId + ": $" + fee);
// Logic to store the fee in a database or other storage system
parkingDao.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 停車費用
*/
private double calculateFee(ParkingRecord record, long durationInMinutes) {
double ratePerMinute = 0.5;
return durationInMinutes * ratePerMinute;
}
}
ParkingApplication.java
package com.example.parking;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot應(yīng)用的主類。
*/
@SpringBootApplication
public class ParkingApplication {
public static void main(String[] args) {
SpringApplication.run(ParkingApplication.class, args);
}
}
測試結(jié)果
“
確保ZooKeeper服務(wù)器正在運行。
車輛進入停車場
http://localhost:8080/parking/enter
{
"carId": "A123"
}
- 響應(yīng):
Vehicle entered parking lot.
車輛離開停車場
http://localhost:8080/parking/leave
{
"carId": "A123",
"durationInMinutes": 60
}
- 響應(yīng):
Vehicle left parking lot. Fee calculated and stored.
- 控制臺輸出:
Calculated fee for A123: $30.0
責(zé)任編輯:武曉燕
來源:
Java知識日歷