计划实现的bonus: - 缓存 - 并行 - GUI前端和完整部署方案 有时间打算实现的bonus(按优先级次序排序): 1. UTF-8中文支持 2. Validator 不打算实现的bonus: - 快照 - 文件系统修复 系统结构图:![系统结构图加载中……](https://cloud.zymsite.ink/f/4esz/%E7%B3%BB%E7%BB%9F%E7%BB%93%E6%9E%84%E5%9B%BE.png) # 功能设计(模块划分)、数据库设计 整个程序分为后端和前端两部分,后端即主体部分 ## 后端 后端只有一个可执行文件,书店实例通过配置文件夹来决定(默认为当前目录下`.bookstore`文件夹,亦可通过命令行参数指定)。当书店系统在运行时,有且仅有一个后端进程,分为interactive模式(使用后端自带的交互,单会话模式)和server模式(通过WebUI交互)。 ### 数据库模块 维护相关数据,支持并行(API阻塞但可同时运行)。 #### 磁盘IO的并发 - 机械硬盘下不应该并发,反复寻址会负优化 - 从安全性角度,没有把握不要同时写 - 进程内部使用同一个文件描述符时,可以并发地调用读取函数,但是会自动相互阻塞 - 当使用不同的文件描述符时,并发调用读取函数时不会自动相互阻塞,如果并发读取的区域重叠,会有安全性问题 ![pic](https://cloud.zymsite.ink/f/8vCd/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202023-11-29%20102719.png) 如果是图中的数据,一次IO的固定开销差不多相当于1e5条CPU指令或1e4次基本操作。但是在OJ上的实测结果为 IOPS 约等于 4e5,写入速率 1GB/s, 读取速率 5GB/s ~~因此我不太打算碰文件系统~~ #### 数据库设计方案 ##### 需求 能够逻辑上并发地响应需求,并且实际上尽可能并行。每秒可响应1e4次请求。 ##### 数据结构 ###### 底层实现 通过一个哈希表实现一个键值数据库,相当于`std::multimap`,一个键值数据库实例拥有恰好一个文件 ###### 逻辑实现 数据库的一条记录有主键(应当是唯一的)、副键和数据构成,从主键到数据建立multimap,从副键到主键建立multimap,一个数据库实例拥有恰好两个文件,一个数据库可以有多个Sheet,用于逻辑上存储不同的表。 ##### 文件访问与缓存 memoryriver类维护一个缓存,简单地缓存高频访问和连续访问;键值数据库缓存散列表。 ##### 并发安全 实际的文件操作只由一个线程负责,万级的IOPS足够应付需求;内存中的资源, ### 引擎模块 具体执行业务,一次请求对应且只对应一次引擎模块API调用,支持并行(API阻塞但可同时运行)。当API入口函数被调用后,执行相关具体操作(不包括会话管理、鉴权),然后返回响应。 ### 调度模块 负责对外提供文本模式的交互,以及维护请求级的并行。从流`request`读取文本格式的命令,并把内部数据格式的响应翻译成文本格式,向引擎模块发送请求(调用引擎模块对外提供的API),并把响应返回到流`response`。支持条件允许时的并行: - 输入子模块从`request`读取请求,打上时间戳,分独占式请求和可并行请求按时间戳顺序分批处理。处理一个请求时,启动一个工作线程调用翻译执行子模块。同一个会话的请求不可并行,把`std::thread`move进相应的该会话的工作线程句柄队列,下一个join前一个。成批执行可并行请求时,启动完当前批次所有工作线程后join每个session的最后一个工作线程,结束之后就可以执行下一批了。为了在服务模式下处理时有时无的请求,两个流在读取时是阻塞的(自己封装一个,用`std::condition_variable`通讯),并且读一个处理一个,碰到“不属于自己批次”的东西才算一批结束。 - 翻译执行子模块(入口函数本身阻塞)join完同一个session的上一个请求后,向引擎模块API发送请求,得到响应后把响应数据传给输出子模块的调用接口(阻塞但可并行,处理部分同时运行,上锁后直接输出到`response`,输出本身不同时进行)。 注意,在`request`流和`response`流中,输入输出仅保证单个请求/响应是完整的,多个请求/响应之间是完全“混杂”的,通过`session token`、`operation token`(操作标识由用户侧会话管理器维护,~~反正`session token`已经划分频道了,用户开F12瞎改后果自负~~)区分,通过`outhentication key`鉴权。`request`流和`response`流始终由信模块控制。调度模块负责会话管理、鉴权,保证引擎只接受形式合法的简单内部数据。 ### 内置交互模块 当处于server模式下,不负责会话管理,直接把`std::cin`和`std::cout`的内容转发给通信与调度模块;处于interactive模式下时,提供用户侧会话管理,然后再调用通信与调度模块。 标准输入输出始终由内置交互模块控制。 ## 前端 ~~不清楚有没有时间写~~。WebUI,采用`Node.JS`+`Socket.IO`,~~不打算弄得很好看,不打算支持响应式设计~~,支持图形化操作面板和“云命令行”。和interactive模式一样,单个会话的操作支持逻辑上并发(上一操作未结束可以发出下一操作)但后端实际上是串行的。对于通讯中断、偶发的服务器未响应,只保证不会彻底崩掉,不保证出问题的业务能恢复。会话管理方面,只负责信息准确投送。WebUI套一层Electron生成全平台客户端。 ~~不考虑卡死的情况,数据库卡死了也拿它没办法,引擎部分不死循环就不会卡死~~,只支持重置session。 ### 服务端 提供会话管理。虽然有log,但后端的响应只会发送一次,用于防止客户端掉线的缓存由服务端维护。 ### 客户端 云命令行和图形化操作面板分两个页面,通过超链接关联。历史记录、会话信息存储于IndexDB中。负责用户侧会话管理。 #### 云命令行 #### 图形化操作面板 实际上是一个页面,不同模式下动态绘制。 # 用户交互设计 ## 内部命令 - `#OpenSession [TempChannelID]`:向调度模块申请一个新会话 - `#CloseSession [SessionToken] [OuthenticationKey]`:显示地告知调度模块停止某个会话 - `#_Request [SessionToken] [OperationToken] [OuthenticationKey]`:向服务端重新请求调取某次操作响应的缓存 - `#Request [SessionToken] [OperationToken] [OuthenticationKey] [UserCommand]`:向后端发送一个请求 - `#ShutDownSystem`:关闭整个系统 ## 向用户提供的命令 - `quit`和`exit`:正常退出系统 - `su [UserID] ([Password])?`:登录 - `logout`:退出上一个登录 - `register [UserID] [Password] [Username]`:注册用户 - `passwd [UserID] ([CurrentPassword])? [NewPassword]`:修改密码 - `useradd [UserID] [Password] [Privilege] [Username]`:店员修改密码 - `delete [UserID]`:删除用户 - `show (-ISBN=[ISBN] | -name="[BookName]" | -author="[Author]" | -keyword="[Keyword]")?`:查找图书 - `buy [ISBN] [Quantity]`:购买图书 - `select [ISBN]`:选中图书 - `modify (-ISBN=[ISBN] | -name="[BookName]" | -author="[Author]" | -keyword="[Keyword]" | -price=[Price])+`:修改图书信息 - `import [Quantity] [TotalCost]`:进货 - `show finance ([Count])?`:查询交易总账 - `report finance`:查询财务报表 - `report employee`:查询员工工作报告 - `log`:查询系统操作日志 # 底层数据接口约定(类、结构体设计) ## 请求 - 引擎接受:内部数据,数据传进函数参数列表 - 调度模块接受:内部命令 - 内置交互模块(interactive模式)接受:字符串,`[UserCommand]` - 云命令行接受:字符串,`[UserCommand]` - WebUI:图形界面 ## 响应 注意,对于申请Session时,响应用`[TempChannelID]`替代`[SessionToken]`,仅用于保证信息投递正确,返回信息内容是`[SessionToken]`和`[AuthenticationKey]`。 - 引擎返回:`std::string` - 调度模块返回:字符串,`[SessionToken] [OperationToken] [LineCounter]\n[ResponseContent]`,其中,`[ResponseContent]`恰有`[LineCounter]`行,每行行末有且仅有一个`\n`。输出为空通过把`[LineCounter]`设置为0来实现。 - 内置交互模块(interactive模式)返回:字符串,`[ResponseContent]` - 云命令行返回:字符串,`[ResponseContent]` - WebUI:图形界面