Jack Jiang

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

本文内容编写时,参考了网上的资料,详见“参考资料”部分,感谢分享者。

1、引言

这个系列文章已经整理了10篇,但都没有涉及到具体的红包算法实现,主要有以下两方面原因。

一方面是各社交/IM产品中的红包功能同质化严重,红包算法的“可玩性”便是“核心竞争力所在”,这是同质化功能的差异化竞争思路,不会随便公开。

另一方面,市场上还存在各种抢红包插件这类灰产存在,一旦公开这些算法,很可能又被这帮插件开发者们搞出什么幺蛾子。

所以,这样的情况下,如果要做社交/IM产品中的红包功能,红包随便算法该怎么实现,基本上只能自已琢磨,很难找到大厂算法直接套用。

本着即时通讯网一贯的im知识传播精神,我收集整理并参考了大量的网上资料,综合了比较靠谱的信息来源,便有了本文。本文根据有限的资料,分享了微信红包随机算法实现中的一些技术要点,并整理了两种比较靠谱的红包算法实现思路(含可运行的实现代码),希望能给你的红包算法开发带来启发。

申明:本文资料整理自网络,仅供学习研究之用,如有不妥,请通知作者。

学习交流:

- 即时通讯开发交流5群:215477170 [推荐]

- 移动端IM开发入门文章:新手入门一篇就够:从零开发移动端IM

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK  [推荐]

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3125-1-1.html

2、系列文章

3、微信红包算法要点汇总

这是目前能找到的仅有的一份,有微信团队人员参与的微信红包算法技术要点的讨论资料。分享于2015年,差不多是微信红包刚火没多久,大概是微信技术团队的人当时没有现在这些技术之外的顾虑,所以作了有限的分享,资料难得,本次重新整理了一下,可以作为参考资料使用。以下是资料正文。

资料来源:来自InfoQ的某架构群的技术讨论,由朱玉华整理(个人博客是:zhuyuhua.com(目前已无法访问))。

资料背景:起因是有朋友在朋友圈咨询微信红包的架构,于是在微信团队成员参与讨论的情况下,我(指“朱玉华”)整理了这次讨论的技术要点,也就是下面的内容(内容为问答形式)。

3.1、算法实现的技术要点

问:微信的金额什么时候算?

答:微信金额是拆的时候实时算出来,不是预先分配的,采用的是纯内存计算,不需要预算空间存储。

为什么采取实时计算金额?原因是:实时效率更高,预算才效率低下。预算还要占额外存储。因为红包只占一条记录而且有效期就几天,所以不需要多大空间。就算压力大时,水平扩展机器是。

问:关于实时实时性,为什么明明抢到红包,点开后发现没有?

答:2014年的红包一点开就知道金额,分两次操作,先抢到金额,然后再转账。

2015年的红包的拆和抢是分离的,需要点两次,因此会出现抢到红包了,但点开后告知红包已经被领完的状况。进入到第一个页面不代表抢到,只表示当时红包还有。

问:关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?

答:随机,额度在 0.01 和剩余平均值 2 之间。 例如:发 100 块钱,总共 10 个红包,那么平均值是 10 块钱一个,那么发出来的红包的额度在 0.01元~20元之间波动。

当前面 3 个红包总共被领了 40 块钱时,剩下 60 块钱,总共 7 个红包,那么这 7 个红包的额度在:0.01~(60/7 * 2)=17.14之间。

注意:这里的算法是每被抢一个后,剩下的会再次执行上面的这样的算法(Tim老师也觉得上述算法太复杂,不知基于什么样的考虑)。

这样算下去,会超过最开始的全部金额,因此到了最后面如果不够这么算,那么会采取如下算法:保证剩余用户能拿到最低1分钱即可。

如果前面的人手气不好,那么后面的余额越多,红包额度也就越多,因此实际概率一样的。

问:红包的设计

答:微信从财付通拉取金额数据过来,生成个数/红包类型/金额放到redis集群里,app端将红包ID的请求放入请求队列中,如果发现超过红包的个数,直接返回。根据红包的逻辑处理成功得到令牌请求,则由财付通进行一致性调用,通过像比特币一样,两边保存交易记录,交易后交给第三方服务审计,如果交易过程中出现不一致就强制回归。

问:并发性处理:红包如何计算被抢完?

答:cache会抵抗无效请求,将无效的请求过滤掉,实际进入到后台的量不大。cache记录红包个数,原子操作进行个数递减,到 0 表示被抢光。财付通按照 20万笔每秒入账准备,但实际还不到 8万每秒

问:通如何保持8w每秒的写入?

答:多主sharding,水平扩展机器。

问:数据容量多少?

答:一个红包只占一条记录,有效期只有几天,因此不需要太多空间。

问:查询红包分配,压力大不?

答:抢到红包的人数和红包都在一条cache记录上,没有太大的查询压力。

问:一个红包一个队列?

答:没有队列,一个红包一条数据,数据上有一个计数器字段。

问:有没有从数据上证明每个红包的概率是不是均等?

答:不是绝对均等,就是一个简单的拍脑袋算法。

问:拍脑袋算法,会不会出现两个最佳?

答:会出现金额一样的,但是手气最佳只有一个,先抢到的那个最佳。

问:每领一个红包就更新数据么?

答:每抢到一个红包,就cas更新剩余金额和红包个数。

问:红包如何入库入账?

答:数据库会累加已经领取的个数与金额,插入一条领取记录。入账则是后台异步操作。

问:入帐出错怎么办?比如红包个数没了,但余额还有?

答:最后会有一个take all操作。另外还有一个对账来保障。

问:既然在抢的时候有原子减了就不应该出现抢到了拆开没有的情况?

答:这里的原子减并不是真正意义上的原子操作,是Cache层提供的CAS,通过比较版本号不断尝试。

问:cache和db挂了怎么办?

答:主备 +对账。

问:为什么要分离抢和拆?

答:总思路是设置多层过滤网,层层筛选,层层减少流量和压力。

这个设计最初是因为抢操作是业务层,拆是入账操作,一个操作太重了,而且中断率高。 从接口层面看,第一个接口纯缓存操作,搞压能力强,一个简单查询Cache挡住了绝大部分用户,做了第一道筛选,所以大部分人会看到已经抢完了的提示。

问:抢到红包后再发红包或者提现,这里有什么策略吗?

答:大额优先入账策略。

针对上面的技术要点,有人还画了张原理图(这是网上能找到的相对清晰的版本): 

3.2、微信抢红包的过程模拟

针对上节中整理的资料,当有人在微信群里发了一个 N 人的红包、总金额 M 元,后台大概的技术逻辑如下。

3.2.1)发红包后台操作:

  • 1)在数据库中增加一条红包记录,存储到CKV,设置过期时间;
  • 2)在Cache(可能是腾讯内部kv数据库,基于内存,有落地,有内核态网络处理模块,以内核模块形式提供服务))中增加一条记录,存储抢红包的人数N。

3.2.2)抢红包后台操作:

  • 1)抢红包分为抢和拆:抢操作在Cache层完成,通过原子减操作进行红包数递减,到0就说明抢光了,最终实际进入后台拆操作的量不大,通过操作的分离将无效请求直接挡在Cache层外面。
  • 这里的原子减操作并不是真正意义上的原子减操作,是其Cache层提供的CAS,通过比较版本号不断尝试,存在一定程度上的冲突,冲突的用户会放行,让其进入下一步拆的操作,这也解释了为啥有用户抢到了拆开发现领完了的情况。
  • 2)拆红包在数据库完成:通过数据库的事务操作累加已经领取的个数和金额,插入一条领取流水,入账为异步操作,这也解释了为啥在春节期间红包领取后在余额中看不到。
  • 拆的时候会实时计算金额,其金额为1分到剩余平均值2倍之间随机数,一个总金额为M元的红包,最大的红包为 M * 2 /N(且不会超过M),当拆了红包后会更新剩余金额和个数。财付通按20万笔每秒入账准备,实际只到8万每秒。

4、微信红包算法模拟实现1(含代码)

根据上一节的微信红包随机算法技术要点资料,实现了一个算法,以下供参考。(注:本节内容引用自《微信红包随机算法初探》一文)

4.1、算法约定

算法很简单,跟微信的算法一样,不是提前算好,而是抢红包时计算。

即:金额随机,额度在0.01和剩余平均值*2之间。(参见上一节的 “关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?” 内容)

4.2、代码实现

算法的逻辑主要是:

public static double getRandomMoney(RedPackage _redPackage) {

    // remainSize 剩余的红包数量

    // remainMoney 剩余的钱

    if(_redPackage.remainSize == 1) {

        _redPackage.remainSize--;

        return (double) Math.round(_redPackage.remainMoney * 100) / 100;

    }

    Random r     = newRandom();

    double min   = 0.01; //

    double max   = _redPackage.remainMoney / _redPackage.remainSize * 2;

    double money = r.nextDouble() * max;

    money = money <= min ? 0.01: money;

    money = Math.floor(money * 100) / 100;

    _redPackage.remainSize--;

    _redPackage.remainMoney -= money;

    return money;

}

LeftMoneyPackage数据结构如下:

class RedPackage {

    int remainSize;

    double remainMoney;

}

测试时初始化相关数据是:

static void init() {

    redPackage.remainSize  = 30;

    redPackage.remainMoney = 500;

}

附件是可以运行的完整Java代码文件:

(无法上传附件,如有需要请从此链接处下载:http://www.52im.net/thread-3125-1-1.html

4.3、测试结果

4.3.1 单次测试

按上述代码中的初始化数据(30人抢500块),执行了两次,结果如下:

//第一次

15.69   21.18   24.11   30.85   0.74    20.85   2.96    13.43   11.12   24.87   1.86    19.62   5.97    29.33   3.05    26.94   18.69   34.47   9.4 29.83   5.17    24.67   17.09   29.96   6.77    5.79    0.34    23.89   40.44   0.92

//第二次

10.44   18.01   17.01   21.07   11.87   4.78    30.14   32.05   16.68   20.34   12.94   27.98   9.31    17.97   12.93   28.75   12.1    12.77   7.54    10.87   4.16    25.36   26.89   5.73    11.59   23.91   17.77   15.85   23.42   9.77

第一次随机红包数据图表如下: 

▲ x轴为抢的顺序,y轴为抢到的金额

第二次随机红包数据图表如下:

▲ x轴为抢的顺序,y轴为抢到的金额

4.3.2 多次均值

重复执行200次的均值:

▲ x轴为抢的顺序,y轴为该次抢到金额的概率均值

重复执行2000次的均值: 

▲ x轴为抢的顺序,y轴为该次抢到金额的概率均值

从以上两张图的均值结果可以看出,这个算法中每一次能抢到的金额几率几乎是均等的,从随机性来说比较合理。

5、微信红包算法模拟实现2(含代码)

我对随机算法很感兴趣,正巧最近研究的方向有点偏随机数这块,所以也自己实现了一下微信的红包分发算法(算法要点参考的是本文第三节内容)。(注:本节内容引用自《微信红包算法的分析》一文)

5.1、代码实现

从第三节中可以了解到,微信并不是一开始就预分配所有的红包金额,而是在拆时进行计算的。这样做的好处是效率高,实时性。本次的代码中,红包具体是怎么计算的呢?请参见第4节中的“关于分配算法,红包里的金额怎么算?为什么出现各个红包金额相差很大?”。

那基于这个思想,可以写出一个红包分配算法:

/**

 * 并不完美的红包算法

 */

public static double rand(double money, int people, List<Double> l) {

    if(people == 1) {

        double red = Math.round(money * 100) / 100.0;

        l.add(red);

        return0;

    }

    Random random = newRandom();

    double min = 0.01;

    double max = money / people * 2.0;

    double red = random.nextDouble() * max;

    red = red <= min ? min : red;

    red = Math.floor(red * 100) / 100.0;

    l.add(red);

    double remain = Math.round((money - red) * 100) / 100.0;

    return remain;

}

算法整体思路很简单,就在在最后一个人的时候要注意,此时不进行随机数计算,而是直接将剩余金额作为红包。

5.2、第一次分析

采用上述算法,可以对用户的抢红包行为做分析。这里的模仿行为是:30 元的红包,10 人抢。操作 100 次。

可以得出如下结果: 

▲ x轴为抢的顺序,y轴为该次抢到金额

从上图中可以很轻易的看出来,越后抢的人,风险越大,同时收益也越大,有较大几率获得“手气最佳”。

那红包面值的分布性如何呢?

▲ x轴为抢的顺序,y轴为该次抢到金额重复 100 次后的平均值

从上图可以看出,都是比较接近平均值(3 元)的。

那重复 1000 次呢?

▲ x轴为抢的顺序,y轴为该次抢到金额重复 1000 次后的平均值

更接近了。。。

可以看出,这个算法可以让大家抢到红包面额在概率上是大致均等的。

5.3、不足之处

有人提出了这个问题: 

他接下来放了好几张他试验的截图。我这里取了一张,如果有兴趣,可以去知乎的问题里查看更多图片。

而此时,我哥们在和我的在讨论中,也告诉我,确实存在某个规律,可能让最后一个抢的人占有某些微小的优势,比如,多 0.01 的之类。

例如发 6 个,总额 0.09 的包,最后一个抢的有极大概率是 0.03。

然而我之前的代码却没办法体现出这一点。

比如 10 人拆 0.11 元的包,我的结果是:

可见以上代码还存在不足之处。

于是我就有一个猜测:

微信可能不是对全金额进行随机的,可能在派发红包之前,已经对金额做了处理,比如,事先减去(红包个数*0.01),之后在每个红包的随机值基础上加 0.01,以此来保证每个红包最小值都是 0.01。

这个猜测或许可以解开那位知友和我哥们这边的疑惑。

5.4、完善算法

在原先的基础上对代码进行简单的修正:

public static double rand(double money, int people, List<Double> l) {

    if(people == 1) {

        double red = Math.round(money * 100) / 100.0;

        l.add(red+0.01);

        return 0;

    }

    Random random = newRandom();

    double min = 0;

    double max = money / people * 2.0;

    double red = random.nextDouble() * max;

    red = red <= min ? min : red;

    red = Math.floor(red * 100) / 100.0;

    l.add(red+0.01);

    double remain = Math.round((money - red) * 100) / 100.0;

    return remain;

}

这个算法,在第一次调用时传入 money 的值是总金额减去红包数*0.01,大概像这样:

_money = _money - people * 0.01;

5.5、第二次分析

5.5.1 验证上次的不足之处

1)10 人抢 0.11 元的包:

2)2 人抢 0.03 元的包: 

3)6 人抢 0.09 的包:

5.5.2 修改后的代码会不会对已知结论造成影响?

30 元的红包,10 人抢,操作 100 次。

▲ x轴为抢的顺序,y轴为该次抢到金额 

▲ x轴为抢的顺序,y轴为该次抢到金额重复 100 次后的平均值

由上面两图可见,结论基本上没有改变。

5.6、结论

经过上述代码实践可知:

