团队协做工具 Worktile 技术架构揭秘

Worktile自上线两年多以来,以良好的用户体验和稳定的服务,得到了用户的承认和喜好。截止笔者写这篇文章的时候,已经有超过10万家团队在使用Worktile。做为团队协做工具,从技术上分析首先要解决以下几个问题:javascript

  1. 基于Web的跨平台设计,让用户在任何地方均可以随时经过浏览器访问
  2. Web形态的产品要具备原生客户端的体验,如任务的拖拽等
  3. 具备高效的实时消息系统,每一个团队成员在Worktile中所作的任何操做,都要实时在其余成员的客户端中自动刷新
  4. 服务要稳定,稳定压倒一切

那么Worktile是如何作到这几点的?今天笔者在这篇文章里一一为你们揭秘。前端

SPA设计

先来讲说Worktile中SPA(单页应用程序)设计,做为团队协做工具,须要尽量减小用户在不一样页面之间的跳转,因此从一开始咱们就决定Worktile必须是单页应用程序,当时面临的选择有不少,首先咱们考虑使用大名鼎鼎的Backbone.js,可是很快又抛弃了,由于在实际使用中Backbone.js太复杂,另外一方面开发效率过低,最终咱们选择了Google出品的AngularJs,下面这幅图是AngularJS的结构图:java

图片描述

选择它主要基于如下几点考虑:mysql

  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>
  1. 语义化标签,AngularJS在设计之初信奉的理念就是:当编写UI的同时又须要编写业务逻辑时,声明式的代码远比命令式代码要好,命令式的代码更适合写业务逻辑,AngularJS在设计上就经过语义化的标签把对DOM元素的操做和逻辑代码分离,如咱们须要展示一个任务列表,只须要下面这段代码便可:web

    <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>
  2. 模块化设计,AngularJS堪称模块化设计方面的典范,经过模块化设计咱们能够很是好的实现Worktile的工程化,在Worktile中涉及的元素很是多,若有项目、任务、日程、文件、话题、文档等等,而这每个元素均可以设计为一个模块,以下所示:redis

    (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'
        ]);
    }());
  3. 引入依赖注入,依赖注入是面向对象中比较成熟的设计模式之一,为了解决面向对象中依赖问题,获得了普遍的应用,AngularJS中大胆使用了依赖注入,极大的减小了各个模块之间的依赖问题:sql

    taskListCtrl.$inject = ['$scope', '$stateParams', 
                '$rootScope', '$popbox', 
                '$location', '$timeout', 
                'bus', 'globalDataContext', 
                'locator'];

结合以上特色,咱们最终决定了前端框架使用AngularJS来实现,从Worktile上线两年多的表现来看,咱们的选择无疑是正确的。固然AngularJS也有一些缺点,在实际使用中仍是要根据具体的产品类型来选择使用,另外AngularJS 2.0也已经初见端倪,和AngularJS 1.0有很大的不一样,感兴趣的同窗能够先去尝鲜一下。mongodb

服务设计

咱们再来看看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-012四、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联合创始人&CTO,关注团队协做领域,致力于用工具解决中小团队的协做问题。

您能够点击Worktile技术博客查看更多干货内容,欢迎访问交流技术问题。

文章转载请注明出处。

相关文章
相关标签/搜索