作者归档:Ning Sun

Ning Sun

关于Ning Sun

AVOS Cloud 推送和实时通信服务后端开发者

实时通信 2.0:对话 API 和多媒体消息

自去年 LeanCloud 发布实时通信服务之后,我们收到了很多用户反馈,经过工程师对需求的消化和对业务的提炼,我们很高兴今天正式发布了实时通信 2.0 版本。新版本除了继续坚持我们的设计理念(灵活、解耦、可组合、可定制),我们更主要地针对第一版用户使用中的痛点做了重点改进,增加了「对话」这一实体概念和相应的 API,帮助开发者更有效地管理消息的上下文。另外值得一提的是,新版本中提供的多媒体消息格式类,开发者可以更加快捷地发送音频、视频、文件和地理位置等富媒体消息。

对话

新版本中,我们用「对话」这一概念整合所有的聊天形式:单聊、群聊和开放聊天室。每一个对话对应数据存储中 _Conversation 表的一条记录,因此开发者可以随时获取自己最近参与对话列表,这解决了我们先前版本在这方面的不足。而且,基于 LeanCloud 强大的数据存储、查询的基础,开发者还可以对「对话」记录增加自定义属性,并通过自定义的查询获取。消息现在通过对话来索引和管理,除了以被动推的方式实时到达客户端之外,用户还可以主动拉取某一对话从任意时刻开始向前的消息记录。实时通信中所有的概念和机制都对开发者透明,尽可能多地满足开发者个性化的需求。更多关于对话的信息可以在 文档 中找到。

新版本的 iOS 发送消息示例:

AVIMMessage *message = [AVIMMessage messageWithContent:@"hello"];
[_conversation sendMessage:message callback:^(BOOL succeeded, NSError *error){
if (error) {
    // 出错了 :(
} else {
    // 成功!
}
}];

接收消息:

-(void)conversation:(AVIMConversation *)conversation didReceiveCommonMessage:(AVIMMessage *)message {
    // 下面的逻辑会把消息直接存入本地数据库缓存。注意:你完全可以根据自己的需要来决定实际如何处理
    [self saveMessageToDatabase:message callback:^(BOOL succeeded, NSError *error) {
        [[NSNotificationCenter defaultCenter] postNotificationName:LC_NOTIFICATION_MESSAGE_UPDATED object:conversation];
    }];
}


富媒体消息

在第一版中,我们提供的是一个纯粹的传输层接口,用户可以通过这个接口发送各种格式的消息,比如纯文本或是格式化的消息。这曾经引起了一部分开发者关于其是否可以发送多媒体消息的困惑。在这方面,回答当然是肯定的。第二版中,我们针对这方面的疑惑,增加有补充:一套可扩展的富媒体消息格式。目前我们提供:文本(TextMessage)、图片(ImageMessage)、音频(AudioMessage)、视频(VideoMessage)、位置(LocationMessage)消息。对于图片、音频和视频等二进制消息,我们包装了文件上传等必要步骤,简化开发者的使用步骤。另外针对个性化的需求,开发者还可以在这套消息体系下定义自己特殊的消息类型,满足个性化的需求。

我们此次发布包含所有平台(Android,iOS,WindowsPhone 和 Web)的 SDK,此外还包括 REST API 用于对对话数据进行增删改查、拉取消息记录和发送消息等功能。 文档 中包含了整套系统中的主要概念和机制,各个平台 SDK 的使用说明等。SDK 现在已经可以在 下载页面 获取,欢迎大家体验。

实时通信云代码集成发布

大家好,我们又发布了一个坳口的功能,实时通信的 云代码集成 。简单地说,现在用户可以通过 自定义云函数 作为 hook,修改实时通信默认的执行流程,增加应用自定义的业务逻辑。

第一阶段我们支持两个 hook:_messageReceived(消息到达服务器)和 _receiversOffline(收件人离线)。

_messageReceived 发生在消息到达服务器,服务器解析完收件人 id 之后,消息存入离线队列之前。这个阶段云函数可以获得的信息包括消息内容、收件人 id 列表、时间戳、发件人等等,用户的云代码可以通过返回值修改消息内容,修改收件人列表甚至直接丢弃消息。这个 hook 可以帮助用户实现自定义的消息处理,甚至实现请求-响应式的模型。

