选择Apache BookKeeper作为金融级日志存储系统的核心组件,主要是因为它具备高性能、高可靠性和良好的可扩展性,能够有效满足金融机构对日志存储的要求。
BookKeeper的优势
高吞吐量和低延迟
- 分布式架构: Apache BookKeeper采用分布式的架构设计,能够支持高并发的写入和读取操作。
- 批量写入: 支持批量写入日志条目,显著提高写入效率。
- 异步I/O: 使用异步I/O操作,减少等待时间,提升整体性能。
数据一致性和持久性
- 强一致性保证: BookKeeper提供强一致性保证,确保所有写入的数据都能被正确读取。
- 多副本复制: 数据在多个Bookies(BookKeeper节点)上进行多副本复制,防止单点故障导致的数据丢失。
- 自动恢复: 在节点故障时,BookKeeper能够自动检测并恢复数据,确保系统的连续运行。
水平扩展能力
- 动态扩展: 可以通过增加Bookies来扩展集群规模,适应不断增长的业务需求。
- 负载均衡: 自动分配负载,确保各节点之间的工作负载平衡,避免热点问题。
- 灵活性: 支持多种部署方式,包括本地部署、云部署等。
数据加密和访问控制
- 数据加密: 支持对存储的日志数据进行加密处理,防止未授权访问。
- 认证和授权: 提供细粒度的权限管理机制,限制不同角色的访问权限。
- 审计日志: 记录所有对系统的访问和操作,便于追踪和审计。
哪些公司采用了BookKeeper?
Intel
- 用途: Intel在其物联网(IoT)解决方案中使用BookKeeper来收集和存储传感器数据。
- 优势: 多副本复制和自动恢复机制,确保数据的可靠性和完整性。
阿里巴巴集团
- 用途: 阿里巴巴在多个核心系统中使用BookKeeper,包括交易日志存储、监控系统和大数据平台。
- 优势: 成熟的社区支持和与现有生态系统的良好集成,提升了开发效率和系统稳定性。
Baidu
- 用途: Baidu在其搜索引擎和推荐系统中使用BookKeeper来存储大量的日志和索引数据。
- 优势: 高效的数据检索能力和灵活的配置选项,适应不同的应用场景。
Microsoft Azure
- 用途: Microsoft Azure在其云平台上使用BookKeeper来支持各种分布式系统和服务。
- 优势: 高性能和可扩展性,满足不同规模的应用需求。
PayPal
- 用途: PayPal使用BookKeeper来存储支付交易日志,确保每一笔交易的完整记录和快速查询。
- 优势: 数据加密和访问控制,保障金融数据的安全性。
Yahoo!
- 用途: Yahoo!在其多个分布式系统中使用BookKeeper,包括搜索引擎日志记录和流处理系统。
- 优势: 强一致性保证和高可用性,支持复杂的数据处理需求。
- 用途: Twitter在其基础设施中使用BookKeeper来处理大量实时数据流,包括推文事件和用户活动日志。
- 优势: 支持高并发写入和读取操作,能够应对快速增长的业务需求。
eBay
- 用途: eBay在其电商平台中使用BookKeeper来存储交易日志和其他关键数据。
- 优势: 安全的数据加密和严格的访问控制,保护敏感信息。
记得启动ZooKeeper服务器
因为BookKeeper依赖于ZooKeeper来进行元数据管理和协调!!!
我这边的本地环境已运行了ZooKeeper。
代码实操
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/><!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>bookkeeper-springboot-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>bookkeeper-springboot-example</name>
<description>Demo project for Spring Boot and Apache BookKeeper integration</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache BookKeeper Client -->
<dependency>
<groupId>org.apache.bookkeeper</groupId>
<artifactId>bookkeeper-server</artifactId>
<version>4.18.0</version>
</dependency>
<!-- Jackson Databind for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Lombok for reducing boilerplate code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
application.properties
# ZooKeeper 连接字符串
bookkeeper.zk.connectString=localhost:2181
server.port=8080
- 1.
- 2.
- 3.
配置类
package com.example.bookkeeperspringbootexample.config;
import org.apache.bookkeeper.client.BookKeeper;
import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.conf.ClientConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PreDestroy;
import java.io.IOException;
@Configuration
publicclass BookKeeperConfig {
privatestaticfinal Logger logger = LoggerFactory.getLogger(BookKeeperConfig.class);
@Value("${bookkeeper.zk.connectString}")
private String zkConnectString;
private BookKeeper bookKeeper;
private LedgerHandle ledgerHandle;
/**
* 初始化BookKeeper客户端
*
* @return BookKeeper实例
* @throws IOException 如果初始化失败
*/
@Bean
public BookKeeper bookKeeper() throws IOException {
ClientConfiguration conf = new ClientConfiguration();
conf.setZkServers(zkConnectString);
bookKeeper = new BookKeeper(conf);
logger.info("BookKeeper客户端已初始化。");
return bookKeeper;
}
/**
* 创建一个新的Ledger
*
* @param bookKeeper BookKeeper实例
* @return LedgerHandle实例
* @throws Exception 如果创建Ledger失败
*/
@Bean
public LedgerHandle ledgerHandle(BookKeeper bookKeeper) throws Exception {
ledgerHandle = bookKeeper.createLedger(
BookKeeper.DigestType.CRC32,
"password".getBytes()
);
logger.info("Ledger已创建,ID: {}", ledgerHandle.getId());
return ledgerHandle;
}
/**
* 关闭BookKeeper客户端和Ledger
*/
@PreDestroy
public void shutdown() throws InterruptedException, BookKeeper.BKException {
if (ledgerHandle != null) {
ledgerHandle.close();
logger.info("Ledger已关闭。");
}
if (bookKeeper != null) {
bookKeeper.close();
logger.info("BookKeeper客户端已关闭。");
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
交易的数据模型
package com.example.bookkeeperspringbootexample.model;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 表示交易的数据模型
*/
@Data
public class Transaction {
private Long transactionId; // 交易ID
private Double amount; // 交易金额
private LocalDateTime timestamp; // 时间戳
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
服务类
package com.example.bookkeeperspringbootexample.service;
import com.example.bookkeeperspringbootexample.model.Transaction;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.bookkeeper.client.BKException;
import org.apache.bookkeeper.client.LedgerHandle;
import org.apache.bookkeeper.proto.BookieProtocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@Service
publicclass BookKeeperService {
privatestaticfinal Logger logger = LoggerFactory.getLogger(BookKeeperService.class);
@Autowired
private LedgerHandle ledgerHandle;
@Autowired
private ObjectMapper objectMapper;
/**
* 异步添加交易到BookKeeper
*
* @param transaction 交易对象
* @return CompletableFuture<Long> 包含新条目的entryId
*/
public CompletableFuture<Long> addTransaction(Transaction transaction) {
try {
byte[] logData = objectMapper.writeValueAsBytes(transaction); // 将交易对象转换为字节数组
return CompletableFuture.supplyAsync(() -> {
try {
long entryId = ledgerHandle.addEntry(logData); // 将字节数组添加到Ledger
logger.info("已添加交易,entryId: {}", entryId);
return entryId;
} catch (BKException | InterruptedException e) {
thrownew RuntimeException(e);
}
});
} catch (IOException e) {
thrownew RuntimeException(e);
}
}
/**
* 异步从BookKeeper读取交易
*
* @param entryId 条目ID
* @return CompletableFuture<Transaction> 包含读取的交易对象
*/
public CompletableFuture<Transaction> readTransaction(long entryId) {
return CompletableFuture.supplyAsync(() -> {
try {
LedgerSequence seq = ledgerHandle.readEntries(entryId, entryId); // 读取指定entryId的条目
if (seq.hasMoreElements()) {
LedgerEntry entry = seq.nextElement(); // 获取条目
byte[] data = entry.getEntryBytes(); // 获取条目的字节数组
logger.info("已读取交易,entryId: {}", entryId);
return objectMapper.readValue(data, Transaction.class); // 将字节数组转换为交易对象
}
thrownew IllegalArgumentException("未找到ID为 " + entryId + " 的交易");
} catch (BKException | InterruptedException | ExecutionException | IOException e) {
thrownew RuntimeException(e);
}
});
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
Controller
package com.example.bookkeeperspringbootexample.controller;
import com.example.bookkeeperspringbootexample.model.Transaction;
import com.example.bookkeeperspringbootexample.service.BookKeeperService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/transactions")
publicclass TransactionController {
@Autowired
private BookKeeperService bookKeeperService;
/**
* 添加新的交易
*
* @param transaction 交易对象
* @return ResponseEntity<Long> 包含新条目的entryId
*/
@PostMapping("/")
public ResponseEntity<Long> addTransaction(@RequestBody Transaction transaction) {
CompletableFuture<Long> futureEntryId = bookKeeperService.addTransaction(transaction); // 异步添加交易
try {
Long entryId = futureEntryId.get(); // 获取结果
return ResponseEntity.ok(entryId); // 返回成功的HTTP响应
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt(); // 中断线程
return ResponseEntity.internalServerError().build(); // 返回内部服务器错误
}
}
/**
* 根据entryId读取交易
*
* @param entryId 条目ID
* @return ResponseEntity<Transaction> 包含读取的交易对象
*/
@GetMapping("/{entryId}")
public ResponseEntity<Transaction> getTransaction(@PathVariable long entryId) {
CompletableFuture<Transaction> futureTransaction = bookKeeperService.readTransaction(entryId); // 异步读取交易
try {
Transaction transaction = futureTransaction.get(); // 获取结果
return ResponseEntity.ok(transaction); // 返回成功的HTTP响应
} catch (InterruptedException | ExecutionException e) {
Thread.currentThread().interrupt(); // 中断线程
return ResponseEntity.notFound().build(); // 返回未找到资源
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
Application
package com.example.bookkeeperspringbootexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BookKeeperSpringBootExampleApplication {
public static void main(String[] args) {
SpringApplication.run(BookKeeperSpringBootExampleApplication.class, args);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
测试
添加交易
curl -X POST http://localhost:8080/transactions/ \
-H "Content-Type: application/json" \
-d '{"transactionId": 1, "amount": 100.50, "timestamp": "2025-03-19T21:36:06"}'
- 1.
- 2.
- 3.
Respons:
1
- 1.
读取交易
curl -X GET http://localhost:8080/transactions/1
- 1.
Respons:
{"transactionId":1,"amount":100.5,"timestamp":"2025-03-19T21:36:06"}
- 1.