# CapChain **Repository Path**: zyflzz/cap-chain ## Basic Information - **Project Name**: CapChain - **Description**: 瓶盖芯 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-03-07 - **Last Updated**: 2024-03-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基于区块链的防伪溯源系统 [TOC] ## 系统架构 该系统的架构自底向上共分为如下几个层次 ![瓶盖芯后端初步架构图](README/瓶盖芯后端初步架构图.png) 该架构只是一个初步架构图,后续还有可能进行一些更改 > 目前正在开发数据传输层的`KVDB`和`binary-proto`,以及核心层的数据账本,网关、工具包以及其他方面目前的架构属于设想 ## 详细介绍 下面简单介绍上述架构中每一个部分的主要功能: **数据传输层** `Http service`主要包括网络之间的数据传递服务,`KVDB`是基于`RocksDB`开发的简单`NoSQL`,以键值对形式存储数据,支持简单的键值对操作,预期可以实现单机部署与集群部署。`binary-proto`是一个二进制的序列化框架,其中包含了二进制的很多操作。之所以封装这样一个处理二进制的框架,是因为在我们的系统中大量用到了字节码的处理。而我们选择字节码的形式处理,是因为这样可以保证数据的传输效率快,解决了传统数据处理时效率慢的问题,同时还使得我们的系统最后在使用时具备可移植的特点,即无需重新编译就可以在任何机器上运行。 **核心层** 核心层包括了两个板块:数据账本和共识服务。数据账本可以概括地描述为四个部分,账户、区块、配置、存储。账户就是指用户的个人账户,使用的存储是基于数据传输层开发的`KVDB`,区块是用来保存账户上的交易信息,从而接入整个区块链。共识服务的基础是一个共识网络,在网络上集成了身份管理、交易处理、安全权限管理、智能合约、数据检索等功能,可以方便、安全地进行交易,也可以准确地进行产品定位。 网关层和工具包还未着手开发,所以暂时是凭经验构想的。 下面两个是暂时用到的比较重要的技术,主要用在了`KVDB`的开发过程中 # WAL 预写日志技术 WAL(Write Ahead Log)预写日志,是数据库系统中常见的一种手段,用于保证数据操作的原子性和持久性。 ## 数据备份 对于数据库而言,如果想要数据可以被恢复,就需要用到数据备份。 具体而言就是,当我需要对一条数据做更新操作前,先将这条数据备份在一个地方,然后去更新,如果更新失败,可以从备份数据中回写回来。 这样做(数据备份)的好处是显而易见的,可以方便数据回滚,一定程度上提升数据库的容灾能力。此外,这样做还有一个很重要的好处,就是可以保证数据的原子性,因为每一条数据都是独立的了,而不是常常被拿过来修改。 > 换言之,数据的可恢复性实际上等价于原子性 那么我们现在需要面临的问题就是如何实现数据的可恢复性(原子性)。 ## WAL 实现数据的可恢复性 WAL 这一方案其实很简单:在修改数据时,并非是直接写入数据库中,而是写到另外一个称为WAL的文件中;如果事务失败了,WAL中的记录会被忽略,撤销修改;如果事务成功了,那么它将在随后的某个时间写入到数据库中,提交修改(什么时候有空什么时候写,具体时间可能需要开发者思考如何设置) 正如“预写日志”名字一样,WAL就是在写入数据库之前先写入其他文件中 ## WAL 的优点 仔细思考上面所说的机制,我们会发现WAL主要有如下几个优点 1. `读操作` 和 `写操作` 这两种操作之间可以完全独立地进行,换言之就是并发执行,不会相互阻塞;不过 `写操作` 与 `写操作` 之间还是不能够并发的 2. 能够减少 `fync` 操作的次数,因为失败的事物无需使用 `fync` 3. 磁盘的 `I/O` 更容易被预测,因为“无效”的 `fync` 不需要了 ## WAL 为什么能够提升性能 我们都知道数据库性能消耗最大的就是磁盘读写了,针对如何提升性能这一问题,主要有三种方案: 1. 将 `随机读写` 改为 `顺序读写` 2. 将 `缓冲单条读写` 改为 `批量读写` 3. 将 `单线程读写` 改为 `并发读写` WAL 就实现了这样的思想,为什么这么说呢,主要体现在如下两个方面: 1. WAL 中记录着事务更新内容,通过 WAL 可以将随机的脏页写入转变成顺序的日志刷盘 2. WAL 通过 Buffer 的方式,将单条磁盘刷入改为缓冲批量刷盘 3. WAL 在一定程度上实现了并发读写 因此 WAL 能够极大地提升性能 # Netty 技术 简单说Netty是一个Java网络编程的框架,受到各大公司的青睐,也备受广大开发者的欢迎,其主要原因可以概括为如下三点: 1. **并发高** 2. **传输快** 3. **封装好** ## 并发高 Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),它的并发性能得到了很大的提高,我们可以通过下图来理解BIO和NIO的区别: **BIO** ```mermaid graph TB subgraph BIO BIO_S1[Socket] BIO_S2[Socket] BIO_S3[Socket] BIO_O1[read/write] BIO_O2[read/write] BIO_O3[read/write] BIO_T1[Thread] BIO_T2[Thread] BIO_T3[Thread] BIO_S1-->BIO_O1-->BIO_T1 BIO_S2-->BIO_O2-->BIO_T2 BIO_S3-->BIO_O3-->BIO_T3 end ``` **NIO** ```mermaid graph TB subgraph NIO NIO_S1[Socket] NIO_S2[Socket] NIO_S3[Socket] NIO_O1[read/write] NIO_O2[read/write] NIO_O3[read/write] Selector[Selecctor] Thread[Thread] NIO_S1-->NIO_O1-->Selector NIO_S2-->NIO_O2-->Selector NIO_S3-->NIO_O3-->Selector Selector--->Thread end ``` **分析** 从上面两幅图可以看出,NIO的单线程能处理的连接数量比BIO要高出很多。而之所以单线程能够处理更多的连接,是因为其中有一个 `Selector`。 当一个连接建立之后,它有两个步骤,第一步是接收完客户端发过来的全部数据,第二步是服务端处理完请求业务之后返回response给客户端。NIO和BIO的主要区别就在于第一步。 在BIO中,等待客户端发数据这个过程是阻塞的,这样就造成了一个线程只能处理一个请求的情况,而机器能支持的最大线程数是有限的,这就是为什么BIO不能支持高并发的原因。 而在NIO中,当一个Socket建立好之后,Thread并不会阻塞去接收这个Socket,而是将这个请求交给Selector,Selector会不断地去遍历所有的Socket,一旦有一个Socket建立完成,它会通知Thread,然后Thread处理完数据再返回给客户端,这个过程是不阻塞的,这样也就能够让一个Thread处理更多的请求了。 > 当然,除了BIO和NIO,还有其他的IO模型,例如多路复用IO、信号驱动IO、异步IO等,这里就不讨论了 ## 传输快 Netty传输快其实也就是依赖了NIO的特性——零拷贝。 我们知道,Java的内存有堆内存、栈内存和字符串常量池等等,其中堆内存是占用空间最大的一块,也是Java对象存放的地方,一般我们的数据如果需要从IO读取到堆内存,中间需要经过Socket缓冲区,也就是说一个数据会被拷贝两次才能到达终点,如果数据量大,就会造成不必要的资源浪费。 Netty针对这种情况,使用了NIO中的零拷贝技术,当需要接收数据的时候,就会在堆内存之外开辟一块内存,数据就直接从IO读到了那块内存中去了,在Netty里面通过ByteBuf就可以直接对这些数据进行直接操作,从而加快了传输的速度。这里所说的零拷贝就是指中间过程零拷贝。下面两张图就展示了传统拷贝和零拷贝的方式。 **传统拷贝** ```mermaid graph TB subgraph Tradition subgraph App C[应用程序缓冲区] end subgraph Core P[页缓存] S[Socket缓冲区] end subgraph Hardware D[磁盘] N[内存] end D-->P N-->S P-->C S-->C end ``` **零拷贝** ```mermaid graph TB subgraph ZreoCopy subgraph App C[应用程序缓冲区] end subgraph Core P[页缓存] S[Socket缓冲区] end subgraph Hardware D[磁盘] N[内存] end D-->C N-->C end ``` > 我们这里提到的ByteBuf是Netty中的一个重要概念,它是Netty数据处理器的容器,也是Netty封装好的一个重要体现,我们下面一部分将介绍 ## 封装好 > 这里可以就简单说一下Netty封装好了很多操作,使得操作更为简便