潜在的用例:

  1. 更新数据库,例如记录用户最近发消息时间
  2. 修改消息内容,删除广告,敏感信息(尽管我们已经内置了敏感词过滤)
  3. 修改收件人列表,自动转发消息到他人
  4. 完全颠覆传统实时通信模型,执行服务器端业务逻辑

Screenshot from 2015-01-08 15:09:29

_receiversOffline 发生在消息发送完成后,离线通知触发前。这个阶段云函数可以获得消息内容,离线收件人 id,关联的群组 id 等。用户可以通过云代码返回值指定离线通知的内容,被通知的用户 id,或者直接跳过默认的推送通知(比如在 hook 中触发短信、邮件等其他通知方式)。这个 hook 可以解决之前大家反馈比较多的推送消息不能动态定义的问题。

云代码集成是可选功能,已有的功能不受此次升级影响。关于云代码集成更完整的参数列表和详细说明,请 参考我们的文档

AVOS Cloud 实时通信服务架构:微服务和服务发现

「微服务」 (Microservice) 是今年特别热的一个概念,Martin Fowler 的文章 对微服务作了详细的介绍。简而言之,微服务鼓励用户把功能拆分以细粒度的服务接口暴露出来,并通过 REST 服务或轻量级消息队列集成。在微服务架构里,一个业务的实现,可能由不同的功能单元组合而实现。

在 AVOS Cloud,我们提供数据存储、统计、实时通信等不同功能的服务,在实现上,这些功能需要共用基础设施,有的服务本身也根据业务性质的不同拆分功能模块,我们目前就是以这种「微服务」架构思路来实现拆分。有句话说,if you cannot split, you cannot scale.

以实时通信服务为例,根据功能角色的不同,我们有这样一些模块:

  • Push 服务:处理推送的订阅关系,触发推送
  • 长连接服务器:维持设备与服务的长连接
  • Router:应用层的 lookup,负责分配合适的长连接服务器给新设备
  • WatchDog:从多台长连接服务器收集运行和统计数据,对异常情况发起报警
  • 数据存储:群组数据,用户可以通过 API 访问
  • HBase:消息记录存储

模块间的集成,根据业务的特性分别使用 Slacker 远程调用框架和 Kestrel 消息队列。
对于可能耗时较大的任务,我们通过 Kestrel 队列放到后台执行,避免阻塞前台服务,影响吞吐量。而另一些需要实时的集成,则使用 Slacker 远程调用实现多个进程间的通信。

Slacker 是一个专门为 Clojure 语言设计的远程调用框架,利用 Slacker 你可以暴露一个 或多个 Clojure 的 namespace 供远程调用。在客户端,Slacker 利用 Clojure 宏 的特性,保持远程调用和本地调用的代码完全一致,这样本地和远程调用的切换只要更改一个 (require) 即可实现,把框架对业务代码的侵入降到最低。此外,Slacker 使用二进制序列化 nippy,在网络连接层面使用异步复用,同时在超时方面也做了良好的控制。

以上的基础设施帮助我们良好地拆分模块,为下一步的扩展提供了可能。

服务发现

长连接服务器是实时通信的功能核心,它的瓶颈在内存和 CPU,可以通过增加部署来达到线性扩展。随着业务量的增加和硬件资源的整合,它可能会面临较频繁的部署变化,另外它也需要有能通过新增部署来快速平滑高峰压力的能力。基于 Pub/Sub 抽象 的消息队列对此有良好的支持,但这对我们以 RPC 为核心的集成方式提出了新的要求,依赖模块也能快速响应服务部署的变化:我们不可能在新增某个服务部署后修改每个依赖的配置再逐一重启。

在这方面,我们利用了 Slacker Cluster 框架 。他的核心思想是在部署和服务间增加一层抽象:对于服务的消费者而言,只需声明自己所依赖的服务,而无需静态地了解进程的地址。

所有的服务提供者将自己能够提供的服务注册在 Zookeeper 集群里,并将部署地址注册为 Ephemeral 节点。Ephemeral 节点在创建它的连接断后会自动删除,这样当一个部署下线后,它相应的节点也会自动删除。

