Skip to content
/ shenjs Public

My slide for ShenJS (JsConf China 2015): Real-Time App Practice

Notifications You must be signed in to change notification settings

loddit/shenjs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

打造实时应用中学到的

这是 ShenJS 上我分享内容的一个整理版,原来的题目就叫 Real-Time App Practice - What I Learned On Building BearyChat

主要就是分享一些用 Web 技术做实时的经验,不怎么涉及到长连接的技术,主要是应用层面的内容,大家可在 http://shenjs.meteor.com 看原 Slide。

关于实时

我从大概2012年开始接触实时应用,自己就一直蛮感兴趣,业余时间做了一些小作品,可直到去年起开始着手打造 BearyChat 这个依赖实时的团队沟通工具,也终于能在工作中有机会做一个相对严肃的实时产品了。

对于实时应用,我曾经怀疑它的网络世界的重要性。对于维基百科,技术论坛,淘宝,搜素引擎这些东西它们不是实时的(指不会在不刷新页面的情况下获得更新)对我并没有什么影响。 那么实时真的重要吗?是不是有点搞技术的自己这边在幻想这个东西很重要。

后来从做了一段时间实时系统后,最大的感受是,实时是也是一种信息量。当你对一个东西对你来讲特别重要的时候,你就想尽快的知道关于它的任何信息,最新的更新有没有及时看到?还有谁也在看同样的内容?在这样一个场景下实时就是十分重要的。

BearyChat 介绍和架构

BearyChat 是一个为工作设计的沟通工具,产品里有团队成员,讨论组,机器人,上传的文件等概念,技术上前端主要在用 Angular。 后端分为 API 服务器和长连接服务器,大部分 CRUD 工作主要依赖 API 服务器通过 AJAX 完成。长连接服务器则负责消息分发,尽量去掉业务逻辑,只转发 API 服务器发送过来的事件,并且也负责传递客户端发来消息。

+--------+                   +------------+  +----+
|        | ------ AJAX ----- | API Server |--| DB |
|        |                   +------------+  +----+
|        |                      |       |
| Client |                      R    +-----+
|        |                      P    |queue|
| Web /  |                      C    +-----+
| Mobile |                      |       |
|        |                    +-------------------+
|        | -- SEND/RECEIVE -- | Keep-Alive Server |
+--------+                    +-------------------+

很多网站的实时功能比如新消息提醒,一般都只是做为一个加分,即使没有也影响不大。 但是对 BearyChat 这样一个用于工作场景的系统来讲,要求就会高很多,比如:

一致性,保证大家看到的东西都是一样的,你所做的操作在哪里都要产生一样的效果。

及时性,要保证在有网络链接的时候,一有新的内容就要立即呈现出来。

稳定性,系统能长期的正常的工作,并且能外界条件不理想的情况下,表现出最合理的行为。

下面就是我们为了满足这些需求,去做了哪些努力。

实时系统的协议设计

要做好复杂的应用,基础要打牢,长连接部分需要有一个合适的协议,来让实时系统运转起来。

现有的 IM 协议里,XMPP 功能强大,有一定扩展性,又有现成的实现。我们在初期是采用他来启动的项目,但是做的过程中发现,XMPP 自己带有的业务逻辑太多,很多和我们需求并不匹配,在尝试了一段时间后放弃了。

后来有了自己对 IM 协议的理解后,我们最终选择了自己定制协议,下面是几个点。

  1. 使用事件类型来让客户端识别,可以灵活的处理不同的情况。

  2. 增加时间戳,让所有的收到的推送都能确定事件发生的时间,而不是依赖客户端收到的时间。

{
  type: "update_user_connection",
  ts: 1436560255813,
  data: {
    uid: "=bw52Q",
    connection: "connected"
  }
}
  1. 使用 Call ID 来做识别,和 API 的 Request/Response 模式不同,长连接的收发不能一一对应上。对于客户端发送的消息,就需要用一个客户端生成的 ID 用来识别对应的关系。
{
  type: "channel_message",
  call_id: 2,
  channel_id: "=bw6Pg",
  text: "shenjs"
}

{
  type: "reply",
  call_id: 2,
  ts: 1436560264158,
  key: "1436560264158.0000",
  status: "ok",
  code: 0
}
  1. 长连接里的数据内容和 API 服务器使用同样的结构和字段,有利于复用,减少出错的可能。
{
  type: "channel_visible",
  data: {
    inactive: false,
    description:null,
    ...
    same as API return
    ...
  }
}
  1. 浏览器自身很难及时知道长连接已经断开,需要定时的发送 ping/pong 请求来尽快知道连接已经断开。
{
  type: "ping",
  call_id: 3
}

{
  type: "reply",
  status: "ok",
  ts: 1436695680405,
  call_id: 3,
  code: 0
}

另外介绍一些更多的选择: DDP 是 Meteor 前后端数据同步协议,还支持类似 RPC 的调用方式,抽象出来让你不必在关系接口的细节。 SwarmJS 是一个基于 CRDT 实时数据层,可以支持实时。

如何处理数据

协议部分结束,进入前端的数据层,有这么几个方法论,可以帮助你让你应用真正的实时起来。

  1. 要知道在服务器的数据才是对的,客户端要做的是尽快尽可能和服务器的数据保持一致。

  2. 给所有的数据建立和唯一标识符的对应关系,客户端可以识别相同的数据实体。

  3. 按照单例的用法使用数据,也保证同样的对象全局唯一,比如说在一条消息里的用户,和用户列表里的数据都是同一个内存对象。

  4. 仔细检查数据获取更新的逻辑,能根据自己业务特点可以把逻辑整理清楚,确保数据一致和同步。

  5. API 则需要保证可以获取请求时间点之前的历史数据。

  6. 在长连接建立后,要将前端需要的所有数据更新都推送到客户端。

  7. 长连接建立起来之后,再去获取历史数据,可以避免时间差造成的数据丢失。

  8. 对于时序上可能存在的问题,比如数据有先后依赖,需要有容错机制。

代码 Tips

  1. 使用 emit/on 衔接 websocket client 和应用代码,按事件类型处理事件

(WebSocket Client)
 onMessage = (e) ->
    data = angular.fromJson e.data
      $rootScope.$emit data.type, data

...

(Event Handlers)

  @on 'channel_message', (event, params) ->
    ...

  @on 'update_message', (event, params) ->
    ...

  @on 'delete_message', (event, params) ->
    ...

  1. 对 API 和长连接使用同样的处理代码

(Channel Model)
joinHandler: (data) ->
  @isMember = true
  @clearUnread()

...

join: ->
  API.joinChannel
    id: @id
  , (data) =>
    @joinHandler()
  .$promise

...

(Event Handlers)
@on 'join_channel', (event, params) ->
  channel = Channels.getOrAdd params
  channel.joinHandler(params.data)

以上是就我们做 BearyChat 经验分享,技术上并没有使用很先进的比较 Magic 的方案,主要考虑是希望各个环节都相对可控,不过不管那种方案其中道理也基本相通,希望能对实时有兴趣的读者有些帮助。

About

My slide for ShenJS (JsConf China 2015): Real-Time App Practice

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published