1)先抢后抢,金额期望都是相同的;

2)微信的红包算法很可能是预先分配给每人 0.01 的“底额”;

3)后抢者风险高,收益大。

5.7、补充

上几张后面测试的图,补充一下之前的观点,发 n 个红包,总金额是(n+1)*0.01,最后一个领的一定是手气最佳。

 

大家也可以试试。

以上,大概可以证明,微信红包是在分配前先给每个人 0.01 的最低金额的!

6、参考资料

[1] 微信红包随机算法初探

[2] 微信红包算法的分析

[3] 微信红包的架构设计简介

[4] 微信红包的随机算法是怎样实现的?

另外,知乎上对于微信红包算法的讨论问题很多人参与,有兴趣可以上去看看,或许会有更多启发:《微信红包的随机算法是怎样实现的?》。

附录:更多微信相关资源

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]

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

posted @ 2020-08-26 14:32 Jack Jiang 阅读(289) | 评论 (0)编辑 收藏

本文由手机淘宝技术团队原创分享,吴志华(天施)、洪海(孤星)、陈虓将(仲升)等专家参与了本文创作,首次发表于公众号“淘系技术”,收录整理时有修订和改动。

1、引言

移动端网络的优化是超级APP们永恒的话题,而对于无线电商来说这更为重要,因为网络请求体验跟用户的购买行为息息相关。

手机淘宝从过去的HTTP API网关,到后来扛住双十一战场主要流量的自研高性能、全双工、安全的ACCS(阿里云通道服务),无论是基础架构的演进、网络调优、协议的优化、异地多活、网络调度上,都有不少宝贵的经验与大家分享,本文借此机会总结了整个技术演进过程。

* 阅读对象:本文属于移动端网络优化的深水区总结性文章,适合有一定移动端网络应用经验的开发者阅读(尤其对移动弱网有一定了解的),初学者如果没有相关知识积累的话,可以简单了解无需深入。如果你对移动弱网很有兴趣,可以进一步阅读本文末尾“附录”部分的推荐文章。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入,原文链接是:http://www.52im.net/thread-3110-1-1.html

2、相关文章

Netty干货分享:京东京麦的生产级TCP网关技术实践总结

知乎技术分享:知乎千万级并发的高性能长连接网关技术实践

手机淘宝消息推送系统的架构与实践(音频+PPT) [附件下载]

3、技术背景

回想移动电商在双十一业务开始兴起的时候,当时双十一当天移动成交243亿占整体571亿的42.6%。

业务高速发展希望更多主动推送去触达用户,一些新的玩法和互动形式,需要连接买家与买家、买家与卖家、买家与达人,因为没有有效的通道能力,业务采取的是不停去轮询服务器,一来对服务器造成不必要的压力,二来对于用户手机的电量流量也是极大的浪费,关键在双十五这种大促的情况下,不必要的请求过大甚至会导致后端集群限流,从而影响到用户体验。

信息传播形态的变化的背后是移动化带来新的技术特征导致的结果。移动电商领域,手机淘宝一直是先行者。移动电商从最初的复制WEB的业务形态到移动特性不断涌现,更多的互动形式的出现,向社交化、娱乐化不断迈进的今天,一个单纯的商品的陈列架形式已经不能满足业务的需求。

业务上需要实时的触达用户,充分发挥移动的特性,将消费时间的碎片利用起来,事实也证明了用户的消费时间随着移动化的进程不断发生变化,逐步分布到全天的碎片时间中。同时货架形态也在向社区化、娱乐化的方向发展,这些都对网络层连接用户有了更高的要求。更多的媒体形态和展示方式,对网络层提出了更多元的要求。

大家可以关注到手机淘宝内的消息盒子这些产品都是业务求变的体现,业务的变化倒逼技术的前进。

4、移动网络环境的挑战性一直都存在

移动网络的速度随便3g、4g、5g的普及,速度有很大提升,但网络环境的多样性和差异性使移动网络的环境更加复杂,在过去双十一前还常遇到一些移动网络劫持的事情。网络劫持这块问题的排查效率很低,需要找到用户、复现现场,甚至找网工、运营商配合排查,一查就是几天过去。

同时在我们的舆情反馈上总是看到用户在说“某个页面加载中、页面打不开、请求很慢、打开某个功能很慢”,面对这些问题过去我们是没有太好的办法,只能猫抓耗子一桩桩去排雷很被动。很多网络的问题是偶现的,一旦错过现在就无从查起。

诸如此类的问题,背后的原因很多:

  • 1)运营商问题;
  • 2)机房部署原因;
  • 3)客户端SDK Bug;
  • 4)弱网和网络抖动;
  • 5)DNS劫持和数据篡改。

在PC时代,我们访问网站的接入条件是相对恒定的,所以在开发时很少考虑网络对用户体验的影响。但是移动APP则不然,尤其是在国内,基础的移动网络环境还不算太好,而且我们有很多用户的访问是发生在地铁、公交车这样的移动环境下,移动基站的频繁切换进一步增加了网络的不稳定。从手机淘宝的数据可以看出,我们每天活跃用户中有不少来自于弱网环境。如果端到云的连接不稳定、高延时,那么所有的用户体验都无从谈起。

基础网络的效率就像一辆列车,时延是火车的速度(启动时间),而带宽就像火车的车厢装载量,整个传输的物理链路就像火车的铁轨。目前现实条件下的移动网络条件非常复杂,我们的目标很简单,就是想让所有用户都能在手机淘宝获得流畅的体验。

下面这张图,能够让大家更加直观的了解国内的移动网络环境。描述了从用户到IDC的端到端的路由情况,不仅数据传输耗时长且丢包率高,同时安全性也是相当糟糕的,DNS劫持、内容劫持在中国就是家常便饭。

因此,我们在改善网络通道上有很多的事情可以去做,去探索突破运营商基础网络的限制,力争为用户创造极致的购物体验。

移动端的DNS问题相当普遍,可以详读以下专题文章:

全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等

美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

百度APP移动端网络深度优化实践分享(一):DNS优化篇

移动端网络优化之HTTP请求的DNS优化

5、整体技术架构

为了满足移动电商业务高速发展的需求,我们决定打造一个世界级的网络接入服务,构建一个无线网络下”水、电、煤“ 一样的基础设施。

这样一个基础设施需要做到的四个目标:

  • 1)全双工;
  • 2)低延时;
  • 3)高安全;
  • 4)开放。

在这四个目标之上是围绕这个接入服务配套的运维体系,帮助最终用户取得良好的端上体验的同时,帮助开发者快速构建自己的业务。 

如上图所示,在整个接入服务上我们划分为两层:

  • 1)接入网关:负责连接的保持、消息的解析、消息的分发;
  • 2)应用网关:实现各种应用层协议:API、SYNC、RPC、PUSH等,在应用网关的背后是具体的业务系统。

同时我们建立了一个统一调度服务,而不是采用传统的DNS,调度服务是我们的控制中心,通过它我们可以强有力的指挥我们的客户端,并且不会受到DNS污染的影响。

与服务端的分层架构对应的是客户端的SDK,最底层的统一网络库SDK集中了我们对网络优化的策略,并向上为各个应用网关技术的SDK提供API。

基于上面的开放架构,业务方可以选择直接开放具体的后端服务对接不同的应用网关,不需要了解网络背后的细节,并通过应用网关如API网关提供的开发工具快速生成客户端代码。业务方也可以基于这个接入层设计自己的协议。

统一接入层集中管理了用户的设备、在线状态,并提供信息的双向传递能力。

如下图所示:

网关将致力于解决中间网络的通讯,为上层的服务提供高质量的双向通讯能力。

6、稳定性与容灾

稳定性与容灾是服务端中间件永恒的主题,统一接入层这样一个汇聚网关收益和风险是并存的,一旦这个入口故障了,波及的用户范围是不可想象的,如何做的更加稳定,是一个巨大的挑战。

6.1 网关架构的优化

对于一个统一网关来说,对接的业务网关的信息传递特点是不一样的,大部分的业务在全天都是比较平缓的,但是个别营销类业务会在短时间内发布海量的信息,这样的信息发布会抢占网关的大量资源,对于用户的正常访问会产生影响。

举个例子:push服务需要通过网关推送2亿条消息,而这些消息需要在短时间内全部推送完,而同时网关在为正常的用户的交互提供服务,海量信息的推送和正常的用户交互相互竞争资源,最终会造成正常用户的交互失败,对于业务来说,这是不可接受的。

基于上面的情况考虑,整个网关在布署上分为两个集群:

  • 1)一个集群处理常态的在线用户访问;
  • 2)一个集群处理海量信息的推送。

如下图所示,通过这样的方式,避免了业务形态不同,对统一网关的冲击,将不同的业务形态进行了隔离。

6.2 异地多活

在异地多活的整体方案中,统一网关承担了快速引导流量的职责,也是这一方案顺利实施的一个重要环节。

异地多活是一个多机房的整体方案,在多个地区同时存在对等的多个机房,以用户维度划分,多机房共同承担全量用户的流量;在单个机房发生故障时,故障机房的流量可以快速的被迁引到可用机房,减少故障的恢复时间。

6.2.1)无线接入层单元化的协商机制:

先看一下web端在这异地多活中的实现方式:

从上图可以看到,浏览器的业务器求会发给CDN,由CDN上保存的分发规则,向后续的单元机房分发。

无线端也这样做吗?

  • 1)客户端拥有强大的能力,可以做的更灵活;
  • 2)CDN的分发节点带来更多的机器成本;
  • 3)对于需要双工通讯能力的客户端,消息投递更为复杂。

这些是我们思考与WEB不同的地方,是不是能做些不一样的选择?

如上图所示, 我们借助了客户端的强大能力,利用协商的机制来完成用户的请求正确被分配到不同的单元。

含以下几点:

  • 1)客户端的请求始终带上当前用户归属单元的信息;
  • 2)当请求到达服务端时,服务端判断用户归属单元是否正确,不正确将用户重定向到正确的单元 ;
  • 3)当前请求由网关在服务端上通过跨单元调用保证业务的正确性;
  • 4)当客户端归属单元更新后,后续的请求都会发到正确的单元机房。

6.2.2)无线接入层单元化的旁路调度:

协商机制看起来很不错,这里一个重磅炸弹丢过来了,机房的入口网络断了!

如上图,外网不可用,协商的机会都没有故障单元的用户无法恢复,这时旁路的调度服务出场了。

如上图,我们设计的调度中心这时又承担了单元化的旁路调度职责,当app访问的单元无法访问的时候,app会访问不同单元的调度中心,询问用户的归属单元。通过这种方式取得可用的单元节点,将用户切到正确的单元。这个方案同样适用于单机房的接入层网关不可用的场景。

6.2.3)应用层网关不可用:

某个单元机房的应用层网关不可用,这时等待应用网关排查问题需要的时间比较久,为了达到最快的故障恢复,我们通过开关把修改接入层的转发规则,将流量切到可用的单元。

如下图所示: 

7、端到端网络优化

7.1 统一网络库

在做网络优化一开始,我们想做一个通用的网络库,这个网络库包含策略、httpDNS、SPDY协议等一切系统网络优化需要的方方面面。(如果你对httpDNS不甚了解,可以详读《全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等》)

上层api网关请求逻辑、推送逻辑、上传下载逻辑对于这样一个通用网络库来说都是业务。在分层上将通用网络库和上层应用逻辑分开、彻底解耦,对长期持续优化网络是很有必要。

如下图所示架构: 

这样架构上分离,可以让我们更专注更系统化去做无线网络优化。

统一网络库的几个重要特性:

  • 1)灵活控制客户端网络行为策略(建连、超时处理、请求协议、是否加密);
  • 2)包含HTTPDNS;
  • 3)支持异地多活;
  • 4)更细粒度控制和调度(域名级和域名下参数级)。

1、2、3、4均由网络调度中心的集群控制,我们希望这个可以做到与业务无关,去掉一些阿里的业务属性后,这个模块大家可以理解为HTTPDNS,可以理解我们在HTTPDNS之外做了大量网络优化的端到端的工作。

7.2 就近就快接入

基于网络库我们实现了一套智能学习的网络策略,智能学习客户端在不同网络环境下建连策略,用户重新回到这个网络环境会给出最优的策略进行快速连接,并定期去更新或淘汰本地cache的历史最优网络策略。

为了建立更加迅速在各自网络下穿透性更好,接入服务器支持了多种协议和端口,客户端建连时可以极速接入网络。

我们有一个重要指标是打开客户端30秒内网络请求成功率,就是关注连的快给用户体验带来的价值。

基于调度中心,我们搭建了一个智能大数据分析平台,将客户端在在网络请求过程中的数据如建连时间、首包收取时间、整包收取时间、ssl握手时间等重要指标收集上来 。根据这些指标分析出网络异常区域,调整我们的就近就快接入规则,甚至推动IDC建设和CDN的布点完善。

7.3 弱网优化和抗抖动

在弱网优化上我们尝试了QUIC协议,在网络延时较高、丢包严重情况下比TCP有更好表现。

线上手机淘宝灰度版本实测切换到QUIC后,平均RT收益有接近20%。考虑QUIC在移动网络可能存在穿透性问题,未来我们将采取SPDY为主,QUIC为辅助的模式来完善我们的网络链接策略。

现在QUIC协议在移动端应用的越来越广泛,有兴趣的话可详细以下文章:

网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议

技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解

让互联网更快:新一代QUIC协议在腾讯的技术实践分享

七牛云技术分享:使用QUIC协议实现实时视频直播0卡顿!

同样在一些网络环境较差情况下,我们采取长短链接结合方式,在长链接遇到请求超时或穿透性较差情况,利用短链接HTTP短链接去请求数据(在移动网络环境下HTTP协议尤其HTTP1.0的穿透性是最好的),这样可以在一些极端情况下最大程度保证用户体验。

数据如下图:

网络切换和网络抖动情况下的技术优化也是一个很重要的方面,我们经常遇到移动设备网络切换和信号不稳定的情况,在这种情况我们怎么保证用户的体验?

针对这种情况我们的思路是有策略合理增加重试。我们对一个网络请求以是否发送到socket缓冲区作为分割,将网络请求生命周期划分为“请求开始到发送到 socket缓冲区”和“已经发送到socket缓冲区到请求结束”两个阶段。在阶段一内请求失败了,会根据业务需求帮助业务请求去做重试。阶段二请求失败只针对读操作提供重试能力。

设想一个场景:用户在进电梯发起一个刷新数据请求,进到电梯因为网络抖动的原因网络链接断了,这个时候我们能够合理策略去做重试,这样当用户离开电梯时很可能网络请求重试成功,帮助用户拉到了想要的数据,提升了用户体验和客户端的网络抗抖动能力。

7.4 加密传输1秒钟法则

众所周知的传统https的整个握手流程是非常重的,在网络质量不高的情况下,造成建连过慢,用户体验惨不能睹,甚至都无法完成安全握手。然而从安全的角度我们是需要一个安全的传输通道保护用户的隐私数据。

