Jack Jiang

我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
posts - 467, comments - 13, trackbacks - 0, articles - 0

关于MobileIMSDK

MobileIMSDK 是一套专门为移动端开发的开源IM即时通讯框架,超轻量级、高度提炼,一套API优雅支持UDP 、TCP 、WebSocket 三种协议,支持iOS、Android、H5、标准Java平台,服务端基于Netty编写。

工程开源地址是:

关于RainbowChat

► 详细产品介绍:http://www.52im.net/thread-19-1-1.html
► 版本更新记录:http://www.52im.net/thread-1217-1-1.html
► 全部运行截图:Android端iOS端
► 在线体验下载:专业版(TCP协议)专业版(UDP协议)      (关于 iOS 端,请:点此查看

 

RainbowChat是一套基于开源IM聊天框架 MobileIMSDK 的产品级移动端IM系统。RainbowChat源于真实运营的产品,解决了大量的屏幕适配、细节优化、机器兼容问题(可自行下载体验:专业版下载安装)。

* RainbowChat可能是市面上提供im即时通讯聊天源码的,唯一一款同时支持TCP、UDP两种通信协议的IM产品(通信层基于开源IM聊天框架  MobileIMSDK 实现)。

v8.4 版更新内容

此版更新内容更多历史更新日志):

(1)Android端主要更新内容通信核心层优化!】:

  • 1)[优化] 可根据http接口的url自动判断并启用https加密;
  • 2)[优化] 升级核心长连接通信层库 MobileIMSDK 至 v6.3
  • 3)[优化] 提供了灵活的接口定制和开启长连接的SSL/TLS加密传输。

(2)服务端主要更新内容:

  • 1)[优化] 升级核心长连接通信层库MobileIMSDK 至 v6.3
  • 2)[优化] 开放了灵活的接口定制和开启长连接的SSL/TLS加密传输。

此版主要功能运行截图更多截图点此查看):

posted @ 2023-02-16 10:42 Jack Jiang 阅读(71) | 评论 (0)编辑 收藏

本文作者:丁同舟,来自金蝶随手记技术团队。

1、引言

接上篇《金蝶随手记团队的Protobuf应用实践(原理篇)》,本文将以iOS端的Objective-C代码为例,图文并茂地向您菔救绾卧趇OS工程中快速使用Protobuf,希望对你有帮助。

 

学习交流:

(本文已同步发布于:http://www.52im.net/thread-4133-1-1.html

2、系列文章

本文是系列文章中的第 9 篇,本系列总目录如下:

另外:如果您还打算系统地学习IM开发,建议阅读《新手入门一篇就够:从零开发移动端IM》。

3、基本介绍

 

Protobuf(全称 Protocol buffers) 是 Google 提出的一种跨平台、多语言支持且开源的序列化数据格式。相对于类似的 XML 和 JSON,Protobuf 更为小巧、快速和简单。相对于传统的 XML 和 JSON, Protobuf 的优势主要在于:更加小、更加快,其语法目前分为proto2和proto3两种格式。

如果你没不了解Protobuf是什么,建议先阅读本系列的前几篇《Protobuf从入门到精通,一篇就够!》、《快速理解Protobuf的背景、原理、使用、优缺点》、《金蝶随手记团队的Protobuf应用实践(原理篇)》,本篇就不再重复介绍了。

目前 Google 官方的 Protobuf最新 release 版本为3.21.12,但本文写作时用的是3.5.1,以下截图都是基于此版本的环境搭建,如果你使用最新版本,差异并不大,因为只是小版本更新。

关于 Protobuf的使用可以查阅官方文档:https://developers.google.com/protocol-buffers/docs/overview,建议养成阅读文档的习惯。

4、准备工作

4.1环境要求

最低开发环境要求:

  • 1)Objective-C 2.0 Runtime (32bit & 64bit iOS, 64bit OS X)
  • 2)Xcode 7.0 以上版本

注意:Protobuf 出于性能考虑没有使用 ARC,但在 ARC 下是可以使用的。

4.2下载安装

