worktile的架构设计
Worktile自上线两年多以来,以良好的用户体验和稳定的服务,获得了用户的认可和喜爱。截止笔者写这篇文章的时候,已经有超过10万家团队在使用Worktile。作为团队协作工具,从技术上分析首先要解决如下几个问题:
- 1. 基于Web的跨平台设计,让用户在任何地方都可以随时通过浏览器访问
- 2. Web形态的产品要具有原生客户端的体验,如任务的拖拽等
- 3. 具有高效的实时消息系统,每个团队成员在Worktile中所做的任何操作,都要实时在其他成员的客户端中自动刷新
- 4. 服务要稳定,稳定压倒一切
那么Worktile是如何做到这几点的?今天笔者在这篇文章里一一为大家揭秘。
SPA设计
先来说说Worktile中SPA(单页应用程序)设计,作为团队协作工具,需要尽可能减少用户在不同页面之间的跳转,所以从一开始我们就决定Worktile必须是单页应用程序,当时面临的选择有很多,首先我们考虑使用大名鼎鼎的Backbone.js,但是很快又抛弃了,因为在实际使用中Backbone.js太复杂,另一方面开发效率太低,最终我们选择了Google出品的AngularJs,下面这幅图是AngularJS的结构图:

选择它主要基于以下几点考虑:
1. 自动化双向数据绑定功能,这一点在Worktile中非常重要,如任务的状态变化都要实时变更到其他成员,如果具有自动化双向数据绑定功能,只需要绑定到UI的数据源发生变化,UI会自动发生改变,不需要工程师再通过代码去修改UI元素的改变,如下面这段代码:
<div class="entry-task-main"
ng-class="{1:'task-completed-style'}[task.completed]">
<a class="entry-task-check"
id="task_check_{{ task.tid }}"
wt-click="js_complete_task($event, entry, task)">
<i ng-class="{0: 'icon-check-empty', 1: 'icon-check-sign'}[task.completed]"></i>
</a>
<a class="entry-task-title" href="javascript:;">{{task.name}}</a>
</div>
2. 语义化标签,AngularJS在设计之初信奉的理念就是:当编写UI的同时又需要编写业务逻辑时,声明式的代码远比命令式代码要好,命令式的代码更适合写业务逻辑,AngularJS在设计上就通过语义化的标签把对DOM元素的操作和逻辑代码分离,如我们需要展现一个任务列表,只需要下面这段代码即可:
<div ng-repeat="task in tasks">
<wt-task view-type="item" show-project="false"
class="slide-trigger"
hide_action="true"
ng-click="locator.openTask(task.pid, task.tid)">
</wt-task>
</div>
3. 模块化设计,AngularJS堪称模块化设计方面的典范,通过模块化设计我们可以非常好的实现Worktile的工程化,在Worktile中涉及的元素非常多,如有项目、任务、日程、文件、话题、文档等等,而这每一个元素都可以设计为一个模块,如下所示:
(function () {
'use strict';
angular.module('wtApp', [
'wt.project.ctrl',
'wt.team.ctrl',
'wt.task.ctrl',
'wt.event.ctrl',
'wt.post.ctrl',
'wt.file.ctrl',
'wt.page.ctrl',
'wt.mail.ctrl'
]);
}());
4. 引入依赖注入,依赖注入是面向对象中比较成熟的设计模式之一,为了解决面向对象中依赖问题,得到了广泛的应用,AngularJS中大胆使用了依赖注入,极大的减少了各个模块之间的依赖问题:
taskListCtrl.$inject = ['$scope', '$stateParams',
'$rootScope', '$popbox',
'$location', '$timeout',
'bus', 'globalDataContext',
'locator'];
结合以上特点,我们最终决定了前端框架使用AngularJS来实现,从Worktile上线两年多的表现来看,我们的选择无疑是正确的。当然AngularJS也有一些缺点,在实际使用中还是要根据具体的产品类型来选择使用,另外AngularJS 2.0也已经初见端倪,和AngularJS 1.0有很大的不同,感兴趣的同学可以先去尝鲜一下。
服务设计
我们再来看看Worktile的后台服务设计,Worktile的整体服务架构设计如下图所示:

其中前端部分在上面的SPA一节中我们已经说过了,下面一一分析下其他的服务:
1. API服务,包括Web API、Mobile API、Open API,这些都运行于NodeJS之上,选用NodeJS的原因主要是它的异步事件驱动,对于高并发的支持比较好,另外一个原因是使用简单,对于前后端可以使用同一门语言去开发。
2. 缓存和队列服务,Worktile中的缓存和队列服务都是基于Redis来实现,Redis是一款非常优秀的开源缓存服务,并且可以选择基于内存还是进行数据持久化,它提供的pub/sub模型对于Worktile来说非常重要,对于一些实时性要求不高的处理,我们都是在Redis中pub一条消息,告知其他服务有数据发生了变化,那些服务在接收到Redis中的消息后,根据消息的类型决定应该如何做出处理。
3. 数据库服务,Worktile产品本身的特点决定了它是一个对实时性和性能的要求,远超过对事务性要求的产品,所以在选择数据库时,我们选用了MongoDB数据库,性能高,集群方便,数据以BSON结构存储,和Node.js天生完美结合。
4. 文件预览服务,使用Worktile的同学肯定知道在Worktile中所有的文件都可以做到无需下载到本地,而直接在线查看,这一切都是预览服务的功劳,因为文件类型的各种各样,在实现文件预览时也要根据文件的类型做出不同的处理,针对txt、pdf、代码片段等文本型的文件,我们只需要读取文件中的内容,然后再前端用相应的视图展现出来即可,相对比较简单。但是对于Office类型的文件,如ppt、doc、xls等文件,就不能这么简单的处理,我们希望文件在Worktile中查看的效果和用户在本地使用Word、Excel、PowerPoint查看的效果差不多,经过我们的调研,最终选用了微软官方提供的Office Web App服务。
消息推送
消息推送服务是Worktile最核心的服务之一,前面提到过作为一款团队协作工具,要能够实现非常好的实时性,任何数据的变化都需要及时变更到团队所有成员当前所在的视图,如下面这幅图,是一个典型的任务看板,团队所有成员可能同时在操作当前项目中的任务,每个操作引起看板的变化都会实时更新,不需要用户做任何刷新操作:

为了达到这种效果,需要在Web客户端和服务器之间维持一个长连接,当有任何改变发生时,给客户端发送不同的消息,告知客户端哪些数据发生了变化,如下面是我们为任务定义的消息中的其中几个:
实现实时消息推送,有以下几种方式可供选择:
on_task_trash : "on_task_trash",
on_task_complete : "on_task_complete",
on_task_move : "on_task_move",
on_task_update : "on_task_update",
on_task_comment : "on_task_comment",
on_task_badges_file : "on_task_badges_file",
on_task_unarchived : "on_task_unarchived",
on_task_badges_check : "on_task_badges_check"
1. 短轮询,页面端通过js定时异步刷新,这种方式优点在于实现简单,但实时效果较差。
2. 长轮询。页面端通过js异步请求服务端,服务端在接收到请求后,如果该次请求没有数据,则挂起这次请求,直到有数据到达或时间片(服务端设定)到,则返回本次请求,客户端接着下一次请求,这种方式对于服务的要求较高,尤其在并发量很大的情况下,对服务端的压力很大。
3. Websocket。浏览器通过websocket协议连接服务端,实现了浏览器和服务器端的全双工通信。需要服务端和浏览器都支持websocket协议。
在Worktile一开始我们选用了Socket.IO作为消息服务,但是随着访问量的增大,需要做集群化的时候感觉到力不从心,尤其对于Socket.IO状态数据的存储,由于并没有官方的解决方案,当时我们采用了一个第三方的开源项目,使用Redis来存储,引起了一些性能上的问题,在后来重构时选用了基于Erlang语言的开源XMPP服务ejabberd作为我们的消息服务。
ejabberd是xmpp协议的一种实现, xmpp广泛应用于即时通信领域。Xmpp协议的实现有很多种,比如java的openfire,但相较其他实现,ejabberd的并发性能无疑使最优秀的。Xmpp协议的前身是jabber协议,早期的jabber协议主要包括在线状态(presence)、好友花名册(roster)、IQ(Info/Query)几个部分。现在jabber已经成为rfc的官方标准,如rfc2799, rfc4622, rfc6121,以及xmpp的扩展协议(xep)。Worktile就是基于XEP-0124、XEP-0206定义的BOSH扩展协议。
由于自身业务的需要,我们对ejabberd的用户认证和好友列表模块的源码进行修改,通过redis保存用户的在线状态,而不是mnesia和mysql。另外好友这块我们是从已有的数据库中(mongodb)中获取Worktile中项目或团队的成员。Web端通过strophe.js来连接(http-bind),strophe.js可以以长轮询和websocket两种方式来连接,由于ejabberd还没有好的websocket的实现,就采用了BOSH的方式模拟长连接。整个系统的结构如下:

后记
关于Worktile整个的技术架构就揭秘到这里,通过上面的介绍,相信大家也能看到Worktile本身就是典型的MEAN(MongoDB、Express、AngularJS、NodeJS)架构,外加上ejabberd作为实时消息推送服务,建议大家在自己的产品中,根据产品自身的特点和团队的技术背景,来选择具体使用哪种技术。
worktile的架构设计的更多相关文章
- 浅谈 jQuery 核心架构设计
jQuery对于大家而言并不陌生,因此关于它是什么以及它的作用,在这里我就不多言了,而本篇文章的目的是想通过对源码简单的分析来讨论 jQuery 的核心架构设计,以及jQuery 是如何利用javas ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- 解构C#游戏框架uFrame兼谈游戏架构设计
1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...
- VICA 架构设计(1)
本文记录最近完成的一个通用实时通信客户端的架构. 背景 我们公司是做税务相关的软件,有针对大客户 MIS 系统,也有针对中小客户的 SaaS 平台.这些系统虽然都是 B/S 的,但是也需要使用 A ...
- 一种简单的CQRS架构设计及其实现
一.为什么要实践领域驱动? 近一年时间我一直在思考一个问题:"如何设计一个松耦合.高伸缩性.易于维护的架构?".之所以有这样的想法是因为我接触的不少项目都是以数据库脚本来实现业务逻 ...
- 基于token的多平台身份认证架构设计
基于token的多平台身份认证架构设计 1 概述 在存在账号体系的信息系统中,对身份的鉴定是非常重要的事情. 随着移动互联网时代到来,客户端的类型越来越多, 逐渐出现了 一个服务器,N个客户端的格 ...
- 架构设计:一种远程调用服务的设计构思(zookeeper的一种应用实践)
在深入学习zookeeper我想先给大家介绍一个和zookeeper相关的应用实例,我把这个实例命名为远程调用服务.通过对这种应用实例的描述,我们会对zookeeper应用场景会有深入的了解. 远程调 ...
- ABP架构设计交流群-上海线下交流会的内容分享(有高清录像视频的链接)
点这里进入ABP系列文章总目录 ABP架构设计交流群-7月18日上海线下交流会内容分享 因为最近工作特别忙,很久没有更新博客了,真对不起关注我博客和ABP系列文章的朋友! 原计划在7月11日举行的AB ...
- 架构设计:负载均衡层设计方案(3)——Nginx进阶
版权声明:欢迎转载,但是看在我辛勤劳动的份上,请注明来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!) 目录(?)[-] Nginx继续进阶 1gzip ...
随机推荐
- MyBatis支持的jdbcType类型
BIT FLOAT CHAR TIMESTAMP OTHER UNDEFINED TINYINT REAL V ...
- HTML5离线缓存问题
HTML5离线缓存问题 1.应用程序缓存 什么是应用程序缓存(Application Cache)? HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. ...
- LeetCode——Letter Combinations of a Phone Number
Given a digit string, return all possible letter combinations that the number could represent. A map ...
- 泛型数组列表 ArrayList
为什么使用泛型数组列表而不使用普通数组? 1.普通数组经常会发生容量太大以致浪费的情况 2.普通数组无法动态更改数组 基本概念: 1.采用[类型参数]的[类]---->[泛型类] 2.[泛型类型 ...
- MultipeerConnectivity框架,近场通信的基本使用
Multipeer connectivity是一个使附近设备通过Wi-Fi网络.P2P Wi-Fi以及蓝牙个人局域网进行通信的框架.互相链接的节点可以安全地传递信息.流或是其他文件资源,而不用通过网络 ...
- 【Java】变量类接口_学习笔记
变量.类和接口 1.变量的类型 实例变量(不以static修饰) 成员变量 类变量(以static修饰) 所有变量 形参(方法签名中定义的变量) 局部变量 方法局部变量(在方法内定义) ...
- 真核转录组(denovo/resequencing)及案例分析
参考: 转录组文章的常规套路 文章解读:<Science>小麦转录组研究文章 转录组数据饱和度评估方法 Paper这个东西是多么的诱人,可以毕业,可以评职称,可以拿绩效. 现在的文章都是有 ...
- Windows 7 封装篇(一)【母盘定制】[手动制作]定制合适的系统母盘
Windows 7 封装篇(一)[母盘定制][手动制作]定制合适的系统母盘 http://www.win10u.com/article/html/10.html Windows 7 封装篇(一)[母盘 ...
- lua实现私有函数
本文是原创文章,如需转载,请注明文章出处 要用lua实现私有函数,关键就是使用metatable的特性来实现. Test.lua: local v = {};v.x = 100;v.y = 200; ...
- webpack入门
一,为什么用webpack 1.由于项目中资源的多样性和依赖性 2.js模块规范复杂化 3.开发与线上文件不一致性 二.webpack 特性 1.对CommonJS.AMD.ES6语法兼容 2.对js ...