安全与网络这一对冲突放在我们的面前,需要在技术上有所突破,因此我们自建了一套slight-ssl的技术,参考了tls1.3的协议,通过合并请求,优化加密算法,运用session-ticket等策略,最终在安全和体验之间找到了一个平衡点,在基本不牺牲用户体验的基础上,达到了安全传输的目地, 同时还大幅度提升了服务端的性能。通过技术的创新,我们实现了无线网络加密传输下1秒钟法则。

关于TLS1.3在移动端的应用,也可以详读微信团队分享的这篇《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解》。

附录:有关移动端弱网方面的资料汇总

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障

聊聊iOS中网络编程长连接的那些事

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等

美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

百度APP移动端网络深度优化实践分享(一):DNS优化篇

百度APP移动端网络深度优化实践分享(二):网络连接优化篇

百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

爱奇艺移动端网络优化实践分享:网络请求成功率优化篇

美团点评的移动端网络优化实践:大幅提升连接成功率、速度等

5G时代已经到来,TCP/IP老矣,尚能饭否?

微信Mars:微信内部正在使用的网络层封装库,即将开源

如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源

谈谈移动端 IM 开发中登录请求的优化

腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》 

腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(下篇)》 

腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(上篇)》 

IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!

IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!

IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!

IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

>> 更多同类文章 ……

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

posted @ 2020-08-19 13:49 Jack Jiang 阅读(294) | 评论 (0)编辑 收藏

1、引言

最近在从头重写 MobileIMSDK 的TCP版,自已组织TCP数据帧时就遇到了字节序大小端问题。所以,借这个机会单独整理了这篇文章,希望能加深大家对字节序问题的理解,加强对IM这种基于网络通信的程序在数据传输这一层的知识掌控情况。

程序员在写应用层程序时,一般不需要考虑字节序问题,因为字节序跟操作系统和硬件环境有关,而我们编写的程序要么不需要跨平台(比如只运行在windows),要么需要跨平台时会由Java这种跨平台语言在虚拟机层屏蔽掉了。

但典型情况,当你编写网络通信程序,比如IM聊天应用时,就必须要考虑字节序问题,因为你的数据在这样的场景下要跨机器、跨网络通信,必须解决不同系统、不同平台的字节序问题。

* 阅读对象:本文属于计算机基础知识,特别适合从事网络编程方面工作(比如IM这类通信系统)的程序员阅读。面视时,面视官一般都会聊到这个知识点。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入 ,原文链接是:http://www.52im.net/thread-3101-1-1.html

2、什么是字节序?

字节序,是指数据在内存中的存放顺序,当字节数大于1时需要考虑(只有一个字节的情况下,比如char类型,也就不存在顺序问题啦)。

从下图中,可以直观的感受到什么是字节序问题: 

上图片改编自《C语言打印数据的二进制格式-原理解析与编程实现

3、字节序的分类

字节序常被分为两类:

  • 1)Big-Endian(大端字节序):高位字节排放在内存的低地址端,低位字节排放在内存的高地址端(这是人类读写数值的方法);
  • 2)Little-Endian(小端字节序):低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

举个具体的例子,0x1234567 的大端字节序和小端字节序写法如下:

如上图所示:大端小端字节序最小单位1字节,即8bit;大端字节序就是和我们平时写法的顺序一样,从低地址到高地址写入0x01234567;而小端字节序就是和我们平时的写法反过来,因为字节序最小单位为1字节,所以从低地址到高地址写入0x67452301。

4、为什么会存在大端、小端字节序问题?

4.1 比较合理的解释

一个比较合理的解释是说:计算机中电路优先处理低位字节,效率比较高,因为计算机都是从低位开始的,所以计算机内部处理都是小端字节序。

而人类人类读写数值的方法,习惯用大端字节序,所以除了计算机的内部处,其他的场理合都是大端字节序,比如:网络传输和文件储存时都是用的大端字节序(关于网络字节序,会在后面继续展开说明)。

大小端字节序问题,最有可能是跟技术算硬件或软件的创造者们,在技术创立之初的一些技术条件或个人习惯有关。

所以大小端问题,体现在实际的计算机工业应用来上,不同的操作系统和不同的芯片类型可能都会有不同。

4.2 常见的操作系统和芯片使用的字节序

具体来说:DEC和Intel的机器(X86平台)一般采用小端,IBM、Motorola(Power PC)、Sun的机器一般采用大端。

当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端,比如:Arm、Alpha、摩托罗拉的PowerPC。 

而且,具体这类CPU是大端还是小端,和具体设置也有关。如:Power PC支持小端字节序,但在默认配置时是大端字节序。

一般来说:大部分用户的操作系统(如:Windows、FreeBsd、Linux)是小端字节序。少部分,如:Mac OS 是大端字节序。

4.3 如何判断用的是什么字节序?

怎么判断我的计算机里使用的是大端还是小端字节序呢?

下面的这段代码可以用来判断计算机是大端的还是小端。判断的思路是:确定一个多字节的值(下面使用的是4字节的整数),将其写入内存(即赋值给一个变量),然后用指针取其首地址所对应的字节(即低地址的一个字节),判断该字节存放的是高位还是低位,高位说明是Big endian,低位说明是Little endian。

#include <stdio.h>

int main ()

{

  unsigned int x = 0x12345678;

  char*c = (char*)&x;

  if(*c == 0x78) {

    printf("Little endian");

  } else{

    printf("Big endian");

  }

  return 0;

}

5、“大端”、“小端”名字由来

根据网上的资料,据说名字的由来跟乔纳森·斯威夫特的著名讽刺小说《格列佛游记》有关。

书中的故事是这样的:一般来说,大家都认为吃鸡蛋前,原始的方法是打破鸡蛋较大的一端。可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。

小人国内部分裂成Big-endian和Little-endian两派,区别在于一派要求从鸡蛋的大头把鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。

小人国国王改变了打开鸡蛋的方位与理由,并由此招致了修改法律、引发战争和宗教改革等一序列事件的发生。

《格列佛游记》中的这则故事,原本是借以讽刺英国的政党之争。而在计算机工业中,也借用了这个故事来代指大家在数据储存字节顺序中的分歧,并把“大端”(Big-endian)、“小端”(Little-endian)的名字,沿用到了计算机中。 

上图片改编自《“字节序”是个什么鬼?

或许,借用这个故事来命名大小端字节序问题,无非就是想告诉大家,所谓的“大端”、“小端”实际上可能无关计算机性能,更多的只是创造者们在创立计算机之初,代入了个人的一些约定俗成的习惯而已。

6、什么是网络字节序?

6.1 字节序问题给网络通信带来的困扰

对于搞网络通信应用(比如IM、消息推送、实时音视频)开发的程序员来说,自已写通信底层的话是一定会遇到大小端问题的,对于网络字节序这个知识点是一定要必知必会。(当然,你要是很没追求的认为,反正我公司就让租租第3方,能用就行,具体通底层怎么写我才不想掉头发去考虑那么多。。。。 那哥也救不了你。。

上面所说的大小端字节序都是在说计算机自己,也被称作主机字节序。同型号计算机上写的程序,在相同的系统上面运行总归是没有问题。

但计算机网络的出现让大小端问题变的复杂化了,因为每个计算机都有自己的主机字节序。不同计算机之间通过网络通信时:我“说”的你听不懂,你“说”我也听不懂,这可怎么办?

6.2 TCP/IP协议强行约定了字节序方案

好消息是,TCP/IP协议很好的解决了这个问题,TCP/IP协议规定使用“大端”字节序作为网络字节序。

这样,即使不使用大端的计算机也没有关系,因为发送数据的时候可以将自己的主机字节序转换为网络字节序(即“大端”字节序),对接收到的数据转换为自己的主机字节序。这样一来,也就达到了与CPU、操作系统无关,实现了网络通信的标准化。

具体的原理就是:

  • 1)TCP/IP协议会把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;
  • 2)而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节。

也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处)。由此可见,多字节数值在发送之前,在内存中就是以大端法存放的。

所以说,网络字节序就是大端字节序。

6.3 主机字机序到网络字节序的转换

那么,为了程序的兼容,程序员们每次发送和接受数据都要进行转换,这样做的目的是保证代码在任何计算机上执行时都能达到预期的效果。

通信时的这种常用的操作,Socket API这一层,一般都提供了封装好的转换函数,方便程序员使用。比如从主机字节序到网络字节序的转换函数:htons、htonl(C语言中常用),从网络字节序到主机字节序的转换函数:ntohs、ntohl(C语言中常用)。当然,也可以编写自己的转换函数。

7、实践中的大小端字节序处理

在我编写MobileIMSDK的TCP版时(MobileIMSDK是我开源的IM通信层库),同样遇到了大小端字节序问题。

以MobileIMSDK的iOS端拼装网络数据收发的代码为例:

如上图代码所示,注意以下两个大小端转换函数的使用:

  • 1)第27行“CFSwapInt32HostToBig”函数:网络发出数据之前,先将主机字节序转为网络字节序(即大端字节序);
  • 2)第53行“CFSwapInt32BigToHost”函数:收到原始网络数据后,转为主机字节序后就可以在程序中正常使用了。

如果对网络大小端转换这方面的实践感兴趣,可以自已去下载MobileIMSDK源码试一试:https://github.com/JackJiang2011/MobileIMSDK

8、参考资料

[1] “字节序”是个什么鬼?

[2] 大小端及网络字节序

[3] C语言打印数据的二进制格式-原理解析与编程实现

附录:系列文章

本文是系列文章中的第9篇,本系列大纲如下:

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?

脑残式网络编程入门(三):HTTP协议必知必会的一些知识

脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

脑残式网络编程入门(六):什么是公网IP和内网IP?NAT转换又是什么鬼?

脑残式网络编程入门(七):面视必备,史上最通俗计算机网络分层详解

脑残式网络编程入门(八):你真的了解127.0.0.1和0.0.0.0的区别?

脑残式网络编程入门(九):面试必考,史上最通俗大小端字节序详解》(本文)

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

posted @ 2020-08-13 14:58 Jack Jiang 阅读(285) | 评论 (0)编辑 收藏

本文作者网易智慧企业web前端开发工程师马莹莹。为了提升内容质量,收录时有修订和改动。

1、引言

在一个完善的即时通讯IM应用中,WebSocket是极其关键的一环,它为基于Web的即时通讯应用提供了一种全双工的通信机制。但为了提升IM等实际应用场景下的消息即时性和可靠性,我们需要克服WebSocket及其底层依赖的TCP连接对于复杂网络情况下的不稳定性,即时通讯的开发者们通常都需要为其设计一套完整的连接保活、验活以及断片网重连方案。

就断网重连而言,其重连响应速度将严重影响了上层应用的“即时性”和用户体验。试想打开网络一分钟后,微信的网络不能即时感知到socket连接的恢复,无法即时收发聊天消息的话,是不是很崩溃?

因此,如何在复杂网络场景下,更即时快速地感知网络变动,并快速恢复WebSocket的可用性,就变得尤为重要。本文将基于笔者的开发实践,分享WebSocket在不同状态下、不同的网络状态下,应该如何实现快速断网重连。

* 阅读对象:本文适合有过IM底层网络实际开发经验,或者对底层网络实现有较深了解的开发者阅读。如果对底层网络了解甚少,建议跳过本文,直接阅读网络本文末尾附录部分的基础后再回头来看。

* 内容点评:本文内容没有高大上,但比较干货,实用性较高,内容也很通俗,建议可详细阅读。文中虽讲的是WebSocket,但思想可以延伸应用到基于TCP协议的同类技术中。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:点此进入原文链接是:http://www.52im.net/thread-3098-1-1.html

2、预备知识

本文中将要分享的内容是基于实践总结,如果你对Web端的即时通讯知识还一头雾水,务必先读:《新手入门贴:史上最全Web端即时通讯技术原理详解》、《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》。

限于篇幅,本文不会深究WebSocket技术细节,如有兴趣请系统学习:

3、快速了解WebSocket

Websocket诞生于2008年,在2011年成为国际标准,现在所有的浏览器都已支持(详见《新手快速入门:WebSocket简明教程》)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通信协议,可以类比HTTP协议来了解websocket协议。

图片引用自《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

它们的不同点:

  • 1)HTTP的协议标识符是http,WebSocket的是ws;
  • 2)HTTP请求只能由客户端发起,服务器无法主动向客户端推送消息,而WebSocket可以;
  • 3)HTTP请求有同源限制,不同源之间通信需要跨域,而WebSocket没有同源限制。

它们的相同点:

  • 1)都是应用层的通信协议;
  • 2)默认端口一样,都是80或443;
  • 3)都可以用于浏览器和服务器间的通信;
  • 4)都基于TCP协议。

两者和TCP的关系图:

图片引用自《新手快速入门:WebSocket简明教程

有关Http和WebSocket的关系,可以详读:

有关WebSocket和Socket的关系,可以详读:《WebSocket详解(六):刨根问底WebSocket与Socket的关系》.

4、WebSocket重连过程拆解

首先考虑一个问题,何时需要重连?

最容易想到的是WebSocket连接断了,为了接下来能收发消息,我们需要再发起一次连接。

但在很多场景下,即便WebSocket连接没有断开,实际上也不可用了。

比如以下场景:

  • 1)设备切换网络;
  • 2)链路中间路由崩溃(常识是一条socket连接对应的网络通路上,会存在很多路由设备);
  • 3)链路的前端出口不可用(比如家庭WiFi中,网络连接正常,但实际运营商的宽带已经欠费被停机);
  • 4)服务器负载持续过高无法响应等。

这些场景下的WebSocket都没有断开,但对上层来说,都没办法正常的收发数据了。

因此在重连前,我们需要一种机制来感知连接是否可用、服务是否可用,而且要能快速感知,以便能够快速从不可用状态中恢复。

一旦感知到了连接不可用,那便可以弃旧图新了,弃用并断开旧连接,然后发起一次新连接。这两个步骤看似简单,但若想达到快,且不是那么容易的。

首先:是断开旧连接,对客户端来说,如何快速断开?协议规定客户端必须要和服务器协商后才能断开WebSocket连接,但是当客户端已经联系不上服务器、无法协商时,如何断开并快速恢复?

其次:是快速发起新连接。此快非彼快,这里的快并非是立即发起连接,立即发起连接会对服务器带来不可预估的影响。重连时通常会采用一些退避算法,延迟一段时间后再发起重连。但如何在重连间隔和性能消耗间做出权衡?如何在“恰当的时间点”快速发起连接?

带着这些疑问,我们来细看下这三个过程:

5、快速重连关键1:快速感知何时需要重连

5.1 场景

需要重连的场景可以细分为三种:

  • 1)连接明确断开了;
  • 2)连接没断但是不可用了;
  • 3)连接对端的服务不可用了。

对于第一种场景:这很简单,连接直接断开了,肯定需要重连了。

对于后两者:无论是连接不可用,还是服务不可用,对上层应用的影响都是不能再收发即时消息了。

5.2 心跳包主动探测网络可用性