下载 Protobuf 代码包(https://github.com/protocolbuffers/protobuf/releases/tag/v21.12),因文章截图时用的是v3.5.1,所以我这里的为了保持一致选择的是 protobuf-objectivec-3.5.1.tar.gz,版本区别不大,建议依此类推。

4.3解压代码包

编译 Protobuf,这里可能需要安装部分工具:

$ brew install autoconf

$ brew install automake

$ brew install libtool

运行下面脚本进行编译:

$ ./autogen.sh

$ ./configure

$ make

$ makeinstall

检查protobuf是否安装成功:

$ protoc --version

如果成功打印版本号则安装成功:

libprotoc 3.5.1

5、在 iOS 中使用 Protobuf

5.1创建.proto文件

这里使用官方文档上的一份示例数据结构创建Person.proto:

syntax = "proto3";

 

message Person {

  string name = 1;

  int32 id = 2;

  string email = 3;

 

  enumPhoneType {

    MOBILE = 0;

    HOME = 1;

    WORK = 2;

  }

 

  message PhoneNumber {

    string number = 1;

    PhoneType type = 2;

  }

 

  repeated PhoneNumber phone = 4;

}

使用命令行编译Person.proto为objective-c的文件,编译出来的文件为Person.pbobjc.h和Person.pbobjc.m:

protoc Person.proto --objc_out=./

5.2引入 Protobuf 运行时资源

Google 官方的文档提供了两种引入方式,但使用第一种的时候编译不能通过,所以这里选择了第二种。

具体就是:复制protobuf目录下的:objectivec/*.h, objectivec/google/protobuf/*.pbobjc.h, objectivec/google/protobuf/*.pbobjc.m, 以及除去 objectivec/GPBProtocolBuffers.m 后的objectivec/*.m。

这里直接用命令行操作。

首先进入protobuf下objectivec的目录:

$ cdprotobuf-3.5.1/objectivec

然后复制符合规则的文件到指定的工程目录下:

$mkdir~/ProtobufDemo/ProtocolBuffers~/ProtobufDemo/ProtocolBuffers/google~/ProtobufDemo/ProtocolBuffers/google/protobuf

$ cp*.h *.m ~/ProtobufDemo/ProtocolBuffers

$ cpgoogle/protobuf/*.pbobjc.h google/protobuf/*.pbobjc.m ~/ProtobufDemo/ProtocolBuffers/google/protobuf

注意:上面的命令并没有排除 GPBProtocolBuffers.m 文件,引入时需要手动排除。

现在把ProtocolBuffers目录下所有文件以及上面编译出来的 Person.pbobjc.h 和 Person.pbobjc.m 都引入到工程中。

现在工程目录结构大概是长这样:

 

注意:由于protobuf没有使用 ARC,因此需要为所有.m文件加上-fno-objc-arc来关闭 ARC。

结果如下:

提示:需要留意工程中的 Header Search Paths 要增加 $(PROJECT_DIR)/ProtocolBuffers(具体的路径视情况而定)

5.3直接引入 ProtocolBuffers 工程

如果觉得手动引入文件的方式过于复杂,可以直接引入ProtocolBuffers工程作为依赖项。

1)进入解压后的protobuf目录下,复制objective目录下的所有文件到ProtobufDemo/ProtocolBuffers目录下。

2)在ProtobufDemo工程中引入ProtocolBuffers_iOS工程:

3)在Build Phases中加入依赖关系并链接库:

 

4)引入Person.pbobjc.hPerson.pbobjc.m文件并为.m加上-fno-objc-arc

5)修改工程配置中部分路径为 $(PROJECT_DIR)/ProtocolBuffers

5.4运行测试

首先引入头文件:

#import "Person.pbobjc.h"

生成Person对象并进行编码和解码:

Person *p = [[Person alloc] init];

p.id_p = 1;

p.name = @"person1";

p.email = @"123@qq.com";

 

//encode

NSData*data = [p data];

NSLog(@"Protocol Buffers:\n%@\nData: %@\nData Length: %lu", p, data, data.length);

 

//decode

Person *newP = [[Person alloc] initWithData:data error:nil];

NSLog(@"Decoded: %@", newP);

运行程序,打印日志如下:

Protocol Buffers:

<;Person 0x60c0000da2b0>: {

    name: "person1"

    id: 1

    email: "123@qq.com"

}

Data: <0a077065 72736f6e 3110011a 0a313233 4071712e 636f6d>

Data Length: 23

Decoded: <;Person 0x6040000d9c90>: {

    name: "person1"

    id: 1

    email: "123@qq.com"

}

6、参考资料

[1] Protobuf 官方开发者指南(中文译版)

[2] Protobuf官方手册

[3] Protobuf从入门到精通,一篇就够!

[4] 如何选择即时通讯应用的数据传输格式

[5] 强列建议将Protobuf作为你的即时通讯应用数据传输格式

[6] APP与后台通信数据格式的演进:从文本协议到二进制协议

[7] 面试必考,史上最通俗大小端字节序详解

[8] 移动端IM开发需要面对的技术问题(含通信协议选择)

[9] 简述移动端IM开发的那些坑:架构设计、通信协议和客户端

[10] 理论联系实际:一套典型的IM通信协议设计详解

[11] 58到家实时消息系统的协议设计等技术实践分享

[12] 金蝶随手记团队的Protobuf应用实践(原理篇)

[13] 新手入门一篇就够:从零开发移动端IM

Coffee time!

(本文已同步发布于:http://www.52im.net/thread-4133-1-1.html

posted @ 2023-02-14 12:52 Jack Jiang 阅读(48) | 评论 (0)编辑 收藏

本文由钉钉技术专家啸台、万泓分享,为了获得更好的阅读效果,本文已对内容进行少修订和重新排版。

1、引言

钉钉后端架构的单元化工作从2018年开始到今年,已经是第五个年头了。五年的时间,钉钉单元化迭代了三个版本,从最初的毛头小子,到达今年已经小有成就。

我们在进行单元化架构建设的过程中,除了网上能找到的屈指可数的文章外,可以直接使用的系统更是乏善可陈,使我们不得不从最基础的系统开始造轮子,极大的影响建设效率。幸运的是,近几年云原生技术的兴起,让我们能复用很多基础设施,进而快速提升我们的单元化建设能力,助力钉钉的发展。

今天想借此文和大家分享我们在钉钉单元化架构实施过程中的心路历程和一些最佳实践。因涉及的技术和业务面太广,本文的分享无法做到面面俱到,主要是想在同路人中形成共鸣,进而能复用一些架构或子系统的设计和实现思路。

学习交流:

(本文同步发布于:http://www.52im.net/thread-4122-1-1.html

2、系列文章

本文是系列文章的第 10 篇,总目录如下:

3、术语概念

本文内容中使用了一些专有的技术名词,为了方便大家理解,我把关键的几个术语概念的缩写及其含义专门列出来,供大家参考。

主要是以下几个

  • 1)Geo:钉钉专有化部署单位,解决数据合规需求,Geo间数据按需互通,并且互通数据在Geo内部做镜像拷贝,解决两化问题;
  • 2)Unit: Geo内部资源物理分区隔离的最小单位,解决Geo内的容灾和容量的问题;
  • 3)L0:客户端路由,决定了用户客户端接入钉钉服务器的所属单元,用户长连接所在的逻辑单元,起到连接加速作用。用户接入单元;
  • 4)L1:接入层路由,以用户为维度进行调度,即用户操作发生的单元。用户归属单元;
  • 5)L2:业务层路由,以业务资源为维度进行调度,大部分的业务资源所在单元应该和用户调度单元一致,但一些业务无法按照用户划分单元,如IM的会话,音视频的会议。 业务归属单元;
  • 6)DMB:负责钉钉应用跨单元RPC调用的转发,可以认为是钉钉单元化RPC路由中间件;
  • 7)DMR:负责钉钉应用跨单元MQ消息的转发,可以认为是钉钉单元化MQ路由中间件;
  • 8)DTIM:钉钉IM系统。

4、单元化架构1.0版:合规驱动下的部署架构

2018年,部分大客户出于法律政策、商业机密数据存储的要求,要求钉钉的数据存储、访问接入、服务部署需要在其信任的区域内。既需要满足其数据存储私有化要求,同时需要满足跨地区网络的rt性能要求。

于是我们结合阿里云机房部署位置、物理距离、用户数据安全等方面出发,钉钉在客户的阿里云机房内建设了一个单元,将通讯录、IM信息等企业数据单独存储在客户机房。

我们通过一条专线,将两个机房逻辑串联到一起,内部通过DMB/DMR系统,实现了请求互通,这就是钉钉单元化架构的1.0版。

1.0版比较简单,纯粹是业务驱动,和支付宝单元化建设的初衷——“容灾驱动”有较大区别。两个站点通过UID分段,将用户划分为中心用户和专有用户。

上图只是一个简化的逻辑结构,内部实现远比上图复杂,但是1.0建设主要是从0到1,和大多数异地多活的系统较相似,这里就只简单的和大家分享一下。

5、单元化架构2.0版:逼出来的容量架构

2020年是一个特殊的年份,由于疫情的原因,带给大家非常多的改变,其中也包括钉钉。

由于在线办公与教育流量的突增,开年第一天上班就给钉钉一个下马威,平峰的流量已经和除夕跨年的持平,但是和除夕不同的是这个流量是持续的,即使节前准备了三倍容量,也抵挡不住流量对系统的冲击。只能借助阿里云的能力,不断的扩容。

但是每天将近30%的流量增幅,单纯的扩容也能难保障服务的连续性,最终也遇到了扩无可扩的场景,张北机房没有机位了,有机器资源但是没有机位让我们有力无处使。我们不得不不断进行系统优化,同时借助限流、降级、双推等措施,勉强抗住了流量的最高峰。

疫情之前,我们一直在做高可用,但是这个高可用主要集中在容灾机制上,比如搭建容灾单元。如同支付宝一样,是因为当时光纤被挖断;又比如银行的两地三中心架构,是担心某一个地域由于天灾或者战争导致数据丢失。疫情的流量给我们上了一课,仅仅关注容灾是不够的,特别是钉钉的DAU从千万走向亿级别之后,更需要在容量上做出提前规划。

正因如此,我们认为“容量架构不是设计出来而是真真切切被逼出来的”,所以容量架构就成为我们单元化核心要素之一。

容量架构是将流量划分到不同单元,每个单元承载各自的流量。容灾架构是单元异常时,能保障核心的能力可用,也可以将流量动态调度到别的单元,实现服务的快速恢复。

因此钉钉单元化进入了2.0时代,专注于容量和容灾的建设。

6、2.0版是基于什么维度进行流量划分的?

要实现流量的划分,必然要基于一个维度进行划分,一部分到A单元,一部分到B单元。

钉钉单元化架构也是参考了淘系和支付宝的单元化架构,前两者都是基于UID划分,钉钉单元化的第一个版本其实也是一样的,基于UID做拆分。

但是当我们设计容量架构时,发现基于UID划分无法解决我们的容量问题。

以IM为例:一条消息其实属于聊天双方的,群聊亦是如此。用户能和任意一个人聊天,这样我们根本无法找到一个切入点来划分流量,强行按照UID拆分,必然导致一个用户的消息出现在N个单元,单元的自封闭就无法做了。

也有同学会说:为什么消息不按照每个人存储,这不就能按照UID划分了吗?结论是不行。首先这个消息变成了写扩散,持久化的时候会变成多单元写,其次是成本翻倍,在DTIM这种过亿规模的场景这条路走不通。这里可以多说一点,因为这个观点来之不易,大家都知道,人是有惯性的,既然淘宝、支付宝甚至是微信都是UID划分,为什么钉钉要特立独行?当时我们团队受到了绝大部分钉钉技术团队的挑战,持续长达将近一个月的技术选型的“争吵”,最终还是达成了一致意见。

DTIM主要有3个维度,分别是UID、会话(CID)、消息。其中会话和消息是绑定的,而系统中最大量的是消息,按照第一性原则来看,一定要将消息划分开来,才能做到将容量划分开来的效果。

我们再来看看音视频,是按照房间维度组织流量和数据的,和IM又完全不同。

同样,文档其实更适合按照企业维度来划分。

不同的业务拥有不同的维度,因此我们认为:单元化最重要的找到自身“最大”的业务维度,将这个维护拆分,才能实现单元的横向扩展,我们称之为“业务路由”。

回头来看:我们之前其实是进入了思考误区,以为淘系和支付宝都是UID维度,我们也要这个维度,其实UID正是前者的业务维度,比如订单,也是围绕用户,并不会有交集的情况,会话就是IM的划分维度,因此做单元化之前要先找到属于自己的业务维度。

7、2.0版是如何实现IM消息的全局路由能力的?

7.1概述

UID路由有个最大的好处,就是可以按照UID分段,能实现高效的静态路由,也不用担心多单元之间的一致性问题。但是这种分段路由局限性也比较明显,需要预先分配,单元之间动态调度流量和数据成本极高,而且只能支持这种数值+顺序的场景。

在钉钉的场景中,有会话维度、房间维度、企业维度等等,想简单采用这种预分段机制难以满足业务需求。因此我们需要构建一个业务路由系统(RoutingService),实现消息流量的精确路由。

 

 以IM为例:每次消息的发送,在单元化框架层面,会通过消息的会话(CID),查询路由信息,如果是本单元,流量下行并持久化;如果是非本单元,路由到对应的单元中。

下图是三个会话:分别是cid:1001、cid:1002、cid:1003,三个会话隶属不同单元,不管用户从哪个单元发送消息,都会路由到会话所在的单元。比如:用户在Unit B的cid:1001 中发送消息,当消息进入Receiver之后,会先查询此cid:1001所在的单元,发现是Unit A,路由框架将请求转到A单元,消息在A单元持久化并通过A单元的同步协议,将数据推送到客户端。

 

 

 从上图可知:每次消息发送,都要查询路由服务,DTIM百万的峰值,对路由必然会带来超大的压力,同时我们能发现,路由数据在多单元实现一致性是一个巨大的挑战。

7.2边缘计算:端到端路由

在DTIM的场景中,会话的路由信息几乎不会变更,只有当我们决定将某些超大的会话或者企业腾挪到新单元时,才会发起路由的变更,因此会话的路由信息几乎可以认为是恒定不变的。那么每次查询路由服务端,效费比太低,是极大的浪费。

既然路由信息几乎不可变,是否将路由信息缓存呢?最常见的是使用一个集中式的Cache系统,缓存Hot的会话,我们也是这么做的,但是这么做还是不够,一旦Cache系统失效,DTIM还是会出现大面积故障,而且这个百万级的请求对Cache也是一个极大的压力。

考虑到钉钉有强大的客户端,借用边缘计算的思路,我们将用户的会话数据缓存到客户端。对于客户端来说,也只用缓存用户自身最热的N会话路由数据,消息发送时,通过Header将路由数据携带到服务端,服务端路由SDK只要做合法性和续约即可,这样就将路由流量降低了95%以上。当路由服务出现异常时,还可以继续使用客户端路由,将路由的可用性提升到一个新的高度。

SDK本地会依据上行请求的返回中是否有新的路由信息,进而更新客户端路由。同时可以借助钉钉有主动下推的能力,通过同步协议将新的路由信息主动推送给客户端,使会话迁移做到更平顺。

7.3计算下沉:多单元一致性


对于新会话:比如小明要创建一个群聊,是应该创建在那个单元呢?

如果在A单元创建了,当会话消息来到B单元,系统怎么能第一时间知道会话已经在被绑定到A单元。

这里一般的方式有两种

  • 1)单元之间的存储系统采用类似DTS的机制进行异步同步,这种机制有秒级延迟;
  • 2)在应用层主动同步,比如接入消息队列。


这两种方式由于都是异步的原因,都会出现不一致的问题,如果会话同时被绑定在两个单元,逻辑上会导致用户的历史消息丢失,这个是不能接受的。

多地域(Region)数据同步其实是通用的技术挑战,我们认为存储系统提供是最好的方式,正如Google的Spanner一样,这样对我们上层才是最友好的方式。

因此我们找到了存储的OTS、Nuwa团队一起共建了GlobalTable。GlobalTable的核心原理还是借助Nuwa的一致性组,组分布在多个地域,采用多数派写入成功即返回的原理,做到20ms以内的一致性写。

8、2.0版的容灾能力

钉钉单元化的容灾能力是深度结合钉钉的业务层场景落地的,和淘系支付宝等有明确的区别。

以DTIM为例,最大的特点是当服务单元异常时,服务侧仍能提供最核心的服务,保障最基本的能力。本质上是由于DTIM是最终一致性系统,可以短暂允许部分环节失败。

可以看一下DTIM发送消息的容灾场景。当某个单元完全不可用的情况下,用户消息发送链路通过降级为local模式,在本地校验非本单元会话数据通过之后直接做消息发送,processor遇到非本单元的会话消息数据可以做单元间投递做数据回放,本地是否落库可选,同步协议推送不必区分是否为本单元会话消息数据直接通过本单元的topic推送给客户端,配合用户无状态快速迁移能力,单元间可以实现真正的分钟级别容灾切换能力。

9、2.0版的成果与突破

以上是钉钉单元化2.0提供给应用的核心能力,在满足容灾和容量设计需求之后,钉钉单元化给应用带来了更多的能力和想象空间。

比如:

  • 1)快速迁移:当某一地域资源不足时,钉钉单元化可以将业务快速的从A单元迁移到B单元;
  • 2)常态化切流:比如新建的教育会话,可以放到独立的单元;
  • 3)热点治理:当前某一个会话过热,特殊时期可以迁移到独立集群;
  • 4)SLA:满足不同的VIP客户需求,基于不同的SLA和售卖价格,将VIP客户放到对应地单元。

核心还是我们拥有单元化能力之后,实现了多单元流量的快速调度,为业务解决了后顾之忧。

10、2.0版在新时代面临的新挑战

10.1鱼和熊掌不可兼得

2022年对钉钉来说是成本之年,成本的压力不光落到了团队,还落到了每个人身上。

正如存储的CAP理论是一样的,我们同时只能满足两个维度,对于流量(性能P)、成本(C)、体验(E)也是一样,在流量不可预知和干预的情况下,选择成本必然导致体验受损,反之选择体验,必然导致成本升高。进入下半年,疫情反复带来流量的反复,为了实现可控的教育成本,只能在高峰期降级部分能力,这又导致体验受损,这段时间的工单量可以窥见一斑。

流量是用户侧触发的,我们无法干预,只能在成本和体验之间寻求平衡。和前面提及的一样,为了减小成本的消耗这就导致我们在扩容和缩容之间疲于奔命,反应不及时甚至有故障的危险,这种机制不可取也不可持续。到底是要流量与成本,还是要流量与体验,给我们技术团队带来了巨大的挑战和矛盾。

10.2商业化路在何方

当前钉钉为支持大客户提供了多种解决方案,专业钉钉、专属存储与打包、专有钉钉。

专属钉钉通过APP专属化以及部分专属功能,比如为一个企业定制一个拥有独立Logo的APP,能满足一般的中大型客户的业务诉求。

对于大型以及超大型客户,我们提供专有钉钉,提供专有化输出,完全隔离的方案,比如浙政钉。

伴随着钉钉的商业化进入深水区,客户对钉钉提出了新的诉求,特别是数据安全与归属、互联互通、完整的能力栈等诉求,当前钉钉输出产品形态都无法同时地满足以上需求。

前几年互联网上出现的几起数据安全事件,数据丢失与泄露,未经客户授权私自访问客户数据,让大多数客户不信任服务提供商,即使服务商的安全能力已经是业界一线能力。其实这个是可以理解的,数据即客户的生命线,数据无法在自身可控范围内,特别是对于很多特殊行业,这是无法接受的,自身性命岂能假手于人。专属钉钉在面临这种客户时,前线售卖同学是无能为力。

那么很多同学肯定会提“如果专属钉钉满足不了需求,我们专有钉钉不是能解决这些问题吗?”,其实单单从诉求来看,专有钉钉场景是切合客户的业务诉求,提供完全独立运行环境、可控的数据安全。但是专有钉钉由于其独特的架构带来高昂的售价以及后期的运维代价,对于超大型的客户来说也难以承担如此高的成本。对于钉钉自身来说,从研发到后续运维,维护一套独立体系也难以在客户侧大面积推广。

11、单元化架构3.0版:混合云架构

11.1概述

钉钉单元化经过四年的发展,在容灾和容量上做出一定的积淀,同时完成了一些核心技术的积累。

当整体架构成熟之后,我们也在思考,单元化能否从技术架构升级为业务架构,比如搭建独立的高可用单元,按照售卖的SLA提供给VIP客户,支持钉钉商业化的发展。

同时我们在云原生逐步发力,将部分核心应用放到云上,经过这一年多的运行,遇到了新的挑战,但更获得云下无法获得的计算弹性能力,云上的弹性对云下是一个降维打击,从一个新的方向解决计算问题。

如上文提到的两个核心挑战,钉钉单元化同样面临这个问题,在持续的发展中找到了一个合适的架构方向。

基本思路是:

  • 1)云下作为基本盘,保障核心流量的问题,毕竟云下经过集团多年的打磨,不管是稳定性还是流程的合理性都有保障;
  • 2)云上应对高涨异常的流量,比如和疫情正相关的教育流量,既保证了服务的稳定性,又能充分利用云上弹性能力,在提供完整能力的前提下做到一个相对较低的成本。

其次是升级Geo概念:

  • 1)将Geo作为一个独立的业务域,实现Geo级别完全独立部署,分布式云模式;
  • 2)同时Geo之间按需互通,从研发体系上能做到一套代码。

因此,钉钉单元化来到了3.0版本,我们称之为钉钉单元化混合云架构。

混合云主要是从两个维度来看:

  • 第一:是云上云下,我们认为云上云下并不是取代的关系,而是相互补充的关系,是一个长期的状态,正如很多大客户随着规模的持续扩张,最终依赖的部分核心能力必然走向自研道理一样,这能做成本的进一步降低,所以架构是一个混合云架构;
  • 第二:业务架构上也是混合云架构,通过不同的Geo,将不同的业务逻辑上聚合到一起,构建起一张钉钉的大网,不同Geo按需互通,实现了业务架构的混合。

3.0从系统架构上相对于2.0,最大的区别就是云原生技术的运用和互通网关的建立。

11.2云原生技术 :抵抗系统架构熵增的有效手段

近几年,互联网圈最火的技术莫过于以Docker为代表的云原生技术最为火热,各大云厂商也都在不遗余力的推广云原生技术以及对应的产品。同时钉钉服务过亿DAU的客户,面对各种可靠性、服务连续性、并发、容灾等技术挑战,也都走到了现有技术的边界。

所以我们也在不断吸收新的技术和架构,希望从体系与架构上降低我们的技术复杂度,以抵抗熵增。

我们在2021年底启动了云原生升级战略,升级云原生技术并不是为了技术而升级,而是切实面临巨大的技术挑战。

1)首先我们面临多语言的挑战:

我们以IM为例,IM的核心逻辑都是使用C++构建,但是我们常用的中间件三大件:存储、缓存、异步队列,其中缓存和异步队列在C++客户端上长期建设不足,导致IM长期在使用低版本。

低版本由于长时间缺乏维护,经常会出现异常,比如队列假死、消费不均等,导致我们自己不得不亲自上阵修改SDK的代码,以致最后难以使用到产品的新能力,阻碍IM服务能力的提升。

2)其次是多产品多云的挑战:

我们以阿里云为例,数据库类目下的产品,从类别上就有关系数据库、NoSQL数据库、数仓等等,还有存储也是一样。

对于我们上层业务,其实绝大部分服务都只依赖了底层的CURD,这么多产品,每次对接一个产品都要开发一轮。

配置系统也是一样,弹内有Diamond,云上有Nacos、Mse,K8s有自己的Configmap等,而且这些配置系统不像数据库有标准,而是百花齐放,但是这样却苦了我们使用者。

这些内容不是我们的核心路径,浪费大把时间在各种产品接口的适配上,明显拖累了钉钉的发展。

3)最后就是通用的流量治理挑战:

钉钉很多系统都是最终一致的系统,IM就是典型的最终一致系统,这类系统和强同步系统在架构设计有一个明显的区别,强一致系统如果遇到失败,必须要持续重试直到成功,所以一般编程上都是重试+退避。

但是最终一致系统不是,这类系统允许部分节点失败,不要阻碍其他流程,失败的流量通过一个异步回旋的队列,将数据逐步回放回来即可。这种回旋需要借助异步队列,而且要设计各种消费机制,比如限速、比如丢弃等等,这是一个通用的逻辑,但是每个业务方或多或少都在实现自己的回旋系统,重复的造轮子。又比如各种故障注入,单元化路由流量等等,要想拥有这个能力,团队不得不投入人力研发。

在对付架构复杂度上,我们主要从两个维度来屏蔽复杂度。

首先代码层面我们选择了DDD模式,我们使用DDD分层核心是把对外系统的依赖全部收拢到Infrastructure这一层,全部采用纯虚函数(Interface)对外提供接口。屏蔽底层中间件差异和细节。

在架构上采用Sidecar的模式,类似于Dapr的思想,通过标准的GRPC和PB实现应用与中间件解耦。Sidecar中集成了各种中间件、配置系统、灰度系统等,等价实现了应用和中间件的解耦。上文中提到的不管是多语言挑战、多云多产品的挑战、重复造轮子等问题,都能很好的解决。

11.3互通网关 :混合架构的基石

云上云下互通,或者说多个云账户VPC之间的互通,我们常见的有两种方案:

  • 1)其一是VPC直接打通,让多个VPC之间形成一个大的局域网,RealServer实现点对点互通;
  • 2)其一是中间搭建一个负载均衡器,通过暴露EIP实现互通。

两个方案都有自己的优缺点。

对于方案一:打通的VPC涉及到IP规划,如果前期没有合理规划,后续很难打通;还有这种方案有水桶短板安全问题,一旦一个VPC被攻破,这张网也被攻破;但是对于内部的应用来说架构就比较简单,可以仅仅借助K8s DNS service就能做到服务发现。

对于方案二:最大的缺点就是中间有一个集中式的负载均衡,需要申请独立的LB才可访问;但是这种方案隔离性好。

对于钉钉单元化来说,涉及N个业务方,N * M个应用,对应X个VPC,要想VPC之间打通,几乎没有可能性,而且VPC打通,还面临应用之间的安全性问题。要实现Geo之间互通,环境之间的隔离性是基本要求,与此同时,我们也要考虑到系统的可扩展性,所以我们必须要构建一套独立的流量网关,实现流量加密、寻址、转发等通用能力。

钉钉互通网关是构建在Envoy之上的系统,双向Ingress和Egress,支持GRPC和钉钉自研协议。具备流量管理、传输加密、单元寻址等能力。钉钉单元化借助互通网关的能力,再配合全局流控系统,我们可以在多单元之间实现精确的流量控制和调度。

12、写在最后

伴随着专属集群的持续输出,客户对专属的场景需求会越来越多,需要我们投入更多的人力持续的建设。

比如:

  • 1)在架构侧:首先是Sidecar持续强化,支持更多的中间件和环境,提供不同维度的安全能力,满足客户和应用的安全需求;
  • 2)在运维侧:我们需要构建多Geo管理能力,完善Geo和单元之间流量快速调度能力,提供自动化的自检系统等;
  • 3)在交付侧:如果实现快速交付,比如是否能做到新应用一周完成单元化改造,新Geo一天部署完成。这些挑战都是接下来我们要重点投入的方向。

对于标准钉钉来说,这个是我们的基本盘,一个稳定可靠且低成本的钉钉是我们持之以恒的目标,接下来我们会加大云上流量的占比,充分的借助云上弹性能力,实现可控的成本。

今天我们只是站在钉钉的角度上抛了一个“砖”,希望在异地多活这个领域激起一层浪花,欢迎大家一起讨论。

13、相关资料

[1] 现代IM系统中聊天消息的同步和存储方案探讨

[2] 企业级IM王者——钉钉在后端架构上的过人之处

[3] 深度解密钉钉即时消息服务DTIM的技术设计

[4] 钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)

[5] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[6] IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

[7] 深度揭密RocketMQ在钉钉IM系统中的应用实践

(本文同步发布于:http://www.52im.net/thread-4122-1-1.html

posted @ 2023-02-13 10:50 Jack Jiang 阅读(121) | 评论 (0)编辑 收藏

一、更新内容简介

本次更新为次要版本更新,进行了若干优化(更新历史详见:码云 Release Nodes)。可能是市面上唯一同时支持 UDP+TCP+WebSocket 三种协议的同类开源IM框架。

二、MobileIMSDK简介

MobileIMSDK 是一套专为移动端开发的原创IM通信层框架:

  • 历经8年、久经考验;
  • 超轻量级、高度提炼,lib包50KB以内;
  • 精心封装,一套API同时支持UDP、TCP、WebSocket三种协议(可能是全网唯一开源的);
  • 客户端支持 iOSAndroid标准JavaH5小程序(开发中..)、Uniapp(开发中..);
  • 服务端基于Netty,性能卓越、易于扩展;👈
  • 可与姊妹工程 MobileIMSDK-Web 无缝互通实现网页端聊天或推送等;👈
  • 可应用于跨设备、跨网络的聊天APP、企业OA、消息推送等各种场景。

MobileIMSDK工程始于2013年10月,起初用作某产品的即时通讯底层实现,完全从零开发,技术自主可控!

您可能需要:查看关于MobileIMSDK的详细介绍

三、代码托管同步更新

OsChina.net

GitHub.com

四、MobileIMSDK设计目标

让开发者专注于应用逻辑的开发,底层复杂的即时通讯算法交由SDK开发人员,从而解偶即时通讯应用开发的复杂性。

五、MobileIMSDK框架组成

整套MobileIMSDK框架由以下5部分组成:

  1. Android客户端SDK:用于Android版即时通讯客户端,支持Android 2.3及以上,查看API文档
  2. iOS客户端SDK:用于开发iOS版即时通讯客户端,支持iOS 8.0及以上,查看API文档
  3. Java客户端SDK:用于开发跨平台的PC端即时通讯客户端,支持Java 1.6及以上,查看API文档
  4. H5客户端SDK:暂无开源版,查看精编注释版
  5. 服务端SDK:用于开发即时通讯服务端,支持Java 1.7及以上版本,查看API文档

整套MobileIMSDK框架的架构组成:

 另外:MobileIMSDK可与姊妹工程 MobileIMSDK-Web 无缝互通,从而实现Web网页端聊天或推送等。

六、MobileIMSDK v6.3更新内容 

【重要说明】:

MobileIMSDK v6.3 为次要版本,进行了若干优化! 查看详情

【新增的特性】:

  • 1. [所有端] 提供了灵活的接口供开发者定制和开启SSL/TLS加密传输;

【其它优化和提升】:

  • 1. [iOS] 解决了iOS端Demo在iOS16下的适配问题;
  • 2. [iOS] 解决了iOS端Demo在黑暗模式下背景和标题栏是黑色的问题;
  • 3. [Android] 优化了Android端Demo在最新Android系统下的适配等;
  • 4. [Android/Java] 对全局单例增加线程安全处理,防止在高版本JDK中出现并发调用而导致单例被重复实例化。

【版本地址】:

https://gitee.com/jackjiang/MobileIMSDK/releases/tag/6.3

posted @ 2023-02-07 10:27 Jack Jiang 阅读(58) | 评论 (0)编辑 收藏

     摘要: 本文引用了“鲜枣课堂”的《史上最强5G科普》文章内容。为了更好的内容呈现,在引用和收录时内容有改动,转载时请注明原文来源。1、内容概述➊ 5G技术的关注度越来越高:在此之前,5G技术对于普通老百姓来说,似乎还很遥远,关注度并不高。但从去年开始,美帝赤裸裸打压中兴和华为的国际事件,让5G技术在国内有了很高的关注度。美帝打压中兴、华为固然是坏事,但因为这个事情,相当于反过来为5...  阅读全文

posted @ 2023-02-04 16:21 Jack Jiang 阅读(73) | 评论 (0)编辑 收藏

     摘要: 本文由金蝶随手记技术团队丁同舟分享。1、引言跟移动端IM中追求数据传输效率、网络流量消耗等需求一样,随手记客户端与服务端交互的过程中,对部分数据的传输大小和效率也有较高的要求,普通的数据格式如 JSON 或者 XML 已经不能满足,因此决定采用 Google 推出的 Protocol Buffers 以达到数据高效传输。本文将基于随手记团队的Protobuf应用实践,分享了Protobuf的技术原...  阅读全文

posted @ 2023-01-28 16:57 Jack Jiang 阅读(112) | 评论 (0)编辑 收藏

     摘要: 1、前言Protobuf是Google开源的一种混合语言数据标准,已被各种互联网项目大量使用。Protobuf最大的特点是数据格式拥有极高的压缩比,这在移动互联时代是极具价值的(因为移动网络流量到目前为止仍然昂贵的),如果你的APP能比竞品更省流量,无疑这也将成为您产品的亮点之一。现在,尤其IM、消息推送这类应用中,Protobuf的应用更是非常广泛,基于它的优秀表现,微信和手机QQ这样的主流IM...  阅读全文

posted @ 2023-01-05 16:14 Jack Jiang 阅读(146) | 评论 (0)编辑 收藏

本文由钉钉技术专家尹启绣分享,有修订和重新排版。

1、引言

短短的几年时间,钉钉便迅速成为一款国民级应用,发展速度堪称迅猛。

IM作为钉钉最核心的功能,每天需要支持海量企业用户的沟通,同时还通过 PaaS 形式为淘宝、高德等 App 提供基础的即时通讯能力,是日均千亿级消息量的 IM 平台。

在钉钉的IM中,我们通过 RocketMQ实现了系统解耦、异步削峰填谷,还通过定时消息实现分布式定时任务等高级特性。同时与 RocketMQ 深入共创,不断优化解决了很多RocketMQ本身的问题,并且孵化出 POP 消费模式等新特性,使 RocketMQ 能够完美支持对性能稳定性和时延要求非常高的 IM 系统。本文将为你分享这些内容。

学习交流:

本文已同步发布于:http://www.52im.net/thread-4106-1-1.html

2、系列文章

本文是系列文章的第9篇,总目录如下:

3、钉钉IM面临的巨大技术挑战

3.1 概述

钉钉作为企业级 IM 领先者,面临着巨大的技术挑战。市面上DAU过亿的App里,只有钉钉是2B产品,我们不仅需要和其他 2C 产品一样,支持海量用户的低时延、高并发、高性能、高可用,还需保证企业级用户在使用钉钉时能够提升沟通协同效率。

下图是概括的是钉钉的主要能力:

3.2 技术挑战1:ToB与ToC的差异

作为企业级应用,需要保证帮助用户提升沟通体验。

ToB 的工作沟通和 ToC 的场景生活沟通存在较大差异, ToC的IM产品比如微信,在有完整的关系链后,只需满足大部分用户需求即可。

然而微信的很多体验其实并不友好:比如聊天消息中的视频图片在固定时间内没有打开则会无法下载,卸载重装之后聊天记录全部丢失。

而 ToB 场景下:聊天记录是非常重要的内容,钉钉为保证用户消息不丢失,提供了多端同步和消息云端存储的能力,用户任意换端都能查看完整的聊天记录。

在工作过程中,大量会议是工作效率杀手,钉钉还提供了已读、Ding 等效率套件,为工作沟通提供新选项。

3.3 技术挑战2:安全要求高

在ToB 的工作场景下,用户对信息安全要求非常高,信息安全是企业的生命线。

钉钉提供了人和组织架构打通的工作群,用户离开组织后自动退出企业工作群,这样就很好地保障了企业信息的安全。

同时,在已经支持的全链路加密能力上提供了三方加密能力,可以最大程度保障企业用户的信息安全性。

3.4 技术挑战3:稳定性要求高

企业用户对稳定性的要求也非常高,如果钉钉出现故障,深度使用钉钉的企业都会受到巨大影响。

因此,钉钉 IM 系统在稳定性上也做了非常深入的建设,架构上对依赖和流量做了深入治理,核心能力所有依赖都为双倍。

比如虽然 RocketMQ 已经非常稳定,也没有发生过故障,但是对 RocketMQ 可能出现故障的产品依然做了很好的保护,使用 RocketMQ 定时消息和堆积能力做热点治理和流量防护,让系统面对大规模流量时能从容应对,并且建设了异地多活和可弹性扩缩容能力,疫情期间很好地保证了学生们的在线课堂。

在稳定性机制上,常态化容灾演练、突袭演练、自动化健康巡检等也能很好地保证线上稳定性。比如波浪式流量就是在做断网演练时发现。

3.5 技术挑战4:业务多样性

针对不同行业的业务多样性,还要尽可能地满足用户的通用性需求,比如万人群、全员群等,目前钉钉已经做到能够支持 10 万人级别的群。

更多的业务需求将依赖于我们抽象出的通用开放能力,将 IM 能力尽可能地开放给企业和三方 ISV,使得不同形态的业务都能在钉钉平台上得到满足 。

4、消息队列在钉钉IM系统中的重要作用

4.1 概述

在如此丰富的企业级能力下,钉钉IM要与微信等 ToC 产品一样,支持亿级用户低时延沟通,系统架构需要具备高并发、高性能、高可用的能力,挑战非常之大。

IM 本身是异步化沟通系统,与开会或者电话沟通相比,让沟通双方异步处理消息能够减少打断次数,提升沟通效率。这种异步的特性和消息队列的能力很契合,消息队列可以很好地帮助 IM 完成异步化解耦、失败重试、削峰填谷等能力。

这里,我们以钉钉IM系统最核心的发消息和已读链路简化流程(如下图所示),来详细说明消息队列在系统里的重要作用。

 

4.2 发消息链路

钉钉IM系统的发消息链路流程如下:

  • 1)处于登录状态的钉钉用户发送一条消息时,首先会将请求发送到 receiver 应用;
  • 2)为保证发消息体验和成功率,receiver 应用只做这条消息能否发送的校验,其他如消息入库、接收者推送等都交由下游应用完成;
  • 3)校验完成之后将消息投递给消息队列,成功后即可返回给用户;
  • 4)消息发送成功,processor 会从消息队列里订阅到这条消息,并对消息进行入库处理,再通过消息队列将消息交给同步服务 syncserver 做处理,将消息同步给在线接收者。

上述过程中,对于不在线的用户:可以通过消息队列将消息推给离线 push 系统。离线 push 系统可以对接接苹果、华为、小米等推送系统进行离线推送。

用户发消息过程中的每一步,失败后都可通过消息队列进行重试处理。如 processor 入库失败,可将消息打回消息队列,继续回旋处理,达到最终一致。同时,可以在订阅的过程中对消费限速,避免线上突发峰值给系统带来灾难性的后果。

4.3 消息已读链路

钉钉IM系统的消息已读链路流程如下:

  • 1)用户对一条消息做读操作后,会发送请求到已读服务;
  • 2)已读服务收到请求后,直接将请求放到消息队列进行异步处理,同时可以达到削峰填谷的目的;
  • 3)已读服务处理完之后,将已读事件推给同步服务,让同步服务将已读事件推送给消息发送者。

从上面两个链路可以看出,消息队列是 IM 系统里非常重要的组成部分。

5、钉钉IM选择RocketMQ的原因

阿里内部曾有 notify、RocketMQ 两套应用消息中间件,也有其他基于 MQTT 协议实现的消息队列,最终都被 RocketMQ 统一。

IM 系统对消息队列有如下几个基本要求:

  • 1)解耦和削峰填谷(这是消息队列的基础能力);
  • 2)高性能、低时延;
  • 3)高可用性。

对于第 3)点:要求消息队列的高可用性方面不仅包括系统可用性,也包括数据可用性,要求写入消息队列时消息不丢失(钉钉 IM 对消息的保证级别是一条都不丢)。

RocketMQ 经过多次双 11 考验,其堆积性能、低时延、高可用已成为业届标杆,完全符合对消息队列的要求。

同时它的其他特性也非常丰富,如定时消息、事务消息,能够以极低的成本实现分布式定时任务,消息可重放和死信队列提供了后悔药的能力,比如线上系统出现 bug ,很多消息没有正确处理,可以通过重置位点、重新消费的方式,订正之前的错误处理。

另外:消息队列的使用场景非常丰富,RocketMQ 的扩展能力可以在消息发送和消费上做切面处理,实现通用性的扩展封装,大大降低开发工作量。 Tag & SQL 过滤能让下游针对性地订阅定业务需要的消息,无需订阅整个 topic 里的所有消息,大幅降低下游系统的订阅压力。

RocketMQ 至今从未发生故障,集群峰值 TPS 可达 300w/s,从生产到消费时延能够保证在 10 ms 以内,支持 30 亿条消息堆积,核心指标数据表现抢眼,性能异常优秀。

6、RocketMQ的消息必达3重保险

如上图所示,发消息流程中,很重要的一步是 receiver 应用做完消息能否发送的校验之后,通过 RocketMQ 将消息投递给 processor做消息入库处理。

投递过程中,将提供三重保险,以保证消息发送万无一失。

第一重保险:receiver 将消息写进 RocketMQ 时, RocketMQ SDK 默认会重试五次(每次尝试不同的 broker ,保障了消息写失败的概率非常小)。

第二重保险:写入 RocketMQ 失败的情况下,会尝试以 RPC 形式将消息投递给 processor 。

第三重保险:如果 RPC 形式也失败,会尝试将本地 redoLog 通过 Crontab 任务定时将消息回放到 RocketMQ 里面。

此外,如何在系统异常的情况下做到消息最终一致?

Processor 收到上游投递的消息时,会尝试对消息做入库处理。即使入库失败,依然会将消息投给同步服务,将消息下发,保证实时消息收发正常。异常情况时会将消息重新投递到异常 topic 进行重试,投递过程中通过设置RocketMQ 定时消息做退避处理,对异常 topic 做限速消费。

重试写不同的 topic 是为了与正常流量隔离,优先处理正常流量,防止因为异常流量消费而导致真正的线上消息处理被延迟。

另外:Rocket MQ 的一个 broker 默认只有一个 Retry 消息队列,如果消费失败量特别大的情况下,会导致下游负载不均,某些机器打死。

此外:如果系统持续发生异常,则会不断地进行回旋重试,如果不做限速处理,线上容易出现流量叠加,导致整个系统雪崩。

7、RocketMQ的独门绝技——分布式定时任务

在几千人的群里发一条消息,假设有 1/4 的成员同时开着聊天窗口,如果不对服务端已读服务和客户端需要更新的已读数做合并处理,更新的 QPS 会高达到 1000/s。钉钉能够支持十几万人的超大群,超大群的活跃对服务端和客户端都会带来很大冲击,而实际上用户的需求只需实现秒级更新。

针对以上场景:可以利用 RocketMQ 的定时消息能力实现分布式定时任务。

以已读流程为例(如下图所示),用户发起请求时,会将请求放入集中式请求队列,再通过 RocketMQ 定时消息生成定时任务,比如 5 秒后批量处理。5秒之后,RocketMQ 订阅到任务触发消息,将队列里面所有请求都取出处理。

▲ 用 RocketMQ 实现分布式定时任务的流程原理

我们抽象了一个分布式定时任务的组件,提供了很多其他实时性可达秒级的功能,如万人群的群状态更新、消息扩展更新都接入了此组件。通过组件的定时合并处理,大幅降低系统压力。

如上图(右边部分),在一些大群活跃的时间点成功地让流量下降并保持平稳状态。

8、钉钉IM使用RocketMQ遇到的技术问题

8.1 概述

RocketMQ 的生产端策略如下:

  • 1)生产者获取到对应 topic 所有 broker 和 Queue 列表,然后轮询写入消息;
  • 2)消费者端也会获取到 topic 所有 broker 和Queue列表;
  • 3)还需要要从 broker 中获取所有消费者 IP 列表进行排序(按照配置负载均衡,如哈希、一次性哈希等策略计算出自己应该订阅哪些 Queue)。

上图中:ConsumerGroupA的Consumer1被分配到MessageQueue0和MessageQueue1,则它订阅MessageQueue0和MessageQueue1。

在RocketMQ的使用过程中,我们面临了诸多问题,下面我们来逐一分享。

8.2 问题1:波浪式流量

我们发现订阅消息集群滚动时,CPU 呈现波浪式飙升。

经过深入排查发现,断网演练后进行网络恢复时,大量 producer 同时恢复工作,同时从第一个 broker 的第一个 Queue 开始写入消息,生产消息波浪式写入 RocketMQ ,进而导致消费者端出现波浪式流量。

最终,我们联系 RocketMQ 开发人员,调整了生产策略,每次生产者发现 broker 数量或状态发生变化时,都会随机选取一个初始Queue写入消息,以此解决问题。

另一个导致波浪式流量的问题是配置问题。

排查线上问题时,从 broker 视角看,每个 broker 的消息量都是平均的,但 consumer 之间流量相差特别大。最终通过在 producer 侧尝试抓包得以定位到问题,是由于 producer 写入消息时超时率偏高。

梳理配置后发现,是由于 producer 写入消息时配置超时太短,Rocket MQ 在写消息时会尝试多次,比如第一个 broker 写入失败后,将直接跳到下一个 broker 的第一个 Queue ,导致每个 broker 的第一个 Queue 消息量特别大,而靠后的 partition 几乎没有消息。

8.3 问题2:负载均衡维度太粗

负载均衡只能到Queue维度,导致需要不时地关注 Queue 数量。

比如线上流量增长过快,需要进行扩容,而扩容后发现机器数大于 Queue 数量,导致无论怎么扩容都无法分担线上流量,最终只能联系 RocketMQ 运维人员调高 Queue 数量来解决。

虽然调高 Queue 数量能解决机器无法订阅的问题,但因为负载均衡策略只到 Queue 维度,负载始终无法均衡。从下图可以看到, consumer 1 订阅了两个 Queue 而 consumer 2 只订阅了一个 Queue。

8.4 问题3:单机夯死导致消息堆积

单机夯死导致消息堆积,这也是负载均衡只能到 Queue 维度带来的副作用。

比如 Broker A 的 Queue 由 consumer 1 订阅,出现宿主机磁盘 IO 夯死但与 broker 之间的心跳依然正常,导致 Queue 消息长时间无法订阅进而影响用户接收消息。最终只能通过手动介入将对应机器下线来解决。

8.5 问题4:rebalance

Rocket MQ 的负载均衡由 client 自己计算,导致有机器异常或发布时,整个集群状态不稳定,时常会出现某些 Queue 有多个 consumer 订阅,而某些 Queue 在几十秒内没有 consumer 订阅的情况。

因而导致线上发布的时候,出现消息乱序或对方已回消息但显示未读的情况。

8.6 问题5:C++ SDK 能力缺失

钉钉IM的核心处理模块Receiver、processor 等应用都是通过 C++ 实现,而RocketMQ 的 C++ SDK 相比于 Java 存在较大缺失。经常出现内存泄漏或 CPU 飙高的情况,严重影响线上服务的稳定。

9、钉钉IM与RocketMQ的相互促进

面对以上困扰,在经过过多次讨论和共创后,最终孵化出 RocketMQ 5.0 POP 消费模式。

这是 RocketMQ 在实时系统里程碑式的升级,解决了大量实时系统使用 RocketMQ 过程中遇到的问题(如下图所示)。

1)Pop消费模式下,每一个 consumer 都会与所有 broker 建立长连接并具备消费能力,以 broker 维护整个消息订阅的负载均衡和位点。重云轻端的模式下,负载均衡、订阅消息、位点维护都在客户端完成,而新客户端只需做长链接管理、消息接收,并且通用 gRPC 协议,使得多语言比如 C++、Go、 Python 等语言客户端都能轻松实现,无需持续投入力去升级维护 SDK 。

2)broker能力升级更简单。重云轻端很好地解决了客户端版本升级问题,客户端改动的可能性和频率大大降低。以往升级新特性或能力只能推动所有相关 SDK 应用进行升级发布,升级过程中还需考虑新老兼容等问题,工作量极大。而新模式只需升级 broker 即可完成工作。

3)单机夯死消息能继续被消费。新模式下 consumer 和 broker 进行网状连接和消息订阅,由 broker 通过负载均衡策略平均分配消息给 consumer 进行消费,以往宕机夯死导致的 Queue 消息堆积问题也迎刃而解。如果 broker 发现 consumer 长时间没有进行消息 ACK ,则将不再对其投递消息,彻底解决单机夯死问题。

4)无需关注partition数量。

5)彻底解决rebalance。

6)负载更均衡。通过新的订阅模式,不管上游流量如何偏移,只要不超过单个 broker 的容量上限,消费端都能实现真正意义上的负载均衡。

POP 模式消费模式已经在钉钉 IM 场景磨合得非常成熟,在对可用性、性能、时延方面要求非常高的钉钉 IM 系统证明了自己,也证明了不断升级的 RocketMQ 是即时通讯场景消息队列的不二选择。

10、相关资料

[1] 现代IM系统中聊天消息的同步和存储方案探讨

[2] 企业级IM王者——钉钉在后端架构上的过人之处

[3] 深度解密钉钉即时消息服务DTIM的技术设计

[4] 钉钉——基于IM技术的新一代企业OA平台的技术挑战(视频+PPT)

[5] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[6] IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?

本文已同步发布于:http://www.52im.net/thread-4106-1-1.html

posted @ 2022-12-30 12:05 Jack Jiang 阅读(105) | 评论 (0)编辑 收藏

1、引言

在社区中,分享了很多篇基于Netty编写的IM聊天入门文章(比如《跟着源码学IM》系列、《基于Netty,从零开发IM》系列等),在这些文章中分享了各种IM通信算法原理和功能逻辑的实现。但是这样简单的IM聊天系统是比较容易被窃听的,如果想要在里面说点悄悄话是不太安全的。

怎么办呢?学过密码学的朋友可能就想到了一个解决办法,聊天的时候对消息加密,处理的时候再对消息进行解密。是的,道理就是这样。

但密码学本身的理论就很复杂,加上相关的知识和概念又太多太杂,对于IM入门者来说,想要快速理清这些概念并实现合适的加解密方案,是比较头疼的。

本文正好借此机会,以Netty编写的IM聊天加密为例,为入门者理清什么是PKI体系、什么是SSL、什么是OpenSSL、以及各类证书和它们间的关系等,并在文末附上简短的Netty代码实示例,希望能助你通俗易懂地快速理解这些知识和概念!

补充说明:本文为了让文章内容尽可能言简意赅、通俗易懂,尽量不深入探讨各个技术知识和概念,感兴趣的读者可以自行查阅相关资料进一步学习。

学习交流:

本文已同步发布于:http://www.52im.net/thread-4104-1-1.html

2、相关文章

  1. 即时通讯安全篇(一):正确地理解和使用Android端加密算法
  2. 即时通讯安全篇(二):探讨组合加密算法在IM中的应用
  3. 即时通讯安全篇(三):常用加解密算法与通讯安全讲解
  4. 即时通讯安全篇(四):实例分析Android中密钥硬编码的风险
  5. 即时通讯安全篇(五):对称加密技术在Android平台上的应用实践
  6. 即时通讯安全篇(六):非对称加密技术的原理与应用实践
  7. 即时通讯安全篇(十):IM聊天系统安全手段之通信连接层加密技术
  8. 即时通讯安全篇(十一):IM聊天系统安全手段之传输内容端到端加密技术

3、什么是PKI?

我们需要先了解一下公钥和私钥的加密标准体系PKI。

3.1 基本概念

PKI的全称是Public Key Infrastructure,是指支持公钥管理体制的基础设施,提供鉴别、加密、完整性和不可否认性服务。

通俗讲:PKI是集机构、系统(硬件和软件)、人员、程序、策略和协议为一体,利用公钥概念和技术来实现和提供安全服务的、普适性的安全基础设施。

在公钥密码中,发送者用公钥(加密密钥)加密,接收者用私钥(解密密钥)解密。公钥一般是公开的,不再担心窃听,这解决了对称密码中的密钥配送问题。但是接收者依然无法判断收到的公钥是否合法(有可能是中间人假冒的)。

事实上,仅靠公钥密码本身,无法防御中间人攻击。于是,需要(认证机构)对公钥进行签名,从而确认公钥没有被篡改。加了数字签名的公钥称为公钥证书,一般简称证书。

有了证书来认证,可以有效防御中间人攻击,随之带来了一系列非技术性工作。

例如:谁来发证书?如何发证书?不同机构的证书怎么互认?纸质证书作废容易,数字证书如何作废?解决这些问题,需要制定统一的规则,即PKI体系。

PKI体系是通过颁发、管理公钥证书的方式为终端用户提供服务的系统,最核心的元素是证书。

围绕证书构成了PKI体系的要素:

  • 1)使用PKI的用户;
  • 2)颁发证书的机构(Certificate Authority,CA);
  • 3)保存证书的仓库。

总之:PKI是一个总称,既包括定义PKI的基础标准,也包括PKI的应用标准。

3.2 PKI体系现状

事实上PKI已经有两代了。

第一代的PKI标准主要是由美国RSA公司的公钥加密标准PKCS、国际电信联盟的ITU-T X.509、IETF的X.509、WAP和WPKI等标准组成。但是因为第一代PKI标准是基于抽象语法符号ASN.1进行编码的,实现起来比较复杂和困难,所以产生了第二代PKI标准。

第二代PKI标准是由微软、VeriSign和webMethods三家公司在2001年发布的基于XML的密钥管理规范也叫做XKMS。

事实上现在CA中心使用的最普遍的规范还是X.509系列和PKCS系列。

X.509系列主要由X.209、X.500和X.509组成,其中X.509是由国际电信联盟(ITU-T)制定的数字证书标准。在X.500基础上进行了功能增强,X.509是在1988年发布的。

X.509证书由用户公共密钥和用户标识符组成。此外还包括版本号、证书序列号、CA标识符、签名算法标识、签发者名称、证书有效期等信息。

而PKCS是美国RSA公司的公钥加密标准,包括了证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。它定义了一系列从PKCS#1到PKCS#15的标准。

其中最常用的是PKCS#7、PKCS#12和PKCS#10。PKCS#7 是消息请求语法,常用于数字签名与加密,PKCS#12是个人消息交换与打包语法主要用来生成公钥和私钥(题外话:iOS程序员对PKCS#12不陌生,在实现APNs离线消推送时就需要导出.p12证明,正是这个)。PKCS#10是证书请求语法。

4、什么是SSL?

4.1 基本概念

SSL(全称 Secure Socket Layer)安全套接层是网景公司(Netscape)率先采用的网络安全协议。它是在传输通信协议(TCP/IP)上实现的一种安全协议,采用公开密钥技术。

通俗地说:SSL被设计成使用TCP来提供一种可靠的端到端的安全服务,它不是单个协议,而是二层协议。低层是SSL记录层,用于封装不同的上层协议,另一层是被封装的协议,即SSL握手协议,它可以让服务器和客户机在传输应用数据之前,协商加密算法和加密密钥,客户机提出自己能够支持的全部加密算法,服务器选择最适合它的算法。

SSL特点是:它与应用层协议独立无关。上层的应用层协议(例如:HTTP、FTP、Telnet等)能透明的建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商以及服务器认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的私密性。

4.2 与TLS的关系

SSL是网景公司(Netscape)设计,但IETF将SSL作了标准化,即RFC2246,并将其称为TLS(Transport Layer Security),其最新版本是RFC5246、版本1.2。

实际上:TLS是IETF在SSL3.0基础上设计的,相当于SSL的后续版本。所以我们通常都是SSL/TLS放一起说。

5、什么是OpenSSL?

5.1 基本概念

 

OpenSSL是一个开放源代码的软件库,应用程序可以使用这个包来进行安全通信,它包括代码、脚本、配置和过程的集合。例如:如果您正在编写一个需要复杂安全加密的软件,那么只有添加一个安全加密库才有意义,这样您就不必自己编写一大堆复杂的加解密函数(而且密码学本身很复杂,要写好它们并不容易)。

其主要库是以 C 语言所写成,实现了基本的加密功能,实现了 SSL 与 TLS 协议。

OpenSSL整个软件包大概可以分成三个主要功能部分:

  • 1)SSL协议库;
  • 2)应用程序;
  • 3)密码算法库。

OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。

OpenSSL 可以运行在 OpenVMS、 Microsoft Windows 以及绝大多数类 Unix 操作系统上。

5.2 具体来说

密钥和证书管理是PKI的一个重要组成部分,OpenSSL为之提供了丰富的功能,支持多种标准。

OpenSSL实现了ASN.1的证书和密钥相关标准,提供了对证书、公钥、私钥、证书请求以及CRL等数据对象的DER、PEM和BASE64的编解码功能。

OpenSSL提供了产生各种公开密钥对和对称密钥的方法、函数和应用程序,同时提供了对公钥和私钥的DER编解码功能。并实现了私钥的PKCS#12和PKCS#8的编解码功能。

OpenSSL在标准中提供了对私钥的加密保护功能,使得密钥可以安全地进行存储和分发。

在此基础上,OpenSSL实现了对证书的X.509标准编解码、PKCS#12格式的编解码以及PKCS#7的编解码功能。并提供了一种文本数据库,支持证书的管理功能,包括证书密钥产生、请求产生、证书签发、吊销和验证等功能。

5.3 发展历程

OpenSSL 计划在 1998 年开始,其目标是发明一套自由的加密工具,在互联网上使用。

OpenSSL 以 Eric Young 以及 Tim Hudson 两人开发的 SSLeay 为基础,随着两人前往 RSA 公司任职,SSLeay 在 1998 年 12 月停止开发。因此在 1998 年 12 月,社群另外分支出 OpenSSL,继续开发下去。

▲ 上图为 Tim Hudson

OpenSSL 管理委员会当前由 7 人组成有 13 个开发人员具有提交权限(其中许多人也是 OpenSSL 管理委员会的一部分)。只有两名全职员工(研究员),其余的是志愿者。

该项目每年的预算不到 100 万美元,主要依靠捐款。 TLS 1.3 的开发由 Akamai 赞助。

5.4 下载方法

OpenSSL可以从其官网上下载,地址是:https://www.openssl.org/source/,感兴趣的读者可以自行下载安装研究。

6、各类证书

6.1 证书类型

操作过证书的朋友可能会对各种证书类型眼花缭乱,典型的体现就是各种不同的证书扩展名上,一般来说会有DER、CRT、CER、PEM这几种证书的扩展名。

以下是最常见的几种:

  • 1)DER文件:表示证书的内容是用二进制进行编码的;
  • 2)PEM文件:是一个文本文件,其内容是以“ - BEGIN -” 开头的,Base64编码的字符;
  • 3)CRT和CER文件:基本上是等价的,他们都是证书的扩展,也是文本文件,不同的是CRT通常用在liunx和unix系统中,而CER通常用在windows系统中。并且在windows系统中,CER文件会被MS cryptoAPI命令识别,可以直接显示导入和/或查看证书内容的对话框;
  • 4)KEY文件:主要用来保存PKCS#8标准的公钥和私钥。

6.2 常用OpenSSL命令

下面的命令可以用来查看文本证书内容:

openssl x509 -incert.pem -text -noout

openssl x509 -incert.cer -text -noout

openssl x509 -incert.crt -text -noout

下面的命令可以用来查看二进制证书内容:

openssl x509 -incert.der -inform der -text -noout

下面是常见的PEM和DER相互转换。

PEM到DER的转换:

openssl x509 -incert.crt -outform der-out cert.der

DER到PEM的转换:

openssl x509 -incert.crt -inform der -outform pem -out cert.pem

补充说明:上述命令中用到的openssl程序,就是本文中提到的OpenSSL开源库提供的程序。

7、Netty中的聊天加密代码示例

7.1 关于Netty

Netty是一个Java NIO技术的开源异步事件驱动的网络编程框架,用于快速开发可维护的高性能协议服务器和客户端,事实上用Java开发IM系统时,Netty是几乎是首选。

有关Netty的介绍我就不啰嗦了,如果不了解那就详读以下几篇:

史上最强Java NIO入门:担心从入门到放弃的,请读这篇!

Java的BIO和NIO很难懂?用代码实践给你看,再不懂我转行!

新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

史上最通俗Netty框架入门长文:基本介绍、环境搭建、动手实战

基它有关Netty的重要资料:

Netty-4.1.x 源码 (在线阅读版)

Netty-4.1.x API文档 (在线查阅版)

7.2 启动SSL Server代码示例

事实上这个标题是不对的,Netty中启动的server还是原来那个server,只是对发送的消息进行了加密解密处理。也就是说添加了一个专门进行SSL操作的Handler。

netty中代表ssl处理器的类叫做SslHandler,它是SslContext工程类的一个内部类,所以我们只需要创建好SslContext即可通过调用newHandler方法来返回SslHandler。

让服务器端支持SSL的代码:

ChannelPipeline p = channel.pipeline();

  SslContext sslCtx = SslContextBuilder.forServer(...).build();

  p.addLast("ssl", sslCtx.newHandler(channel.alloc()));

让客户端支持SSL的代码:

ChannelPipeline p = channel.pipeline();

   SslContext sslCtx = SslContextBuilder.forClient().build();

   p.addLast("ssl", sslCtx.newHandler(channel.alloc(), host, port));

netty中SSL的实现有两种方式,默认情况下使用的是OpenSSL,如果OpenSSL不可以,那么将会使用JDK的实现。

要创建SslContext,可以调用SslContextBuilder.forServer或者SslContextBuilder.forClient方法。

这里以server为例,看下创建流程。

SslContextBuilder有多种forServer的方法,这里取最简单的一个进行分析:

publicstaticSslContextBuilder forServer(File keyCertChainFile, File keyFile) {

    returnnewSslContextBuilder(true).keyManager(keyCertChainFile, keyFile);

}

该方法接收两个参数:

  • 1)keyCertChainFile是一个PEM格式的X.509证书文件;
  • 2)keyFile是一个PKCS#8的私钥文件。

熟悉OpenSSL的童鞋应该知道使用openssl命令可以生成私钥文件和对应的自签名证书文件。

具体openssl的操作可以查看我的其他文章,这里就不详细讲解了。

除了手动创建证书文件和私钥文件之外,如果是在开发环境中,大家可能希望有一个非常简单的方法来创建证书和私钥文件,netty为大家提供了SelfSignedCertificate类。

看这个类的名字就是知道它是一个自签名的证书类,并且会自动将证书文件和私钥文件生成在系统的temp文件夹中,所以这个类在生产环境中是不推荐使用的。默认情况下该类会使用OpenJDK's X.509来生成证书的私钥,如果不可以,则使用 Bouncy Castle作为替代。

7.3 启动SSL Client代码示例

同样的在client中支持SSL也需要创建一个handler。

客户端的SslContext创建代码如下:

// 配置 SSL.

finalSslContext sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();

上面的代码我们使用了一个InsecureTrustManagerFactory.INSTANCE作为trustManager。

什么是trustManager呢?

当客户端和服务器端进行SSL连接的时候,客户端需要验证服务器端发过来证书的正确性。

通常情况下,这个验证是到CA服务器中进行验证的,不过这样需要一个真实的CA证书环境,所以在测试中,我们使用InsecureTrustManagerFactory,这个类会默认接受所有的证书,忽略所有的证书异常。

当然:CA服务器也不是必须的,客户端校验的目的是查看证书中的公钥和发送方的公钥是不是一致的,那么对于不能联网的环境,或者自签名的环境中,我们只需要在客户端校验证书中的指纹是否一致即可。

netty中提供了一个FingerprintTrustManagerFactory类,可以对证书中的指纹进行校验。

该类中有个fingerprints数组,用来存储安全的授权过的指纹信息。通过对比传入的证书和指纹,如果一致则校验通过。

使用openssl从证书中提取指纹的步骤如下:

openssl x509 -fingerprint -sha256 -inmy_certificate.crt

8、小结一下

上面我们对Netty聊天用到的加密技术和相关概念进行了梳理,我来简单这些概念之间的关系。

这些概念之间的关系,简单来说就是:

  • 1)PKI:是一套加密体系和标准的合集,它是理论方案;
  • 2)SSL:是利用了PKI理论体系,针对Socket网络这个场景设计的一套安全通信标准,属于是PKI的一个具体应用场景;
  • 3)OpenSSL:是PKI体系及SSL标准的算法和代码实现,它包括了具体的开源代码、工具程序等;
  • 4)各种证书:是在SSL或其它基于PKI体系的安全协议标准中需要使用的到一些加密凭证文件等。

而具体到Netty中的聊天加密,那就是应用了上述的PKI体系,基于SSL协议,在OpenSSL等开源库的帮助下实现的安全程序。

9、参考资料

[1] 公钥基础设施(PKI)国际标准进展

[2] 一篇文章让你彻底弄懂SSL/TLS协议

[3] 什么是OpenSSL?它有什么用途

[4] OpenSSL是什么软件

[5] netty系列之对聊天进行加密

[6] 跟着源码学IM

[7] 基于Netty,从零开发IM

[8] TCP/IP详解(全网唯一在线阅读版)

[9] 快速理解TCP协议一篇就够

[10] Netty-4.1.x 源码(在线阅读版)

[11] Netty-4.1.x API文档(在线版)

本文已同步发布于http://www.52im.net/thread-4104-1-1.html

posted @ 2022-12-22 17:34 Jack Jiang 阅读(98) | 评论 (0)编辑 收藏

     摘要: 本文由陶文分享,InfoQ编辑发布,有修订和改动。1、前言本系列的前几篇主要是从各个角度讲解Protobuf的基本概念、技术原理这些内容,但回过头来看,对比JSON这种事实上的数据协议工业标准,Protobuf到底性能到底高多少?本篇将以Protobuf为基准,对比市面上的一些主流的JSON解析库,通过全方位测试来证明给你看看Protobuf到底比JSON快几倍。学习交流:- 移动端IM开发入门文...  阅读全文

posted @ 2022-12-16 12:43 Jack Jiang 阅读(122) | 评论 (0)编辑 收藏

仅列出标题
共47页: First 上一页 11 12 13 14 15 16 17 18 19 下一页 Last 
Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang