近年来, 随着互联网技术的飞速发展, 各企事业单位为了更好的履行职能, 提高办公效率, 提升管理水平, 都在大力发展信息化建设. 投票评审系统作为促进业务发展的一种手段在各单位已成为常态化, 投票表决事项、投票规则、统计方式等也趋于多样化. 传统的投票评审方式准备周期长且复杂, 系统扩展性较差, 难以应对投票过程中产生的各类突发情况, 用户并发操作时, 系统性能下降明显. 如何利用先进的科学技术和手段替代传统低效率的投票评审方式成为了亟待解决的问题.
1 系统设计 1.1 系统总体功能本系统基于有限状态机原理, 利用WebSocket等互联网技术设计并实现了一个实时高可靠的投票评审系统. 本系统具有较高的扩展性, 可通过灵活配置, 满足基于人员、事项等多种对象的投票信息化管理, 涵盖了多种投票形式.
1.2 系统功能架构图1所示的功能架构中, 管理员通过管理端进行会议及投票计划的创建, 并对投票流程进行控制. 评委通过评委端进行投票及投票结果的实时查看.
1.3 系统技术架构 1.3.1 整体架构
如图2所示, 系统使用了Nginx+Node.js+MongoDB的开发框架进行快速迭代开发. 通过Machina.js组件实现系统中有关状态机的各种逻辑. 通过Socket.IO组件运用WebSocket技术实现系统应用前后端实时通信, 并保证了应用系统的高可靠性.
1.3.2 投票处理逻辑的设计
如图3所示, 投票模型(Voting Model, VM)是投票流程的状态机模型, 主要用于处理计算及流程状态相关逻辑.
投票处理器(Voting Processor, VP)主要负责投票流程中的数据预/后处理、存取逻辑并负责具体处理WebSocket请求, 一个VP对象实例管理并操作对应的一个VM对象实例, 一对VP与VM形成一套投票流程模板, 具体负责一类投票流程.
投票处理器管理器(Voting Processor Manager, VPM)主要负责多个VP实例的管理, 包括VP的加载、同步、切换等等, 并负责处理转发WebSocket请求至相应的VP实例.
2 关键技术 2.1 有限状态机有限状态机(Finite State Machine, FSM)是计算机领域中一种用来表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型[1].
有限状态机可以用来进行对象行为建模, 其作用主要是描述对象在它的生命周期内所经历的状态序列, 以及如何响应来自外界的各种事件[2].
在本系统中, 使用了JavaScript的有限状态机库Machina.js来实现状态机相关的逻辑.
2.2 WebSocket由于HTTP协议的请求-响应特性, Web应用都是以一种单向的方式进行通讯——所有的通信都是由客户端发起和控制的. 于是许多网站为了实现向客户端实时推送数据, 采用了一种轮询的技术, 即每隔一段时间由客户端向服务端请求最新数据[3]. 然而由于服务端需要不断的响应客户端发出HTTP请求, 造成了很大的网络资源浪费. 图4是采用轮询技术和WebSocket的网络负载对比[4].
由此可见, 采用轮询技术的网络负载远远大于WebSocket技术的网络负载.
于是, HTML5标准定义了WebSocket协议. 该协议在客户端和服务端之间建立了一个全双工的套接字连接, 客户端和服务端会通过这个持续存在的连接通道自由地传递数据. WebSocket协议是基于TCP的一种通信协议, 所以其工作流程也需要经过发起连接请求、握手、建立连接三个步骤[5]:
1) 客户端发送连接请求;
2) 服务器响应并切换协议;
3) 通信两端建立全双工连接.
最后当通信中的某一方认为可以结束会话时, 便会给对对方发送关闭连接的请求, 这样便结束了此次连接.
WebSocket协议工作流程如图5所示.
作为一种新的Web标准, 虽然WebSocket协议仍在发展中, 却已经具有一系列优秀的特性. 首先, WebSocket是一种有状态的协议, 所以通信时可以省略部分状态信息, 提高了数据传输的效率, 具有更强的实时性; 其次, WebSocket有更好的二进制支持, 相对于HTTP可以更好地处理二进制内容; 最后, WebSocket支持扩展, 用户可以由此实现用户自定义的子协议[6].
本系统中我们采用了开源的跨平台实时通信库Socket.IO. 它提供了客户端和基于Node.js服务器的解决方案.
2.3 Node.js应用运行环境Node.js是一个服务器端的JavaScript运行环境, 由于封装了Google的V8引擎, 使得其执行JavaScript代码的速度和性能都有很大提升. Node.js的事件驱动和非阻塞I/O特性, 使得它通常被用于I/O密集的实时应用系统[7].
传统的Web服务器技术中, 每当新增一个连接请求时便会生成一个新的线程进行处理和响应[8]. 而Node.js使用了非阻塞的单线程方式, 利用JavaScript的事件机制, 在收到一个查询请求后便将CPU的控制权交出, 直到当数据处理完成后触发一个事件, 再取得控制权继续执行.
如图6所示, Node.js的结构与Chrome非常相似, 他们都是基于事件驱动的异步架构. 浏览器通过事件来处理界面上的交互, Node.js通过事件来处理I/O. 在服务器端, Node.js虽然不再处理CSS、与DOM和BOM打交道, 但是却具有JavaScript的一切语言特性——基于原型链的对象继承、事件处理机制、回调函数等. 此外, Node.js还可以方便地访问本地文件、数据库以及网络等资源[9].
在本系统中, 我们采用了Express作为Node.js Web框架. Express是一种简洁易用、功能强大的Web应用程序框架, 它提供了一系列丰富的API及中间件用于进行快速开发. 我们还使用了基于分布式文档存储的非关系型的MongoDB作为底层数据库.
3 重点问题解决方案下面我们将首先聚焦于有限状态机在VM(投票模型)实现中所起到的作用, 然后我们还将具体描述投票流程以及VM、VP、VPM的联动关系, 并阐述系统可靠性的实现方法.
3.1 可扩展的投票流程模型的实现本系统支持多种投票形式, 如差额投票、多轮次投票、候选逐一投票等等, 并支持将来的扩展. 我们整理了十几种不同的投票场景、规则, 将它们进行归纳整理, 并最终使用有限状态机(FSM)来对投票流程进行建模.
我们举一个比较简单的投票场景为例, 其规则如下:
1) X个候选人中选出Y(目标额度)个人通过(X>Y);
2) 根据候选人所得赞成票多少进行统计, 须得到与会评委相应比例(如2/3)的赞成票方有资格通过;
3) 满足规则2的候选人超过规定额度时, 按得票多少取满目标额度;
4) 在规则3情景下, 如出现目标额度位置前后得票并列的情况, 则对并列的候选人重新投票, 直到选满目标额度为止. 假设我们按票数从高到低对候选人进行排列, 如出现票数Y=票数Y+1=票数Y+2或票数Y–1=票数Y=票数Y+1的情况就需要进行并列逻辑处理, 如出现票数Y–1=票数Y且票数Y>票数Y+1的情况, 则不需要进行并列逻辑处理.
针对此投票场景, 我们使用如图7所示的状态机来对其投票流程进行建模.
该状态机共有5个状态, 各状态及转移情况描述如下:
(1) 初始状态(uninitialized)
接收init事件及相关参数后, 转移至knockout状态, 并执行init初始化方法对首轮投票参数进行初始化.
(2) 投票状态(knockout)
接收init事件及参数来初始化投票轮次.
每一张投票触发一次vote事件, 记录选票, 直到所有评委均投票完成, 触发计票逻辑, 计算本轮投票结果. 若未满足投票目标则状态机转移至rest状态, 计算好下一轮投票可能的参数(如剩余额度、评委投票权数(评委可投的票数)、候选项等), 准备进行下一轮投票; 若满足投票目标则状态机转移至goal状态, 完成投票计划.
(3) 投票轮次结束状态(rest)
接收init事件及相关参数后, 转移至knockout状态, 并执行init初始化方法对下一轮投票参数进行初始化.
接收abort事件, 转移至aborted状态, 终止本次投票计划.
(4) 最终态(goal)
投票正常完成.
(5) 最终态(aborted)
投票被手动终止.
根据此状态机设计, 投票规则1)至4)中所涉及的计算逻辑可在knockout状态的vote事件的计票逻辑中实现, 从而可完成单轮或多轮的投票场景, 并可人工进行终止.
针对更复杂的投票场景, 如含有多个规则不同的环节的投票计划(如含有预选、复议、定评等环节), 我们可以建立多个针对不同环节的状态, 如qualification、knockout、finalreview, 完成各自的初始化和记/计票逻辑, 并复用uninitialized、rest、goal、aborted状态的相关逻辑, 便可完成该投票场景的计算模型.
状态机的实现上我们使用了machina.js框架, 一个成熟的javascript状态机框架. 在该框架中我们完成了状态、状态转移、状态事件的定义, 由于machina.js定义的FSM是基于EventEmitter的, 所以我们方便地进行了输出事件的定义. 关键代码如下:
module.exports = machina.Fsm.extend({
initialize: function (options) {}, //初始化fsm的方法
initialState: “uninitialized”, //初始状态
states: { //各状态定义及状态间转移定义
uninitialized: {},
knockout: {
init: function (param) {
//初始化本轮投票
},
vote: function (ballot) {
//记录投票
//计票
//根据计票结果进入goal或rest状态
}
},
rest: {},
aborted: {},
goal: {
goal: function (result) {
this.emit(“goal”, result); //发出goal事件(由VP捕获处理), 触发投票结束后的逻辑
}
}
},
//以下为状态机接收的事件
init: function (param) {
this.handle(“init”, param);
},
vote: function (ballot) {
this.handle(“vote”, ballot);
}
});
3.2 投票流程及系统高可靠性的实现 3.2.1 数据IO(事务性)与计算分离系统采取了以数据库为中心的计算与IO分离的设计策略, 对于每一步经过确认的数据先存入数据库, 存储成功后再进行下一步处理, 每一次的投票状态变化都先存储或更新数据库再进行下一步数据操作. 以步步为营的方式记录最新的投票状态, 这样保障了数据的一致性, 同时为状态恢复机制提供了依据.
如图8, 整个投票过程的前后端数据交互都由WebSocket协议实时传递, 评委端的所有选票数据由WebSocket发送给后端的VPM, VPM对每一份收到的数据进行验证、收集、存储和计算, 得出投票结果后通过WebSocket对评委端、管理端和投票监控大屏等展示终端进行实时广播, 各端收到投票结果数据立即进行展示.
具体为: 首先选票数据由评委端发送给VPM, VPM交给对应投票类型的VP, VP对每一份选票数据进行验证后连同评委信息一起存入数据库, 仅当存库成功后才进行步骤3, 将选票传入VM状态机用于收集和计算, 当所有与会评委投票完成后, VM自动计算投票结果, 并将结果返回给相应VP, VP进行存库, 最后广播结果数据给前端.
投票过程中数据的验证、传递和存储由VP负责, 它是基于持久化存储的, 非易失的, 可靠性强; 计算由VM负责, VM的数据运行在内存中, 是易失的, 可靠性弱. 当系统经历意外宕机等情况时, VP可以从数据库中获取宕机前投票计划的最后状态的数据, 然后用计划状态数据初始化VM状态机和前端页面, 即可恢复投票状态.
3.2.2 投票状态恢复与监控投票过程中服务端和客户端都可能出现意外情况而断电或断网, 因此服务端和客户端都需要完备的状态恢复机制.
对于服务端, 当系统经历意外中断之后, 重启程序时VPM会先从数据库中读取尚未结束且处于活动状态的计划, 然后VP依据具体的计划状态和票箱(已投)数据确定恢复计划的具体数据, 包括当前的轮次信息, 评委提交信息, 组装好具体数据后VP将其广播给各前端(评委端、管理端、投票监控大屏), 主动刷新了前端的状态. 服务端的相关程序, 如应用、数据库、nginx等程序都部署了开机自启动脚本, 因此整个投票系统可以在无人工干预的状态下自动恢复到投票中断前的状态.
由于前端设备数量较多, 可控性较差, 在投票的任何阶段都可能出现终端连接断开和重新连接的情况, 因此投票过程中一旦有前端发起同步请求, 服务端先检视当前投票状态, 根据当前状态准备前端的恢复数据, 返回给前端进行页面更新.
图9所示的时序图主要描述了前端和服务端之间的数据交互. 图中所有消息都是异步发送, 由socket.emit事件触发, 然后监听返回事件, 依据返回数据进行页面更新. 以ctrl开头的指令表示控制指令, 控制投票状态, 例如投票开始; 以view开头的指令表示更新页面. 根据时序图步骤0, 每个新建立的WebSocket连接都会收到服务端发出的sync同步指令, 该指令数据中定义了投票状态和渲染页面所需的信息, 前端根据sync指令的数据来更新页面内容. 此步骤保证了前端页面随时都可以与服务端通过WebSocket接口恢复投票状态.
当投票正在进行时, 前端步骤3发送的投票数据在服务端收到后会广播viewdata:vote投票进程数据, 各端页面收到后会即时更新当轮投票的进度. 如果当轮投票结束且满足整个投票结束的条件后, 服务端根据步骤3.3也会将结果数据广播给各前端页面, 当管理员手动终止投票时也是同样的流程.
这样, 系统以数据库为中心, 采用WebSocket实时双工数据传输协议, 以投票状态为依据, 从应用设计和部署层面实现了投票的高可靠性.
4 系统实际应用本系统目前已经正式投入使用, 完成了本单位多次社领导级别的重要评审会议的技术保障工作, 包括社编务会通报表扬事项表决、采编业务考核非固定加减分事项表决、全社创新工作奖励集中评审表决、年度社级优秀新闻作品评审表决、驻外分社职称评审表决等等.
投票模板的应用使得切换投票方式变得简单易行, 从前端展示到后台统计均可一次性完成, 大大节省了投票准备工作时间. 传统投票系统在轮次间需要人工进行下轮参数的计算和设置, 平均人工准备时间大约需要2到5分钟. 使用本系统后, 评审过程中系统自动计算出各种参数(如候选项), 流程衔接顺畅, 没有了人工准备时间, 极大地提高了评审效率. 系统智能程度较高, 学习成本低, 简单易懂的操作界面提升了用户体验. 高可靠性使得系统能灵活应对各种突发事件, 确保数据正确完整.
“界面新颖、体验良好、功能灵活、流程顺畅”的新一代投票评审系统, 投入使用后效果显著, 得到了本单位社领导、各部门领导和组织单位的一致好评.
5 结语本系统具有较高的可靠性, 满足基于人员、事项等多种对象的投票信息化管理, 并可扩展多种模板以满足不断增长的业务需求. 系统的使用极大地提高了评审管理效率, 增加了评审工作的标准化与科学化, 互联网思维的用户体验也增加了用户的黏合度. 未来, 计划在现有系统基础上增加基于系统使用数据的大数据分析, 辅助领导决策, 促进业务发展.
[1] |
钱忠胜, 邹俊. 正规文法与有限自动机的等价构造. 计算机应用与软件, 2008, 25(6): 110-112. |
[2] |
为Linux应用构造有限状态机. https://www.ibm.com/developerworks/cn/linux/l-fsmachine/. [2014-10-01].
|
[3] |
易仁伟. 基于WebSocket的实时Web应用的研究[硕士学位论文]. 武汉: 武汉理工大学, 2013.
|
[4] |
WebSockets简介: 将套接字引入网络. https://www.html5rocks.com/zh/tutorials/websockets/basics/. [2010-10-20].
|
[5] |
李锡辉, 杨丽. 基于WebSocket的服务器推送技术研究. 网络安全技术与应用, 2014(6): 45-46. |
[6] |
齐华, 李佳, 刘军. 基于Websocket的消息实时推送设计与实现. 微处理机, 2016, 37(3): 36-39, 43. |
[7] |
万里晴, 杨浩. 探究基于V8引擎的Node.js在各应用领域的发展. 通讯世界, 2015(13): 97. DOI:10.3969/j.issn.1006-4222.2015.13.066 |
[8] |
骆文亮. Node.js服务器技术初探. 无线互联科技, 2014(3): 227. |
[9] |
朴灵. 深入浅出Node.js. 北京: 人民邮电出版社, 2013.
|