所以从上面这个角度出发,感知何时需要重连的一种简单粗暴的方法就是通过心跳包超时:发送一个心跳包,如果超过特定的时间后还没有收到服务器回包,则认为服务不可用,如下图中左侧的方案(这种方法最直接)。

那如果想要快速感知呢,就只能多发心跳包,加快心跳频率。但是心跳太快对移动端流量、电量的消耗又会太多,所以使用这种方法没办法做到快速感知,可以作为检测连接和服务可用的兜底机制。

5.3 被动监听网络状态改变

如果要检测连接不可用,除了用心跳检测,还可以通过判断网络状态来实现,因为断网、切换wifi、切换网络是导致连接不可用的最直接原因,所以在网络状态由offline变为online时,大多数情况下需要重连下,但也不一定,因为webscoket底层是基于TCP的,TCP连接不能敏锐的感知到应用层的网络变化,所以有时候即便网络断开了一小会,对WebSocket连接是不会有影响的,网络恢复后,仍然能够正常地进行通信。

因此在网络由断开到连接上时,立即判断下连接是否可用,可以通过发一个心跳包判断,如果能够正常收到服务器的心跳回包,则说明连接仍是可用的,如果等待超时后仍没有收到心跳回包,则需要重连,如上图中的右侧。这种方法的优点是速度快,在网络恢复后能够第一时间感知连接是否可用,不可用的话可以快速执行恢复,但它只能覆盖应用层网络变化导致WebSocket不可用的情况。

5.4 小结

综上所述:

  • 1)定时发送心跳包检测的方案贵在稳定,能够覆盖所有场景,但速度不即时(心跳间隔是固定的);
  • 2)判断网络状态的方案速度快,无需等待心跳间隔,较为灵敏,但覆盖场景较为局限。

因此,我们可以结合两种方案:

  • 1)定时以不太快的频率发送心跳包,比如40s/次、60s/次等,具体可以根据应用场景来定;
  • 2)然后在网络状态由offline变为online时立即发送一次心跳,检测当前连接是否可用,不可用的话立即进行恢复处理。

这样在大多数情况下,上层的应用通信都能较快从不可用状态中恢复,对于少部分场景,有定时心跳作为兜底,在一个心跳周期内也能够恢复。

6、快速重连关键2:快速断开旧连接

通常情况下,在发起下一次连接前,如果旧连接还存在的话,应该先把旧连接断开。

这样做的目的:

  • 1)一来可以释放客户端和服务器的资源;
  • 2)二来可以避免之后误从旧连接收发数据。

我们知道WebSocket底层是基于TCP协议传输数据的,连接两端分别是服务器和客户端,而TCP的TIME_WAIT状态是由服务器端维持的,因此在大多数正常情况下,应该由服务器发起断开底层TCP连接,而不是客户端。

也就是说:

  • 1)要断开WebSocket连接时,如果是服务器收到指示要断开WebSocket,那它应该立即发起断开TCP连接;
  • 2)如果是客户端收到指示要断开WebSocket,那它应该发信号给服务器,然后等待底层TCP连接被服务器断开或直至超时。

那如果客户端想要断开旧的WebSocket,可以分为WebSocket连接可用和不可用两种情况来讨论。

具体如下:

  • 1)当旧连接可用时,客户端可以直接给服务器发送断开信号,然后服务器发起断开连接即可;
  • 2)当旧连接不可用时,比如客户端切换了wifi,客户端发送了断开信号,但是服务器收不到,客户端只能迟迟等待,直至超时才能被允许断开。

超时断开的过程相对来说是比较久的,那有没有办法可以快点断开?

上层应用无法改变只能由服务器发起断开连接这种协议层面的规则,所以只能从应用逻辑入手,比如在上层通过业务逻辑保证旧连接完全失效,模拟连接断开,然后在发起新连接,恢复通讯。

这种方法相当于尝试断开旧连接不行时,直接弃之,然后就能快速进入下一流程,所以在使用时一定要确保在业务逻辑上旧连接已完全失效。

比如:

  • 1)保证丢掉从旧连接收到所有数据;
  • 2)旧连接不能阻碍新连接的建立
  • 3)旧连接超时断开后不能影响新连接和上层业务逻辑等等。

7、快速重连关键3:快速发起新连接

有IM开发经验的同学应该有所了解,遇到因网络原因导致的重连时,是万万不能立即发起一次新连接的,否则当出现网络抖动时,所有的设备都会立即同时向服务器发起连接,这无异于黑客通过发起大量请求消耗网络带宽引起的拒绝服务攻击,这对服务器来说简直是灾难(即:服务端雪崩效应)。

所以在重连时通常采用一些退避算法,延迟一段时间再发起重连,如下图中左侧的流程。

如果要快速连上呢?最直接的做法就是缩短重试间隔,重试间隔越短,在网络恢复后就能越快的恢复通讯。但是太频繁的重试对性能、带宽、电量的消耗就比较严重。

如何在这之间做一个较好的权衡呢?

  • 1)一种比较合理的方式是随着重试次数增多,逐渐增大重试间隔;
  • 2)另一方面监听网络变化,在网络状态由offline变为online这种比较可能重连上的时刻,适当地减小重连间隔。

上述第2)种方案,如上图中的右侧所示,随重试次数的增多,重连间隔也会变大。这两种方式配合使用,更为合理。

除此之外,还可以结合业务逻辑,根据成功重连上的可能性适当的调整间隔,如网络未连接时或应用在后台时重连间隔可以调大一些,网络正常的状态下可以适当调小一些等等,加快重连上的速度。

8、本文小结

最后总结一下。

本文将WebSocket断网重连逻辑细分为三个步骤:

  • 1)确定何时需要重连;
  • 2)断开旧连接;
  • 3)发起新连接。

然后分别分析了在WebSocket的不同状态下、不同的网络状态下,如何快速完成这个三个步骤。

过程具体总结就是:

  • 1)首先:通过定时发送心跳包的方式检测当前连接是否可用,同时监测网络恢复事件,在恢复后立即发送一次心跳,快速感知当前状态,判断是否需要重连;
  • 2)其次:正常情况下由服务器断开旧连接,与服务器失去联系时直接弃用旧连接,上层模拟断开,来实现快速断开;
  • 3)最后:发起新连接时使用退避算法延迟一段时间再发起连接,同时考虑到资源浪费和重连速度,可以在网络离线时调大重连间隔,在网络正常或网络由offline变为online时缩小重连间隔,使之尽可能快地重连上。

以上就是我关于如何实现WebSocket快速重连的技术分享,欢迎留言与我探讨。

9、参考资料

[1] RFC 6455 文档

[2] 新手快速入门:WebSocket简明教程

[3] WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

[4] WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)

[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系

附录:更多Web端即时通讯资料

新手入门贴:史上最全Web端即时通讯技术原理详解

Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE

SSE技术详解:一种全新的HTML5服务器推送事件技术

Comet技术详解:基于HTTP长连接的Web端实时通信技术

socket.io实现消息推送的一点实践及思路

LinkedIn的Web端即时通讯实践:实现单机几十万条长连接

Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践

Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)

开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器

使用WebSocket和SSE技术实现Web端消息推送

详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket

MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?

理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性

微信小程序中如何使用WebSocket实现长连接(含完整源码)

八问WebSocket协议:为你快速解答WebSocket热门疑问

快速了解Electron:新一代基于Web的跨平台桌面技术

一文读懂前端技术演进:盘点Web前端20年的技术变迁史

Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?

>> 更多同类文章 ……

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

posted @ 2020-08-05 15:36 Jack Jiang 阅读(366) | 评论 (0)编辑 收藏

本文由有赞技术团队原创分享,原题“有赞 APP IM SDK 组件架构设计”,即时通讯网收录时有修订和改动,感谢原作者的无私分享。

1、引言

本文主要以Android客户端为例,记录了有赞旗下 App 中使用自研 IM,并将IM提炼成组件化SDK的设计思路。此项工作由有赞移动开发组 IM SDK 团队共同讨论完成。

 

在有赞产品中,存在大量需要交易双方沟通交流的场景,比如,客户咨询商家产品信息,售前售后简单的答疑和维权等。另外,有赞业务还存在一些特殊的复杂场景,如供应商、分销商、客户三方之间需要同步沟通,会同时存在多种沟通角色。

此时需要较为完善的即时通信(IM)解决方案,但是由于有赞针对不同的商户和使用场景有多个APP,APP自行实现IM功能代价较大,且维护起来人力分散,于是,IM SDK项目便应运而生了,APP 通过接入此给件化SDK,可以快速实现IM基本功能。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/ANp1kuj65Ww5RpABl2M9RQ,原文链接是:http://www.52im.net/thread-3088-1-1.html

2、相关文章

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》(* 推荐

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

从零到卓越:京东客服即时通讯系统的技术架构演进历程

一套原创分布式即时通讯(IM)系统理论架构方案

蘑菇街即时通讯/IM服务器开发之架构选择

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

3、设计目标

本次IM组件化SDK的设计目标有以下几点:

  • 1)IM 主流程稳定可用:消息传输具有高可靠性;
  • 2)UI 组件直接集成进入SDK,并支持可定制化;
  • 3)富媒体发送集成进入SDK,并可按需定制需要的富媒体类型;
  • 4)实现消息传输层SDK,与带有UI的SDK的功能分离,业务调用方既可以使用消息传输SDK,处理消息,然后自行处理UI,也可以使用带有UI组件的SDK,一步实现较为完备的IM功能。

4、整体结构

下图中简要描述了有赞客户端中IM系统的基本结构 : 

如上图所示,各分层的职责分工如下:

  • 1)消息通道层:维护Socket长连接作为消息通道,消息收发流程主要在这一层中完成;
  • 2)持久化层:主要将消息存入数据库中,富媒体文件存入文件缓存中,方便第二次展示消息时候,从本地加载,而不是网络层获取;
  • 3)逻辑处理层:完成各种消息相关的逻辑处理,如排序,富媒体文件的预处理等;
  • 4)UI显示层:将数据在UI上进行呈现。

5、设计要点1:Socket长连接的创建与维护

IM SDK 所有数据收发流程,均通过Socket长连接完成,如何维护一个稳定Socket通道,是IM系统是否稳定的重要一环。 

下面描述下Socket通道几个重要的流程。

1)创建流程(连接) :

如图上所示,当IM SDK初始化后,业务调用连接请求接口,会开始连接的创建过程,创建成功后,会完成鉴权操作,当创建和鉴权都完成后,会开启消息收发线程,为了维持长连接,会有心跳机制,特别的,会开启一个心跳轮询线程。

2)心跳机制 :

心跳机制,是IM系统设计中的常见概念,简单的解释就是每隔若干时间发送一个固定信息给服务端,服务端收到后及时回复一个固定信息,如果服务端若干时间内没有收到客户端心跳信息则视客户端断开,同理如果客户端若干时间没有收到服务端心跳回值则视服务端断开。 

 

当长连接创建成功后,会开启一个轮询线程,每隔一段时间发送心跳消息给服务器端,以维持长连接。

有关IM心跳方面的专项文章,请见:

手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

为何基于TCP协议的移动端IM仍然需要心跳保活机制?

移动端IM实践:实现Android版微信的智能心跳机制

移动端IM实践:WhatsApp、Line、微信的心跳策略分析

一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)

一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)

手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

3)重连流程 :

重连被触发时,如果该次连接成功,退出重连。反之重连失败后,会判断当前重连的次数是否超过预期值(这里设为6次),并对重连次数计数,如果超过就会退出重连,反之休眠预设的时间后再次进行重连操作。

重连触发条件分为三种:

  • a. 主动连接不成功(主动连接Socket,如果连接失败,会触发重连机制);
  • b. 网络被主动断开(正常建立连接,操作过程中,网络被断开,通过系统广播触发重连);
  • c. 服务器没响应,心跳没回值(服务端心跳预设时间内没回值,客户端认为服务端已经断开,触发重连)。

有关重连机制的深入学习,可以阅读以下两篇:

4)网络状态判断:

TCP API并没有提供一个可靠的方法判断当前长连接通道状态,isConnected()和isClosed()仅仅告诉你当前的Socket状态,不是是长连接断开是一回事。 isConnected()告诉你是否Socket与Romote host保持连接,isClosed()告诉你是否Socket被关闭。 

假如你判断长连接通道是否被关闭,只能通过和流操作相关的以下方法:

  • a. read() return -1;
  • b. readLine() return null;
  • c. readXXX() throw EOPException for any other XXX;
  • d. write 将抛出IOException: Broken pipe(通道被关闭)。

所以SDK封装isConnected(方法的时候,是根据这几种情况综合判断当前的通道状态,而不是仅仅通过Socket.isConnected()或者Socket.isClosed()。

6、设计要点2:消息发送流程

消息发送流程主要有两大类:

1)一类是IM相关数据的请求,例如:历史消息列表,会话列表等;

2)另一类是IM消息的发送,主要是文字消息。

富媒体消息发送,会将富媒体文件先上传服务器后,拿到文件URL, 通过文字消息,将此URL发给接收方,接收方下载后进行UI展示)。 

以上两类消息发送,均使用上图的流程进行发送,可通过发送回调感知请求的结果。

如上图所示,消息发送流程,需要先封装消息请求,在通过发送队列发送至服务器,发送前,在将请求id和对应回调存入本地Map数据结构中。

if(requestCallBack != null) { 

  mCallBackMap.put(requestId, requestCallBack);

}

之后接收服务器推送消息(此消息带有发送请求时的请求id),在本地的Map数据找到请求id对应的回调,然后通过回调返回服务器推送过来的数据。 

请求可以通过泛型指定返回值类型,SDK中会自行解析服务器数据返回的数据,直接返回给业务调用方model对象,方便使用。(目前支持json格式的数据解析)

private void IMResponseOnSuccess(String requestid, String response) { 

        if(mCallBackMap != null) {

           IMCallBack callBack = mCallBackMap.get(requestid);

           if(callBack == null) {

               return;

           }

           if(callBack instanceofJsonResultCallback) {

               finalJsonResultCallback resultCallback = (JsonResultCallback) callBack;

               if(resultCallback.mType == String.class) {

                   callBack.onResponse(response);

               } else{

                   Object object = newGson().fromJson(response, resultCallback.mType);

                   callBack.onResponse(object);

               }

               removeCallBack(requestid);

           }

        }

}

如下的示例中,展示了一个获取会话列表的请求,可以看出目前的请求封装,和一些第三方的的网络库类似,使用起来较为方便。

RequestApi requestApi = new RequestApi(IMConstant.REQ_TYPE_GET_CONVERSATION_LIST, Enums Manager.IMType.IM_TYPE_WSC.getRequestChannel());

 

requestApi.addRequestParams("limit", 100); 

requestApi.addRequestParams("offset", 0);

 

