團隊協做工具 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技術博客查看更多幹貨內容,歡迎訪問交流技術問題。

文章轉載請註明出處。

相關文章
相關標籤/搜索