# Zookeeper 目录结构
ls /slacker/example-cluster/namespaces/
[my.serviceA, my.serviceB]

ls /slacker/example-cluster/namespaces/my.serviceA
[192.168.1.100:2104, 192.168.1.101:2014...]

所有服务的客户端会 watch 自己感兴趣的 Zookeeper 节点,而部署变化时,所有的客户端都会得到通知,进而刷新服务列表,将流量引向新的节点。

在实时通信服务中,Router 服务会通过这个 RPC 机制轮询所有在线的长连接服务器,记录他们实时的运行状态。所有的用户设备并非直接连接到固定的长连接服务器,而是先询问 Router,由后者分配一台压力较轻的实例。当有新的长连接服务器部署后,Router 收到通知,新的连接将优先连接这个新进程。此外,监控和数据收集的服务也会自动地把新实例加入管理范围。

有了这样一套服务发现机制,我们就可以对整个架构中的任意模块随时增减部署,保证服务可以以健康的状态运行。未来,我们还会集成云主机的提供商的 API,来实现基础设施的自动化:当系统压力达到阀值时,云主机自动分配新的资源自动开机,jenkins 自动部署,加上现有的服务发现机制,实现 0 手工操作。这将是云服务运维的新篇。

AVOS Cloud 实时通信服务发布:拉近用户间的距离

1070333248_kDaqub

AVOS Cloud 的实时通信服务发布了。您可以 从网站上下载我们的 2.5.9 及以上 SDK 版本(Android 和 iOS)使用这个功能。

什么是实时通信服务? 不同于传统的基于 HTTP 「请求」-「响应」的机制,实时通信的客户端会保持一个到服务器的持久连接,开发者在这条信道上可以实现与服务器的全双工通信。这意味着,发送数据时「连接」的延时可以完全忽略,接收数据时可以使用真正的推模型,不再需要为了获取一次数据而发送额外的请求。

实时通信服务的来历。 今年第一季度,我们改造了 AVOS Cloud 的推送,实际上从那时起我们已经在使用自己研发的持久连接技术实现推送了。推送是一种单向的通信,手机客户端只接收数据。这次发布的实时通信功能,就是在推送的持久连接上,增加了数据的上行通道。如果你使用的是 Android 平台,我们的推送和实时通信可以共用同一个持久连接,减少额外的开销。

实时通信能用来做什么? 说到实时,最先想到的就是聊天工具。利用全双工的通信通道,你可以轻松实现设备间的低延迟对话。我们的第一个用户 百姓网 ,就是利用这套基础服务实现了他们的实时私信功能。此外,你也可以利用两台设备间的实时连接,实现远程的对战游戏,比如像 QuizUp 这样的实时猜迷竞赛。实时的体验可以使用户间的互动更加流畅,无形中拉近了用户间的距离。

它的 API 怎样? 我们这次发布的是一套完全底层的 API,它的目标是帮助用户解决几个根本问题:

  • 点对点收发数据
  • 上下线通知
  • 权限认证

在设计 API 时,我们考虑到自己的 SDK 做的越多,对用户应用的侵入越大,用户能做的就越少。High-level 的 API 用起来固然快速,但往往限制了你创造的空间和思路。在移动互联网瞬息万变的时代,一套轻装上阵,简单易于理解的 API 相信更贴近你的应用,也免去了你费力 hack,转换内部概念的功夫。从平台的角度,我们也有信心把最底层的 API 暴露出来,接受不同使用场景的考验。

另外,和推送、统计服务一样,AVOS Cloud 的实时通信服务并不强制要求用户使用我们的核心存储功能。任何的应用都可以接入实时通信服务,权限认证由开发者的接口或云代码上的服务端程序完成,通过签名的形式与实时通信服务确认。

实时通信服务如何收费? 当前的价格请查看 价格页面

开始使用:

PS: 7 月 18 日我们将在 InfoQ Archsummit 2014 大会 上分享 我们构建实时云服务的经验和心得 ,欢迎关注。

编辑:修正 SDK 版本号。