IMEngine.getInstance().request(requestApi, newJsonResultCallback<List<ConversationEntity>>() { 

    @Override

    publicvoidonResponse(List<ConversationEntity> response) {

        mSwipeRefreshLayout.setRefreshing(false);

        mAdapter.mDataset.clear();

        mAdapter.mDataset.addAll(response);

        mAdapter.notifyDataSetChanged();

    }

 

    @Override

    publicvoidonError(intstatusCode) {

        //do something

    }

});

可以看出,该请求直接返回了一个会话类型的List集合,业务方可直接使用。

7、设计要点3:消息接收流程

消息的监听流程主要使用了一个全局监听的方式来进行,需要先注册监听器,监听器中有默认的回调。

public interface IMListener { 

    /**

     * 连接成功

     */

    void connectSuccess();

 

    /**

     * 连接失败

     */

    void connectFailure(EnumsManager.DisconnectType type);

 

    /**

     * 鉴权成功

     */

    void authorSuccess();

 

    /**

     * 鉴权失败

     */

    void authorFailure();

 

    /**

     * 接收数据成功

     */

    void receiveSuccess(int reqType, String msgId, String requestChannel, String message, int statusCode);

 

    /**

     * 接收数据失败

     */

    void receiveError(int reqType, String msgId, String requestChannel, int statusCode);

}

该监听器中可以接收如下类型的消息:

  • 1)Socket连接状态的返回结果;
  • 2)鉴权状态的返回结果,(鉴权流程因有赞业务需要);
  • 3)接收的IM消息,或者其他类型的返回消息。可根据消息类型进行后续的分发处理。

业务如需使用此全局监听器,需要自行实现此接口,并在业务初始化时,注册此监听器即可。SDK中会根据注册的监听器,在读取到服务器推送消息后,直接通过监听器到回调进行分发。

private void distributeData(IMEntity imEntity) { 

        if(mIMListener != null&& imEntity != null) {

       // 省略部分逻辑代码

       ……

       if(status == Response.SUCCESS) {

           switch(responseModel.reqType) {

               caseIMConstant.REQ_TYPE_AUTH: // 鉴权成功

                   mIMListener.authorSuccess();

                   return;

 

               caseIMConstant.REQ_TYPE_OFFLINE: //  服务端踢客户端下线

                   mIMListener.connectFailure(EnumsManager.DisconnectType.SERVER);

                   break;

 

               caseIMConstant.REQ_TYPE_HEARTBEAT: // 心跳成功

               caseIMConstant.REQ_TYPE_RECEIVER_MSG: // 收到回调消息

                   handleMessageID(responseModel.body);

                   break;

               default:

                   break;

           }

           mIMListener.receiveSuccess(responseModel.reqType, msgId, responseModel

                   .requestChannel, responseModel.body, 0);

       } else{

           mIMListener.receiveError(responseModel.reqType, msgId, responseModel

                   .requestChannel, status);

       }

   }

}

部分接收消息,如心跳,多端登录时被踢下线通知等,sdk内部会自行处理,业务基本无感知。

8、设计要点4:可定制化的UI

随着公司规模的扩大与业务线的快速迭代,可能新的业务也需要 IM 这个功能,众所周知,IM UI 功能的嵌入会占据大量的开发与调试时间, 为了解决这个痛点,决定将 IM UI 部分抽成一个 Library,实现可定制与单独维护,做到真正的敏捷开发与快速迭代。

8.1 UIKit设计 

IM UIKit暴露相应的api接口,业务方注入相应的功能定制项,针对UI的点击回调通过EventBus总线post分发,减少了业务方与UIKit的耦合,底层业务方通过MVP模式对View与Model进行解耦。

定制项一般通过如下几种方式。

1)XML(定制业务信息,资源信息,显示条数,各个业务功能开关等):

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <stylename="limit">

        <!--每屏展示的条数-->

        <itemname="swiplimit">5</item>

        ......

    </style>

    ......

    ......

    <stylename="itembox">

        <itemname="showvoice">true</item>

        ......

        ......

        <itemname="more"show="true">

            <more>

                <iconstyle="mipmap">im_plus_image</icon>

                <itemname>测试</itemname>

                <callback>false</callback>

            </more>

             ......

             ......

            <more>

                <iconstyle="mipmap">ic_launcher</icon>

                <itemname>测试</itemname>

                <callback>true</callback>

            </more>

        </item>

        ......

        ......

    </style>

</resources>

2)Style(定制UI背景,气泡颜色,字体大小等):

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <!--im 聊天背景-->

    <stylename="imui_background">

        <itemname="android:background">@android:color/holo_red_dark</item>

    </style>

    ......

    ......

 

    <!--气泡背景-->

    <stylename="bubble_background">

        <itemname="android:background">@mipmap/bubble_right_green</item>

    </style>

 

       <!--背景和和字段颜色定制-->

    <stylename="bg_and_textcolor"parent="bubble_background">

        <itemname="android:textColor">@android:color/holo_red_dark</item>

    </style>

    ......

    ......

</resources>

3)Model定制(传入预设的定制Model模板填入相应参数,UIKit里面做相应解析):

public class Entity {

    publicString action1;

    publicString action2;

    publicString aciton3;

    ......

}

8.2 UIKit 支持的富媒体类型

除了文字消息之外,现在主流的IM系统中也支持各种富媒体发送,在有赞IM SDK UIKit中,目前也支持几种富媒体发送。 以下是发送流程图和两类常见富媒体消息简介。

  • 1)语音消息:除了使用常见的录制和解码播放的技术之外。还利用了 AudioManager 中 requestAudioFocus,abandonAudioFocus 相关方法,实现了录制和播放语音消息,如果有第三方播放音乐,会自动暂停,录制和播放语音消息结束后,声音会自动播放。
  • 2)图片消息:通过七牛服务器设置了缩略图,接收方收到消息后,会先下载缩略图,当用户再点击进入图片详情页时,会下载大图,Andorid客户端使用Picasso加载库加载图片,并做本地缓存。

9、设计要点5:UI 中聊天会话数据加载策略

参考业界主流的IM系统方案,用户聊天时,需要将已经发送和接收到的聊天信息保存到本地,而不是每次都拉取历史数据。以达到节约流量和无网络状态下也查看数据的效果。

为此IM SDK持久化层的数据库中,也实现了简单存储加载机制,下面描述典型的数据加载场景。

1)IM会话首次请求数据流程:

2)IM下拉获取历史数据流程: 

3)IM单条消息发送持久化方案:

4)IM单条数据重发流程: 

10、设计不足之处

1)消息回执:

当前的设计方案中,没有消息回执的机制,也就是说接受方收到消息后,不会返回服务器收到消息的通知,服务器无法判断消息是否推送成功,这样在突然断网,网络模式切换,或者弱网环境下,会影响消息的到达率。 

一种可行的设计方式是,发送方增加已送到和未送达的状态,接收方收到消息后,给服务器返回已收到消息的通知,服务器再推送给发送方该状态,如果没有收到接收方回执,服务器可尝试重新推送。发送方接受到接收方的收到回执后,更新发送状态已发送,如果未收到,则显示未送达。为了防止接收方回执丢失,接收方接收消息时候,可维护本地去重队列。

2)本地请求超时的判断:

本地发起的请求,没有用定时器,完全依赖服务器返回或者出现Socket通道异常后上抛的通知作为超时判断,部分场景可能覆盖不到,需要对请求增加固定的超时处理机制,固定时候未收到请求,即认为超时。

* 推荐学习:针对以上两点不足,感兴趣的读者,可以研究一下MobileIMSDK开源工程源码https://github.com/JackJiang2011/MobileIMSDK,MobileIMSDK已经实现了完整的消息送达保证机制(包括:ACK回执、重传、去重、超时判定等等)。

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

posted @ 2020-07-29 14:09 Jack Jiang 阅读(227) | 评论 (0)编辑 收藏

     摘要: 本文引用自公众号“开发的猫”,本次收录时有改动,感谢原作者“开发的猫”的分享。1、引言随着移动网络速度越来越快、质量越来越来,实时音视频技术已经在各种应用场景下全面开花,语音通话、视频通话、视频会议、远程白板、远程监控等等。实时音视频技术的开发也越来越受到重视,但是由于音视频开发涉及知识面比较广,入门门槛相对较高,让许许多多开发者望而生畏。虽然网上有...  阅读全文

posted @ 2020-07-24 14:13 Jack Jiang 阅读(364) | 评论 (0)编辑 收藏

1、点评

IM聊天消息的可靠投递,是每个线上产品都要考虑的IM热点技术问题。

IM聊天消息能保证可靠送达,对于用户来说,就好比把钱存在银行不怕被偷一样,是信任的问题。试想,如果用户能明显感知到聊天消息无法保证送达,谁还愿意来用你的APP?谁也不希望自已的话就像浮云一样随风飘逝。

必竟用IM聊天,虽然很多时候是费话,但总有关键时刻存在——比如向女神表白(哪怕明知被拒),作为合格的舔狗一定不希望女神错过这条消息。

所以,消息的可靠投递是每款IM产品和立足之本,也是IM开发者们孜孜不倦追求的技术目标。

本文作者将以自已IM开发过程中的真实总结,分享针对大量离线聊天消息,在确保用户端体验不降级的前提下,保证离线消息的可靠投递。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/T2w9h_AN_T2UnqNdVikX0Q,原文链接是:http://www.52im.net/thread-3069-1-1.html

 

2、本文作者 

fzully(柳林勇):2005年数学系毕业,先后就职于福建新大陆、福建富士通、北京世纪奥通。长期从事服务端软件开发,涉及SIP服务器、内核RTP转送、电信级AAA认证系统、IM即时通讯系统等。在分布式高性能系统设计有多年经验积累。

本作者的另一篇:《IM群聊消息的已读未读功能在存储空间方面的实现思路探讨》也已被即时通讯网收录并整理发布,有兴趣可以前往阅读。

3、相关文章

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

移动端IM中大规模群消息的推送如何保证效率、实时性?

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递》(* 强烈推荐

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM群聊消息如此复杂,如何保证不丢不重?》(* 强烈推荐

移动端IM登录时拉取数据如何作到省流量?》(* 强烈推荐

完全自已开发的IM该如何设计“失败重试”机制?

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的》(* 强烈推荐

4、正文引言

暗恋女神良久,终于鼓起勇气决定向女神写一封情书。但如何表达才能感动女神?自感才疏学浅,于是通读四书五经、熟背唐诗宋词、遍览四大名著,已然腹有诗书气自华。一周末冥思苦想整日才写就一首七言律诗,虽无惊天地泣鬼神之势,但诚挚的爱念在字里行间里流淌,亦歌亦诗,相信会感动到女神,手机欣然发出。

发出一秒后,手心冒汗,感觉脸颊发烫,心脏像受惊吓的野兔一样快速跳动,就像第一次看见女神那时的感觉。闭着眼睛,想象女神看到消息时的情形,她是否也期盼我的表白?看到消息时是否心跳加速、小脸绯红?

一分钟后,紧盯手机屏幕,等待、期盼女神回复。

时间一分一秒地逝去,等一分钟像等一年一样漫长。

一小时后,仍然杳无音讯,难道她没看到消息么?或许在忙什么而没留意手机吧!

一天过去了,坐立不安,等待是一种痛苦的煎熬,期待和煎熬在心中交织翻滚,有几个瞬间甚至希望女神赶快拒绝自己,好让自己解脱!茶饭无味,失眠多天,整日魂不守舍。

一个月过去了,死心。

半年后,女神出嫁,婚礼那天前去祝福。席间亦随众觥筹交错,略有醉意,向女神敬酒:祝福你,但愿以后能遇见像你这样的女人。女神先是愣住、收起笑容,低下头,目光无神地看着大红地毯,长叹一声,言:我等你表白,等了一年!空气凝滞了几秒,女神强作欢颜:从今往后,各自安好吧,干杯!

我转身踱回到座位,拿起手机,打开那个App,看着曾经发出的情书,一切仿佛还在昨日,但故事脚本已被别人书写,欲哭无泪,叹老天为何如此捉弄我?为何我发的消息女神没收到啊!

失魂落魄地回到家里,从冰箱里拿出几瓶罗斯福10号来麻醉自己,在酒精强烈的作用下,迷迷入睡。

第二天醒来,我明白了一个道理:对IM系统而言,消息必达永远摆在第一位!

以上是胡说八道,以下开始正文。。。

5、用全量离线消息实现消息必达

我们在重构IM系统时,需解决上一代设计的痛点之一就是确保消息必达。

5.1 离线消息实现消息必达的流程

自然而然地会想到这么做——即由服务端为每个人保存一个“离线消息列表”。

具体的思路是这样:

  • 1)当用户在线时,由IMS主动确保消息下发且收到客户端的应答确认时,才认为消息送达客户端,相应地把消息从“离线消息列表”移除;
  • 2)如果客户端没有发回应答确认,IM服务端会再发送。

以此来确保消息一定送到客户端,看起来是很符合逻辑。当时调查过市面上多款IM,行为基本如此。

5.2 海啸般的离线消息

5.2.1)和平时期:

重构后的IM上线,内部测试及在公网运行,离线消息的工作一直很正常。

5.2.2)被签到签死了:

后来,为某客户部署的私有环境,其用户量达几十万,其中的一个组织接近三万人,全员群也接近三万人;还有,底下的部门也有相应的群组,几百到几千人群不等。

“报到”、“签到”。。。大量的类似消息被发到几千、几万人的群内,然后如果有人一两天没上线,或者被加入到多个组织内,等到其上线时,几万条离线消息像海啸一般涌来,您想象一下:手机用户刚登陆的几分钟内,是什么场景?

用户真的很无辜:我不就是登陆了一下App,叮叮咚咚响了几分钟,还卡,还发热。。。

客户端承受不起大规模离线消息的轰炸,怎么办?

5.3 临时运用方案

  • 1)对若干大组织的全员群,对非管理员禁言;
  • 2)通知所有用户不要在大群签到。

我承认,这确实不算是个正经方案。。。

6、远离全量离线消息

我承认,一开始设计离线消息时,真没想到是这样的使用场景。对于大多数IM的开发者,或许不会碰到这种场景(但凡事住最坏的可能性想,总是没错的)。

6.1 放弃以离线消息的形式实现消息必达

我开始思考什么是消息必达,以前的想法是:把用户该收的消息都送到其客户端,是消息必达。

后来,给消息必达下了新的定义:

  • 1)用户有新消息时,确保让用户知道;
  • 2)当用户要查看这些消息时,确保其可一条不漏地看到。

打个比方:

  • 1)客户要把钱给您,不必送到您家里才算送到;
  • 2)而是转账到您的银行账户上,并告知您;
  • 3)当您要用钱时,直接从银行账户上消费即可。

从此,不会在用户上线时向其发送大量离线消息(即全量推送)。

6.2 以会话列表为基础来实现消息必达

客户端在上线时,先从服务端更新会话列表,也就是你通常在每个IM客户端的首页看到的这个(如下图所示)。

上图引用自《IM开发快速入门(一):什么是IM系统?

每一个会话列表项包含如下信息(此处简化了与本文无关的成员变量):

{

        // 会话对象的角色类型,比如私聊、群聊、系统通知、业务通知。。。

        uint32  session_role;

        // 会话对象的ID

        uint32        session_id;

        // 会话时间戳,用于消息同步;

        // 指会话的最后操作时间,比如清除角标的时间,与会话最后一条的消息时间未必一致

        uint64 session_timestamp;

        // true表示新增或更新,false表示被删除

        boolis_add;

 

        // 当is_add=false时,忽略以下信息

 

        // 仅用于显示角标的未读数量,当用户查看该会话后清零,且客户端多端同步

        uint32 new_msg_count;

        // 会话的最后一条消息

        MessageItem         latest_msg;

        // 跳转消息的时间戳,即new_msg_count的最旧1条消息的时间

        uint64 goto_timestamp;

}

为方便讨论,假设以下前提:

  • 1)周五傍晚18:00下班,我关闭App,我是9527;
  • 2)有1小姐姐向我发了5条消息留言,约我周末去海边玩,她是杨幂3306;
  • 3)然后,另1小姐姐也向我发了33条消息留言,内容我不便透露,她是景甜5672;
  • 4)严正声明:我跟她们很清白,其实我喜欢的是6379。

对,既然是假设,假一点也无妨。

我下班回到家,看到手机有通知栏消息,打开App将会发生哪些事呢?

App和IM后端的交互:

1)登录后,App以18:00填充参数latest_session_time,向IMS获取会话列表(其实不是以下线时间18:00,但这样更易理解);

2)IM后端检查发现我从18:00开始,有2个会话更新了,于是向App发送应答,以增量形式携带2个会话项:杨幂3306,景甜5672。其中景甜5672的会话项信息如下:

{

        uint32  session_role = Role_User; //表示私聊

        uint32        session_id = 5672; //景甜的ID

        uint64  session_timestamp = 1594464295335672; //最后一条消息的时间戳,微秒

        boolis_add = true; // true表示是更新项

        uint32  new_msg_count = 33; // 景甜向我发了33条消息

        MessageItem         latest_msg = "房号是0520"; //最后1条消息,结构体MessageItem简略不表

        uint64  goto_timestamp = 1594463697556677; // 向我发的33条消息的最早1条的时间

}

3)App收到步骤2的应答,我在App的会话列表窗口里,能看到2项更新,景甜发来的未读消息数33条,杨幂的是5条,如下图所示:

 

4)点开景甜5672的会话,App将向IMS发起同步消息的请求,获取最新的10条聊天消息(为了显示一屏):

{

        uint32  session_role = Role_User; //表示私聊

        uint32        session_id = 5672; //景甜的ID

        uint64        begin_time  = 1594464295335672; //步骤2返回的session_timestamp

        uint64        end_time  = 1594434153444222; //景甜上午向我发的最后一条消息的时间

        uint32        max_pieces = 10; //本次最多取10条,PC屏幕大则不妨取20条

}

5)IM后端收到步骤4请求,将返回33条新消息的最后10条给App,呈现聊天窗口内,且聊天窗口上方有一个tip:“↑ 33条新消息”,如下图所示:

 
 

6)我可以向上翻动聊天记录,那么App将持续向IMS获取第2批同步消息;或者也可以点击tip:“↑ 33条新消息”,直接跳转到33条消息的最旧一条,这样支持从最旧的消息向新的翻看。

相比于客户端简单地被动接收服务端的离线通知方式,这种设计使得客户端的处理逻辑更复杂。

主要体现在:

  • 1)客户端向服务端取的同步消息是未必完整,这些存在客户端的消息,在时间区间上可能不连续的;
  • 2)客户端需要知道不同消息之间是否有断代,如果有则需要向服务端查询同步消息来merge本地信息,使其连续,即客户端要实现消息融合。

我的建议:用C++实现一个统一的底层imsdk库,来负责这些共通的消息处理和存储。避免各客户端(Windows,iOS,Android等)各自实现这些逻辑,减少工作量,也降低各端不一致的风险。

6.3 以会话列表为基础与用全量离线消息的方案对比

6.3.1)用全量离线消息实现的方案优缺点:

实现原理:由IM服务端确保消息送达客户端,客户端存储后发回确认。

方案优点:逻辑简单。

在聊天消息不同数量级时的表现:

  • a. 离线消息量不多(如几百条):没有效率问题,且消息全部达到客户端本地,方便进行查找等动作;
  • b. 离线消息量巨大(如几万条):用户登录瞬间CS间瞬时流量大,客户端瞬时要存储、更新的数据量巨大,可能出现卡顿、假死等情况。

6.3.2)用会话列表为基础的方案优缺点:

实现原理:客户端先同步会话列表,由用户驱动不定次获取同步消息。

方案缺点:逻辑复杂,客户端增加不少工作。

在聊天消息不同数量级时的表现:

  • a. 离线消息量不多(如几百条):没优势;
  • b. 离线消息量巨大(如几万条):登录时交互数据小,对IM后端、客户端、用户体验,都比较友好。

7、多终端条件下,如何得到完整消息履历?

由于同一个用户的每个终端,其会话最后更新时间、每个会话的最后一条时间可能都不一样,参照上一节的实现思路,可以得到解决方案。

具体如下:

  • 1)参照第6.2章节的“App和IM后端的交互”第1个步骤,可取到不同的增量变化的会话列表项;
  • 2)参照第6.2章节的“App和IM后端的交互”第4个步骤,可取到任一区间的同步消息,得到完整消息。

8、离线消息是否就彻底废弃了?

有若干情况,仍然需要保留离线消息,以确保消息送达。

比如以下情形:

  • 1)别人向我发送离线文件:这种情况下不能依赖同步消息来获取。因为不以离线消息通知的话,用户在没有拉取到对应的同步消息前,是不知道有离线文件的;
  • 2)撤回消息:即使接收者不拉取同步,仍然要保证在上线后其数据在第一时间被撤回。注意:这里可能存在多端撤回问题;
  • 3)用户在线时的消息下发:由于用户在线时,IM后端向客户端发送消息可能碰到网络抖动等情况,导致消息下发失败,这些消息先可以直接存在离线消息队列,IM后端可在收到客户端的心跳包时重发消息。相当于维护了一个在线消息的离线队列。

9、本文结语

曾经有一段真挚的爱情摆在我面前,如果时间倒流到半年前,我会选择一个靠谱的IM来发送消息,也许故事的脚本就由自己书写——是否要整一个时光倒流的版本,抱得美人归的那种?

不整了不整了,我得不到女神,你们才欢喜,我太了解你们了。。。各位爷欢喜就好。

附录:IM开发干货系列文章

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

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

如何保证IM实时消息的“时序性”与“一致性”?

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)

移动端IM登录时拉取数据如何作到省流量?

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

浅谈移动端IM的多点登陆和消息漫游原理

IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理

IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?

IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议

IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

IM群聊消息的已读回执功能该怎么实现?

IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

一个低成本确保IM消息时序的方法探讨

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路

IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质

IM的扫码登功能如何实现?一文搞懂主流应用的扫码登陆技术原理

IM要做手机扫码登陆?先看看微信的扫码登录功能技术原理

IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!

IM开发实战干货:我是如何解决大量离线聊天消息导致客户端卡顿的

IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》(* 本文)

另外,如果您是IM开发初学者,强烈建议首先阅读《新手入门一篇就够:从零开发移动端IM》。

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

posted @ 2020-07-21 13:58 Jack Jiang 阅读(441) | 评论 (0)编辑 收藏

本文在编写时参考了博客作者“鹿呦呦”和在线课程“即时消息技术剖析与实战”的相关资料,一并表示感谢。

1、系列文章引言

IM系统看似简单(没错,很多土老板认为开发个qq和微信也就是几万块钱的事... ),实责是众多技术的应用合体,包括网络编程、移动开发、后端开发、高并发、高可用、高安全等技术范畴,再加上多端使用不同的编程语言,想要凑齐一个典型的IM产品技术栈那也不是个容易事。

而对于IM开发入门者来说,想要在众多的IM技术术语和概念中找到学习的方向和需要的资料,那也是件很让人抓狂的事。如果看到不该看的技术深水区文章,直接从入门到放弃——被活活吓退,那也是相当悲剧的。

本系列文章将尽量从理论概念入手,通俗易懂的梳理IM中的基础技术概念和热门技术点,希望能帮你理清看似一团乱麻的IM知识体系,助你找到清晰的IM技术学习方向,来日工资翻倍、迎娶白富美也未必不可能!

 

友情提示:本系列文章侧重于理论概念的讲述,篇幅有限,点到即止,如需系统、深入、具体地学习IM技术的方方面面,请从此文入手:《新手入门一篇就够:从零开发移动端IM》(史诗级文章,适合从入门到放弃)。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

2、系列文章目录

IM开发快速入门(一):什么是IM系统?》(* 本文

《IM开发快速入门(二):什么是IM系统的实时性? (稍后发布)》

《IM开发快速入门(三):什么是IM系统的可靠性? (稍后发布)》

《IM开发快速入门(四):什么是IM系统的一致性? (稍后发布)》

《IM开发快速入门(五):什么是IM系统的安全性? (稍后发布)》

《IM开发快速入门(六):什么是IM系统的的心跳机制? (稍后发布)》

《IM开发快速入门(七):如何理解并实现IM系统消息未读数? (稍后发布)》

《IM开发快速入门(八):如何理解并实现IM系统的多端消息漫游? (稍后发布)》

3、本文内容概述

本文将带你快速了解一个主流IM系统的应用场景、典型架构、技术特点和功能组成,帮你快速建立对IM系统的主观认知。

如果你不想从技术的角度理解IM原理,可以尝试阅读此文:《知识科普:IM聊天应用是如何将消息发送给对方的?(非技术篇)》。

本文已收入即时通讯网的入门纲领性文章《新手入门一篇就够:从零开发移动端IM》。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/h7L4UGHRl7qI1bi8WyZ5iw,原文链接是:http://www.52im.net/thread-3065-1-1.html

4、IM的应用场景

IM其实并不局限于聊天、社交这类“典型”应用中,实际上它已经广泛运用于我们身边形形色色的软件中。

聊天、直播、在线客服、物联网等所有需要实时互动、高实时性的场景等等,都需要应用到 IM 技术。

下面这些场景是我们大家都熟悉的,都用到了IM技术:

  • 1)微信、qq、钉钉等主流IM应用:这是IM技术的典型应用场景;
  • 2)微博、知乎等社区应用:它们利用IM技术实现了用户私信等点对点聊天;
  • 3)抖音、快手等直播/短视频应用:它们利用IM技术实现了与主播的实时互动;
  • 4)米家等智能家居物联网应用:利用IM技术实现实时控制、远程监控等;
  • 5)滴滴、Uber等共享家通类应用:利用IM技术实现位置共享;
  • 6)在线教育类应用:利用IM技术实现在线白板。 

5、IM的典型架构

一个典型的IM架构类似于下图这样:

本图引用自《即时消息技术剖析与实战》学习笔记1——IM系统的架构》一文

如上图所示,IM架构中的各分层职责如下:

  • 1)客户端:作为与服务端进行消息收发通信的终端;
  • 2)接入层:也叫网关层,为客户端收发消息提供入口;
  • 3)逻辑层:负责IM系统各功能的核心逻辑实现;
  • 4)存储层:负责IM系统相关数据的持久化存储,包括消息内容、账号信息、社交关系链等;
  • 5)第三方服务:保证APP在未打开或后台运行时也能收到消息通知(这主要是第第3方消息推送服务)。

尤其对于“接入层”,它的职责最为关键,具体是:

  • 1)保持海量用户连接;
  • 2)解析协议,对传输内容进行编解码;
  • 3)维护客户端的连接(也叫“Session”);
  • 4)推送消息。

以下文章适合IM架构设计入门,有兴趣可以读一读:

6、IM技术的特点

IM技术的特点主要就是以下4点: 

▶ 1)实时性:

对于IM系统,“实时”二字是精髓,也是这项技术存在关键意义所在。它保证的是消息的实时触达。

举个例子:如果跟你的好友微信或qq聊天,我发的消息他不能即时收到,或者他发的信息你也不知道什么时候能收到,这基本上也就没法聊下去了(干吗不痛快打个电话呢)。

▶ 2)可靠性:

保证消息的不丢失和不重复,是IM系统的另一个关键技术特点。试想,当你在用qq或微信跟女朋友聊天,好不容易鼓起勇气向“她”表白,结果这消息要是丢包了,那肯定得卸载应用了,搞不好砸手机都有可能。当然,好话不说二遍,消息重复也同样恼人。

以下文章对消息的不丢/不重问题进行了深入探讨,有兴趣可以详读:

▶ 3)一致性:

对于单聊消息而言,保证同一个设备的时间顺序、不同设备的漫游同步,也是相当重要的一环。

IM系统中的消息交互,就到底就是人跟人在“说话”,前言不搭理后言、或者胡言乱语式的消息展现,那不是人疯了就是程序疯了,总之就是没法再聊下去了。

以下文章对消息时序问题进行了深入探讨,有兴趣可以详读:

▶ 4)安全性:

保证数据传输安全、数据存储安全、消息内容安全,也是IM系统必不可少的特性。尤其在私聊场景下,如果不能做到安全性,聊天的体验跟被人偷窥的感觉是没有区别的。

以下文章对IM的安全问题进行了深入探讨,有兴趣可以详读:

7、IM的功能组成

浅显的角度讲,一个典型的IM功能组成,无非就是以下5样:

  • 1)联系人列表;
  • 2)聊天界面;
  • 3)消息发送通道;
  • 4)消息接收通道;
  • 5)消息存储;
  • 6)消息未读数。

我们一样一样来说说各自的用途。

▶ 1)联系人列表:

这个很好理解,使用IM系统的第一步,就是要解决“跟谁聊”的问题。从功能表象上来说,联系人列表也就是社交关系列表,无非就是个信息列表界面,有什么特殊的地方?

联系人列表看似简单,实际上它是一系列IM系统的社交关系确立动作的结果体现。

要想建立联系人列表,你可能需要实现以下逻辑:

  • 1)怎么能找到想要聊天的人?(需要实现随机查找?精确查找?)
  • 2)怎么决定要不要跟这个人聊?(需要实现对方的个人信息查看)
  • 3)开始发出好友请求;
  • 4)被请求的一方,还可以决定是“同意”还是“拒绝”(“同意”该怎么处理?“拒绝”又该怎么处理?)。

总的来说,联系人列表的建立,是一个IM系统聊天关系确立的表现,不可或缺。

▶ 2)聊天界面: 

聊天界面看似很平常,实际它就是IM系统客户端的核心功能所在,所有主要的IM功能都是通过它展现。

它应该具备的能力有:

  • 1)各种聊天功能按钮:语音留言、图片、文字、表情、文件、实时电话、实时视频等;
  • 2)各种聊天消息显示:各种消息都有不同的UI显示元素和处理逻辑;
  • 3)流畅的使用体验:大量不同类型的消息显示时,不能卡顿;
  • 4)即时显示聊天消息:网络线程收到的消息,要马上在UI上显示出来;
  • 5)历史消息的加载:上次聊过的内容也得显示出来吧。

以上只是简单罗列,这看似简单的聊天界面,能把上面列表的事情做好,工作量也不小吧。

▶ 3)消息发送通道:

下图是一个典型的IM消息收发通道示意: 

如上图所示,消息发送通道这个比较好懂,最浅显易懂的理解就是用tcp或udp,建立socket长连接,需要发消息的时候,wirte一下就过去了,好简单!

但,事情往往不是想象的这么简单:

  • 1)如何保证这条socket长连接时一直处于可用的状态?
  • 2)当socket长连接不可用时,用户此时发送的消息该怎么处理?
  • 3)怎么保证发送的消息不丢?
  • 4)怎么保证发送的消息不复重?
  • 5)怎么保证发送的消息乱序?
  • 6)当对方不在线时,发送的消息去哪了?
  • 7)发送的消息,能保证实时送到?

这么一说,事情还挺多(那不废话吗。。。)。

▶ 4)消息接收通道:

正如上节中的消息收发通道示意图所示,消息接收通道也很好理解,对方通过消息发送通道write的消息,我得收到并显示啊。

要实现一个可靠的消息接收通道,也并非易事:

  • 1)如何保证socket长连接通道能随时处于良好的边接状态(随时接收对方write的消息);
  • 2)当socket长连接断开时,对方发送消息该怎么实现?
  • 3)当socket恢复连接时,怎么恢复之前的聊天现场?
  • 4)当我收到对方的消息时,对方怎么知道我已经收到了?
  • 5)当重复收到对方的消息时,该怎么处理?
  • 6)当收到的消息时序有错乱,该怎么处理?

▶ 5)消息存储:

消息存储这个功能好理解,聊天的消息如果存储,下次再聊的时候就不知道之前聊过什么,做不到这一点,这个IM系统的聊天体验好不起来。

那么,哪些情况下需要进行消息存储呢:

  • 1)对方不在线时:聊天消息应该存储(这叫离线消息存储);
  • 2)对方在线时:聊天消息也要存到本地存储(这叫消息缓存);
  • 3)对方在线或不在线时:聊天消息都要存到服务端(用于实现多设备的消息漫游和同步)。

具体要存储的内容和时机也就上面这几样。

但技术落到实处,要做的事情同样少不了:

  • 1)离线消息该怎么多久?
  • 2)图片、短视频、大文件这类的离线消息,多媒体文件该怎么存(有可能量会很大)?
  • 3)当本地的消息积累太多时,怎么能保证本地存储的性能?
  • 4)当应用更新、升级或异常时,怎么能保证本地存储的完整性(不被破坏)?
  • 5)怎么能保证多设备消息能不丢、不重、不乱?

这么多需要考虑的内容,也挺让人抓狂。

下图是一个IM系统的典型存储架构设计,了解一下:

本图引用自《现代IM系统中聊天消息的同步和存储方案探讨》一文

存储是IM系统的基石,以下文章可以深入阅读:

▶ 6)消息未读数:

消息未读数?看起来也就是那个所有IM应用都有的未读小红点嘛。是的,看起来也好简单!

然而,消息未读数功能的实现也一样不简单:

  • 1)未读数是客户端实现还是服务端实现?
  • 2)会话未读和总未读怎么保持一致?
  • 3)多终端情况下,怎么保证未读数的一致性(我在这台设备上读没读,那台设备怎么知道的?)?

是的,看起来就这么简简单单的3件事,但深入思考一下,还真的简单不起来。

8、本文小结

IM系统的应用场景已经不单单是IM聊天应用这一种形态,它已经融入到互联网应用的方方面面,必竟谁都想自已的应用具备“实时”交互这种能力,因为体验太好了。

IM系统典型架构无非就是网络接入层、业务逻辑层、数据存储层,除开网络接入层,其它各层其实跟普通的应用系统看起来差别并不是太大。

IM系统的技术特点来说,就是实时性、可靠性、一致性、安全性,除了实时性对于多数应用来说并不关心,其它的指标也很好理解。

IM系统的功能组成上,联系人列表用于数据模型的建立、聊天界面承载了IM系统的终端展现、消息的收发通道用于实现“实时”这个特性、存储和未读数看似不是必须但用户体验上确必不可少。

附录:更多IM开发资料汇总

[1] 有关IM架构设计的文章:

浅谈IM系统的架构设计

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

一套海量在线用户的移动端IM架构设计实践分享(含详细图文)

一套原创分布式即时通讯(IM)系统理论架构方案

从零到卓越:京东客服即时通讯系统的技术架构演进历程

蘑菇街即时通讯/IM服务器开发之架构选择

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT

微信后台基于时间序的海量数据冷热分级架构设计实践

微信技术总监谈架构:微信之道——大道至简(演讲全文)

如何解读《微信技术总监谈架构:微信之道——大道至简》

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

17年的实践:腾讯海量产品的技术方法论

移动端IM中大规模群消息的推送如何保证效率、实时性?

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

WhatsApp技术实践分享:32人工程团队创造的技术神话

微信朋友圈千亿访问量背后的技术挑战和实践总结

王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等

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

腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面

以微博类应用场景为例,总结海量社交系统的架构设计步骤

快速理解高性能HTTP服务端的负载均衡技术原理

子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)

新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等

社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进

社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节

社交软件红包技术解密(四):微信红包系统是如何应对高并发的

社交软件红包技术解密(五):微信红包系统是如何实现高可用性的

社交软件红包技术解密(六):微信红包系统的存储层架构演进实践

社交软件红包技术解密(七):支付宝红包的海量高并发技术实践

社交软件红包技术解密(八):全面解密微博红包技术方案

社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等

社交软件红包技术解密(十):手Q客户端针对2020年春节红包的技术实践

从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路

从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

从游击队到正规军(三):基于Go的马蜂窝旅游网分布式IM系统技术实践

微信后台基于时间序的新一代海量数据存储架构的设计实践

IM开发基础知识补课(九):想开发IM集群?先搞懂什么是RPC!

>> 更多同类文章 ……

[2] IM开发热门综合文章:

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

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

IM开发基础知识补课:正确理解前置HTTP SSO单点登录接口的原理

移动端IM中大规模群消息的推送如何保证效率、实时性?

移动端IM开发需要面对的技术问题

开发IM是自己设计协议用字节流好还是字符流好?

请问有人知道语音留言聊天的主流实现方式吗?

IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

IM消息送达保证机制实现(二):保证离线消息的可靠投递

如何保证IM实时消息的“时序性”与“一致性”?

一个低成本确保IM消息时序的方法探讨

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

谈谈移动端 IM 开发中登录请求的优化

移动端IM登录时拉取数据如何作到省流量?

浅谈移动端IM的多点登录和消息漫游原理

完全自已开发的IM该如何设计“失败重试”机制?

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

微信对网络影响的技术试验及分析(论文全文)

即时通讯系统的原理、技术和应用(技术论文)

开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀

如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源

全面掌握移动端主流图片格式的特点、性能、调优等

子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践

IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列

微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)

自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)

融云技术分享:解密融云IM产品的聊天消息ID生成策略

IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!

适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

拿起键盘就是干:跟我一起徒手开发一套分布式IM系统

适合新手:手把手教你用Go快速搭建高性能、可扩展的IM系统(有源码)

IM里“附近的人”功能实现原理是什么?如何高效率地实现它?

IM要做手机扫码登录?先看看微信的扫码登录功能技术原理

IM消息ID技术专题(一):微信的海量IM聊天消息序列号生成实践(算法原理篇)

IM消息ID技术专题(二):微信的海量IM聊天消息序列号生成实践(容灾方案篇)

IM消息ID技术专题(三):解密融云IM产品的聊天消息ID生成策略

IM消息ID技术专题(四):深度解密美团的分布式ID生成算法

IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

IM开发宝典:史上最全,微信各种功能参数和逻辑规则资料汇总

IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

IM开发快速入门(一):什么是IM系统?

>> 更多同类文章 ……

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

posted @ 2020-07-09 14:16 Jack Jiang 阅读(1030) | 评论 (0)编辑 收藏

1、引言

IM系统中,特别是在企业应用场景下,消息的已读未读状态是一个强需求。

以阿里的钉钉为例,钉钉的产品定位是用于商务交流,其“强制已读回执”功能,让职场人无法再“假装不在线”、“假装没收到”。更有甚者,钉钉的群聊“强制已读回执”功能,甚至能够知道谁读了消息,谁没有读消息(老板的福音啊)。

 

▲ 钉钉里的群聊消息已读未读功能效果

功能看起来很酷,但用起来是一言难尽(上班族心里苦.... )。实际上,技术实现也并不容易。

那么,对于已读未读状态:

  • 1)如果是私聊:消息的阅读状态比较容易实现,在性能和存储上也不存在问题;
  • 2)如果是群聊:考虑到存储和处理性能,特别当处于一个云环境时,如何高效地处理群聊的已读未读状态是一个非常值得探讨的话题。

这里提到的“高效”含3个方面:

  • 1)存储空间;
  • 2)处理速度;
  • 3)传输字节数。

本文将从服务端的角度来探讨已读未读状态,在具体的技术实现上对于存储空间占用方面的思路差异。能力有限,权当个人笔记,欢迎交流。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/yUkKPOBsdqLlxiFrGmwFRQ,原文链接是:http://www.52im.net/thread-3054-1-1.html

2、内容点评

在收录本文前,Jack Jiang建议原作者对某些具体的技术点进行更深入的分享,但因作者工作较忙,本文中的某些关键技术点未来的及作进一步展开。

所以,本文可以作为IM聊天消息(主要是群聊)中已读未读功能的基本实现思路方面的参考,但不建议盲目迷信文中的结论或方案,避免被一些不够具体的技术指标而误导。

3、相关文章

如果你还想了解更多有关IM群聊中已读未读功能的实现逻辑,可以进一步阅读干货文章《IM群聊消息的已读回执功能该怎么实现?》(强烈推荐)。

如果你对IM中的已读未读功能有产品方面的痛点困惑,可以参考一下微信对已读未读功能的设计定位,详见《IM热门功能思考:为什么微信里没有消息“已读”功能?》。

更多IM群聊技术方面的文章详见文本附录部分。

4、已读未读状态交互流程

发送者发送的IM聊天消息,在接收者阅读消息后,是否要求阅读者通知已读,可能是由系统配置、组织配置、群组配置等决定,也可能由发送者根据业务需求决定。以下的讨论,均假设消息需要已读未读状态。

客户端与服务端之间,关于阅读状态的命令只需3个,每个命令含请求和应答。

4.1 通知消息已读(私聊、群聊通用)

当小宝阅读了一条或若干条消息,需向服务端发送消息已读通知:“众爱卿发的x+y+z消息,朕已阅”。

服务端收到小宝的已读通知时,需完成以下事项:

  • 1)存储消息的已读状态;
  • 2)返回应答给小宝;
  • 3)向已读列表的消息的原始发送者通知消息已读。

对于第“3)”步:

  • 1)私聊的场合,比较好理解,就是发送给私聊的对方;
  • 2)群聊的场合,可很不一样:因为小宝发送的已读消息列表,可能是由众爱卿发送的。考虑这种假设:张三、李四、王五发出的群聊消息,被小宝一下都阅读了,那么小宝发出的已读通知包含的消息列表,需要被IMS分解成3个已读通知(3个不同的消息列表),分别通知给张三、李四、王五,通知内容是“爱卿(不含'"众")发的这些消息,朕已阅”。

下面是大致的逻辑流程图: 

4.2 查询消息的未读人数(私聊、群聊通用)

消息的发送者,加载消息列表到聊天窗口时,可能需要展示消息是否被已读。

对群聊而言,显示的信息可能是n人未读的提示,那么需要向服务端查询消息的未读人数,由于客户端可能在UI显示自己发出的多条消息,需支持一次请求查询多条消息。

以未读人数的方式来表示消息的阅读状态,统一了私聊、群聊的查询,使得客户端-服务端间的接口更简单,同时使客户端的实现逻辑更统一。

就像下面这样:

  • 1)对于私聊:如果未读人数n>0,表示消息未读;
  • 2)对于群聊:直接显示n人未读即可,当然,当n等于0时表示全部已读。

4.3 查询群消息的已读、未读人员清单(群聊)

当客户端希望显示某一条群聊消息的已读、未读人员列表,需向服务端发起查询。

大致的逻辑流程图如下:

5、几种具体的已读未读状态存储思路探讨

5.1 基本约定

群聊的阅读状态比私聊复杂,因此这里着重讨论群聊的阅读状态。

假设群成员数是n,各个客户端立即IM服务端发送已读通知。服务端需存储每个人的阅读状态,包括那些未读的成员。由于群的成员清单可能变化,比如今天增加了一个成员,则昨天发的消息、与今天发的消息,其接收者列表不一样。

即:

  • 1)同一个群的不同消息,对应的接收者列表可能不一样。
  • 2)换言之,每一条消息都需要记录完整的接收者列表和已读人员列表。

为了方便讨论,本章假设群成员有640人为前提。

5.2 存储思路1

每一条消息都维护:

  • 1)接收人员列表receiver_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建receiver_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,用receiver_list的个数减去read_list的个数;
  • 2)当需要获取已读、未读人员列表时,需用receiver_list减去read_list得到未读人员列表。

那么,思路1每条消息的存储空间是:

640个ID + 不定数量的已读人员ID

5.3 存储思路2

每一条消息维护:

  • 1)未读人员列表unread_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建unread_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员从unread_list移出,同时加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,直接计算unread_list的个数;
  • 2)当需要获取已读、未读人员列表时,直接返回unread_list和read_list。

那么,思路2每条消息的存储空间是:

未读人员ID + 已读人员ID,合计640个ID

思路2的实现,占用的空间是案1的0.5倍~1.0倍。即案2占用的空间少,但在每次收到客户端的已读通知时,比案1多了一个操作:从unread_list进行减员。

5.4 存储思路3(我的实现)

5.4.1)探讨5.2节、5.3节的不足:

5.2节、5.3节这两种思路,都能满足功能需求,但存在巨大的存储浪费。

该群有640人,如果群内聊天每天有1024条消息,人员ID以4字节存储计算,那么为该群每天的消息阅读状态需要消耗的空间是:

5.2节思路1:1024 * (640 * 4 + 已读人数 * 4),范围是 2.5MB ~ 5MB;

5.3节思路2:1024 * 640 * 4,等于2.5MB。

这仅仅是一个群在一天之内产生的阅读状态数据,如果是在云平台运行,单此功能消耗的空间,呵呵~~

题外话:如果成员不是用4字节整型存储,而改用字符串,比如"1123356777",那就更可观了。

5.4.2)如何减少存储空间:

考虑群成员并非时时刻刻都在变化,多数情况下,群成员的列表是相对稳定的,今天的和上周(甚至更久以前)的列表甚至可能是一样的,那么有可能几百条消息,甚至几万条消息对应的群成员列表是相同的。

因此,引出本文的重点思想:

考虑让不同的消息共用群成员列表,即把消息的阅读状态与群成员列表分开存储,并记录它们之间的关联。

假定平均每1024条消息共用一个群成员列表,发了1024条消息后,群成员变化了,此后需要用新的群成员列表。

那么这一千条消息的阅读状态所占用的空间是:

群成员列表空间 + 1024条消息的阅读状态:640 * 4 + 1024 * 每条消息的阅读状态所占空间

在具备群成员列表的前提下,如何减少每条消息的阅读状态所占空间?

很自然会想到用bit来表示已读人员,因为一个32位整型可表示32个人的已读状态。bit的顺序只需与群成员列表的顺序一致即可。

当一条消息没有人已读时,阅读状态占用0字节;当群内每个人都阅读时,占用的空间最大,即640 / 32 = 20字节。

因此优化之后,这一千条消息的阅读状态所占用的空间,范围是2.5KB ~ (2.5KB + 1024 * 20B),即2.5KB ~ 22.5KB,此数值与5.2节思路1、5.3节思路2对比,有了极大幅度地下降。

如下图所示:

该表格的前提条件:

  • 1)一个群有640人;
  • 2)该群连续1024条消息对应的群成员列表是稳定的。

退一步考虑,哪怕这1024条消息对应的群成员列表不稳定,中间变化了10次,那么也仅会多出2.5KB * 10即25KB的存储空间,与案1、案2相比仍然有极大优势。

6、如何提高已读未读状态的处理速度

小宝往公司群发了一条消息我来给大家介绍一下新来的女同事,大家立即、马上、瞬间、闪电般地查看消息,感觉迟1秒就会失去秒杀女神的机会一样,意味着一瞬间会有N多条已读通知发送到IMS。

对这些消息的处理流程是一样的:

  • 1)可合并这些操作以批量形式进行存储、转发;
  • 2)由于存储消息的阅读状态是一个设置bit的过程,所以不存在互斥的问题,即使在分布式环境也可以放心操作;
  • 3)消息对应的成员列表信息可临时缓存在内存对象内,以减少查询IO,提高效率。

附录:更多IM群聊技术文章

快速裂变:见证微信强大后台架构从0到1的演进历程(一)

如何保证IM实时消息的“时序性”与“一致性”?

IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?

IM群聊消息如此复杂,如何保证不丢不重?

微信后台团队:微信后台异步消息队列的优化升级实践分享

移动端IM中大规模群消息的推送如何保证效率、实时性?

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

关于IM即时通讯群聊消息的乱序问题讨论

IM群聊消息的已读回执功能该怎么实现?

IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?

一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[技术脑洞] 如果把14亿中国人拉到一个微信群里技术上能实现吗?

IM群聊机制,除了循环去发消息还有什么方式?如何优化?

网易云信技术分享:IM中的万人群聊技术方案实践总结

阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处

IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

>> 更多同类文章 ……

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

posted @ 2020-07-02 14:04 Jack Jiang 阅读(506) | 评论 (0)编辑 收藏

1、引言

IM在Android上的保活问题经常在即时通讯网的论坛和技术群里被讨论,自从Android 8.0后系统大大降低了后台运行应用的保活容忍度(详见《Android P正式版即将到来:后台应用保活、消息推送的真正噩梦》),保活从黑科技横行的时代进入了技术蛮荒阶段,真要实现保活,技术难度越来越大。

不过话说回来,既然用黑科技进行保活是Andriod技术的逆潮流,那何不回头是岸,做个“良民”?

本文将以某款线上的IM产品为例,介绍它是如何引导用户在多款主流机型上加白名单的,并分享了该款IM中已制作完成的多达7款主流Andriod机型的详细加白FAQ页面资源(含完整HTML+图片),方便您进行参考、学习和研究,希望能为你的应用开发带来帮助。

特别申明:本文示例中的资源来自某款真实的IM产品,仅供学习和研究,请勿用作非法用途,如有侵权,请告之于我。

学习交流:

- 即时通讯/推送技术开发交流5群:215477170[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:

▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/JqWloZLBYicpxElVL_HKYw ,原文链接是:http://www.52im.net/thread-3033-1-1.html

2、Android保活,变的越来越不可能了

IM产品在Android上的保活问题从早期的系统版本到现在,从未有人停止过尝试。即时通讯通讯网也随着Andriod系统版本的升级,持续整理了很多篇相关文章,比如下面这些(文章的顺序按照Android系统的版本从低到高)。

上面这些文章,我们可以看到,自从Android 8.0(即Andriod P)以后,IM以及其它需要在后台保活的产品,存活难度越来越高,黑科技几乎都不起作用了。

于是,一些技术从牛们只能从更深的Android系统层面尝试突破系统的保活限制,比如这两篇:《史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术》、《Android进程永生技术终极揭密:进程被杀底层原理、APP应对被杀技巧》。

正如上面两篇文章,为了跟系统作斗争,可谓斗智斗勇。但Android系统的历史进程终究无人能阻挡,越来越严格的保活限制已经是Android官方及各大手机厂商的共识。

好吧,之前费尽心机折腾的各种黑科技,如今就像浮云一样。。。

 

3、死磕保活?别做梦了,回头是岸

正如上节所述,鉴于Andriod保活变的越来越不可能,很多原本靠黑科技保活的产品,开始重新审视保活技术实现,到底是把保活黑科技这条路走到黑,还是回归Android官方最佳实践(乖乖引导用户手动设置白名单)?

我个人认为,后者是保活技术发展的必然结果,就像之前分享的这篇文章里所做的尝试一样:《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!》,规范地引导用户“加白”。

放弃“黑科技”,并不意味着技术不行,回归“良民”,反而变的一身轻松。

 

4、调用系统代码引导用户加白名单,也不完美

之前整理的《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!》一文,是按照不同的机型,自动适配代码并在代码中调用系统的加白名单设置功能。

比如像下面这样的代码调用:

▲ 以下代码引用自《2020年了,Android后台保活还有戏吗?看我如何优雅的实现!

会弹出这样一个窗口:

这个方法确实不错,但因为机型不同、同机型的ROOM版本不同,代码的兼容处理,可能会相当复杂,所以方法虽好,但也并不能一劳永逸的解决所有问题。

5、应用内提供更多机型的“加白”FAQ帮助,是一个补充办法

正如上节所示,调用系统代码引导用户加白名单确实算的上“优雅”,但在不同的机型、同机型的不同系统版本上,可能差异很大,代码兼容性是个头疼的问题,总之这不是个百分百完美的办法。

这就需要一个补充手段,比如我们可以针对大量不同的机型,针对它的最行或最常用系统版本,在应用内以FAQ帮助网页的方式,为用户提供帮助。

比如可以在手机里打开像下面这样FAQ网页页面:

至少能在调用系统代码无法实现的情况下,可以让用户自主找到解决问题的办法。而这便是本文要分享,下节内容会以一个市面上做的比较好的IM应用为例,为你提供一个完整示例。

6、一个完整的“加白”FAQ帮助示例

最近发现的一款市面上的IM应用(此产品跟即时通讯网无任何关系,仅仅是作为技术研究参考对象而已),它内置的“加白”FAQ帮助就很完善。

以下是从该款IM中截下来的图: 

 

以下是该款IM应用中的运行演示视频(点此打开视频链接):

 

目前该应用中FAQ帮助已覆盖7款主流Andriod机,以下是完整示例页面链接:

可以看到,这款IM里的“加白”FAQ做的还是比较细、覆盖的机型也比较典型, 如果你有类似的想法或需求,完全可以参考这款产品的实现。尤其在一些特定的场景(比如企业内部的IM等)下,这种方式还是能解决大部分终端用户的问题的。

7、覆盖7款主流机型的“加白”FAQ页面静态资源(附件下载)

我整理了上节中提到的这款IM产品中的全部“加白”FAQ帮助页面静态资源,覆盖7款主流Andriod机型,如果你也需要同样的东西,可以参考这份完整的示例实现,打包到手机中使用之。

以下是这份静态资源示例的内容(图太长,已截掉了一部分): 

以下是这份静态资源示例的打包附件:

请从原文附件中下载:http://www.52im.net/thread-3033-1-1.html

附录:更多精品资源汇总

[1] 精品源码下载:

Java NIO基础视频教程、MINA视频教程、Netty快速入门视频 [有源码]

轻量级即时通讯框架MobileIMSDK的iOS源码(开源版)[附件下载]

开源IM工程“蘑菇街TeamTalk”2015年5月前未删减版完整代码 [附件下载]

微信本地数据库破解版(含iOS、Android),仅供学习研究 [附件下载]

NIO框架入门(四):Android与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(三):iOS与MINA2、Netty4的跨平台UDP双向通信实战 [附件下载]

NIO框架入门(二):服务端基于MINA2的UDP双向通信Demo演示 [附件下载]

NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示 [附件下载]

用于IM中图片压缩的Android工具类源码,效果可媲美微信 [附件下载]

高仿Android版手机QQ可拖拽未读数小气泡源码 [附件下载]

一个WebSocket实时聊天室Demo:基于node.js+socket.io [附件下载]

Android聊天界面源码:实现了聊天气泡、表情图标(可翻页) [附件下载]

高仿Android版手机QQ首页侧滑菜单源码 [附件下载]

开源libco库:单机千万连接、支撑微信8亿用户的后台框架基石 [源码下载]

分享java AMR音频文件合并源码,全网最全

微信团队原创Android资源混淆工具:AndResGuard [有源码]

一个基于MQTT通信协议的完整Android推送Demo [附件下载]

Android版高仿微信聊天界面源码 [附件下载]

高仿手机QQ的Android版锁屏聊天消息提醒功能 [附件下载]

高仿iOS版手机QQ录音及振幅动画完整实现 [源码下载]

Android端社交应用中的评论和回复功能实战分享[图文+源码]

Android端IM应用中的@人功能实现:仿微博、QQ、微信,零入侵、高可扩展[图文+源码]

仿微信的IM聊天时间显示格式(含iOS/Android/Web实现)[图文+源码]

Android版仿微信朋友圈图片拖拽返回效果 [源码下载]

[2] 精品文档和工具下载:

计算机网络通讯协议关系图(中文珍藏版)[附件下载]

史上最全即时通讯软件简史(精编大图版)[附件下载]

重磅发布:《阿里巴巴Android开发手册(规约)》[附件下载]

阿里技术结晶:《阿里巴巴Java开发手册(规约)-终极版》[附件下载]

基于RTMP协议的流媒体技术的原理与应用(技术论文)[附件下载]

独家发布《TCP/IP详解 卷1:协议》CHM版 [附件下载]

良心分享:WebRTC 零基础开发者教程(中文)[附件下载]

MQTT协议手册(中文翻译版)[附件下载]

经典书籍《UNIX网络编程》最全下载(卷1+卷2、中文版+英文版)[附件下载]

音视频开发理论入门书籍之《视频技术手册(第5版)》[附件下载]

国际电联H.264视频编码标准官方技术手册(中文版)[附件下载]

Apache MINA2.0 开发指南(中文版)[附件下载]

网络通讯数据抓包和分析工具 Wireshark 使用教程(中文) [附件下载]

最新收集NAT穿越(p2p打洞)免费STUN服务器列表 [附件下载]

高性能网络编程经典:《The C10K problem(英文)》[附件下载]

即时通讯系统的原理、技术和应用(技术论文)[附件下载]

技术论文:微信对网络影响的技术试验及分析[附件下载]

华为内部3G网络资料: WCDMA系统原理培训手册[附件下载]

网络测试:Android版多路ping命令工具EnterprisePing[附件下载]

Android反编译利器APKDB:没有美工的日子里继续坚强的撸

一款用于P2P开发的NAT类型检测工具 [附件下载]

两款增强型Ping工具:持续统计、图形化展式网络状况 [附件下载]

Android保活从入门到放弃:乖乖引导用户加白名单吧(附7大机型加白示例)

[3] 精选视频、演讲PPT下载:

美图海量用户的IM架构零基础演进之路(PPT)[附件下载]

开源实时音视频工程WebRTC的架构详解与实践总结(PPT+视频)[附件下载]

QQ空间百亿级流量的社交广告系统架构实践(视频+PPT)[附件下载]

海量实时消息的视频直播系统架构演进之路(视频+PPT)[附件下载]

YY直播在移动弱网环境下的深度优化实践分享(视频+PPT)[附件下载]

QQ空间移动端10亿级视频播放技术优化揭秘(视频+PPT)[附件下载]

RTC实时互联网2017年度大会精选演讲PPT [附件下载]

微信分享开源IM网络层组件库Mars的技术实现(视频+PPT)[附件下载]

微服务理念在微信海量用户后台架构中的实践(视频+PPT)[附件下载]

移动端IM开发和构建中的技术难点实践分享(视频+PPT)[附件下载]

网易云信的高品质即时通讯技术实践之路(视频+PPT)[附件下载]

腾讯音视频实验室:直面音视频质量评估之痛(视频+PPT)[附件下载]

腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT[附件下载]

微信朋友圈海量技术之道PPT[附件下载]

手机淘宝消息推送系统的架构与实践(音频+PPT)[附件下载]

如何进行实时音视频的质量评估与监控(视频+PPT)[附件下载]

Go语言构建高并发消息推送系统实践PPT(来自360公司)[附件下载]

网易IM云千万级并发消息处理能力的架构设计与实践PPT [附件下载]

手机QQ的海量用户移动化实践分享(视频+PPT)[附件下载]

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

微信技术总监谈架构:微信之道——大道至简(PPT讲稿)[附件下载]

Netty的架构剖析及应用案例介绍(视频+PPT)[附件下载]

声网架构师谈实时音视频云的实现难点(视频采访)

滴滴打车架构演变及应用实践(PPT讲稿)[附件下载]

微信海量用户背后的后台系统存储架构(视频+PPT)[附件下载]

在线音视频直播室服务端架构最佳实践(视频+PPT)[附件下载]

从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT)[附件下载]

微信移动端应对弱网络情况的探索和实践PPT[附件下载]

Android版微信从300KB到30MB的技术演进(PPT讲稿)[附件下载]

从零开始搭建瓜子二手车IM系统(PPT)[附件下载]

极光分享:高并发海量消息推送系统架构演进(视频+PPT)[附件下载]

微信红包系统可用性设计实践(PPT) [附件下载]

微信红包数据架构演变(PPT) [附件下载]

百度网盘千万节点的P2P架构设计(PPT) [附件下载]

瓜子IM智能客服系统的数据架构设计(PPT) [附件下载]

基于C++构建微信客户端跨平台开发框架(PPT) [附件下载]

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

posted @ 2020-06-24 13:55 Jack Jiang 阅读(610) | 评论 (0)编辑 收藏

仅列出标题
共47页: First 上一页 24 25 26 27 28 29 30 31 32 下一页 Last 
Jack Jiang的 Mail: jb2011@163.com, 联系QQ: 413980957, 微信: hellojackjiang