Jack Jiang

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

2016年7月22日

     摘要: 本文由vivo互联网服务器团队李青鑫分享,有较多修订和改动。1、引言本文内容来自vivo互联网服务器团队李青鑫在“2021 vivo开发者大会”现场的演讲内容整理而成(现场演讲稿可从本文末附件中下载)。本文将要分享的是手机厂商vivo的系统级推送平台在架构设计上的技术实践和总结。这也是目前为止首次由手机厂商分享的自建系统级推送平台的技术细节,我们也得以借此机会一窥厂商ROO...  阅读全文

posted @ 2022-08-09 12:11 Jack Jiang 阅读(46) | 评论 (0)编辑 收藏

一、关于RainbowChat-Web

RainbowChat-Web是一套Web网页端IM系统,是RainbowChat的姊妹产品(RainbowChat是一套基于开源IM聊天框架 MobileIMSDK(Github地址) 的产品级移动端IM系统)。

不同于市面上某些开源或淘宝售卖的demo级代码,RainbowChat-Web的产品级代码演化自真正运营过的商业产品,其所依赖的通信层核心SDK(即MobileIMSDK-Web)已在数年内经过大量客户及其辐射的最终用户的使用和验证。

二、v4.1 版更新内容

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

  • 1)[bug][前端]解决了掉线后发出的消息,在被判定未送达的情况下,重连成功时会再次重发的问题(这是MobileIMSDK-Web的bug);
  • 2)[优化][前端]解决了发送的html等内容,对方显示正常,而自已这边显示不正常的问题(没被转义);
  • 3)[优化][服务端-独立交付版]解决了log4j2的两个jar包冲突导致在linux下不能正常输出log的问题;
  • 4)[优化][服务端-RainbowChatMQserver]优化了使用mysql8.0驱动时,不能正确读取SQL异常信息的问题(会报空指针异常);
  • 5)[优化][前端]解决了位置消息发送功能无法正常使用的问题(高德地图官方API升级,已适配并升级完成);
  • 6)[优化][前端]解决了位置消息查看时的地图控制工具不正常的问题(高德地图官方API升级,已适配并升级完成)。

升级后的位置消息相关功能截图(更多截图点此查看):

三、关于兼容性

截止目前:RainbowChat-Web努力保证在各主流系统、主流浏览器、不同分辨率屏幕上的一致体验,包括但不限于:Chrome、Safari、FireFox、Edge、360浏览器、世界之窗浏览器等▼

▲ 在各种主流浏览器上的运行情况更多截图点此进入更多演示视频点此进入

▲ 超宽屏上的显示情况更多截图点此进入更多演示视频点此进入

 

▲ 不同系统、不同分辨率屏幕的真机运行情况更多截图点此进入更多演示视频点此进入) 

四、主要界面截图概览

 

▲ 主界面更多截图点此进入更多演示视频点此进入

▲ 主界面(聊天窗全屏时)更多截图点此进入更多演示视频点此进入

▲ 主界面(聊天窗关闭时)更多截图点此进入更多演示视频点此进入

posted @ 2022-08-06 12:14 Jack Jiang 阅读(38) | 评论 (0)编辑 收藏

     摘要: 本文由vivo互联网技术团队LinDu、Li Guolin分享,有较多修订和改动。1、引言IM即时消息模块是直播系统的重要组成部分,一个稳定、有容错、灵活的、支持高并发的消息模块是影响直播系统用户体验的重要因素。本文针对秀场直播,结合我们一年以来通过处理不同的业务线上问题,进行了技术演进式的IM消息模块架构的升级与调整,并据此进行了技术总结、整理成文,希望借此机会分享给大家。在目前大部分主流的直播...  阅读全文

posted @ 2022-08-01 12:37 Jack Jiang 阅读(57) | 评论 (0)编辑 收藏

本文由作者“大白菜”分享,有较多修订和改动。注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷!

1、引言

前两篇《编码实践篇(单聊功能)》、《编码实践篇(群聊功能)》分别实现了控制台版本的IM单聊和群聊的功能。

通过前两篇这两个小案例来体验的只是Netty在IM系统这种真实的开发实践,但对比在真实的Netty应用开发当中,本系列的案例是非常的简单的,主要目的其实是让大家可以更好地了解其原理,从而写出更高质量的 Netty 代码。

不过,虽然 Netty 的性能很高,但是也不能保证随意写出来的项目就是性能很高的,所以本篇将主要讲解几个基于Netty的IM系统的优化实战技术点。

学习交流:

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

2、写在前面

建议你在阅读本文之前,务必先读本系列的前三篇《IM系统设计篇》、《编码实践篇(单聊功能)》、《编码实践篇(群聊功能)》。

最后,在开始本文之前,请您务必提前了解Netty的相关基础知识,可从本系列首篇《IM系统设计篇》中的“知识准备”一章开始。

3、系列文章

本文是系列文章的第3篇,以下是系列目录:

4、基于Netty的IM系统常见优化方向

常见优化方向脑图:

我们逐条详细解释一下这些优化的目的:

  • 1)心跳检测:主要是避免连接假死现象;
  • 2)连接断开:则删除通道绑定属性、删除对应的映射关系,这些信息都是保存在内存当中的,如果不删除则造成资源浪费;
  • 3)性能问题:用户 ID 和 Channel 的关系绑定存在内存当中,比如:Map,key 是用户 ID,value 是 Channel,如果用户量多的情况(客户端数量过多),那么服务端的内存将被消耗殆尽;
  • 4)性能问题:每次服务端往客户端推送消息,都需从Map里查找到对应的Channel,如果数量较大和查询频繁的情况下如何保证查询性能;
  • 5)安全问题:HashMap 是线程不安全的,并发情况下,我们如何去保证线程安全;
  • 6)身份校验:如何 LoginHandler 是负责登录认证的业务 Handler,AuthHandler 是负责每次请求时校验该请求是否已经认证了,这些 Handler 在链接就绪时已经被添加到 Pipeline 管道当中,其实,我们可以采用热插拔的方式去把一些在做业务操作时用不到的 Handler 给剔除掉。

以上是基于Netty的IM系统开发当中,需要去注意的技术优化点,当然还有很多其他的细节,比如:线程池这块,需要大家慢慢去从实战中积累。

5、本篇优化方向

本篇主要的优化内容主要是在第二篇单聊功能第三篇群聊功能的基础上继续完善几点。

具体的优化方向如下:

  • 1)无论客户端还是服务端都分别只有一个 Handler,这样的话,业务越来越多,Handler 里面的代码就会越来越臃肿,我们应该想办法把 Handler 拆分成各个独立的 Handler;
  • 2)如果拆分的 Handler 很多,每次有连接进来,那么都会触发 initChannel () 方法,所有的 Handler 都得被 new 一遍,我们应该把这些 Handler 改成单例模式(不需要每次都 new,提高效率);
  • 3)发送消息时,无论是单聊还是群聊,对方不在线,则把消息缓存起来,等待其上线再推送给他;
  • 4)连接断开时,无论是主动和被动,需要删除 Channel 属性、删除用户和 Channel 映射关系。

6、业务拆分以及单例模式优化

6.1 概述

主要优化细节如下:

  • 1)自定义 Handler 继承 SimpleChannelInboundHandler,那么解码的时候,会自动根据数据格式类型转到相应的 Handler 去处理;
  • 2)@Shareable 修饰 Handler,保证 Handler 是可共享的,避免每次都创建一个实例。

6.2 登录Handler优化

@ChannelHandler.Sharable

public class ClientLogin2Handler extends SimpleChannelInboundHandler<LoginResBean> {

    //1.构造函数私有化,避免创建实体

    private ClientLogin2Handler(){}

    //2.定义一个静态全局变量

    public static ClientLogin2Handler instance=null;

    //3.获取实体方法

    public static ClientLogin2Handler getInstance(){

        if(instance==null){

            synchronized(ClientLogin2Handler.class){

                if(instance==null){

                    instance=new ClientLogin2Handler();

                }

            }

        }

        return instance;

    }

 

    protected void channelRead0(

        ChannelHandlerContext channelHandlerContext,

        LoginResBean loginResBean) throws Exception {

 

        //具体业务代码,参考之前

    }

}

6.3 消息发送Handler优化

@ChannelHandler.Sharable

public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResBean> {

    //1.构造函数私有化,避免创建实体

    private ClientMsgHandler(){}

    //2.定义一个静态全局变量

    public static ClientMsgHandler instance=null;

    //3.获取实体方法

    public static ClientMsgHandler getInstance(){

        if(instance==null){

            synchronized(ClientMsgHandler.class){

                if(instance==null){

                    instance=new ClientMsgHandler();

                }

            }

        }

        return instance;

    }

 

    protected void channelRead0(

        ChannelHandlerContext channelHandlerContext,

        MsgResBean msgResBean) throws Exception {

 

        //具体业务代码,参考之前

    }

}

6.4 initChannel方法优化

.handler(newChannelInitializer<SocketChannel>() {

    @Override

    public void initChannel(SocketChannel ch) {

        //1.拆包器

        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,5,4));

        //2.解码器

        ch.pipeline().addLast(new MyDecoder());

        //3.登录Handler,使用单例获取

        ch.pipeline().addLast(ClientLogin2Handler.getInstance());

        //4.消息发送Handler,使用单例获取

        ch.pipeline().addLast(ClientMsgHandler.getInstance());

        //5.编码器

        ch.pipeline().addLast(new MyEncoder());

    }

});

6.5 小结

这种业务拆分以及单例模式优优化是Netty开发当中很常用的,可以更好的维护基于Netty的代码并提高应用性能。

7、数据缓存优化

为了提高用户体验,在发送消息(推送消息)时,如果接收方不在线,则应该把消息缓存起来,等对方上线时,再推送给他。

7.1 数据缓存到集合

//1.定义一个集合存放数据(真实项目可以存放数据库或者redis缓存),这样数据比较安全。

private List<Map<Integer,String>> datas=new ArrayList<Map<Integer,String>>();

 

//2.服务端推送消息

private void pushMsg(MsgReqBean bean,Channel channel){

    Integer touserid=bean.getTouserid();

    Channel c=map.get(touserid);

 

    if(c==null){//对方不在线

        //2.1存放到list集合

        Map<Integer,String> data=new HashMap<Integer, String>();

        data.put(touserid,bean.getMsg());

        datas.add(data);

 

        //2.2.给消息“发送人”响应

        MsgResBean res=new MsgResBean();

        res.setStatus(1);

        res.setMsg(touserid+">>>不在线");

        channel.writeAndFlush(res);

 

    }else{//对方在线

        //2.3.给消息“发送人”响应

        MsgResBean res=new MsgResBean();

        res.setStatus(0);

        res.setMsg("发送成功);

        channel.writeAndFlush(res);

 

        //2.4.给接收人推送消息

        MsgRecBean res=new MsgRecBean();

        res.setFromuserid(bean.getFromuserid());

        res.setMsg(bean.getMsg());

        c.writeAndFlush(res);

    }

}

7.2 上线推送

private void login(LoginReqBean bean, Channel channel){

    Channel c=map.get(bean.getUserid());

    LoginResBean res=new LoginResBean();

    if(c==null){

        //1.添加到map

        map.put(bean.getUserid(),channel);

        //2.给通道赋值

        channel.attr(AttributeKey.valueOf("userid")).set(bean.getUserid());

        //3.登录响应

        res.setStatus(0);

        res.setMsg("登录成功");

        res.setUserid(bean.getUserid());

        channel.writeAndFlush(res);

 

        //4.根据user查找是否有尚未推送消息

        //思路:根据userid去lists查找.......

 

    }else{

        res.setStatus(1);

        res.setMsg("该账户目前在线");

        channel.writeAndFlush(res);

    }

}

8、连接断开事件处理优化

如果客户端网络故障导致连接断开了(非主动下线),那么服务端就应该能监听到连接的断开,且此时应删除对应的 map 映射关系。但是映射关系如果没有删除掉,将导致服务器资源没有得到释放,进而影响客户端的下次同一个账号登录以及大量的客户端掉线时性能。

8.1 正确写法

实例:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    //映射关系

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

    //连接断开,触发该事件

    @Override

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        //1.获取Channel

        Channel channel=ctx.channel();

 

        //2.从map里面,根据Channel找到对应的userid

        Integer userid=null;

        for(Map.Entry<Integer, Channel> entry : map.entrySet()){

            Integer uid=entry.getKey();

            Channel c=entry.getValue();

            if(c==channel){

                userid=uid;

            }

        }

        //3.如果userid不为空,则需要做以下处理

        if(userid!=null){

            //3.1.删除映射

            map.remove(userid);

            //3.2.移除标识

            ctx.channel().attr(AttributeKey.valueOf("userid")).remove();

        }

    }

}

8.2 错误写法

Channel 断开,服务端监听到连接断开事件,但是此时 Channel 所绑定的属性已经被移除掉了,因此这里无法直接获取的到 userid。

实例:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

    //映射关系

    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

 

    //连接断开,触发该事件

    @Override

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        //1.获取Channel绑定的userid

        Object userid=channel.attr(AttributeKey.valueOf("userid")).get();

 

        //2.如果userid不为空

        if(userid!=null){

            //1.删除映射

            map.remove(userid);

            //2.移除标识

            ctx.channel().attr(AttributeKey.valueOf("userid")).remove();

        }

    }

}

9、本篇小结

本篇内容还是相对容易理解的,主要是优化前面两篇实现的IM聊天功能,优化内容是业务 Handler 的拆分以及使用单例模式、接受人不在线则缓存数据、等其上线再推送、监听连接断开删除对应的映射关系。

限于篇幅,本系列文章文章没办法真正讲解开发一个完整IM系统所涉及的方方面面,如果有兴趣,可以继续阅读更有针对性的IM开发文章,比如IM架构设计IM通信协议IM通信安全群聊优化弱网优化网络保活等。

10、参考资料

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

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

[3] 浅谈IM系统的架构设计

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

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

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

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

[8] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[9] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[10] 基于实践:一套百万消息量小规模IM系统技术要点总结

[11] 探探的IM长连接技术实践:技术选型、架构设计、性能优化

[12] 拿起键盘就是干,教你徒手开发一套分布式IM系统

[13] 万字长文,手把手教你用Netty打造IM聊天

[14] 基于Netty实现一套分布式IM系统

[15] SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能

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

posted @ 2022-07-25 12:02 Jack Jiang 阅读(60) | 评论 (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.2更新内容 

【重要说明】:

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

【新增的特性】:

  1. [服务端] 新增两个聊天消息前置处理回调,方便开发者进行内容鉴黄、过滤、修改等运营管理;
  2. [服务端] 新增新增了一个与 Web 互通情况下的 C2C 模式回调,用于开发者在互通模式下实现离线消息 Push 逻辑;

【其它优化和提升】:

  1. [Andriod] 支持最新的 Andriod 12,解决了 Demo 工程中的 Andriod12 兼容问题;
  2. [Andriod] 解决了 Demo 工程在最新 Android Studio 编译时报方法数超过 65535 的经典问题;
  3. [服务端] 升级 log4j2 至 2.17.0,解决 Log4j2 远程代码执行高危漏洞;
  4. [服务端] 为 ServerEventListener 类中的 onUserLogout 回调增加 beKickoutCode 参数;
  5. [服务端] [优化] 尝试解决与 Web 互通情况下,MQProvider 中的 work 方法会因异步消息导致的 AlreadCloseException 问题;

【版本地址】:

https://gitee.com/jackjiang/MobileIMSDK/releases/6.2

posted @ 2022-07-20 10:29 Jack Jiang 阅读(50) | 评论 (0)编辑 收藏

     摘要: 本文由作者“大白菜”分享,有较多修订和改动。注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷!1、引言接上两篇《IM系统设计篇》、《编码实践篇(单聊功能)》,本篇主要讲解的是通过实战编码实现IM的群聊功能,内容涉及群聊技术实现原理、编码实践等知识。学习交流:- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》- 开源IM框架源码:https://...  阅读全文

posted @ 2022-07-18 15:06 Jack Jiang 阅读(60) | 评论 (0)编辑 收藏

     摘要: 本文由作者“大白菜”分享,个人博客 cmsblogs.cn,有较多修订和改动。注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷!1、引言接上篇《IM系统设计篇》,本篇主要讲解的是通过实战编码实现IM的单聊功能,内容涉及技术原理、编码实践。补充说明:因为本系列文章主要目的是引导IM初学者在基于Netty的情况下,如何一步一步从零写出IM的逻辑和思维能力,因而为了简...  阅读全文

posted @ 2022-07-11 11:39 Jack Jiang 阅读(53) | 评论 (0)编辑 收藏

本文收作者“大白菜”分享,有改动。注意:本系列是给IM初学者的文章,IM老油条们还望海涵,勿喷!

1、引言

这又是一篇基于Netty的IM编码实践文章,因为合成一篇内容太长,读起来太累,所以也就顺着作者的思路分开成4篇,读起来心理压力也就没那么大了。

这个系列的几篇文章分享的是:假设在没有任何成型的第3方IM库或SDK的情况下,以网络编程的基础技术视野,思考和实践如何基于Netty网络库从零写一个可以聊天的IM系统的过程,没有眼花缭乱的架构设计、也没有高端大气的模式设计方法论,有的只是从IM入门者的角度的思路和实战,适合IM初学者阅读。

本篇主要是徒手撸IM系列的开篇,主要讲解的是的IM设计思路,不涉及实践编码,希望给你带来帮助。

学习交流:

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

2、知识准备

* 重要提示:本系列文章主要是代码实战分享,如果你对即时通讯(IM)技术理论了解的不多,建议先详细阅读:《零基础IM开发入门:什么是IM系统?》、《新手入门一篇就够:从零开发移动端IM》。

不知道 Netty 是什么?这里简单介绍下:

Netty 是一个 Java 开源框架。Netty 提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。

Netty 相当简化和流线化了网络应用的编程开发过程,例如,TCP 和 UDP 的 Socket 服务开发。

Netty的基础入门好文章:

如果你连Java的NIO都不知道是什么,下面的文章建议优先读:

Netty源码和API的在线查阅地址:

3、系列文章

本文是系列文章的第1篇,以下是系列目录:

  1. 基于Netty,徒手撸IM(一):IM系统设计篇》(* 本文
  2. 《基于Netty,徒手撸IM(二):编码实践篇(单聊功能)》
  3. 《基于Netty,徒手撸IM(三):编码实践篇(群聊功能)》
  4. 《基于Netty,徒手撸IM(一):编码实践篇(系统优化)》

4、需求分析

业务场景: 本次实战就是模拟微信的IM聊天,每个客户端和服务端建立连接,并且可以实现点对点通信(单聊),点对多点通信(群聊)。

设计思路: 我们要实现的是点(客户端)对点(客户端)的通讯,但是我们大部分情况下接触的业务都是客户端和服务端之间的通讯(所谓的C/S模式?),客户端只需要知道服务端的 IP 地址和端口号即可发起通讯了。那么客户端和客户端应该怎么去设计呢?

技术思考:难道是手机和手机之间建立通讯连接(所谓的P2P),互相发送消息吗?

这种方案显然不是很好的方案:

  • 1)首先: 客户端和客户端之间通讯,首先需要确定对方的 IP 地址和端口号,显然不是很现实;
  • 2)其次: 即使有办法拿到对方的 IP 地址和端口号,那么每个点(客户端)既作为服务端还得作为客户端,无形之中增加了客户端的压力。

其实:我们可以使用服务端作为IM聊天消息的中转站,由服务端主动往指定客户端推送消息。如果是这种模式的话,那么 Http 协议是无法支持的(因为Http 是无状态的,只能一请求一响应的模式),于是就只能使用 TCP 协议去实现了。

Jack Jiang注:此处作者表述不太准确,因为虽然HTTP是无状态的,但一样可以实现即时通讯能力,有兴趣的读者可以阅读以下几篇文章,了解一下这些曾经利用HTTP实现即时通讯聊天的技术方法:

  1. 新手入门贴:史上最全Web端即时通讯技术原理详解
  2. Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
  3. 网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket

5、IM单聊思路设计

5.1 通讯架构原理

以下是通讯架构原理图:

 

如上图所示,通讯流程解析如下:

  • 1)实现客户端和客户端之间通讯,那么需要使用服务端作为通讯的中转站,每个客户端都必须和服务端建立连接;
  • 2)每个客户端和服务端建立连接之后,服务端保存用户 ID 和通道的映射关系,其中用户 ID 作为客户端的唯一标识;
  • 3)客户端 A 往客户端 B 发送消息时,先把消息发送到服务端,再有服务端往客户端 B 进行推送。

针对上述第“3)”点,服务端如何找到客户端 B 呢?

客户端 A 往服务端发送消息时,消息携带的信息有:“客户端 A 用户 ID”、“客户端 B 用户 ID”、“消息内容”。这样服务端就能顺利找到服务端 B 的通道并且进行推送消息了。

5.2 消息推送流程

每个客户端和服务端建立连接的时候,必须把个人用户信息上传到服务端,由服务端统一保存映射关系。如果某个客户端下线了,则服务端监听到连接断开,删除对应的映射关系。

其次:发起群聊的时候,需要传递 touser 字段,服务端根据该字段在映射表里面查找到对应的连接通道并发起消息推送。

上述逻辑原理如下图所示:

5.3 更多的细节

其实在真正要做IM之前,要考虑的技术细节还是很多的,以下这几篇文章就步及到了典型的几个IM热门技术点,有兴趣的一定要读一读:

  1. 移动端IM开发需要面对的技术问题
  2. 谈谈移动端 IM 开发中登录请求的优化
  3. IM消息送达保证机制实现(一):保证在线实时消息的可靠投递
  4. IM消息送达保证机制实现(二):保证离线消息的可靠投递
  5. 如何保证IM实时消息的“时序性”与“一致性”?

6、IM群聊思路设计

群聊指的是一个组内多个用户之间的聊天,一个用户发到群组的消息会被组内任何一个成员接收 。

具体架构思路如下所示:

如上图所示,群聊通讯流程解析如下。

1)群聊其实和单聊整体上思路都是一致的,都是需要保存每个用户和通道的对应关系,方便后期通过用户 ID 去查找到对应的通道,再跟进通道推送消息。

2)如何把消息发送给多个组内的成员呢?

其实很简单,服务端再保存另外一份映射关系,那就是聊天室和成员的映射关系。发送消息时,首先根据聊天室 ID 找到对应的所有成员,然后再跟进各个成员的 ID 去查找到对应的通道,最后由每个通道进行消息的发送。

3)成员加入某个群聊组的时候,往映射表新增一条记录,如果成员退群的时候则删除对应的映射记录。

通过上面的架构图可以发现,群聊和单聊相比,其实就是多了一份映射关系而已。

其实群聊是IM里相对来说技术难度较高的功能,有兴趣的读者可以阅读下面这几篇:

  1. IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?
  2. IM群聊消息如此复杂,如何保证不丢不重?
  3. 移动端IM中大规模群消息的推送如何保证效率、实时性?
  4. 现代IM系统中聊天消息的同步和存储方案探讨
  5. 关于IM即时通讯群聊消息的乱序问题讨论
  6. IM群聊消息的已读回执功能该怎么实现?
  7. IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?
  8. 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

另外,对于超大规模群聊,技术难度更是指数上升:

  1. 网易云信技术分享:IM中的万人群聊技术方案实践总结
  2. 阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处
  3. IM群聊消息的已读未读功能在存储空间方面的实现思路探讨
  4. 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等
  5. 融云IM技术分享:万人群聊消息投递方案的思考和实践
  6. 微信直播聊天室单房间1500万在线的消息架构演进之路

7、本文小结

本篇主要是帮助读者掌握单聊和群聊的核心设计思路。

单聊: 主要是服务器保存了一份用户和通道之间的映射关系,发送消息的时候,根据接收人 ID 找到其对应的通道 Channel,Channel 的 write () 可以给客户端发送消息。

群聊: 保存两份关系,分别是用户 ID 和 Channel 之间的关系、群组 ID 和用户 ID 的关系。推送消息的时候,首先根据聊天组 ID 找到其对应的成员,遍历每个成员再进行找出其对应的通道即可。

整体来说,思路还是很简单的,掌握了该设计思路以后,你会发现设计一款 IM 聊天软件其实也不是很复杂。

8、相关文章

如果你觉得对本系列文章还不够详细,可以系统学习以下系列文章:

  1. 跟着源码学IM(一):手把手教你用Netty实现心跳机制、断线重连机制
  2. 跟着源码学IM(二):自已开发IM很难?手把手教你撸一个Andriod版IM
  3. 跟着源码学IM(三):基于Netty,从零开发一个IM服务端
  4. 跟着源码学IM(四):拿起键盘就是干,教你徒手开发一套分布式IM系统
  5. 跟着源码学IM(五):正确理解IM长连接、心跳及重连机制,并动手实现
  6. 跟着源码学IM(六):手把手教你用Go快速搭建高性能、可扩展的IM系统
  7. 跟着源码学IM(七):手把手教你用WebSocket打造Web端IM聊天
  8. 跟着源码学IM(八):万字长文,手把手教你用Netty打造IM聊天
  9. 跟着源码学IM(九):基于Netty实现一套分布式IM系统
  10. 跟着源码学IM(十):基于Netty,搭建高性能IM集群(含技术思路+源码)
  11. SpringBoot集成开源IM框架MobileIMSDK,实现即时通讯IM聊天功能

9、参考资料

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

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

[3] 浅谈IM系统的架构设计

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

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

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

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

[8] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[9] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[10] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[11] 基于实践:一套百万消息量小规模IM系统技术要点总结

[12] 探探的IM长连接技术实践:技术选型、架构设计、性能优化

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

posted @ 2022-07-04 18:38 Jack Jiang 阅读(74) | 评论 (0)编辑 收藏

本文由作者jhon_11分享,有大量修订和改动。

1、引言

如何设计一款高性能、高并发、高可用的im综合消息平台是很多公司发展过程中会碰到且必须要解决的问题。比如一家公司内部的通讯系统、各个互联网平台的客服咨询系统,都是离不开一款好用且维护的方便im综合消息系统。

那么,我们应该怎么样来设计一款三高特性的im系统,并能同时支持各个业务线的接入(比如:内部OA通讯、客服咨询、消息推送等等功能)有呢?

下面就由我来介绍一下我所负责的公司IM综合消息系统所经历的架构设计历程,以及架构设计过程中的一些思路和总结,希望能给你带来启发。

学习交流:

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

2、初版IM架构

2.1 概述

im第一版设计的初衷是公司需要一款im消息中间件用于支撑客服咨询业务。

但是,考虑到为了方便日后其他业务线也能接入消息沟通平台,所以一开始就将整个消息中心的能力需求给到中间件团队进行开发,以便除客服外的各业务线接入综合消息中心,从而实现多元的消息实时触达能力。

2.2 初版架构介绍

初版架构图如下图所示:

针对上面的架构图,我们逐个解释一下各模块的作用。

1)存储端:

在初版的架构下,存储端我们使用tidb、redis作为主要存储:

  • [1] redis用于存储消息已读未读,缓存连接信息等功能;
  • [2] tidb作为开源的分布式数据库,选择它是为了方便消息的存储。

2)mq消息总线:

我们使用rocketmq来实现消息总线(PS:即分布式情况下,不同im实例间通过MQ进行消息交互)。

消息总线是整个im的核心,使用rocketmq能支持十万级别的tps。基本所有服务都要从消息总线中消费消息进行业务处理。

3)zookeeper注册中心:各个服务会注册到zk中,方便服务之间内部进行调用,同样也可以暴露服务给外部进行调用。

4)link服务:

link服务主要用于接收客户端的ws(WebSocket协议)、tcp、udp等协议的连接。

同时调用用户服务进行认证,并投递连接成功的消息给位置服务进行消费,存储连接信息。

ws(WebSocket协议)过来的消息先到link再投递到消息总线。

5)消息分发服务:

消息分发服务主要用于接收消息总线推过来的消息进行处理,按照im内部消息协议构造好消息体后,又推送到消息总线中(比如会推给会话服务、消息盒子、link服务)。

6)位置服务:

存储link的(WebSocket协议)连接、tcp连接等信息,并使用redis进行缓存(key为userId),方便根据UserId查询到该用户所登录的客户端连接在哪个link上。

一个用户在相同设备只能登录一个,但可以支持多端登录。

7)用户服务:用于存储所有用户,提供认证查询接口。

8)消息盒子:存储所有消息,提供消息查询、消息已读未读、消息未读数、消息检索等功能。

9)会话服务:管理会话、群聊会话、单聊会话等功能。

2.3 整体时序图

整体架构的时序图如下:

3、初版IM架构存在的问题及思考

在上节的架构设计介绍中,我们详细分享了初版IM系统架构的设计思路以及具体流程。

那么在初版IM架构设计中还存在什么样的问题,又该如何优化呢?我们一条条来看看。

3.1 使用MQ消息总线的问题

正如上节所分享的那样,我们初版IM架构中,link服务到消息分发服务的消息使用的MQ消息总线。

初版架构设计中,link服务将消息下推给消息分发服务进行处理时,使用的是mq消息总线(通俗了说,IM集群内不同IM实例间的通信是依赖于MQ进行的消息传递),而mq消息总线必然做对有一定的时延(而且时延受制于MQ本身的系统实现和技术策略)。

举个例子:

当两个处于不同IM实例的客户端A和B聊天时,A用户发送消息到link --> 消息总线 --> 消息分发服务 --> 消息总线 --> link --> B用户。

正如上面这个例子,im消息投递流程太长了,并且这样也会大大降低系统的吞吐量。

3.2 消息落库为写扩散的问题

其实现阶段我们使用的是跟微信一样的写扩散策略(详见《企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等》)。

那么为啥微信使用写扩散不是缺陷,而对于我们的IM架构来说确是缺陷呢?

微信的技术特性:

  • 1)微信号称没有存储用户的聊天记录,全是实时推送;
  • 2)微信聊天记录全部会在我们手机端存储一份,两台手机终端上的聊天记录并不互通,并且互不可见。

我们的IM综合消息中心技术特性:

  • 1)综合消息中心是会有拉取历史聊天记录(服务端拉取)的功能,存储了全量消息;
  • 2)综合消息中心的客户端,需要支持网页版本。

综上所述:

  • 1)写扩散对微信这样有移动端的富客户端版本的即时通讯产品十分友好,每个消息在消息分发的时候给处于这个会话(单聊,群聊)下的所有用户所在客户端先推送消息,没找到连接就针对这个用户写一个离线缓存消息,那么下次该用户登录进来,可以从缓存中拉取到该消息,并且清掉缓存;
  • 2)写扩散对于我们这类通用综合消息平台并不友好,由于接入方大部分是网页版的客户端,所以没有缓存消息的能力,浏览器刷新就没有了任何消息,所以需要实时去服务端拉取历史消息。假设我是写扩散,在一个群聊中有五百个用户,针对这五百个用户在这个会话,我需要去写五百条消息,大大的增加了写io,并且还不能写缓存(得写数据库)。

3.3 tidb存在不稳定性和事务并发的问题

tidb是目前主流的开源分布式数据库,查询效率高、无需分库分表。

但同样的,tidb存在一些隐藏的问题:

  • 1)tidb在高并发情况下,并发事务会导致事务失败,具体原因不知;
  • 2)tidb排错成本高,公司很少有tidb专业运维,经常遇到不走索引的情况。

3.4 群聊、单聊冗余在同一个服务的问题

在我们初版的IM架构设计中,单聊和群聊是冗余在会话服务中的,并且冗余在同一张表的。

其实单聊、群聊从数据角度来说,还是会有些不同(比如业务属性)虽然都是会话,我们还是需要将这两个服务拆分开,细粒度的服务拆分能更好的把控整体的逻辑。

4、升级版IM架构

4.1 初始架构问题

正如前面两节分享的那样,渐渐的我们发现初版im架构有很大的不足之处。

在生产上暴露出了以下问题:

  • 1)tps没达到预期,吞吐量不能满足公司业务的发展;
  • 2)使用的存储中间件难以维护(主要是tidb),试错成本高,经常在生产暴露问题,并且速度越来越慢;
  • 3)消息写扩散没有太大必要,并大大增加了系统io次数(原因见上一节);
  • 4)一些特性无法支持,比如消息图文检索,消息已读未读。

4.2 升级版im架构介绍

本次升级后的im架构如下图所示:

如上图所示,改版后的各模块情况如下:

  • 1)存储端:存储端我们改用了mysql,针对消息服务单独使用了主从mysql集群(主节点用于写消息、从节点用于消息检索)——;
  • 2)mq消息总线:与第一版相比没有改动;
  • 3)link服务:与第一版相比,改动了link服务到消息分发服务的消息推送方式(由MQ总线方式变更为tcp实时推送);
  • 4)消息分发服务:集成了消息处理能力、路由能力,每台消息分发服务拥有所有link服务的tcp连接;
  • 5)单聊服务:负责单聊会话的管理能力;
  • 6)群聊服务:负责群聊会话的管理能力;
  • 7)用户服务:提供用户认证,登录\注册能力。

5、详细对比针对初版IM架构的改动

升级版的IM架构,对比初始初始,具体主要是下面这些改动。

5.1 改进了不同im实例间的消息分发方式

针对初版MQ消息总结的问题,升级版架构中,我们将link到消息分发服务改为tcp实时连接,百万客户端连接同一台link机器,消息实时触达能力tps达到16万。

link到消息分发服务的改版是本次设计的亮点之一,完全消除了mq推送的时延性,并且路由简单,几乎实时触达。

举个例子:当两个处于不同IM实例的客户端A和B聊天时

  • 1)初版架构中是:A用户发送消息到link --> 消息总线 --> 消息分发服务 --> 消息总线 --> link --> B用户;
  • 2)升级版架构是:用户A --> link --> 消息分发 --> link --> 用户B。

而且:link服务到消息分发服务集群的消息推送使用轮询负载均衡的方式,保证公平,不会导致个别机器负载过高。

5.2 取消了位置服务

取消了位置服务(这里的位置不是指的IM消息里的地理位置消息哦),消息分发服务集成位置服务的能力。

消息分发服务本身业务简单,不需要再单独划分位置服务,因为会增加网络io,并且消息分发服务直连link,而让它负责路由则更加方便。

5.3 存储由tidb改成了mysql

存储端由tidb改成了mysql,增强了可维护性,消息服务使用mysql主从读写分离方式,提高了消息落库速度与检索速度的同时,也减轻数据库压力。

前面有提到过使用tidb这样维护成本高,排查问题难的分布式数据库是一件很痛苦的事情。

而我们使用mysql更加稳定,大家对mysql的学习成本相对较低。针对消息服务使用读写分离的方式,能大大提高消息的吞吐量。

5.4 实现了初版无法实现的特性功能

升级版架构中,我们实现了初版无法实现的特性功能,比如消息已读未读、红包推送、商品链接推送等功能。

新版综合消息中心加入了消息已读未读、发送红包、链接推送等功能,但这些功能带有一定的业务特性,毕竟不是所有Im都需要,可通过配置取消这些功能。

5.5 消息由写扩散改为读扩散

升级版IM架构中,消息存储由写扩散改为了读扩散。

前面我们有提到写扩散和读扩散的利弊,对于网页端IM我们更适合使用读扩散,只需要落一条消息,大大提高消息服务的吞吐量.

5.6 增加了门面服务

升级版IM架构中,我们增加门面服务 im-logic,用于暴露给第三方业务线接口调用。

初版架构中,都是im的各个服务各自暴露接口给到外部进行调用, 而升级版架中我们统一使用logic服务暴露给外部调用。

在logic服务针对调用可以做一些处理,这样不会影响到整体im的通用,不会增加im底层代码的复杂度,从而将业务逻辑与底层进行解耦。

6、优化后的效果对比

针对升级版和初版IM架构,我们也做了一些对比测试,具体的测试过程就是详细展开了。

以下是测试结果:

7、业务线接入im综合消息系统的业务划分思考

7.1 到底该如何设计高性能通用im综合消息系统

关于业务线接入im综合消息系统的业务划分,我也做了一些总结和思考,为了更形象和易于理解,我这里以客服系统以及企业微信为例来进行分析。

假如我开发了一款通用的im综合消息系统,现在有很多业务方需要接入我们,我们该如何进行业务域的清晰划分就显得尤为重要,需要在妥协与不妥协中进行平衡。

就像当前市面上开源的im消息平台来说,存在的问题主要是:要么是集成了很多的业务逻辑,要么就只是一款单纯的客服系统,再或者就是一款IM好友聊天系统,中间的业务划分并不明确。当然,这也有好处,拿来就能用,并不需要进行二次业务封装。

那么,到底如何将im设计为一款真正的高性能通用im综合消息系统呢?

通用的综合消息消息平台只需要有通用的底层能力:

以下案例假设在我已经按照上述架构设计了一版im综合消息中心。

7.2 以客服系统为例

客服系统:

客服系统不光需要实现自身业务,还需要整合im的消息能力(消费im的消息),来进行场景分析,实现会话变更、信令消息推送等逻辑。

客服系统内部需要根据im的底层支持能力进行相应的业务封装以及客服系统的客服用户池,c端用户池如何初始化到im的用户中心这些问题都是需要考虑进去的。

7.3 内部OA通信为例

内部OA通信:

 

员工内部OA通信系统需要集成IM好友功能,需要根据im的用户中心封装组织架构,用户权限等功能。

同时,内部通信系统需要根据im实现消息已读未读,群聊列表,会话列表拉取等功能。

8、本文小结

im的综合消息平台是一款需要高度结合业务的中间件系统,它直接与业务打交道,跟普通的中间件有根本的区别。

一款好用的im综合消息平台,直接取决于你的通用性,可扩展性以及系统吞吐能力。

希望这篇文章所分享的内容,能对大家开发im时候的思路有所启迪。

9、参考资料

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

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

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

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

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

[6] 零基础IM开发入门(一):什么是IM系统?

[7] 基于实践:一套百万消息量小规模IM系统技术要点总结

[8] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[9] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[10] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[12] 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路

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

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

posted @ 2022-06-28 10:40 Jack Jiang 阅读(90) | 评论 (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.2 版更新内容

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

(1)Android端主要更新内容新增“扫一扫”等功能及优化!】:

  • 1)[bug]解决了客户端被踢掉后,再次登陆时提示socket错误的问题;
  • 2)[优化]优化了扫码加群界面中,群头像加载失败时的默认显示样式;
  • 3)[优化]优化了切换账号和被踢时跳转到登陆界面的切换性能;
  • 4)[优化]重构了主要类代码,更方便集成;
  • 5)[新增]搜索功能(支持好友、群聊、聊天记录搜索(与微信逻辑一样));
  • 6)[新增]“聊信信息”界面中新增“查找聊天记录”功能;
  • 7)[新增]“群聊信息”界面中新增“查找聊天记录”、“清空聊天记录”功能。

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

  • 1)[优化][服务端]升级了MobileIMSDK至v6.2beta(改动了onUserLoginout方法参数);
  • 2)[优化][服务端]解决了log4j2的两个jar包冲突导致在linux下不能正常输出log的问题.

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

posted @ 2022-06-25 22:37 Jack Jiang 阅读(66) | 评论 (0)编辑 收藏

     摘要: 本文由字节跳动技术团队开发工程师王浩分享,即时通讯网收录时有较多修订。1、引言对于移动互联网时代的用户来说,短视频应用再也不是看看视频就完事,尤其抖音这种头部应用,已经是除了传统IM即时通讯软件以外的新型社交产品了。对于中国人一年一度最重的节日——春节来说,红包是必不可少的节日特定社交元素,而抖音自然不会被错过。在2022年的春节活动期间,抖音将视频和春节红包相结合,用户可...  阅读全文

posted @ 2022-06-20 17:12 Jack Jiang 阅读(82) | 评论 (0)编辑 收藏

本文由B站微服务技术团队资深开发工程师周佳辉原创分享。

1、引言

如果你在 2015 年就使用 B 站,那么你一定不会忘记那一年 B 站工作日选择性崩溃,周末必然性崩溃的一段时间。

也是那一年 B 站投稿量激增,访问量随之成倍上升,而过去的 PHP 全家桶也开始逐渐展露出颓势,运维难、监控难、排查故障难、调用路径深不见底。

也就是在这一年,B 站开始正式用 Go 重构 B 站,从此B站的API网关技术子开始了从0到1的持续演进。。。

* 补充说明:本次 API 网关演进也以开源形式进行了开发,源码详见本文“12、本文源码”。

PS:本文分享的API网关涉及到的主要是HTTP短连接,虽然跟长连接技术有些差异,但从架构设计思路和实践上是一脉相承的,所以也就收录到了本《长连接网关技术专题》系列文章中。

学习交流:

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

2、关于作者

周佳辉:哔哩哔哩资深开发工程师。始终以简单为核心的技术设计理念,追求极致简单有效的后端架构。

2017 年加入 B 站,先后从事账号、网关、基础库等开发工作。编码 C/V 技能传授者,技术文档背诵者。开源社区爱好者,安全技术爱好者,云计算行业活跃用户,网络工程熟练工。史诗级 bug 生产者,熟练掌握 bug 产生的各类场景。

3、专题目录

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

4、正式用Go重构B站

鉴于引言中所列举的各种技术问题,也是在2015年,财队开始正式用 Go 重构 B 站。

B站第一个 Go 项目——bilizone,由冠冠老师(郝冠伟)花了一个周末时间编码完成。

commit 4ccb1497ca6d94cec0ea1b2555dd1859e6f4f223

Author: felixhao <g******[url=mailto:1@gmail.com]1@gmail.com[/url]>

Date:   Wed Jul 1 18:55:00 2015 +0800

    project init

commit 6e338bc0ee638621e01918adb183747cf2a9e567

Author: 郝冠伟 <h*******@bilibili.com>

Date:   Wed Jul 1 11:21:18 2015 +0800

    readme

▲ 郝冠伟:哔哩哔哩主站技术中心架构师

bilizone 其实还是一个大而全的应用,bilizone 在当时重构的主要意义是将谁也理不清的 PHP 逻辑梳理成了一个比较标准的 Go 应用。

bilizone 在当时最大的意义就是为用户终端提供了基本稳定的数据结构、相对可靠的接口和比较有效的监控。

但因 bilizone 依旧是一个单体应用,所以它依旧继承了单体应用所具有的缺点:

  • 1)代码复杂度高:方法被滥用、超时设置混乱、牵一发而动全身;
  • 2)一挂全挂:最常见的比如,超时设置不合理、goroutine 大量堆积、雪崩;
  • 3)测试及维护成本高:小改动都需要测试所有 case,运维发布胆战心惊。

所以此时B站的崩溃频率虽然已经有所降低,但一炸全炸的问题依旧是一个心腹大患。

5、基于微服务的B站架构初具雏形

鉴于bilizone所面临的单体应用技术缺点,接下来的一次重构,让B站基于微服务的全局架构面貌就将初具雏形。

为了实现微服务模式下的 bilibili,我们将一个 bilizone 应用拆分成多个独立业务应用,如账号、稿件、广告等等,这些业务通过 SLB 直接对外提供 API。

当时的调用模式如下图所示:

但是随着功能拆分后,我们对外暴露了一批微服务,但是因为缺乏统一的出口而面临了不少困难。

这些困难主要是:

  • 1)客户端与微服务直接通信,强耦合;
  • 2)需要多次请求,客户端聚合数据,工作量巨大,延迟高;
  • 3)协议不利于统一,各个部门间有差异,反而需要通过客户端来兼容;
  • 4)面向“端”的 API 适配,耦合到了内部服务;
  • 5)多终端兼容逻辑复杂,每个服务都需要处理;
  • 6)统一逻辑无法收敛,比如安全认证、限流。

6、基于BFF模式的微服务架构

基于上节的初阶微服务架构带来的技术问题,以及我们想要将对端的处理进行内聚的想法,我们自然的而然的就想到在客户端与后端服务之间加一个 app-interface 的组件,这就是接下来的 BFF(Backend for Frontend)模式。

app-interface 的工作模式如下图所示:

有了这个 BFF 之后,我们可以在该服务内进行大量的数据聚合,按照业务场景来设计粗粒度的 API。

这样,后续服务的演进也带来了很多优势:

  • 1)轻量交互:协议精简、聚合;
  • 2)差异服务:数据裁剪以及聚合、针对终端定制化 API;
  • 3)动态升级:原有系统兼容升级,更新服务而非协议;
  • 4)沟通效率提升:协作模式演进为移动业务和网关小组。

BFF 可以认为是一种适配服务,将后端的微服务为客户端的需要进行适配(主要包括聚合裁剪和格式适配等逻辑),向终端设备暴露友好和统一的 API,方便无线设备接入访问后端服务,在其中可能还伴随有埋点、日志、统计等需求。

然而,这个时期的 BFF 还有一个致命的一个问题是——整个 app-interface 属于 single point of failure,严重代码缺陷或者流量洪峰可能引发集群宕机所有接口不可用。

7、基于多套BFF模式的微服务架构

针对上节中BFF模式下架构的技术问题,于是我们在上述基础上进一步迭代,将 app-interface 进行业务拆分。

进而多套 BFF 的模式横空出世:

由此模式开始,基本确定了 B 站微服务接口的对接模式,这套模式也随之在全公司内推广开来。

8、垂直BFF模式时代(2016年至2019年)

接上节,当 B 站网关的架构发展为多套垂直 BFF 之后,开发团队围绕该模式平稳迭代了相当长的一段时间。

而后随着B站业务的发展,团队人员的扩充和几次组织架构调整,此时开始出现直播、电商等独立业务,这些业务的发展我们之后再细说。

而在这些调整之后,有一个团队的职责越来越清晰:主站网关组。

主站网关组的主要职责就是维护上述各类功能的 BFF 网关,此时 bilibili 的主要流量入口为粉板 App。这里可以简单细说一下粉板 App 上的所有业务组成。

主站业务:

  • 1)网关组维护的 BFF,如推荐、稿件播放页等;
  • 2)业务层自行维护的 BFF,如评论、弹幕、账号等。

独立业务:

  • 1)电商服务;
  • 2)直播服务;
  • 3)动态服务。

主站业务的 BFF 其实被分为两类:

  • 1)一类是由网关组负责的 BFF;
  • 2)另一类是业务自行维护的 BFF。

而这两类 BFF 的技术栈其实基本一致,基本功能职责也相差不多。如此划分的原因是让网关组可以更专注于迭代客户端特性功能,免去理解部分独立业务场景的接口,如登陆页应该让对安全更专业账号的同学自行维护。

在这里我们也可以简述一下,一个新需求应该如何决定参与的 BFF :

  • 1)如果这个功能能由业务层的业务 BFF 独立完成,则网关组不需介入;
  • 2)如果该功能是一个客户端特性需求,如推荐流等复合型业务,需要对接公司大量部门时,则由网关同学参与开发 BFF。

当时主站技术部的后端同学遵循以上两个规则,基本能够满足业务的快速开发和迭代。

我把这段时间称为垂直 BFF 时代,因为基本主站每个业务或多或少都有各种形式的网关存在,大家通过这个网关向外提供接口,该网关和 SLB 进行直接交互。

9、基于业务的统一API网关架构

接上节,我们再来谈一谈几项重要的业务:电商、直播和动态。

电商和直播其实并不是同一时期衍生的,直播在主站 PHP 时期就诞生了,而电商相对更晚一些。

当时直播的技术栈组成有 C++、PHP、Go,其中早期大部分业务逻辑由 PHP 和 C++ 实现,稍晚一些也开始逐步试用主站的 Go 实现部分业务逻辑。其中 PHP 负责对终端提供接口,C++ 主要实现核心业务功能。因此我们可以简单理解为直播使用由 PHP 编写的 BFF 网关。

动态团队其实派生自直播团队,因此技术栈和直播当时基本一致,这里可以简单省略。

而众所周知,大部分电商团队的技术栈都是 Java 和 Spring 或 Dubbo。

因这几个业务实现上几乎没有相似的地方,且大家对 gRPC 协议逐渐地认同,因此技术栈上大家基本没有大一统的想法,互相能调通即可。

而随着 B 站团队进一步的壮大、流量持续的增长,进而经历了诸多线上故障、事故分析之后,大家慢慢发现了这套架构下的各种问题。

这些问题主要是:

  • 1)单个复杂模块也会导致后续业务集成的高难度,根据康威法则,复杂聚合型 BFF 和多团队之间就出现不匹配问题,团队之间沟通协调成本高,交付效率低下;
  • 2)很多跨横切面逻辑,比如安全认证,日志监控,限流熔断等。随着时间的推移,功能的迭代,代码变得越来越复杂,技术债越堆越多。

此时:我们可能还需要一个能协调横跨切面的组件,将路由、认证、限流、安全等组件全部上提,能够统一更新发布,把业务集成度高的 BFF 层和通用功能服务层进行分层,进而大家开始引入基于业务的“统一API网关”架构(如下图所示)。

在新的架构中:统一网关承担了重要的角色,它是解耦拆分和后续升级迁移的利器。

在统一网关的配合下:单块 BFF 实现了解耦拆分,各业务线团队可以独立开发和交付各自的微服务,研发效率大大提升。

另外:把跨横切面逻辑从 BFF 剥离到网关上去以后,BFF 的开发人员可以更加专注业务逻辑交付,实现了架构上的关注分离(Separation of Concerns)。

10、从基于业务的多网关到全局统一网关(2022年至今)

在这两三年的时间里,各个业务团队或多或少都有自己业务网关组建独立的维护团队,也为网关的功能作出过相当多的投入。

但随着 B 站业务的发展,公司级中间件功能的不断更替演进,如果将对接各个中间件的工作在每个网关上都实现一次的话带来的人力投入和沟通成本会相当巨大,且实现标准不统一、运营方式不统一无法起到 API 网关所带来的最佳收益。

因此微服务团队开发了一款 B 站内部意义上的标准 API 网关(全局统一API网关),该 API 网关汇集以往各型网关中流量治理的优秀经验,对相关功能做出完善设计改进。

该 API 网关的目前的主要功能除了常规的限流、熔断、降级、染色外,还会基于这些基础功能和公司各类中间件的基础上,提供各种额外能力。

这些额外进阶型AP 质量治理的相关功能主要是:

  • 1)全链路灰度;
  • 2)流量采样分析、回放;
  • 3)流量安全控制;
  • ...

业务团队在接入 API 网关后都可以一并获得这些功能,为业务的迅速迭代做出力所能及的保障。

11、不仅仅是 API 网关

在开发 API 网关的同时,我们也会更进一步关注业务团队开发、对接 API 时的体验,我们将以网关作为统一标准 API 规范的起点,为业务团队提供更有效的 API 开发生态。

这些API 开发生态可能是:

  • 1)规划 API 业务域,简化 SRE 运维;
  • 2)标准 API 元信息平台;
  • 3)精确的 API 文档和调试工具;
  • 4)类型安全的 API 集成 SDK;
  • 5)API 兼容性保障服务。

API 网关是我们 API 治理生态中的一个标志性里程碑,我们希望在 API 网关的开发中能够多多倾听大家的意见,希望能有更多的声音来帮助我们理清思路。

本次 API 网关演进也以开源形式进行了开发,在这里欢迎大家指导(本次源码详见本文“12、本文源码)。

12、本文源码

主地址:https://github.com/go-kratos/gateway

备地址:https://github.com/52im/gateway

或从原文链接中下载附件:http://www.52im.net/thread-3941-1-1.html

13、参考资料

[1] 喜马拉雅自研亿级API网关技术实践

[2] 手淘亿级移动端接入层网关的技术演进之路

[3] 从100到1000万高并发的架构演进之路

[4] 一文读懂大型分布式系统设计的方方面面

[5] 零基础理解大型分布式架构的演进历史、技术原理、最佳实践

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

posted @ 2022-06-14 11:56 Jack Jiang 阅读(108) | 评论 (0)编辑 收藏

本文引用了文章“月活 12.8 亿的微信是如何防止崩溃的?”和论文“Overload Control for Scaling WeChat Microservices”的内容,有大量改动、优化和修订。

1、引言

微信是一款国民级的即时通讯IM应用,月活用户早就超过10亿,而且经常过年过节会遇到聊天消息量暴增的情况,服务是很容易出现过载的,但事实是微信的后台服务一直比较稳定,那么他们是怎么做到的呢?

本文以微信发表的论文《Overload Control for Scaling Wechat Microservices》 为基础(论文PDF原文下载见文末附件),分享了微信基于大规模微服务架构的后台过载管控和保护策略,以及微信根据IM业务特点的一些独特的架构设计做法,其中很多方法很有借鉴意义,值得一读。

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

2、微信所面临的并发压力

截止论文《Overload Control for Scaling Wechat Microservices》发表前,微信后端有超过3000多个服务(包括即时聊天、社交关系、移动支付和第三方授权等),占用20000多台机器(随着微信的广泛普及,这些数字仍在不断增加)。

面向前端请求的入口服务每天需要处理10亿到100亿级别的请求,而每个这样的请求还会触发更多内部的关联服务,从整体来看,微信后端需要每秒处理数亿个请求。

随着微信的不断发展,这些服务子系统一直在快速进行更新迭代。以2018 年的3月到5月为例,在短短的两个月时间里,微信的各服务子系统平均每天发生近千次的变更,运维压力可想而之。

另外:微信每天请求量的分布很不平均,高峰期请求量能达到平时的3倍。而在特殊日子里(比如过年的时候),高峰期的流量能飙升到平时的10倍。有时朋友圈里有什么刷屏的活动,流量肯定也会突增。由此可见,微信后端系统的并发压力相当之大。

而且:微信后端的这些服务所处的环境也是不断变化的,包括硬件故障、代码bug、系统变更等,都会导致服务可承受的容量动态变化。

3、微信的后端服务架构

微信后端采用的也是微服务架构。说是微服务,其实我理解就是采用统一的 RPC 框架搭建的一个个独立的服务,服务之间互相调用,实现各种各样的功能,这也是现代服务的基本架构。毕竟谁也不希望看到我朋友圈崩了,导致跟我聊天也不行了,这也是微信的典型好处。

微信后端的微服务架构一般分为3层:

如上图所示,这3层服务分别是:

  • 1)“入口跳板”服务(接收外部请求的前端服务);
  • 2)“共享跳板”服务(中间层协调服务);
  • 3)“基础服务”(不再向其他服务发出请求的服务,也就是充当请求的接收器)。

微信后端的大多数服务属于“共享跳板”服务,“入口跳板”服务比如登录、发送聊天消息、支付服务等。“基础服务”也就是日常最好理解的这些信息数据接口类,比如账户数据、个人信息、好友/联系人信息等。

按照微信后端服务的请求量(每日在十亿到百亿之间),入口协议触发对“共享跳板”服务和“基础服务”更多的请求,核心服务每秒要处理上亿次的请求,也就是显而易见的了。

4、什么是过载保护

1)什么是服务过载?

服务过载就是服务的请求量超过服务所能承受的最大值,从而导致服务器负载过高,响应延迟加大。

用户侧表现就是无法加载或者加载缓慢,这会引起用户进一步的重试,服务一直在处理过去的无效请求,导致有效请求跌 0,甚至导致整个系统产生雪崩。

2)为什么会发生服务过载?

互联网天生就会有突发流量、秒杀、抢购、突发大事件、节日甚至恶意攻击等,都会造成服务承受平时数倍的压力,比如微博经常出现某明星官宣结婚或者离婚导致服务器崩溃的场景,这就是服务过载。

3)过载保护的好处

过载保护主要是为了提升用户体验,保障服务质量,在发生突发流量时仍然能够提供一部分服务能力,而不是整个系统瘫痪。

系统瘫痪就意味着用户流失、口碑变差、夫妻吵架,甚至威胁生命安全(假如腾讯文档崩溃,这个文档正好用于救灾)。

而微信团队在面对这种量级的高并发请求挑战,做法是精细化的服务过载控制。我们继续往下学习。

5、微信面临的过载控制技术挑战

过载控制对于大规模在线应用程序来说至关重要,这些应用程序需要在不可预测的负载激增的情况下实现 24×7 服务可用性。

传统的过载控制机制是为具有少量服务组件、相对狭窄的“前门”和普通依赖关系的系统而设计的。

而微信这种现代即时通讯im应用的全时在线服务特性,在架构和依赖性方面正变得越来越复杂,远远超出了传统过载控制的设计目标。

这些技术痛点包括:

  • 1)由于发送到微信后端的服务请求没有单一的入口点,因此传统的全局入口点(网关)集中负载监控方法并不适用;
  • 2)特定请求的服务调用图可能依赖于特定于请求的数据和服务参数,即使对于相同类型的请求也是如此(因此,当特定服务出现过载时,很难确定应该限制哪些类型的请求以缓解这种情况);
  • 3)过多的请求中止浪费了计算资源,并由于高延迟而影响了用户体验;
  • 4)由于服务的调用链极其复杂,而且在不断演化,导致有效的跨服务协调的维护成本和系统开销过高。

由于一个服务可能会向它所依赖的服务发出多个请求,并且还可能向多个后端服务发出请求,因此我们必须特别注意过载控制。我们使用一个专门的术语,叫作“后续过载”,用于描述调用多个过载服务或多次调用单个过载服务的情况。

“后续过载”给有效的过载控制带来了挑战。当服务过载时随机执行减载可以让系统维持饱和的吞吐量,但后续过载可能会超预期大大降低系统吞吐量 …

即:在大规模微服务场景下,过载会变得比较复杂,如果是单体服务,一个事件只用一个请求,但微服务下,一个事件可能要请求很多的服务,任何一个服务过载失败,就会造成其他的请求都是无效的。如下图所示。

 比如:在一个转账服务下,需要查询分别两者的卡号, 再查询 A 时成功了,但查询 B 失败,对于查卡号这个事件就算失败了。比如查询成功率只有 50%, 那对于查询两者卡号这个成功率只有 50% * 50% = 25% 了, 一个事件调用的服务次数越多,那成功率就会越低。

6、微信的过载控制机制

微信的微服务过载控制机制叫“DAGOR”(因为微信把它的服务间关系模型叫“directed acyclic graph ”,简称DAG)。

显然这种微服务底层的机制必须是和具体的业务实现无关的。DAGOR还必须是去中心化的,否则的话在微信这么大且分布不均的流量下,过载控制很难做到实时和准确。同时也无法适应微服务快速的功能迭代发布(平均每天要发生近1000次的微服务上下线)。

此外,DAGOR还需要解决一个问题:服务调用链很长,如果底层服务因为过载保护丢弃了请求,上层服务耗费的资源全浪费了,而且很影响用户体验(想想进度条走到99%告诉你失败了)。所以过载控制机制在各服务之间必须有协同作用,有时候需要考虑整个调用链的情况。

首先我们来看怎么检测到服务过载。

7、微信如何判断过载

通常判断过载可以使用吞吐量、延迟、CPU 使用率、丢包率、待处理请求数、请求处理事件等等。

微信使用在请求在队列中的平均等待时间作为判断标准。平均等待时间就是从请求到达,到开始处理的时间。

为啥不使用响应时间?因为响应时间是跟服务相关的,很多微服务是链式调用,响应时间是不可控的,也是无法标准化的,很难作为一个统一的判断依据。

那为什么也不使用 CPU 负载作为判断标准呢? 因为 CPU 负载高不代表服务过载,因为一个服务请求处理及时,CPU 处于高位反而是比较良好的表现。实际上 CPU 负载高,监控服务是会告警出来,但是并不会直接进入过载处理流程。

腾讯微服务默认的超时时间是 500ms,通过计算每秒或每 2000 个请求的平均等待时间是否超过 20ms,判断是否过载,这个 20ms 是根据微信后台 5 年摸索出来的门槛值。

采用平均等待时间还有一个好处是:独立于服务,可以应用于任何场景,而不用关联于业务,可以直接在框架上进行改造。

当平均等待时间大于 20ms 时,以一定的降速因子过滤调部分请求,如果判断平均等待时间小于 20ms,则以一定的速率提升通过率,一般采用快降慢升的策略,防止大的服务波动,整个策略相当于一个负反馈电路。

 

8、微信的过载控制策略

微信后台一旦检测到服务过载,就需要按照一定的过载保户策略对请求进行过滤控制,来决定哪些请求能被过载服务处理,哪些是需要丢弃的。

前面我们分析过,对于链式调用的微服务场景,随机丢弃请求会导致整体服务的成功率很低。所以请求是按照优先级进行控制的,优先级低的请求会优先丢弃。

那么从哪些维度来进行优化级的分级呢?

8.1 基于业务的优先级控制

对于微信来说,不同的业务场景优先级是不同的, 比如:

  • 1)登录场景是最重要的业务(不能登录一切都白瞎);
  • 2)支付消息比普通im聊天消息优先级高(因为用户对金钱是更敏感的);
  • 3)普通消息又比朋友圈消息优先级高(必竟微信的本质还是im聊天)。

所以在微信内是天然存在业务优先级的。

微信的做法是,预先定义好所有业务的优先级并保存在一个Hash Table里:

没有定义的业务,默认是最低优先级。

业务优先级在各个业务的入口服务(Entry Services)中找到请求元信息里。由于一个请求成功与否依赖其下游服务所有的后续请求,所以下游服务的所有后续请求也会带上相同的业务优先级。当服务过载时,会处理优先级更高的请求,丢弃优先级低的请求。

然而,只用业务优先级决定是否丢弃请求,容易造成系统颠簸,比如:

  • 1)支付请求突然上涨导致过载,消息请求被丢弃;
  • 2)丢弃消息请求后,系统负载降低了,又开始处理消息请求;
  • 3)然而,处理消息请求又导致服务过载,又会在下一个窗口抛弃消息请求。

这样反复调整服务请求管制,整体体验非常不好。所以微信需要更精细化的服务请求管制。

PS:微信尝试过提供API让服务提供方自己修改业务优先级,后来在实践中发现这种做法在不同的团队中极难管理,且对于过载控制容易出错,最终放弃了。

8.2 基于用户的优先级控制

很明显,正如上节内容所述,只基于业务优先级的控制是不够的:

  • 1)首先不可能因为负载高,丢弃或允许通过一整个业务的请求,因为每个业务的请求量很大,那一定会造成负载的大幅波动;
  • 2)另外如果在业务中随机丢弃请求,在过载情况下还是会导致整体成功率很低。

为了解决这个问题,微信引入用户优先级。

微信在每个业务优先级内按用户ID计算出的128个优先级:

首先用户优先级也不应该相同,对于普通人来说通过 hash 用户唯一 ID计算用户优先级(这个hash函数每小时变一次,让所有用户都有机会在相对较长的时间内享受到高优先级,保证“公平”)。跟业务优先级一样,单个用户的访问链条上的优先级总是一致的。

这里有个疑问:为啥不采用会话 ID 计算优先级呢?

从理论上来说采用会话 ID 和用户 ID 效果是一样的,但是采用会话 ID 在用户重新登录时刷新,这个时候可能用户的优先级可能变了。在过载的情况下,他可能因为提高了优先级就恢复了。

这样用户会养成坏习惯,在服务有问题时就会重新登录,这样无疑进一步加剧了服务的过载情况。

于是,因为引入了用户优先级,那就和业务优先级组成了一个二维控制平面。根据负载情况,决定这台服务器的准入优先级(B,U),当过来的请求业务优先级大于 B,或者业务优先级等于 B,但用户优先级高于 U 时,则通过,否则决绝。

下图就是这个“优先级(B,U)”控制逻辑(我们会在后面再具体讨论):

8.3 自适应优先级调整

在大规模微服务场景下,服务器的负载变化是非常频繁的。所以服务器的准入优先级是需要动态变化的,微信分了几十个业务优先级,每个业务优先级下有 128 个用户优先级,所以总的优先级是几千个。

如何根据负载情况调整优先级呢?

最简单的方式是从右到左遍历:每调整一次判断下负载情况。这个时间复杂度是 O(n), 就算使用二分法,时间复杂度也为 O(logn),在数千个优先级下,可能需要数十次调整才能确定一个合适的优先级,每次调整好再统计优先级,可能几十秒都过去了,这个方法无疑是非常低效的。

微信提出了一种基于直方图统计的方法快速调整准入优先级:服务器上维护者目前准入优先级下,过去一个周期的(1s 或 2000 次请求)每个优先级的请求量。当过载时,通过消减下一个周期的请求量来减轻负载。假设上一个周期所有优先级的通过的请求总和是 N,下一个周期的请求量要减少 N*a,怎么去减少呢?每提升一个优先级就减少一定的请求量,一直提升到 减少的数目大于目标量,恢复负载使用相反的方法,只不是系数为 b ,比 a 小,也是为了快降慢升。根据经验值 a 为 5%,b 为 1%。

为了进一步减轻过载机器的压力,能不能在下游过载的情况下不把请求发到下游呢?否则下游还是要接受请求、解包、丢弃请求,白白的浪费带宽,也加重了下游的负载。

为了实现这个能力:在每次请求下游服务时,下游把当前服务的准入优先级返回给上游,上游维护下游服务的准入优先级,如果发现请求优先级达不到下游服务的准入门槛,直接丢弃,而不再请求下游,进一步减轻下游的压力。

9、实验数据

微信的这套服务过载控制策略(即DAGOR)在微信的生产环境已经运作多年,这是对它的设计可行性的最好证明。

但并没有为学术论文提供必要的图表,所以微信同时进行了一组模拟实验。

下面的图表突出显示了基于排队时间而非响应时间的过载控制的好处。在发生后续过载的情况下,这些好处最为明显(图右)。

10、小结一下

微信的整个过载控制逻辑流程如下图所示:

针对上面这张图,我们来解读一下:

  • 1)当用户从微信发起请求,请求被路由到接入层服务,分配统一的业务和用户优先级,所有到下游的字请求都继承相同的优先级;
  • 2)根据业务逻辑调用 1 个或多个下游服务,当服务收到请求,首先根据自身服务准入优先级判断请求是接受还是丢弃(服务本身根据负载情况周期性的调整准入优先级);
  • 3)当服务需要再向下游发起请求时,判断本地记录的下游服务准入优先级(如果小于则丢弃,如果没有记录或优先级大于记录则向下游发起请求);
  • 4)下游服务返回上游服务需要的信息,并且在信息中携带自身准入优先级;
  • 5)上游接受到返回后解析信息,并更新本地记录的下游服务准入优先级。

微信的整个过载控制策略有以下三个特点:

  • 1)业务无关的:使用请求等待时间而不是响应时间,制定用户和业务优先级,这些都与业务本身无关;
  • 2)高效且公平: 请求链条的优先级是一致的,并且会定时改变 hash 函数调整用户优先级,过载情况下,不会总是影响固定的用户;
  • 3)独立控制和联合控制结合:准入优先级取决于独立的服务,但又可以联合下游服务的情况,优化服务过载时的表现。

11、写在最后

微信团队的分享只提到过载控制,但我相信服务调用方应该还有一些其他机制,能够解决不是因为下游服务过载,而是因为网络抖动导致的请求超时问题。

微信的这套微服务过载控制机制(即DAGOR)提供的服务无关、去中心化、高效和公平等特性很好地在微信后端跑了很多年。

最后,微信团队还分享了他们设计和运维DAGOR宝贵经验:

  • 1)大规模微服务架构中的过载控制必须在每个服务中实现分散和自治;
  • 2)过载控制应该要考虑到各种反馈机制(例如 DAGOR 的协作准入控制),而不是仅仅依赖于开环启发式;
  • 3)应该通过分析实际工作负载来了解过载控制设计。

12、参考资料

[1] Overload Control for Scaling WeChat Microservices

[2] 罗神解读“Overload Control for Scaling WeChat Microservices”

[3] 2W台服务器、每秒数亿请求,微信如何不“失控”?

[4] DAGOR:微信微服务过载控制系统

[5] 月活 12.8 亿的微信是如何防止崩溃的?

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

[7] QQ 18年:解密8亿月活的QQ后台服务接口隔离技术

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

[9] 架构之道:3个程序员成就微信朋友圈日均10亿发布量[有视频]

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

[11] 一份微信后台技术架构的总结性笔记

13、论文原文

论文PDF请下载此附件:

因无法上传附件,请从此链接:http://www.52im.net/thread-3930-1-1.html文末的“参考资料”附件中下载

论文PDF全部内容概览:

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

posted @ 2022-06-06 16:31 Jack Jiang 阅读(483) | 评论 (0)编辑 收藏

本文由蘑菇街前端开发工程师“三体”分享,原题“蘑菇街云端直播探索——启航篇”,有修订。

1、引言

随着移动网络网速的提升与资费的降低,视频直播作为一个新的娱乐方式已经被越来越多的用户逐渐接受。特别是最近这几年,视频直播已经不仅仅被运用在传统的秀场、游戏类板块,更是作为电商的一种新模式得到迅速成长。

本文将通过介绍实时视频直播技术体系,包括常用的推拉流架构、传输协议等,让你对现今主流的视频直播技术有一个基本的认知。

学习交流:

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

2、蘑菇街的直播架构概览

目前蘑菇街直播推拉流主流程依赖于某云直播的服务。

云直播提供的推流方式有两种:

  • 1)一是通过集成SDK的方式进行推流(用于手机端开播);
  • 2)另一种是通过RTMP协议向远端服务器进行推流(用于PC开播端或专业控台设备开播)。

除去推拉流,该云平台也提供了云通信(IM即时通讯能力)和直播录制等云服务,组成了一套直播所需要的基础服务。

3、推拉流架构1:厂商SDK推拉流

如上题所示,这一种推拉流架构方式需要依赖腾讯这类厂商提供的手机互动直播SDK,通过在主播端APP和用户端APP都集成SDK,使得主播端和用户端都拥有推拉流的功能。

这种推拉流架构的逻辑原理是这样的:

  • 1)主播端和用户端分别与云直播的互动直播后台建立长连接;
  • 2)主播端通过UDT私有协议向互动直播后台推送音视频流;
  • 3)互动直播后台接收到音视频流后做转发,直接下发给与之建立连接的用户端。

这种推拉流方式有几点优势:

  • 1)只需要在客户端中集成SDK:通过手机就可以开播,对于主播开播的要求比较低,适合直播业务快速铺开;
  • 2)互动直播后台仅做转发:没有转码,上传CDN等额外操作,整体延迟比较低;
  • 3)主播端和用户端都可以作为音视频上传的发起方:适合连麦、视频会话等场景。

4、推拉流架构2:旁路推流

之前介绍了通过手机SDK推拉流的直播方式,看起来在手机客户端中观看直播的场景已经解决了。

那么问题来了:如果我想要在H5、小程序等其他场景下观看直播,没有办法接入SDK,需要怎么处理呢?

这个时候需要引入一个新的概念——旁路推流。

旁路推流指的是:通过协议转换将音视频流对接到标准的直播 CDN 系统上。

目前云直播开启旁路推流后,会通过互动直播后台将音视频流推送到云直播后台,云直播后台负责将收到音视频流转码成通用的协议格式并且推送到CDN,这样H5、小程序等端就可以通过CDN拉取到通用格式的音视频流进行播放了。

目前蘑菇街直播旁路开启的协议类型有HLS、FLV、RTMP三种,已经可以覆盖到所有的播放场景,在后续章节会对这几种协议做详细的介绍。

5、推拉流架构3:RTMP推流

随着直播业务发展,一些主播逐渐不满足于手机开播的效果,并且电商直播需要高保真地将商品展示在屏幕上,需要通过更加高清专业的设备进行直播,RTMP推流技术应运而生。

我们通过使用OBS等流媒体录影程序,对专业设备录制的多路流进行合并,并且将音视频流上传到指定的推流地址。由于OBS推流使用了RTMP协议,因此我们称这一种推流类型为RTMP推流。

我们首先在云直播后台申请到推流地址和秘钥,将推流地址和秘钥配置到OBS软件当中,调整推流各项参数,点击推流以后,OBS就会通过RTMP协议向对应的推流地址推送音视频流。

这一种推流方式和SDK推流的不同之处在于音视频流是直接被推送到了云直播后台进行转码和上传CDN的,没有直接将直播流转推到用户端的下行方式,因此相比SDK推流延迟会长一些。

总结下来RTMP推流的优势和劣势比较明显。

优势主要是:

  • 1)可以接入专业的直播摄像头、麦克风,直播的整体效果明显优于手机开播;
  • 2)OBS已经有比较多成熟的插件,比如目前蘑菇街主播常用YY助手做一些美颜的处理,并且OBS本身已经支持滤镜、绿幕、多路视频合成等功能,功能比手机端强大。

劣势主要是:

  • 1)OBS本身配置比较复杂,需要专业设备支持,对主播的要求明显更高,通常需要一个固定的场地进行直播;
  • 2)RTMP需要云端转码,并且本地上传时也会在OBS中配置GOP和缓冲,延时相对较长。

6、高可用架构方案:云互备

业务发展到一定阶段后,我们对于业务的稳定性也会有更高的要求,比如当云服务商服务出现问题时,我们没有备用方案就会出现业务一直等待服务商修复进度的问题。

因此云互备方案就出现了:云互备指的是直播业务同时对接多家云服务商,当一家云服务商出现问题时,快速切换到其他服务商的服务节点,保证业务不受影响。

直播业务中经常遇到服务商的CDN节点下行速度较慢,或者是CDN节点存储的直播流有问题,此类问题有地域性,很难排查,因此目前做的互备云方案,主要是备份CDN节点。

目前蘑菇街整体的推流流程已经依赖了原有云平台的服务,因此我们通过在云直播后台中转推一路流到备份云平台上,备份云在接收到了直播流后会对流转码并且上传到备份云自身的CDN系统当中。一旦主平台CDN节点出现问题,我们可以将下发的拉流地址替换成备份云拉流地址,这样就可以保证业务快速修复并且观众无感知。

7、视频直播数据流解封装原理

介绍流协议之前,先要介绍我们从云端拿到一份数据,要经过几个步骤才能解析出最终需要的音视频数据。

如上图所示,总体来说,从获取到数据到最终将音视频播放出来要经历四个步骤。

第一步:解协议。

协议封装的时候通常会携带一些头部描述信息或者信令数据,这一部分数据对我们音视频播放没有作用,因此我们需要从中提取出具体的音视频封装格式数据,我们在直播中常用的协议有HTTP和RTMP两种。

第二步:解封装。

获取到封装格式数据以后需要进行解封装操作,从中分别提取音频压缩流数据和视频压缩流数据,封装格式数据我们平时经常见到的如MP4、AVI,在直播中我们接触比较多的封装格式有TS、FLV。

第三步:解码音视频。

到这里我们已经获取了音视频的压缩编码数据。

我们日常经常听到的视频压缩编码数据有H.26X系列和MPEG系列等,音频编码格式有我们熟悉的MP3、ACC等。

之所以我们能见到如此多的编码格式,是因为各种组织都提出了自己的编码标准,并且会相继推出一些新的议案,但是由于推广和收费问题,目前主流的编码格式也并不多。

获取压缩数据以后接下来需要将音视频压缩数据解码,获取非压缩的颜色数据和非压缩的音频抽样数据。颜色数据有我们平时熟知的RGB,不过在视频的中常用的颜色数据格式是YUV,指的是通过明亮度、色调、饱和度确定一个像素点的色值。音频抽样数据通常使用的有PCM。

第四步:音视频同步播放。

最后我们需要比对音视频的时间轴,将音视频解码后的数据交给显卡声卡同步播放。

PS:如果你对上述流程还不太理解,建议进一步阅读以下系列文章:

  1. 移动端实时音视频直播技术详解(一):开篇
  2. 移动端实时音视频直播技术详解(二):采集
  3. 移动端实时音视频直播技术详解(三):处理
  4. 移动端实时音视频直播技术详解(四):编码和封装
  5. 移动端实时音视频直播技术详解(五):推流和传输
  6. 移动端实时音视频直播技术详解(六):延迟优化

另外:有关音视频编解码技术的文章,也可以详细学习以下文章:

  1. 视频编解码之:《理论概述》、《数字视频介绍》、《编码基础》、《预测技术介绍
  2. 认识主流视频编码技术H.264
  3. 如何开始音频编解码技术的学习
  4. 音频基础及编码原理入门
  5. 常见的实时语音通讯编码标准
  6. 实时视频编码H.264的特点与优势》、《视频编码H.264、VP8的前世今生
  7. 详解音频编解码的原理、演进和应用选型》、《零基础,史上最通俗视频编码技术入门

8、视频直播传输协议1:HLS

首先介绍一下HLS协议。HLS是HTTP Live Streaming的简写,是由苹果公司提出的流媒体网络传输协议。

从名字可以明显看出:这一套协议是基于HTTP协议传输的。

说到HLS协议:首先需要了解这一种协议是以视频切片的形式分段播放的,协议中使用的切片视频格式是TS,也就是我们前文提到的封装格式。

在我们获取TS文件之前:协议首先要求请求一个M3U8格式的文件,M3U8是一个描述索引文件,它以一定的格式描述了TS地址的指向,我们根据M3U8文件中描述的内容,就可以获取每一段TS文件的CDN地址,通过加载TS地址分段播放就可以组合出一整段完整的视频。

使用HLS协议播放视频时:首先会请求一个M3U8文件,如果是点播只需要在初始化时获取一次就可以拿到所有的TS切片指向,但如果是直播的话就需要不停地轮询M3U8文件,获取新的TS切片。

获取到M3U8后:我们可以看一下里面的内容。首先开头是一些通用描述信息,比如第一个分片序列号、片段最大时长和总时长等,接下来就是具体TS对应的地址列表。如果是直播,那么每次请求M3U8文件里面的TS列表都会随着最新的直播切片更新,从而达到直播流播放的效果。

HLS这种切片播放的格式在点播播放时是比较适用的,一些大的视频网站也都有用这一种协议作为播放方案。

首先:切片播放的特性特别适用于点播播放中视频清晰度、多语种的热切换。比如我们播放一个视频,起初选择的是标清视频播放,当我们看了一半觉得不够清晰,需要换成超清的,这时候只需要将标清的M3U8文件替换成超清的M3U8文件,当我们播放到下一个TS节点时,视频就会自动替换成超清的TS文件,不需要对视频做重新初始化。

其次:切片播放的形式也可以比较容易地在视频中插入广告等内容。

在直播场景下,HLS也是一个比较常用的协议,他最大的优势是苹果大佬的加持,对这一套协议推广的比较好,特别是移动端。将M3U8文件地址喂给video就可以直接播放,PC端用MSE解码后大部分浏览器也都能够支持。但是由于其分片加载的特性,直播的延迟相对较长。比如我们一个M3U8有5个TS文件,每个TS文件播放时长是2秒,那么一个M3U8文件的播放时长就是10秒,也就是说这个M3U8播放的直播进度至少是10秒之前的,这对于直播场景来说是一个比较大的弊端。

HLS中用到的TS封装格式,视频编码格式是通常是H.264或MPEG-4,音频编码格式为AAC或MP3。

一个ts由多个定长的packtet组成,通常是188个字节,每个packtet有head和payload组成,head中包含一些标识符、错误信息、包位置等基础信息。payload可以简单理解为音视频信息,但实际上下层还有还有两层封装,将封装解码后可以获取到音视频流的编码数据。

9、视频直播传输协议2:HTTP-FLV

HTTP-FLV协议,从名字上就可以明显看出是通过HTTP协议来传输FLV封装格式的一种协议。

FLV是Flash Video的简写,是一种文件体积小,适合在网络上传输的封包方式。FlV的视频编码格式通常是H.264,音频编码是ACC或MP3。

HTTP-FLV在直播中是通过走HTTP长连接的方式,通过分块传输向请求端传递FLV封包数据。

在直播中,我们通过HTTP-FLV协议的拉流地址可以拉取到一段chunked数据。

打开文件后可以读取到16进制的文件流,通过和FLV包结构对比,可以发现这些数据就是我们需要的FLV数据。

首先开头是头部信息:464C56转换ASCII码后是FLV三个字符,01指的是版本号,05转换为2进制后第6位和第8位分别代表是否存在音频和视频,09代表头部长度占了几个字节。

后续就是正式的音视频数据:是通过一个个的FLV TAG进行封装,每一个TAG也有头部信息,标注这个TAG是音频信息、视频信息还是脚本信息。我们通过解析TAG就可以分别提取音视频的压缩编码信息。

FLV这一种格式在video中并不是原生支持的,我们要播放这一种格式的封包格式需要通过MSE对影视片的压缩编码信息进行解码,因此需要浏览器能够支持MSE这一API。由于HTTP-FLV的传输是通过长连接传输文件流的形式,需要浏览器支持Stream IO或者fetch,对于浏览器的兼容性要求会比较高。

FLV在延迟问题上相比切片播放的HLS会好很多,目前看来FLV的延迟主要是受编码时设置的GOP长度的影响。

这边简单介绍一下GOP:在H.264视频编码的过程中,会生成三种帧类型:I帧、B帧和P帧。I帧就是我们通常说的关键帧,关键帧内包括了完整的帧内信息,可以直接作为其他帧的参考帧。B帧和P帧为了将数据压缩得更小,需要由其他帧推断出帧内的信息。因此两个I帧之间的时长也可以被视作最小的视频播放片段时长。从视频推送的稳定性考虑,我们也要求主播将关键帧间隔设置为定长,通常是1-3秒,因此除去其他因素,我们的直播在播放时也会产生1-3秒的延时。

10、视频直播传输协议3:RTMP

RTMP协议实际可以与HTTP-FLV协议归做同一种类型。

他们的封包格式都是FlV,但HTTP-FLV使用的传输协议是HTTP,RTMP拉流使用RTMP作为传输协议。

RTMP是Adobe公司基于TCP做的一套实时消息传输协议,经常与Flash播放器匹配使用。

RTMP协议的优缺点非常明显。

RTMP协议的优点主要是:

  • 1)首先和HTTP-FLV一样,延迟比较低;
  • 2)其次它的稳定性非常好,适合长时间播放(由于播放时借用了Flash player强大的功能,即使开多路流同时播放也能保证页面不出现卡顿,很适合监控等场景)。

但是Flash player目前在web端属于墙倒众人推的境地,主流浏览器渐渐都表示不再支持Flash player插件,在MAC上使用能够立刻将电脑变成烧烤用的铁板,资源消耗很大。在移动端H5基本属于完全不支持的状态,兼容性是它最大的问题。

11、视频直播传输协议4:MPEG-DASH

MPEG-DASH这一协议属于新兴势力,和HLS一样,都是通过切片视频的方式进行播放。

他产生的背景是早期各大公司都自己搞自己的一套协议。比如苹果搞了HLS、微软搞了 MSS、Adobe还搞了HDS,这样使用者需要在多套协议封装的兼容问题上痛苦不堪。

于是大佬们凑到一起,将之前各个公司的流媒体协议方案做了一个整合,搞了一个新的协议。

由于同为切片视频播放的协议,DASH优劣势和HLS类似,可以支持切片之间多视频码率、多音轨的切换,比较适合点播业务,在直播中还是会有延时较长的问题。

12、如何选择最优的视频直播传输协议

视频直播协议选择非常关键的两点,在前文都已经有提到了,即低延时和更优的兼容性。

首先从延时角度考虑:不考虑云端转码以及上下行的消耗,HLS和MPEG-DASH通过将切片时长减短,延时在10秒左右;RTMP和FLV理论上延时相当,在2-3秒。因此在延时方面HLS ≈ DASH > RTMP ≈ FLV。

从兼容性角度考虑:HLS > FLV > RTMP,DASH由于一些项目历史原因,并且定位和HLS重复了,暂时没有对其兼容性做一个详尽的测试,被推出了选择的考虑范围。

综上所述:我们可以通过动态判断环境的方式,选择当前环境下可用的最低延迟的协议。大致的策略就是优先使用HTTP-FLV,使用HLS作为兜底,在一些特殊需求场景下通过手动配置的方式切换为RTMP。

对于HLS和HTTP-FLV:我们可以直接使用 hls.js 和 flv.js 做做解码播放,这两个库内部都是通过MSE做的解码。首先根据视频封装格式提取出对应的音视频chunk数据,在MediaSource中分别对音频和视频创建SourceBuffer,将音视频的编码数据喂给SourceBuffer后SourceBuffer内部会处理完剩下的解码和音视频对齐工作,最后MediaSource将Video标签中的src替换成MediaSource 对象进行播放。

在判断播放环境时我们可以参照flv.js内部的判断方式,通过调用MSE判断方法和模拟请求的方式判断MSE和StreamIO是否可用:

// 判断MediaSource是否被浏览器支持,H.264视频编码和Acc音频编码是否能够被支持解码

window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"');

如果FLV播放不被支持的情况下:需要降级到HLS,这时候需要判断浏览器环境是否在移动端,移动端通常不需要 hls.js 通过MSE解码的方式进行播放,直接将M3U8的地址交给video的src即可。如果是PC端则判断MSE是否可用,如果可用就使用hls.js解码播放。

这些判读可以在自己的逻辑里提前判断后去拉取对应解码库的CDN,而不是等待三方库加载完成后使用三方库内部的方法判断,这样在选择解码库时就可以不把所有的库都拉下来,提高加载速度。

13、同层播放如何解决

电商直播需要观众操作和互动的部分比起传统的直播更加多,因此产品设计的时候很多的功能模块会悬浮在直播视频上方减少占用的空间。这个时候就会遇到一个移动端播放器的老大难问题——同层播放。

同层播放问题:是指在移动端H5页面中,一些浏览器内核为了提升用户体验,将video标签被劫持替换为native播放器,导致其他元素无法覆盖于播放器之上。

比如我们想要在直播间播放器上方增加聊天窗口,将聊天窗口通过绝对定位提升z-index置于播放器上方,在PC中测试完全正常。但在移动端的一些浏览器中,video被替换成了native播放器,native的元素层级高于我们的普通元素,导致聊天窗口实际显示的时候在播放器下方。

要解决这个问题,首先要分多个场景。

首先在iOS系统中:正常情况下video标签会自动被全屏播放,但iOS10以上已经原生提供了video的同层属性,我们在video标签上增加playsinline/webkit-playsinline可以解决iOS系统中大部分浏览器的同层问题,剩下的低系统版本的浏览器以及一些APP内的webview容器(譬如微博),用上面提的属性并不管用,调用三方库iphone-inline-video可以解决大部分剩余问题。

在Android端:大部分腾讯系的APP内置的webview容器用的都是X5内核,X5内核会将video替换成原生定制的播放器已便于增强一些功能。X5也提供了一套同层的方案(该方案官方文档链接已无法打开),给video标签写入X5同层属性也可以在X5内核中实现内联播放。不过X5的同层属性在各个X5版本中表现都不太一样(比如低版本X5中需要使用X5全屏播放模式才能保证MSE播放的视频同层生效),需要注意区分版本。

在蘑菇街App中,目前集成的X5内核版本比较老,在使用MSE的情况下会导致X5同层参数不生效。但如果集成新版本的X5内核,需要对大量的线上页面做回归测试,成本比较高,因此提供了一套折中的解决方案。通过在页面URL中增加一个开关参数,容器读取到参数以后会将X5内核降级为系统原生的浏览器内核,这样可以在解决浏览器视频同层问题的同时也将内核变动的影响范围控制在单个页面当中。

14、相关文章

[1] 移动端实时音视频直播技术详解(四):编码和封装

[2] 移动端实时音视频直播技术详解(五):推流和传输

[3] 实现延迟低于500毫秒的1080P实时音视频直播的实践分享

[4] 浅谈开发实时视频直播平台的技术要点

[5] 直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践

[6] 从0到1:万人在线的实时音视频直播技术实践分享(视频+PPT) [附件下载]

[7] 实时视频编码H.264的特点与优势

[8] 视频编码H.264、VP8的前世今生

[9] 零基础,史上最通俗视频编码技术入门

[10] 视频编解码之编码基础

[11] 零基础入门:实时音视频技术基础知识全面盘点

[12] 实时音视频面视必备:快速掌握11个视频技术相关的基础概念

[13] 写给小白的实时音视频技术入门提纲

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

posted @ 2022-05-31 15:26 Jack Jiang 阅读(137) | 评论 (0)编辑 收藏

     摘要: 本文作者张彦飞,原题“聊聊TCP连接耗时的那些事儿”,有少许改动。1、引言对于基于互联网的通信应用(比如IM聊天、推送系统),数据传递时使用TCP协议相对较多。这是因为在TCP/IP协议簇的传输层协议中,TCP协议具备可靠的连接、错误重传、拥塞控制等优点,所以目前在应用场景上比UDP更广泛一些。相信你也一定听闻过TCP也存在一些缺点,能常都是老生常谈的开销要略大。但是各路技...  阅读全文

posted @ 2022-05-26 16:10 Jack Jiang 阅读(94) | 评论 (0)编辑 收藏

     摘要: 本文作者“Carson”,现就职于腾讯公司,原题“高效保活长连接:手把手教你实现自适应的心跳保活机制”,有较多修订和改动。1、引言当要实现IM即时通讯聊天、消息推送等高实时性需求时,我们一般会选择长连接的通信方式。而真正当实现长连接方式时,会遇到很多技术问题,比如最常见的长连接保活问题。今天,我将通过本篇文章,手把手教大家实现一套可自适应的心跳保活机...  阅读全文

posted @ 2022-05-18 15:09 Jack Jiang 阅读(127) | 评论 (0)编辑 收藏

本文由ELab技术团队分享,原题“探秘HTTPS”,有修订和改动。

1、引言

对于IM开发者来说,IM里最常用的通信技术就是Socket长连接和HTTP短连接(通常一个主流im会是这两种通信手段的结合)。从通信安全的角度来说,Socket长连接的安全性,就是基于SSL/TLS加密的TCP协议来实现的(比如微信的mmtls,见《微信新一代通信安全解决方案:基于TLS1.3的MMTLS详解);而对于HTTP短连接的安全性,也就是HTTPS了。

到底什么是HTTPS?为什么要用HTTPS?今天就借此机会,跟大家一起深入学习一下HTTPS的相关知识,包括HTTP的发展历程、HTTP遇到的问题、对称与非对称加密算法、数字签名、第三方证书颁发机构等概念。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

本文是IM通讯安全知识系列文章中的第9篇,此系列总目录如下:

3、写在前面

说到HTTPS,那就得回到HTTP协议。

对于HTTP协议,大家肯定都熟得不能再熟了。那么HTTPS和HTTP的区别大家了解吗?

对于这个经典的面试题,大部分人会这么回答:

  • 1)HTTPS比HTTP多了一个S(Secure):也就是说HTTPS是安全版的HTTP;
  • 2)端口号不同:HTTP使用80端口,HTTPS使用443端口;
  • 3)加密算法:HTTPS用的是非对称加密算法。

上面的回答能给几分?等看完本文我们可以再回头来看下这个回答。

那么,HTTPS是如何实现安全的短连接数据传输呢?想彻底搞明白这个问题,还是要从HTTP的发展历程说起 ......

4、HTTP协议回顾

4.1 基础常识

HTTP是Hypertext Transfer Protocal 的缩写,中文全称是超文本传输协议(详见《深入浅出,全面理解HTTP协议)。

通俗了解释就是:

  • 1)超文本是指包含但不限于文本外的图片、音频、视频等多媒体资源;
  • 2)协议是通信双方约定好的数据传输格式以及通信规则。

HTTP是TCP/IP协议簇的最高层——应用层协议:

▲ 上图引用自《深入浅出,全面理解HTTP协议

浏览器和服务器在使用HTTP协议相互传递超文本数据时,将数据放入报文体内,同时填充首部(请求头或响应头)构成完整HTTP报文并交到下层传输层,之后每一层加上相应的首部(控制部分)便一层层的下发,最终由物理层将二进制数据以电信号的形式发送出去。

HTTP的请求如下图所示:

▲ 上图引用自《深入浅出,全面理解HTTP协议

HTTP报文结构如下:

4.2 发展历程

HTTP的发展历程如下:

由HTTP的发展历程来看,最开始版本的HTTP(HTTP1.0)在每次建立TCP连接后只能发起一次HTTP请求,请求完毕就释放TCP连接。

我们都知道TCP连接的建立需要经过三次握手的过程,而每次发送HTTP请求都需要重新建立TCP连接,毫无疑问是很低效的。所以HTTP1.1改善了这一点,使用长连接的机制,也就是“一次TCP连接,N次HTTP请求”。

HTTP协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。

在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。

PS:对于IM开发者来说,为了与Socket长连接通道区分,通常认为HTTP就是“短连接”虽然这个“短连接”不一定真的“短”)。

HTTP1.0若要开启长连接,需要加上Connection: keep-alive请求头。有关HTTP协议的详细发展历程可阅读《一文读懂HTTP协议的历史演变和设计思路》一文。

4.3 安全问题

随着HTTP越来越广泛的使用,HTTP的安全性问题也逐渐暴露。

回忆一下多年前遍地都是的运营商劫持,当你访问一个本来很正常的网页,但页面上却莫名其妙出现了一些广告标签、跳转脚本、欺骗性的红包按钮,甚至有时候本来要下载一个文件,最后下载下来却变成了另外一个完全不同的东西,这些都是被运营商劫持了HTTP明文数据的现象。

下图就是似曾相识的运营商劫持效果图:

PS:关于运营商劫持问题,可以详细阅读《全面了解移动端DNS域名劫持等杂症:原理、根源、HttpDNS解决方案等》。

HTTP主要有以下3点安全性问题:

 

归纳一下就是:

  • 1)数据保密性问题:因为HTTP无状态,而且又是明文传输,所有数据内容都在网络中裸奔,包用户括身份信息、支付账号与密码。这些敏感信息极易泄露造成安全隐患;
  • 2)数据完整性问题:HTTP数据包在到达目的主机前会经过很多转发设备,每一个设备节点都可能会篡改或调包信息,无法验证数据的完整性;
  • 3)身份校验问题:有可能遭受中间人攻击,我们无法验证通信的另一方就是我们的目标对象。

因此,为了保证数据传输的安全性,必须要对HTTP数据进行加密。

5、常见的加密方式

5.1 基本情况

常见的加密方式分为三种:

  • 1)对称加密;
  • 2)非对称加密;
  • 3)数字摘要。

前两种适合数据传输加密,而数字摘要不可逆的特性常被用于数字签名。

接下来,我们逐一简要学习一下这三种常见的加密方法。

5.2 对称加密

对称加密也称为密钥加密或单向加密,就是使用同一套密钥来进行加密和解密。密钥可以理解为加密算法。

对称加密图示如下:

广泛使用的对称加密有:

对称加密算法的优缺点和适用场景:

  • 1)优点:算法公开、简单,加密解密容易,加密速度快,效率高;
  • 2)缺点:相对来说不算特别安全,只有一把钥匙,密文如果被拦截,且密钥也被劫持,那么,信息很容易被破译;
  • 3)适用场景:加解密速度快、效率高,因此适用于大量数据的加密场景。由于如何传输密钥是较为头痛的问题,因此适用于无需进行密钥交换的场景,如内部系统,事先就可以直接确定密钥。

PS:可以在线体验对称加密算法,链接是:http://www.jsons.cn/textencrypt/

小知识:base64编码也属于对称加密哦!

5.3 非对称加密

非对称加密使用一对密钥(公钥和私钥)进行加密和解密。

非对称加密可以在不直接传递密钥的情况下,完成解密,具体步骤如下:

  • 1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的;
  • 2)甲方获取乙方的公钥,然后用它对信息加密;
  • 3)乙方得到加密后的信息,用私钥解密。

以最典型的非对称加密算法RSA为例,举个例子:

想要彻底搞懂RSA,需要了解数论的知识,全部推导过程RSA加密算法。简单介绍思路:使用两个超大质数以及其乘积作为生成公钥和私钥的材料,想要从公钥推算出私钥是非常困难的(需要对超大数因式分解为两个很大质数的乘积)。目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。

非对称加密算法的优缺点和适用场景:

  • 1)优点:强度高、安全性强于对称加密算法、无需传递私钥导致没有密钥泄露风险;
  • 2)缺点:计算量大、速度慢;
  • 3)适用场景:适用于需要密钥交换的场景,如互联网应用,无法事先约定密钥。

实践应用过程中,其实可以与对称加密算法结合:

  • 1)利用非对称加密算法安全性较好的特点来传递对称加密算法的密钥。
  • 2)利用对称加密算法加解密速度快的特点,进行数据内容比较大的加密场景的加密(如HTTPS)。

PS:对于IM开发者来说,《探讨组合加密算法在IM中的应用》一文值得一读。

5.4 如何选择?

1)如果选择对称加密:

HTTP请求方使用对称算法加密数据,那么为了接收方能够解密,发送方还需要把密钥一同传递到接收方。在传递密钥的过程中还是可能遭到嗅探攻击,攻击者窃取密钥后依然可以解密从而得到发送的数据,所以这种方案不可行。

2)如果选择非对称加密:

接收方保留私钥,把公钥传递给发送方。发送方用公钥来加密数据,接收方使用私钥解密数据。攻击者虽然不能直接获取这些数据(因为没有私钥),但是可以通过拦截传递的公钥,然后把自己的公钥传给发送方,再用自己的私钥对发送方发送数据进行解密。

整个过程通信双方都不知道中间人的存在,但是中间人能够获得完整的数据信息。

3)两种加密方法的混合:

先使用非对称加密算法加密并传递对称加密的密钥,然后双方通过对称加密方式加密要发送的数据。看起来没什么问题,但事实是这样吗?

中间人依然可以拦截公钥的传递,并以自己的公钥作为替换,治标不治本。

想要治本,就要找到一个第三方公证人来证明公钥没有被替换,因此就引出了数字证书的概念,这也是下一节将分享的内容。

6、数字证书

6.1 CA机构

CA就是 Certificate Authority,即颁发数字证书的机构。

作为受信任的第三方,CA承担公钥体系中公钥的合法性检验的责任。

证书就是源服务器向可信任的第三方机构申请的数据文件。这个证书除了表明这个域名是属于谁的,颁发日期等,还包括了第三方证书的私钥。

服务器将公钥放在数字证书中,只要证书是可信的,公钥就是可信的。

下面两图是飞书域名的证书中部分内容的信:

6.2 数字签名

摘要算法:一般用哈希函数来实现,可以理解成一种定长的压缩算法,它能把任意长度的数据压缩到固定长度。这好比是给数据加了一把锁,对数据有任何微小的改动都会使摘要变得截然不同。

通常情况下:数字证书的申请人(服务器)将生成由私钥和公钥以及证书请求文件(Certificate Signing Request,CSR)组成的密钥对。CSR是一个编码的文本文件,其中包含公钥和其他将包含在证书中的信息(例如:域名、组织、电子邮件地址等)。密钥对和CSR生成通常在将要安装证书的服务器上完成,并且 CSR 中包含的信息类型取决于证书的验证级别。与公钥不同,申请人的私钥是安全的,永远不要向 CA(或其他任何人)展示。

生成 CSR 后:申请人将其发送给 CA,CA 会验证其包含的信息是否正确,如果正确,则使用颁发的私钥对证书进行数字签名,然后将签名放在证书内随证书一起发送给申请人。

在SSL握手阶段:浏览器在收到服务器的证书后,使用CA的公钥进行解密,取出证书中的数据、数字签名以及服务器的公钥。如果解密成功,则可验证服务器身份真实。之后浏览器再对数据做Hash运算,将结果与数字签名作对比,如果一致则可以认为内容没有收到篡改。

对称加密和非对称加密是公钥加密、私钥解密, 而数字签名正好相反——是私钥加密(签名)、公钥解密(验证),如下图所示。

限于篇幅,关于数字证书的内容本文就不再赘述,想详细了解的可以阅读:

7、为什么要使用HTTPS

图解HTTP》一书中提到HTTPS就是身披SSL外壳的HTTP。

7.1 SSL

SSL 在1999年被更名为TLS

所以说:HTTPS 并不是一项新的应用层协议,只是 HTTP 通信接口部分由 SSL 和 TLS 替代而已。

具体就是:HTTP 会先直接和 TCP 进行通信,而HTTPS 会演变为先和 SSL 进行通信,然后再由 SSL 和 TCP 进行通信。

SSL是一个独立的协议,不只有 HTTP 可以使用,其他应用层协议也可以使用,比如FTP、SMTP都可以使用SSL来加密。

7.2 HTTPS请求流程

HTTPS请求全流程如下图:

如上图所示:

  • 1)用户在浏览器发起HTTPS请求,默认使用服务端的443端口进行连接;
  • 2)HTTPS需要使用一套CA 数字证书,证书内会附带一个服务器的公钥Pub,而与之对应的私钥Private保留在服务端不公开;
  • 3)服务端收到请求,返回配置好的包含公钥Pub的证书给客户端;
  • 4)客户端收到证书,校验合法性,主要包括是否在有效期内、证书的域名与请求的域名是否匹配,上一级证书是否有效(递归判断,直到判断到系统内置或浏览器配置好的根证书),如果不通过,则显示HTTPS警告信息,如果通过则继续;
  • 5)客户端生成一个用于对称加密的随机Key,并用证书内的公钥Pub进行加密,发送给服务端;
  • 6)服务端收到随机Key的密文,使用与公钥Pub配对的私钥Private进行解密,得到客户端真正想发送的随机Key;
  • 7)服务端使用客户端发送过来的随机Key对要传输的HTTP数据进行对称加密,将密文返回客户端;
  • 8)客户端使用随机Key对称解密密文,得到HTTP数据明文;
  • 9)后续HTTPS请求使用之前交换好的随机Key进行对称加解密。

7.3 HTTPS到底解决了什么问题

HTTPS确实解决了HTTP的三个安全性问题:

  • 1) 保密性:结合非对称加密和对称加密实现保密性。用非对称加密方式加密对称加密的秘钥,再用对称加密方式加密数据;
  • 2) 完整性:通过第三方CA的数字签名解决完整性问题;
  • 3) 身份校验:通过第三方CA的数字证书验证服务器的身份。

7.4 HTTPS优缺点

最后我们总结一下HTTPS的优缺点:

可以看到:HTTPS的确是当今安全传输HTTP的最优解,但他并不是完美的,仍会有漏洞。

8、参考资料

[1] 深入浅出,全面理解HTTP协议

[2] HTTP协议必知必会的一些知识

[3] 从数据传输层深度解密HTTP

[4] 一文读懂HTTP协议的历史演变和设计思路

[5] 你知道一个TCP连接上能发起多少个HTTP请求吗?

[6] 如果这样来理解HTTPS,一篇就够了

[7] 一分钟理解 HTTPS 到底解决了什么问题

[8] 你知道,HTTPS用的是对称加密还是非对称加密?

[9] HTTPS时代已来,打算更新你的HTTP服务了吗?

[10] 一篇读懂HTTPS:加密原理、安全逻辑、数字证书等

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

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

posted @ 2022-05-13 16:27 Jack Jiang 阅读(97) | 评论 (0)编辑 收藏

关于MobileIMSDK

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

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

关于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.1 版更新内容

此版更新内容:

(1)Android端主要更新内容【新增“扫一扫”等功能及优化!】:

  • 1)[新增]“扫一扫”界面及完整功能(支持扫码加好友、加群);
  • 2)[新增]“我的二维码”界面及完整功能;
  • 3)[新增]“群聊二维码”界面及完整功能;
  • 4)[升级]升级okhttp库至4.9.3;
  • 5)[优化]其它小优化。

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

  • 1)[优化]针对扫码加群等功能的相关修改。

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

posted @ 2022-05-11 17:49 Jack Jiang 阅读(89) | 评论 (0)编辑 收藏

     摘要: 一、前言MobileIMSDK 是什么?MobileIMSDK  是一套专门为移动端开发的开源IM即时通讯框架,超轻量级、高度提炼,一套API优雅支持UDP 、TCP 、WebSocket 三种协议,支持iOS、Android、H5、标准Java平台,服务端基于Netty编写。工程地址是:1)Gitee码云地址:https://www.oschina.net/p/mobilei...  阅读全文

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

本文由融云技术团队原创分享,原题“IM 消息数据存储结构设计”,内容有修订。

1、引言

在如今的移动互联网时代,IM类产品已是我们生活中不可或缺的组成部分。像微信、钉钉、QQ等是典型的以 IM 为核心功能的社交产品。另外也有一些应用虽然IM功能不是核心,但IM能力也是其整个应用极其重要的组成部分,比如在线游戏、电商直播等应用。

在IM技术应用场景越来越广泛的前提下,对即时通讯IM技术的学习和掌握就显的越来越有必要。

在IM庞大的技术体系中,消息系统无疑是最核心的,而消息系统中,最关键的部分是消息的分发和存储,而离线消息和历史消息又是这个关键环节中不可回避的技术要点。

本文将基于IM消息系统的技术实践,分享关于离线消息和历史消息的正确理解,以及具体的技术配合和实践,希望能为你的离线消息和历史消息技术设计带来最佳实践灵感。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、相关文章

技术相关文章:

  1. 什么是IM系统的可靠性?
  2. 闲鱼IM的在线、离线聊天数据同步机制优化实践
  3. 闲鱼亿级IM消息系统的可靠投递优化实践
  4. 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等
  5. IM消息送达保证机制实现(二):保证离线消息的可靠投递
  6. 我是如何解决大量离线消息导致客户端卡顿的

融云技术团队分享的其它文章:

  1. 融云安卓端IM产品的网络链路保活技术实践
  2. 全面揭秘亿级IM消息的可靠投递机制
  3. 解密融云IM产品的聊天消息ID生成策略
  4. 万人群聊消息投递方案的思考和实践
  5. 基于WebRTC的实时音视频首帧显示时间优化实践
  6. 融云IM技术分享:万人群聊消息投递方案的思考和实践

3、IM消息投递的一般做法

在通常的IM消息系统中,对于实时消息、离线消息、历史消息大概都是下面这样的技术思路。

对于在线用户:消息会直接实时发送到在线的接收方,消息发送完成后,服务器端并不会对消息进行落地存储。

而对于离线的用户:服务器端会将消息存入到离线库,当用户登录后,从离线库中将离线消息拉走,然后服务器端将离线消息删除。

这样实现的缺点就是消息不持久化,导致消息无法支持消息漫游,降低了消息的可靠性。

PS:实际上,这其实也不能算是缺点,因为一些场景下存储历史消息并不是必须的,所谓的消息漫游能力也不是必备的,比如微信。

而在我们设计的消息系统中,服务器只要接收到了发送方发上来的消息,在转发给接收方的同时也会在离线数据库及历史消息库中进行消息的落地存储,而历史消息的落地也就能支持消息漫游等相关功能了。

4、什么是离线消息和历史消息?

关于离线消息和历史消息,在技术上,我们是这样定义。

1)离线消息:

离线消息就是用户(即接收方)在离线过程中收到的消息,这些消息大多是用户比较关心的消息,具有一定的时效性。

以我们的系统经验来说,我们的离线消息默认只保存最近七天的消息。

用户(即接收方)在下次登录后会全量获取这些离线消息,然后在客户端根据聊天会话进行离线消息的UI展示(比如显示一个未读消息气泡等)。

PS:用户离线的可能性在技术上其实是由很多种情况组成的,比如对方不在线、对方网络断掉了、对方手机崩溃了、服务器发送时出错了等等,严格来讲——只要无法实时发送成的消息,都算“离线消息”。

2)历史消息:

历史消息存储了用户所有的聊天消息,这些消息包括发出的消息以及接收到的消息。

在客户端获取历史消息时,通常是按照会话进行分页获取的。

以我们的系统经验来说,历史消息的存储时间我们设计默认为半年,当然这个时间可以按实际的产品运营规则来定,没有硬性规定。

5、IM消息的发送及存储流程

以下是我们系统整体的消息发送及存储流程:

 如上图所示:当用户发送聊天消息到服务器端后,首先会进入到消息系统中,消息系统会对消息进行分发以及存储。

这个过程中:对于在线的接收方,会选择直接推送消息。但是遇到接收方不在线或者是消息推送失败的情况下,也会有另外的消息获取方式,比如接收方会主动向服务器拉取未收到的消息。但是接收方何时来服务器拉取消息以及从哪里拉取是未知的,所以消息存入到离线库的意义也就在这里。

消息系统存储离线的过程中,为了不影响整个系统的更为平稳,我们使用了MQ消息队列进行IO解偶,所以聊天消息实际上是异步存入到离线库中的(通过MQ进行慢IO解偶,这其实也是惯常做法)。

在分发完消息后:消息服务会同步一份消息数据到历史消息服务中,历史消息服务同样会对消息进行落地存储。

对于新的客户端设备:会有同步消息的需求(所谓的消息漫游能力),而这也正是历史消息的主要作用。在历史消息库中,客户端是可以拉取任意会话的全量历史消息的。

6、IM离线消息、历史消息在存储逻辑上的区别

6.1 概述

通过上面的图中能清晰的看到:

  • 1)离线消息我们存储介质选用的是 Redis
  • 2)历史消息我们选用的是 HBase

对于为什么选用不同的存储介质,其实我们考虑的是离线消息和历史消息不同的业务场景和读写模式。

下面我们重点介绍一下离线消息和历史消息存储的区别。

6.2 离线消息存储模式——“扩散写”

离线消息的存储模式我们用的是扩散写。

如上图所示:每个用户都有自己单独的收件箱和发件箱:

  • 1)收件箱存放的是需要向这个接收端同步的所有消息;
  • 2)发件箱里存放的是发送端发出的所有消息。

以单聊为例:聊天中的两人会话中,消息会产生两次写,即发送者的发件箱和接收端的收件箱。

而在群的场景下:写入会被更加的放大(扩散),如果群里有 N 个人,那一条群消息就会被扩散写 N 次。

小结一下:

  • 1)扩散写的优点是:接收端的逻辑会非常清晰简单,只需要从收件箱里读取一次即可,大大降低了同步消息所需的读的压力;
  • 2)扩散写的缺点是:写入会被成指数地放大,特别是针对群这种场景。

6.3 历史消息存储模式——“扩散读”

历史消息的存储模式我们用的是扩散读。

因为历史消息中,每个会话都保存了整个会话的全量消息。在扩散读这种模式下,每个会话的消息只保存一次。

对比扩散写模式,扩散读的优点和缺点如下:

  • 1)优点是:写入次数大大降低,特别是针对群消息,只需要存一次即可;
  • 2)缺点是:接收端接收消息非常的复杂和低效,因为这种模式客户端想拉取到所有消息就只能每个会话同步一次,读就会被放大,而且可能会产生很多次无效的读,因为有些会话可能根本没有新消息。

6.4 小结

在 IM 这种应用场景下,通常会用到扩散写这种消息同步模型,一条消息产生一条,但是可能会被读多次,是典型的读多写少的场景。

一个优化好的IM系统,必须从设计上平衡读写压力,避免读或者写任意一个维度达到天花板。

当然扩散写这种模式也有其弊端,比如万人群,会导致一条消息,写入了一万次。

综合来讲:我们需要根据自己的业务场景做相应设计选择,以我们的IM系统为例,就是是根据了离线和历史消息的不同场景选择了写扩散和读扩散的组合模式。适合的才是最好的,没有必要死搬硬套理论。

7、IM客户端的拉取消息逻辑

7.1 离线消息拉取逻辑

对于IM客户端而言,离线消息的获取针对的是自己的整个离线消息,包括所有的会话(直白了说,就是上线时拉取此次离线过程中的所有未收取的离线消息)。

离线消息的获取是自上而下的方式(按时间序),我们的经验是一次获取 200 条(PS:如果离线消息过多,会分页多次拉取,拉取1“次”可以理解为拉取1“页”)。

在客户端拉取离线消息的信令中,需要带上当前客户端缓存的消息的最大时间戳。

通过上节的图我们应该知道,离线消息我们存储的是一个线性结构(指的是按时间顺序),Server 会根据这个时间戳向下查找离线消息。当重装或者新安装 App 时,客户端的“当前客户端缓存的消息的最大时间戳”可以传 0 上来。

Server 也会缓存客户端拉取到的最后一条消息的时间戳,然后根据业务场景,客户端类型等因素来决定从哪里开始拉取,如果没有拉取完 Server 会在拉取消息的应答中带相应的标记位,告诉客户端继续拉取,客户端循环拉取,直到所有离线消息拉完。

7.2 历史消息拉取逻辑

历史消息的获取通常针对的是单一会话。

在拉取过程中,需要向服务端提交两个参数:

  • 1)对方的 ID(如果是单聊的话就是对方的 UserID,如果是群则是群组ID);
  • 2)当前会话的最前面消息的时间戳(即当前会话最老一条消息的时间戳)。

Server据这两个参数,可以定位到这个客户端的此会话,然后一次获取 20 条历史消息。

消息的拉取时序上采用的是自下而上的方式(也就是时间序逆序),即从最后面往前翻。只要有消息,客户端可以一直向前翻,手动触发获取会话的历史消息。

上面的拉取逻辑,在IM界面功能上通常对应的是下拉或点击“加载更多”,比如这样:

8、本文小结

本文主要分享了IM中有关离线消息和历史消息的正确,主要包括离线消息和历史消息的区别,以及二者在存储、分发、拉取逻辑方面的最佳践等。如对文中内容有异议,欢迎留言讨论。

9、参考资料

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

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

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

[4] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[5] 闲鱼亿级IM消息系统的架构演进之路

[6] 闲鱼亿级IM消息系统的可靠投递优化实践

[7] 闲鱼亿级IM消息系统的及时性优化实践

[8] 基于实践:一套百万消息量小规模IM系统技术要点总结

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

[10] 理解IM消息“可靠性”和“一致性”问题,以及解决方案探讨

[11] 零基础IM开发入门(一):什么是IM系统?

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

posted @ 2022-04-19 14:56 Jack Jiang 阅读(115) | 评论 (0)编辑 收藏

本文由网易云信技术团队分享,原题“如何保障一场千万级大型直播?”,有修订和改动。

1、引言

本文以TFBOYS“日光旅行”七周年这场直播演唱会为案例,为你分享大型直播系统后端架构设计的方方面面,包括:基本架构、稳定性保障、安全性障、监控报警、应急预案等技术范畴。

案例中的这次演唱会采用了在线实时互动及演唱会现场的多场景导播切换,提供了主机位和三个艺人专属机位流,同时每个机位流实时转码四个清晰度档位,用户可以根据喜好选择自己想看的内容。这场演唱会最高同时在线人数达78.6万,打破线上付费演唱会世界记录。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、本文作者

费曼:网易智企服务端开发工程师。硕士毕业于华中科技大学电信系,2016年加入网易云信,热衷于大规模分布式系统和音视频相关技术,爱好文学、体育和电影。

3、架构方面

3.1 基本

 

上图是该次TFBOYS在线演唱会的直播媒体架构简图。

可以看出一场大型活动直播涵盖的技术方案点非常庞杂,本节接下来的内容我们将以推拉流链路、全局智能调度、流量精准调度以及单元化部署,对这套直播方案做一个展开介绍。

3.2 推拉流链路

 

如上图所示,直播技术架构,分为几大部分:

  • 1)视频直播中心(LMS——Live Manage Service):负责直播流的逻辑管理和操作控制,包括存储和下发实时转码、加密等媒体处理的配置信息;
  • 2)实时互动直播服:由连麦互动和直播两部分组成,主播和连麦者的音视频数据在互动直播高性能服务器合成为一道流后推流到直播流媒体服务器;
  • 3)直播源站服务(LSS——Live Source Service):网易云信自建的直播流媒体服务器节点,结合全局智能调度系统,提供第一公里的最佳链路选择,同时融合支持接入多家CDN厂商;
  • 4)媒体处理服务(MPS——Media Processing Service):提供实时水印、实时转码、媒体数据加密等强大的流媒体处理能力;
  • 5)融合CDN与全局智能调度(GSLB——Golabal Server Load Balancing):提供敏捷智能的CDN调度策略和分配算法,结合全链路、端到端的流媒体控制,来达到最终端侧优良的用户体验;
  • 6)客户端SDK:提供推流、拉流以及上下行的调度能力,便于用户快速接入使用网易云信平台一站式的音视频解决方案。

3.3 融合CDN与智能调度

这是一个端到端的服务,通过平台的SDK执行一个类似HTTPDNS的调度,来做到真正根据用户IP做就近的接入。

针对国内相对复杂的运营商网络环境,在直播上行方面通过BGP网络以及与相关运营商在网络接入方面的合作,能够更加精准地控制网络链路的选择。

而对于下行,也提供了播放端的SDK接入,通过端到端的调度策略就近选择合适的下行链路。

调度的准确性以及最终效果,依赖及时准确的数据支撑。

我们有一个全链路、立体的数据监控体系,一方面利用CDN上的一些实时日志,另一方面结合自建节点、客户端侧上报收集链路上探测的数据,然后整合做一个实时计算来支撑整个调度的策略。

融合CDN方案,通过调度、监控、高可用等技术和手段来解决CDN网络方面的问题。但是对于技术人员来说,就和在使用一个传统的CDN网络一样没有大的差异,这些技术细节对技术人员透明无感知。

3.4 流量精准调度

大型演唱会直播活动,尤其是正式开播时的进场阶段,突发流量峰值会非常高,这就需要实时精准的智能调度策略。

融合CDN的智能调度包含两大部分:CDN分配调度和节点调度。

节点调度:比较常见的是DNS协议解析调度和IP调度(302/HTTPDNS)。前者由于DNS协议原因,调度生效时间较慢,而后者则可以做到请求级别的调度,也就是支持任意比例的负载均衡,更加及时精准。在我们的智能调度的场景里,正常情况下会遵循IP调度,在IP调度解析失败时,客户端上会启动loacl DNS解析逻辑,两者的结合确保了调度的精准和稳定可靠。

Don't put all your eggs in one basket.

“永远不要将鸡蛋放在同一个篮子里”。

从风险管控的角度来说:大型活动保障的CDN厂商资源,通常没法通过一家CDN资源进行满足。融合CDN方案则是将多家CDN厂商进行整合与流量分配调度。

通常在一次大型直播中,多家CDN厂商提供的容量(区域带宽、最高带宽)、质量会各不相同。我们则是通过动态调整调度比例,在确保不超过最大带宽的前提下,精确化按比例分配流量,以及尽可能地确保体验。

我们设计了一套针对CDN厂商的打分算法:影响因子包含当前带宽、保底带宽、最大带宽、带宽预测、带宽质量。

算法遵循以下原则:

  • 1)没超保底的带宽,比超过保底的带宽,得分更高;
  • 2)没超保底的时候,剩余保底和剩余总带宽越大,得分更高;
  • 3)超过保底的时候,剩余总带宽越大、质量越好,得分更高。

各CDN的分数之比决定了调度比例,CDN打分算法是在持续地迭代更新计算,最大化分配使用各家CDN的带宽,然后再分配各家CDN厂商的保障之外的资源。同时优先选择质量较好的厂家,避免单价CDN厂商超分配。

3.5 单元化部署

上面所说,在大型直播活动中,短时间大量涌入的用户请求,对以全局智能调度服务为主的相关非媒体流链路应用,也提出了更高的并发处理挑战。

除了上行的推流链路我们做了主备两个单元的部署,非媒体数据链路上的服务也采用了单元化的部署方案。

在此部署方案下,可用性做到任意单元机房故障,不影响整体可用性,即异地多活。

单元化部署遵循以下原则:

  • 1)单元化的依赖也必须单元化(核心业务);
  • 2)单元化粒度为应用,非api;
  • 3)单元化技术栈对应用尽量避免产生侵入性。

 

如上图所示:非单元化的业务部署在主机房,单元化的业务则部署在主机房和单元机房。

4、稳定性保障

4.1 上行链路稳定

超大型直播方案最核心的诉求就是直播稳定性,下面我们将以该次在线演唱会为案例,重点阐述一下直播的全链路稳定性架构。

上图是我们直播的媒体流链路示意简图:整体方案可以承受任何单节点、单线路、单机房网络出口的故障。

如直播源站部分:采用了多线策略收流,包含机房专线和4G背包方案,一主一备两个线路。同时每个单元的源站集群都有4层负载均衡,一台机器宕机不会影响整体可用性。LMS、LSS、MPS都是跨机房部署,所有服务模块都可配置专有资源池供使用,保证不会受其他租户影响。

整个推流链路:采用双路热流、互为主备,且部署上是互相独立的两个单元,能做到支持Rack级别的故障灾备。双路热流实现了自动主备切换,端上无需专门添加应用层的线路切换逻辑。当任何一个链路出现问题的时候,观众的直播流不会受到影响,端上平均卡顿感知时间在1s以内。

除了推流链路的整体主备单元容灾,每个单元的服务本身也会有容灾手段。比如UPS接入,可以接受30min的供电故障,比如当实时互动流出现问题时,导播台会推垫片流以保证链路数据不中断。

4.2 下行链路稳定

在访次直播活动中,全局智能调度服务会承受较大的峰值压力,在单元化部署的基础上,我们经过多轮压测和性能调优,模型上可支撑千万级用户在半分钟内全部进入直播间。

除了上述关于推流链路的高可用,下行链路也有相关的容灾策略。当GSLB智能调度服务整体不可用,在客户端SDK预埋了融合CDN的local DNS灾备逻辑与比例配置,将云端的全局智能调度fail-over到客户端的本地兜底调度,并保持大数据统计层面的各CDN厂商的流量分配均衡。

同时:客户端也会有播放体验方面的容灾策略,诸如清晰度降级、线路调整等。

5、安全性保障

除了直播全链路的稳定之外,直播安全也很重要。

该次直播活动中,为TFBOYS活动链路多环节都提供了安全保障机制(如防盗链鉴权、IP黑白名单、HTTPS等能力),以及地区、运营商等下行调度的动态限制,实现全链路安全保障。

在此基础上:此次活动采用了端到端的视频流数据加密。

直播场景的加密有几点基本要求:压缩比不变、实时性和低计算复杂度。

除此之外:在融合多cdn的方案背景下,视频流的加密必须考虑到CDN厂商的兼容性。

比如须满足以下要求:

  • 1)不破坏流媒体协议格式、视频容器格式;
  • 2)metadata/video/audio tag的header部分不加密;
  • 3)对于avcSequenceHeader和aacSequenceHeader tag整体不加密。

具体加密算法,可以采用一些流式加密算法,这里我们不再赘述。

 

6、监控与报警

6.1 概述

一场大型直播将会有大量的计算节点参与,除了媒体数据处理与分发的各个服务器节点,还有分布在国内外的海量客户端。

我们对网络链路、服务节点、设备端的健康与质量感知,都离不开数据监控系统。

同时:我们在现有系统无法自动fail-over的故障场景下,需要人工预案介入,而后者的决策判断,也强依赖于完善的全链路数据质量监控与报警系统。

6.2 全链路监控

整个直播链路的监控包含了:

  • 1)上行推流链路的流质量;
  • 2)媒体流实时转码处理;
  • 3)端上播放质量;
  • 4)智能调度系统的可用性;
  • 5)业务量水位等相关监控数据。

上行链路常见的QoS指标有:帧率、码率、RTT等,其维度包含主备线路、出口运营商、CDN厂商节点等。

端上的QoS指标则包含了:拉流成功率、首帧时长、卡顿率、httpdns缓存命中率,维度则覆盖包含CDN厂商、国家、省份、运营商、直播流、清晰度档位、客户端等。

此次直播中:内容上支持了多种机位流以及多个清晰度的转码输出流,同时通过多个CDN厂商进行分发,我们把上行链路中节点的码率、帧率,直观地通过N个指标卡集中展示在单个大盘页面上,并且通过增加预警值进行异常显示和弹窗消息告警。活动作战室现场,我们采用了多个大屏展示,非常直观地展现当前主备双推流链路的实时帧率、码率等情况,为现场地指挥保障提供了强大的数据决策支撑。

以下图为例:蓝色表示上行帧率,绿色表示正常的上行码率,红色表示码率值过低,N/A表示当前没有上行推流数据。

而在下行播放链路中,比较常用的指标就是卡顿率。

下面是我们对卡顿相关的描述:

  • 1)一次卡顿:播放器持续2s发生缓冲区空,即播放器2s没有拉到流;
  • 2)一分钟用户卡顿:1分钟窗口内,用户只要卡顿一次,则该用户计作卡顿用户;
  • 3)一分钟用户卡顿率:1分钟窗口内,卡顿用户数/总的用户数;
  • 4)一分钟用户零卡顿率:1分钟窗口内,(总的用户数 - 卡顿用户数)/总的用户数。

为什么会选择用户卡顿率这个指标,而不是使用整体的卡顿采样点/总采样数呢?

是因为:我们更想看到有多少用户没有出现过卡顿现象,这更能直观体现优质网络的整体占比。通过对各省份用户零卡顿率、用户数排行,以及各省用户卡顿率的观察,我们可以非常直观地找到卡顿严重的地区,以便重点关注,进行资源调度优化。

7、应急预案

任何一个系统,无论你号称它被设计得多么健壮,它仍然会有故障时间的存在。

硬件故障、软件bug、人为操作失误等等,这些都无可避免地存在着。他们未必是一个必须多少时间内将其彻底解决的问题,他们是我们必须认清并接受共存的一个事实。

所以:预案管理是大型直播活动保障中不可缺少的一环。

我们遵循以下的预案原则:

  • 1)预案信息明确:大盘自动监控不具备二义性,确保预案信息来源正确,触发执行预案的条件明确且有数值化约束;
  • 2)预案操作简洁:所有的预案操作都有有简洁明确(开关型)的操作输入;
  • 3)预案操作安全:所有预案要经过充分预演,同时预演操作本身需要有明确的确认机制,以确保在正常情况下不会被误触发;
  • 4)预案影响验证:明确理清预案操作的影响,QA在预演阶段需要对相关影响进行充分验证。

此次活动的前期筹备中,我们总计进行了3次直播全链路的拟真演练,以及2次联合互动现场、导播台现场的活动全流程级别的彩排,另外进行了大大小小总计数十次的各类风险预案演练。所有演练过程中发现的问题,都会进行专项解决。

风险预案这块,包含了各类资源故障、上下行链路质量、地区性网络故障、CDN异常流量水位等在内的场景应对。其中资源故障包含了机器宕机、机架整体断电、堆叠交换机宕机、机房外网出口不可用,我们均进行了风险预案演练覆盖。

下面列举几点直播解决方案中的部分预案机制:

  • 1)如果因为误操作等导致非正常解密等,可在推流不中断的情况下,动态中止流加密,客户端无任何感知影响;
  • 2)某家cdn在某地区运营商出现大面积故障瘫痪,该地区相应运营商线路的QoS指标会大幅度下降并触发报警,将故障cdn在该地区运营商进行黑名单处理,动态停止对其的调度,将流量调度至正常提供服务的cdn厂商;
  • 3)在两路热流均正常的情况下,但是正在分发的一路出现质量问题,方案可支持手动触发主备切换,让监控数据质量更好的另一路流参与分发,客户端感知时间在1s以内;
  • 4)因为一些不可抗因素,某机房出现大面积故障整体不可用,触发链路报警,此时我们会紧急将流切至另一机房,故障感知与恢复的时间在一分钟内。

8、相关文章

[1] 移动端实时音视频直播技术详解(一):开篇

[2] 移动端实时音视频直播技术详解(二):采集

[3] 移动端实时音视频直播技术详解(三):处理

[4] 移动端实时音视频直播技术详解(四):编码和封装

[5] 移动端实时音视频直播技术详解(五):推流和传输

[6] 移动端实时音视频直播技术详解(六):延迟优化

[7] 淘宝直播技术干货:高清、低延时的实时视频直播技术解密

[8] 爱奇艺技术分享:轻松诙谐,讲解视频编解码技术的过去、现在和将来

[9] 零基础入门:实时音视频技术基础知识全面盘点

[10] 实时音视频面视必备:快速掌握11个视频技术相关的基础概念

[11] 网易云信实时视频直播在TCP数据传输层的一些优化思路

[12] 浅谈实时音视频直播中直接影响用户体验的几项关键技术指标

[13] 首次披露:快手是如何做到百万观众同场看直播仍能秒开且不卡顿的?

[14] 直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路

[15] 直播系统聊天技术(二)阿里电商IM消息平台,在群聊、直播场景下的技术实践

[16] 直播系统聊天技术(三):微信直播聊天室单房间1500万在线的消息架构演进之路

[17] 直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践

[18] 直播系统聊天技术(五):微信小游戏直播在Android端的跨进程渲染推流实践

[19] 直播系统聊天技术(六):百万人在线的直播间实时聊天消息分发技术实践

[20] 直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践

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

posted @ 2022-04-13 10:58 Jack Jiang 阅读(134) | 评论 (0)编辑 收藏

本文由小枣君分享,文案:小枣君、漫画:杨洋,来自鲜枣课堂,有少许改动,原文链接见文末。

1、引言

网络编程能力对于即时通讯技术开发者来说是基本功,而计算机网络又是网络编程的理论根基,因而深刻准确地理解计算机网络知识显然能夯实你的即时通讯应用的实践品质。

本文风格延续了社区里的《网络编程懒人入门》、《脑残式网络编程入门》两个系列,没有更多的理论堆砌,通俗而不失内涵,非常适合希望轻松快乐地学习计算机网络知识的网络编程爱好者们阅读,希望能给你带来不一样的网络知识入门视角。

本篇文章将利用简洁生动的文字,配上轻松幽默的漫画,助你从零开始快速建立起对IPv6技术的直观理解,非常适合入门者阅读。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

本文是该系列文章中的第3篇:

  1. 网络编程入门从未如此简单(一):假如你来设计网络,会怎么做?
  2. 网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?
  3. 网络编程入门从未如此简单(三):什么是IPv6?漫画式图文,一篇即懂!》(本文

本文是IPv6的轻松入门文章,希望你能喜欢。

* 推荐阅读:本文作者的另一篇也同样优秀:网络编程懒人入门(十一):一文读懂什么是IPv6,感兴趣的建议一并阅读 。

3、技术背景

随着移动网络的不断建设和普及,加速了我们迈入万物互联时代的步伐。

我们的整个互联网络,正在发生翻天覆地的变化。急剧增加的网络连接数和流量,对网络的承载和传送能力,提出了前所未有的挑战。

除了速率和带宽之外,5G在垂直行业的落地,也要求网络能够提供灵活的差异化定制服务能力。

也就是说,面对不同的行业应用场景,网络需要能够提供套餐式的服务,支持不同的QoS(Quality of Service,服务质量),支持端到端的切片。

4、IP协议

众所周知,我们现在形影不离的互联网,最早诞生于上世纪60年代。它的核心基础,就是大名鼎鼎的IP协议Internet Protocol,网际互连协议,见《技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)。

如果没有IP协议,以及基于它的IP地址,我们就没办法刷剧、网购、吃鸡、聊微信。

说白了,互联网就是一套“快递系统”。IP地址是你的快递地址,而IP协议,则是快递公司的“工作流程和制度”。

所有我们需要传递的信息,包括文字、图片、音频、视频等,都需要被打包成一个个的“快递包裹”,然后经过快递系统的运输,送到最终目的地。

5、第一、第二代“快递系统”:IPv4

互联网诞生后,长期使用的是v4版本的IP协议,也就是大家熟知的IPv4。

我们可以把它理解为第一代快递系统,它为互联网的早期发展奠定了坚实基础。

后来,随着互联网的迅速发展扩张,原始的IPv4系统暴露出了很多的问题,进行了一些技术上的升级改进。尤其是MPLSMulti-Protocol Label Switching,多协议标签交换)技术的引入,将这个快递系统升级到了第二代。

到了最近这几年,因为前面我们提到的网络挑战,远远超过了第二代快递系统的能力范围。

6、第三代“快递系统”:IPv6

于是,IPv6以及IPv6+,作为第三代快递系统,正式闪亮登场。

IPv6,是v6版本的IP协议。而IPv6+,则是IPv6的升级加强版。

具体来说,IPv6+基于IPv6,实现了更多的创新。

这些创新,既包括以IPv6分段路由、网络切片、随流检测、新型组播和应用感知网络等协议为代表的协议创新,又包括以网络分析、自动调优、网络自愈等网络智能化为代表的技术创新。

凭借这些创新,IPv6+更适合行业用户,更能够有力支撑行业的数字化转型和发展。

接下来,我们仔细看看,IPv6+究竟带来了哪些变化和升级。

7、IPv6优势1:IP地址大幅增加

首先,IPv6最广为人知的优点就是IP地址的大幅增加。具体来说,IPv6的地址数量是IPv4的2的96次方倍(详见《一文读懂什么是IPv6》的第6节内容)。

这么说吧,如果采用IPv6,即便是给地球上的每粒沙子都赋予一个IP地址,都绰绰有余。

传统的IPv4快递系统,邮箱地址不够,快递员往往需要将快速送到门卫处或快递柜,然后再二次派送给用户(在IPv4时代,这就是NAT路由技术啦,详见《NAT详解——详细原理、P2P简介》、《什么是公网IP和内网IP?NAT转换又是什么鬼?)。

 

在IPv6快速系统下,每个用户都有属于自己的邮箱地址,快递员可以直接将快递送到用户手中。

很显然,这样不仅提升了快递的收发速度,也节省了门卫或快递柜的开支,简化了维护,减少了能耗,降低了成本。

其实,IP地址数量的压力,主要来自物联网场景。因为物的数量远远超过人的数量。而且,物联网的控制,更需要端到端的直达。这样才能有更低的时延,实现更精准的控制。

8、IPv6优势2:“快递包装”的升级

IPv6的第二个重大改进,在于“快递包装”的升级。IPv6的数据报文结构变得更加丰富,里面可以记录更多的内容和信息。

简单来说,就是运输快递的纸箱变得更高级了。

传统的快递系统,包装很简单,我们并不知道里面到底是什么物品。

IPv6的快递系统,纸箱上可以贴更多的标签,标识纸箱里的货物属性,例如重货、易碎品、紧急文件等。系统根据标签,可以快速判断这个快递包裹所需的服务,例如需要加急、需要小心轻放等。

这样一来,快递公司可以根据包裹显示的信息,为不同的客户提供更精细化的服务,采用差异化的收费标准。

快递公司还可以走精品路线,提供专属的快递通道,实现高端用户的资源独享。

IPv6+对数据包属性的精准识别,也可以帮助运营商更好地掌握整个网络中数据业务的流动趋势,更好地调动和分配资源。

例如,从A地到B地的视频大颗粒传输需求很多,那么,就可以建立视频大颗粒业务专线,更好地满足传输需求。

这就好像从A地到B地的海鲜运输需求很多,那快递公司就采购更多的冷链运输车,专门投入到这条线路上,赚取更多的利润。

9、IPv6优势3:升级了“导航能力”

传统快递系统的运输路径,是相对固定和死板的。运输车从起点到终点,经过每一个路口,都由路口指定下一步前进的方向。

 

而IPv6+的话,通过与SR(Segment Routing,分段路由)技术、SDN(Software Defined Network,软件定义网络)技术进行结合,具有更强的路径选择能力。

快递包裹在出发时,就已经从管理中心获得了从起点到终点的最佳路径。每一次选路,都按照规划进行,可以避开拥堵,也可以避免绕路。

换言之,IPv6+超强的路径编排能力,可以实现数据报文的一跳入云,大幅提升效率。

10、IPv6优势4:降低运维成本

因为网络的管理功能集中,可以更方便地将配置意图转换成脚本,自动部署给各个网络节点。

引入AI之后,更能够对故障现象进行自动分析,更快地找到原因。

甚至说,AI还可以根据对故障模型的学习,主动提前识别网络中潜在的故障风险,实现事故预防。

集中管理+AI管理,大幅降低了网络的维护难度,提升了运维效率,减少了维护成本。

11、IPv6优势5:更安全

IPv6+的安全防御能力相比IPv4有了很大的提升,真正实现了云、网、安一体化防御。

传统网络中,因为大量私网的存在,恶意行为很难溯源。也就是说,很多坏人躲在暗处,发出有问题的包裹,对快递系统造成破坏。

在IPv6+网络中,节点采用公网地址取代私网地址,这就意味着,在快递系统中运输的每一个包裹,都有真实可溯源的寄件人信息。失去了私网的伪装,破坏行为将无所遁形。

升级后的快递包装(数据报文结构),也大幅增加了破坏分子对包裹进行恶意伪造和窃听的难度,增强了包裹的安全性和私密性。

 

12、写在最后

总而言之,IPv6+是一个高速、高效、灵活、智能的先进“快递系统”。

它可以提供满足千行百业应用需求的差异化服务能力,适配不同行业的业务承载需求,支撑各个行业的数字化转型,助力消费互联网向产业互联网升级,推动整个社会数字经济的发展。

目前,IPv6在我国已经取得了显著的成果。截至今年8月,我国IPv6地址资源储备位居世界第一。IPv6活跃用户数达5.51亿,占我国全部网民数的54.52%。

IPv6+的黄金时代,已然到来!

 

13、参考资料

[1] TCP/IP详解 卷1 - 第3章 IP:网际协议

[2] 网络编程懒人入门(十一):一文读懂什么是IPv6

[3] IPv6技术详解:基本概念、应用现状、技术实践(上篇)

[4] IPv6技术详解:基本概念、应用现状、技术实践(下篇)

[5] Java对IPv6的支持详解:支持情况、相关API、演示代码等

[6] NAT详解——详细原理、P2P简介

[7] 什么是公网IP和内网IP?NAT转换又是什么鬼?

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

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

本文由小米技术团队分享,原题“小爱接入层单机百万长连接演进”,有修订。

1、引言

小爱接入层是小爱云端负责设备接入的第一个服务,也是最重要的服务之一,本篇文章介绍了小米技术团队2020至2021年在这个服务上所做的一些优化和尝试,最终将单机可承载长连接数从30w提升至120w+,节省了机器30+台

提示:什么是“小爱”?

小爱(全名“小爱同学”)是小米旗下的人工智能语音交互引擎,搭载在小米手机、小米AI音箱、小米电视等设备中,在个人移动、智能家庭、智能穿戴、智能办公、儿童娱乐、智能出行、智慧酒店、智慧学习共八大类场景中使用。

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

2、专题目录

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

  1. 长连接网关技术专题(一):京东京麦的生产级TCP网关技术实践总结
  2. 长连接网关技术专题(二):知乎千万级并发的高性能长连接网关技术实践
  3. 长连接网关技术专题(三):手淘亿级移动端接入层网关的技术演进之路
  4. 长连接网关技术专题(四):爱奇艺WebSocket实时推送网关技术实践
  5. 长连接网关技术专题(五):喜马拉雅自研亿级API网关技术实践
  6. 长连接网关技术专题(六):石墨文档单机50万WebSocket长连接架构实践
  7. 长连接网关技术专题(七):小米小爱单机120万长连接接入层的架构演进》(* 本文

3、什么是小爱接入层

整个小爱的架构分层如下:

接入层主要的工作在鉴权授权层和传输层,它是所有小爱设备和小爱大脑交互的第一个服务。

由上图我们知道小爱接入层的重要功能有如下几个:

  • 1)安全传输和鉴权:维护设备和大脑的安全通道,保障身份认证有效和传输数据安全;
  • 2)维护长连接:维持设备和大脑的长连接(Websocket等),做好连接状态存储,心跳维护等工作;
  • 3)请求转发:针对每一次小爱设备的请求做好转发,保障每一次请求的稳定。

4、早期接入层的技术实现

小爱接入层最早的实现是基于AkkaPlay,我们使用它们搭建了第一个版本,该版本特点如下:

  • 1)基于Akka我们基本做到了初步的异步化,保障核心线程不被阻塞,性能尚可。
  • 2)Play框架天然支持Websocket,因此我们在有限的人力下能够快速搭建和实现,且能够保障协议实现的标准性。

5、早期接入层的技术问题

随着小爱长连接的数量突破千万大关,针对早期的接入层方案,我们发现了一些问题。

主要的问题如下:

1)长连接数量上来后,需要维护的内存数据越来越多,JVM的GC成为不可忽略的性能瓶颈,且一旦代码写的不好有GC风险。经过之前事故分析,Akka+Play版的接入层其单实例长连接数量的上限在28w左右。

2)老版本的接入层实现比较随意,其Akka Actor之间存在非常多的状态依赖而不是基于不可变的消息传递这样使得Actor之间的通信变成了函数调用,导致代码可读性差且维护很困难,没有发挥出Akka Actor在构建并发程序的优势。

3)作为接入层服务,老版本对协议的解析是有很强的依赖的,这导致它要随着版本变动而频繁上线,其上线会引起长连接重连,随时有雪崩的风险。

4)由于依赖Play框架,我们发现其长连接打点有不准确的问题(因为拿不到底层TCP连接的数据),这个会影响我们每日巡检对服务容量的评估,且依赖其他框架在长连接数量上来后我们没有办法做更细致的优化。

6、新版接入层的设计目标

基于早期接入层技术方案的种种问题,我们打算重构接入层。

对于新版接入层我们制定的目标是:

  • 1)足够稳定:上线尽可能不断连接且服务稳定;
  • 2)极致性能:目标单机至少100w长连接,最好不要受GC影响;
  • 3)最大限度可控:除了底层网络I/O的系统调用,其他所有代码都要是自己实现/或者内部实现的组件,这样我们有足够的自主权。

于是,我们开始了单机百万长连接的漫漫实践之路。。。

7、新版接入层的优化思路

7.1 接入层的依赖关系

接入层与外部服务的关系理清如下:

7.2 接入层的功能划分

接入层的主要功能划分如下:

  • 1)WebSocket解析:收到的客户端字节流,要按照WebSocket协议要求解析出数据;
  • 2)Socket状态保持:存储连接的基本状态信息;
  • 3)加密解密:与客户端通讯的所有数据都是加密过的,而与后端模块之间传输是json明文的;
  • 4)顺序化:同一个物理连接上,先后两个请求A、B到达服务器,后端服务中B可能先于A得到了应答,但是我们收到B不能立刻发送给客户端,必须等待A完成后,再按照A,B的顺序发给客户端;
  • 5)后端消息分发:接入层后面不止对接单个服务,可能根据不同的消息转发给不同的服务;
  • 6)鉴权:安全相关验证,身份验证等。

7.3 接入层的拆分思路

把之前的单一模块按照是否有状态,拆分为两个子模块。

具体如下:

  • 1)前端:有状态,功能最小化,尽量少上线;
  • 2)后端:无状态,功能最大化,上线可做到用户无感知。

所以,按照上面的原则,理论上我们会做出这样的功能划分,即前端很小、后端很大。示意图如下图所示。

8、新版接入层的技术实现

8.1 总览

模块拆分为前后端:

  • 1)前端有状态,后端无状态;
  • 2)前后端是独立进程,同机部署。

补充:前端负责建立与维护设备长连接的状态,为有状态服务;后端负责具体业务请求,为无状态服务。后端服务上线不会导致设备连接断开重连及鉴权调用,避免了长连接状态因版本升级或逻辑调整而引起的不必要抖动;

前端使用CPP实现:

  • 1)Websocket协议完全自己解析:可以从Socket层面获取所有信息,任何Bug都可以处理;
  • 2)更高的CPU利用率:没有任何额外JVM代价,无GC拖累性能;
  • 3)更高的内存利用率:连接数量变大后与连接相关的内存开销变大,自己管理可以极端优化。

后端暂时使用Scala实现:

  • 1)已实现的功能直接迁移,比重写代价要低得多;
  • 2)依赖的部分外部服务(比如鉴权)有可直接利用的Scala(Java)SDK库,而没有C++版本,若用C++重写代价非常大;
  • 3)全部功能无状态化改造,可以做到随时重启而用户无感知。

通讯使用ZeroMQ

进程间通讯最高效的方式是共享内存,ZeroMQ基于共享内存实现,速度没问题。

8.2 前端实现

整体架构:

 

如上图所示,由四个子模块组成:

  • 1)传输层:Websocket协议解析,XMD协议解析;
  • 2)分发层:屏蔽传输层的差异,不管传输层使用的什么接口,在分发层转化成统一的事件投递到状态机;
  • 3)状态机层:为了实现纯异步服务,使用自研的基于Actor模型的类Akka状态机框架XMFSM,这里面实现了单线程的Actor抽象;
  • 4)ZeroMQ通讯层:由于ZeroMQ接口是阻塞实现,这一层通过两个线程分别负责发送和接收。

8.2.1)传输层:

WebSocket 部分使用 C++ 和 ASIO 实现 websocket-lib。小爱长连接基于WebSocket协议,因此我们自己实现了一个WebSocket长连接库。

这个长连接库的特点是:

  • a. 无锁化设计,保障性能优异;
  • b. 基于BOOST ASIO 开发,保障底层网络性能。

压测显示该库的性能十分优异的:

这一层同时也承担了除原始WebSocket外,其他两种通道的的收发任务。

目前传输层一共支持以下3种不同的客户端接口:

  • a. websocket(tcp):简称ws;
  • b. 基于ssl的加密websocket(tcp):简称wss;
  • c. xmd(udp):简称xmd。

8.2.2)分发层:

把不同的传输层事件转化成统一事件投递到状态机,这一层起到适配器的作用,确保无论前面的传输层使用哪种类型,到达分发层变都变成一致的事件向状态机投递。

8.2.3)状态机处理层:

主要的处理逻辑都位于这一层中,这里非常重要的一个部分是对于发送通道的封装。

对于小爱应用层协议,不同的通道处理逻辑是完全一致的,但是在处理和安全相关逻辑上每个通道又有细节差异。

比如:

  • a. wss 收发不需要加解密,加解密由更前端的Nginx做了,而ws需要使用AES加密发送;
  • b. wss 在鉴权成功后不需要向客户端下发challenge文本,因为wss不需要做加解密;
  • c. xmd 发送的内容与其他两个不同,是基于protobuf封装的私有协议,且xmd需要处理发送失败后的逻辑,而ws/wss不用考虑发送失败的问题,由底层Tcp协议保证。

针对这种情况:我们使用C++的多态特性来处理,专门抽象了一个Channel接口,这个接口中提供的方法包含了一个请求处理的一些关键差异步骤,比如如何发送消息到客户端,如何stop连接,如何处理发送失败等等。对于3种(ws/wss/xmd)不同的发送通道,每个通道有自己的Channel实现。

客户端连接对象一创建,对应类型的具体Channel对象就立刻被实例化。这样状态机主逻辑中只实现业务层的公共逻辑即可,当在有差异逻辑调用时,直接调用Channel接口完成,这样一个简单的多态特性帮助我们分割了差异,确保代码整洁。

8.2.4)ZeroMQ 通讯层:

通过两个线程将ZeroMQ的读写操作异步化,同时负责若干私有指令的封装和解析。

8.3 后端实现

8.3.1)无状态化改造:

后端做的最重要改造之一就是将所有与连接状态相关的信息进行剔除。

整个服务以 Request(一次连接上可以传输N个Request)为核心进行各种转发和处理,每次请求与上一次请求没有任何关联。一个连接上的多次请求在后端模块被当做独立请求处理。

8.3.2)架构:

Scala 服务采用 Akka-Actor 架构实现了业务逻辑。

服务从 ZeroMQ 收到消息后,直接投递到 Dispatcher 中进行数据解析与请求处理,在 Dispatcher 中不同的请求会发送给对应的 RequestActor进行 Event 协议解析并分发给该 event 对应的业务 Actor 进行处理。最后将处理后的请求数据通过XmqActor 发送给后端 AIMS&XMQ 服务。

一个请求在后端多个 Actor 中的处理流程:

8.3.3)Dispatcher 请求分发:

前端与后端之间通过 Protobuf 进行交互,避免了Json 解析的性能消耗,同时使得协议更加规范化。

后端服务从 ZeroMQ 收到消息后,会在 DispatcherActor 中进行PB协议解析并根据不同的分类(简称CMD)进行数据处理,分类包括如下几种。

* BIND 命令:

鉴权功能,由于鉴权功能逻辑复杂,使用C++语言实现起来较为困难,目前依然放在 scala 业务层进行鉴权。该部分对设备端请求的 HTTP Headers 进行解析,提取其中的 token 进行鉴权,并将结果返回前端。

* LOGIN 命令:

设备登入,设备鉴权通过后当前连接已成功建立,此时会进行 Login 命令的执行,用于将该长连接信息发送至AIMS并记录于Varys服务中,方便后续的主动下推等功能。在 Login 过程中,服务首先将请求 Account 服务获取长连接的 uuid(用于连接过程中的路由寻址),然后将设备信息+uuid 发送至AIMS进行设备登入操作。

* LOGOUT 命令:

设备登出,设备在与服务端断开连接时需要进行 Logout 操作,用于从 Varys 服务中删除该长连接记录。

* UPDATE 与 PING 命令:

a. Update 命令,设备状态信息更新,用于更新该设备在数据库中保存的相关信息;

b. Ping 命令,连接保活,用于确认该设备处于在线连接状态。

* TEXT_MESSAGE 与 BINARY_MESSAGE:

文本消息与二进制消息,在收到文本消息或二进制消息时将根据 requestid 发送给该请求对应的RequestActor进行处理。

8.3.4)Request 请求解析:

针对收到的文本和二进制消息,DispatcherActor 会根据 requestId 将其发送给对应的RequestActor进行处理。

其中:文本消息将会被解析为Event请求,并根据其中的 namespace 和 name 将其分发给指定的业务Actor。二进制消息则会根据当前请求的业务场景被分发给对应的业务Actor。

8.4 其他优化

在完成新架构 1.0 调整过程中,我们也在不断压测长连接容量,总结几点对容量影响较大的点。

8.4.1)协议优化:

a. JSON替换为Protobuf: 早期的前后端通信使用的是 json 文本协议,后来发现 json 序列化、反序列化这部分对CPU的占用较大,改为了 protobuf 协议后,CPU占用率明显下降。

b. JSON支持部分解析:业务层的协议是基于json的,没有办法直接替换,我们通过"部分解析json"的方式,只解析很小的 header 部分拿到 namespace 和 name,然后将大部分直接转发的消息转发出去,只将少量 json 消息进行完整反序列化成对象。此种优化后CPU占用下降10%。

8.4.2)延长心跳时间:

在第一次测试20w连接时,我们发现在前后端收发的消息中,一种用来保持用户在线状态的心跳PING消息占了总消息量的75%,收发这个消息耗费了大量CPU。因此我们延长心跳时间也起到了降低CPU消耗的目的。

8.4.3)自研内网通讯库:

为了提高与后端服务通信的性能,我们使用自研的TCP通讯库,该库是基于Boost ASIO开发的一个纯异步的多线程TCP网络库,其卓越的性能帮助我们将连接数提升到120w+。

9、未来规划

经过新版架构1.0版的优化,验证了我们的拆分方向是正确的,因为预设的目标已经达到:

  • 1)单机承载的连接数 28w => 120w+(普通服务端机器 16G内存 40核 峰值请求QPS过万),接入层下线节省了50%+的机器成本;
  • 2)后端可以做到无损上线。

再重新审视下我们的理想目标,以这个为方向,我们就有了2.0版的雏形:

具体就是:

  • 1)后端模块使用C++重写,进一步提高性能和稳定性。同时将后端模块中无法使用C++重写的部分,作为独立服务模块运维,后端模块通过网络库调用;
  • 2)前端模块中非必要功能尝试迁移到后端,让前端功能更少,更稳定;
  • 3)如果改造后,前端与后端处理能力差异较大,考虑到ZeroMQ实际是性能过剩的,可以考虑使用网络库替换掉ZeroMQ,这样前后端可以从1:1单机部署变为1:N多机部署,更好的利用机器资源。

2.0版目标是:经过以上改造后,期望单前端模块可以达到200w+的连接处理能力。

10、参考资料

[1] 上一个10年,著名的C10K并发连接问题

[2] 下一个10年,是时候考虑C10M并发问题了

[3] 一文读懂高性能网络编程中的线程模型

[4] 深入操作系统,一文读懂进程、线程、协程

[5] Protobuf通信协议详解:代码演示、详细原理介绍等

[6] WebSocket从入门到精通,半小时就够!

[7] 如何让你的WebSocket断网重连更快速?

[8] 从100到1000万高并发的架构演进之路

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

posted @ 2022-03-22 17:00 Jack Jiang 阅读(583) | 评论 (0)编辑 收藏

本文由阿里闲鱼技术团队书闲分享,原题“如何有效缩短闲鱼消息处理时长”,有修订和改动。

1、引言

闲鱼技术团队围绕IM这个技术范畴,已经分享了好几篇实践性总结文章,本篇将要分享的是闲鱼IM系统中在线和离线聊天消息数据的同步机制上所遇到的一些问题,以及实践性的解决方案。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

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

  1. 阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处
  2. 阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践
  3. 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路
  4. 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践
  5. 阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践
  6. 阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化
  7. 阿里IM技术分享(七):闲鱼IM的在线、离线聊天数据同步机制优化实践》(* 本文

3、问题背景

随着用户数的快速增长,闲鱼IM系统也迎来了前所未有的挑战。

历经多年的业务迭代,客户端侧IM的代码已经因为多年的迭代层次结构不足够清晰,之前一些隐藏起来的聊天数据同步问题,也随着用户数的增大而被放大。

这里面的具体流程在于:后台需要同步到用户端侧的数据包,后台会根据数据包的业务类型划分成不同的数据域,数据包在对应域里面存在唯一且连续的编号,每一个数据包发送到端侧并且被成功消费后,端侧会记录当前每一个数据域已经同步过的版本编号,下一次数据同步就以本地数据域的编号开始,不断的同步到客户端。

当然用户不会一直在线等待消息,所以之前端侧采用了推拉结合的方式保证数据的同步。

具体就是:

  • 1)客户端在线时:使用ACCS实时的将最新的数据内容推送到客户端(ACCS是淘宝无线向开发者提供全双工、低延时、高安全的通道服务);
  • 2)客户端从离线状态启动后:根据本地的数据域编号,拉取不在线时候的数据差;
  • 3)当数据获取出现黑洞时:触发数据同步拉取(“黑洞”即指数据包Version不连续的状态)。

4、问题分析

当前的聊天数据同步策略确实是可以基本保障IM的数据同步的,但是也伴随着一些隐含的问题。

这些隐含的问题主要有:

  • 1)短时间密集数据推送时,会快速的触发多次数据域同步。域同步回来的数据如果存在问题,又会触发新一轮的同步,造成网络资源的浪费。冗余数据包/无效的数据内容会占用有效内容的处理资源,又对CPU和内存资源造成浪费;
  • 2)数据域中的数据包客户端是否正常消费,服务端侧无感知,只能被动地根据当前数据域信息返回数据;
  • 3)数据收取/消息数据体解析/存储落库逻辑拆分不够清晰,无法针对性的对某一层的代码拆分替换进行ABTest。

针对上述问题,我们对闲鱼IM进行了分层改造——即抽离数据同步层。这样优化,除了希望以后这个数据的同步内容可以用在IM之外,也希望随着稳定性的增加,赋能其他的业务场景。

接下来的内容,我们重点来看下解决客户端侧闲鱼IM聊天数据同步问题的一些实践思路。

5、优化思路

5.1 分层拆分

对于服务端来说:业务侧产出数据包后,会拼接上当前的数据域信息,然后通过数据同步层将数据推送到端侧。

对于客户端来说:接收到数据包后,会根据当前的数据域信息,来确定需要消费数据包的业务方,确保数据包在数据域内完整连续后,将数据体脱壳后交于业务侧消费,并且应答消费的状况。

数据同步层的抽取:把数据同步中的加壳、脱壳、校验、重试流程封装到一起,可以让上层业务只需要关心自己需要监听的数据域信息,然后当这些数据域更新数据的时候,可以获取到这些数据进行消费,而不再需要关心数据包是否完整。

这样做的话:

  • 1)业务侧只需要关心业务侧对接的协议;
  • 2)数据侧只需要关心数据侧包装的协议;
  • 3)网络层负责真实的数据传输。

整体的架构原理如下:

总结一下就是:

  • 1)对齐数据层数据传输协议、描述当前数据包体数据域信息;
  • 2)将消息的处理/合并/落库抽离成数据消费者;
  • 3)上下楼依赖抽象化,去除对于具体实现的依赖。

5.2 数据层结构模型

基于对于数据模型剥离和对当下遇见问题的解决方案规整,将数据同步层拆分为下图这样的架构。

具体的实施思路就是:

  • 1)App启动时建立ACCS长链接服务,保证推推送信道链接,并且根据当前本地数据域信息触发一次数据拉取;
  • 2)数据消费者注册消费者信息和需要监听的数据域信息,这里是一对多的关系;
  • 3)新的数据抵达端侧后,将数据包放到指定的数据域的缓冲池,批量数据归纳结束后,重新出发数据的读取;
  • 4)根据当前数据域优先级弹出最高优的数据包,判断数据域版本是否符合消费者要求,符合则将数据包脱壳后丢给消费者消费,不符合则根据上一次正确的数据包的域信息触发增量的数据域同步拉取;
  • 5)触发数据域同步拉取时,block数据读取,此时通过ACCS触达的数据依旧会在继续归纳到指定的数据域队列中,等待数据域同步拉取结果,将数据包进行排序、去重,合并到对应的数据域队列中。然后重新激活数据读取;
  • 6)数据包体被消费者正确消费后,更新域信息并且通过上行信道告知服务端已经正确处理的数据域信息。

* 数据域同步协议:

Region中携带的数据不必过多,但需将数据包的内容描述清楚,具体是:

  • 1)目标用户的ID,用以确定目标数据包是否正确;
  • 2)数据域ID和优先级信息;
  • 3)当前数据包的域优先级版本。

* 排序策略:

针对于域数据归纳,无论是在写入数据的时候进行排序还是在读取的时候进行查找都需要进行一次排序的操作,时间复杂度最优也是O(logn)级别的。

在实际coding中发现由于在一个数据域里面,数据包的Version信息是连续唯一并且不存在断层的,上一个稳定消费的数据体的Version信息自增就是下一个数据包的Version,所以这里采用了以Versio为主键的Map存储,既降低了时间复杂度,也使得唯一标识的数据包后抵达端侧的包内容可以覆盖之前的包内容。

6、新的问题及解决策略

6.1 多数据来源和唯一数据消费的平衡

每当产生一条针对于当前用户的数据包:

  • 1)如果当前ACCS长链接存在,就会通过ACCS将数据包推送到客户端;
  • 2)如果App切换到后台一段时间,或者直接被杀死,ACCS链接断开,那么只能通过离线推送到用户的通知面板。

所以:每当App切换到活跃状态,都需要根据当前本地存储的数据域信息从后台触发一次数据同步。

数据包触达到客户端侧的来源主要是ACCS长链接的推送和域同步时的拉取,但是数据包的消费是根据数据域的监听划分的唯一消费者,也就是同一时间内只能消费一个数据包。

在压力测试中:当后台短时间内密集的将数据包通过ACCS推送到端侧时,端侧接收到的数据包并不有序,不连续的数据包域版本又会触发新的数据域同步,导致同样的一份数据包会通过两个不同的渠道多次的触达到端侧,浪费了不必要的流量。

当数据域同步时:这个时间节点产生的新数据包也会推送到端侧,数据体有效,并且需要被正确的消费。

针对上述这些问题的解决策略:

即在数据消费和数据获取中间装载一个数据中间层,当触发数据域同步的时候block数据的读取并且ACCS推送下来的数据包会被存放在一个数据的中转站里面,当数据域同步拉取的数据回来后,对数据进行合并后再重启数据读取流程。

6.2 数据域优先级

需要推送到客户端侧的数据包,根据业务的不同优先级也有不同的划分。

用户和用户的聊天产生的数据包会比运营类的消息的数据包优先级要高一些,所以要当多优先级的数据包快速的抵达端侧时,高优先级数据域的数据包需要被优先消费,而数据域的优先级也是需要动态调整,需要不断变换的优先级策略。

针对这个问题的解决策略:

不同的数据域,产生不同的数据队列,高优队列里面的数据包会被优先读取消费。

每一个数据包体中带回的数据域信息,都可以标注当前的数据域优先级,当数据域优先级发生变化的时候,调整数据包消费优先级策略。

7、优化后的效果

除去结构上分层梳理,使得数据同步层和依赖的服务内容可便捷解耦/每一个环节可插拔之外,数据同步中对于消息消费时长/流量节省,压力测试场景下优化效果更加明显。

在“500ms内100条全乱序数据包推送”压力测试场景下:

  • 1)消息处理时长(接收-上屏)缩短 31%;
  • 2)流量损耗(最终拉取到端侧数据包累积大小)降低35%。

8、后续的优化计划

8.1 数据同步层能力提升

数据同步侧的目标,既要保证数据包完整的到达端侧,又要在保证稳定性的前提下尽可能的减少数据的拉取,使得每一次数据的获取都有效。

后续数据同步层会着手于有效数据率和到达率进行更进一步的优化。

针对不同的场景,动态智能调整数据同步的优先级策略。

阻塞式长链接推送,保证同一时间只存在推模式或者拉模式,进一步减少冗余数据包的推送。

8.2 IM端侧整体架构升级

升级数据同步层策略主要还是要提升IM的能力,将数据同步分层后,接下来就是将消息的处理流程化,对每一个流程都可监控可回溯,提升IM数据包的正确解析存储和落库率。

细化一下就是:

  • 1)在数据来源侧剥离开后,后续对IM的整改也会逐步的将消息的处理分层剥离;
  • 2)消息处理关键节点的流程式上报、建立完整的监控体系,让问题发现先于用户舆情;
  • 3)消息完整性的动态自检,最小化数据补偿补全。

9、参考资料

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

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

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

[4] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[5] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[6] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

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

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

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

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

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

[12] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?

[13] IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的

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

posted @ 2022-03-15 17:32 Jack Jiang 阅读(378) | 评论 (0)编辑 收藏

本文由作者小林coding分享,来自公号“小林coding”,有修订和改动。

1、引言

说到TCP协议,对于从事即时通讯/IM这方面应用的开发者们来说,再熟悉不过了。随着对TCP理解的越来越深入,很多曾今碰到过但没时间深入探究的TCP技术概念或疑问,现在是时候回头来恶补一下了。

本篇文章,我们就从系统层面深入地探讨一个有趣的TCP技术问题:拔掉网线后,再插上,原本的这条TCP连接还在吗?或者说它还“好”吗?

可能有的人会说:网线都被拔掉了,那说明物理层(也叫实体层)被断开了(关于网络协议分层模型请见《快速理解网络通信协议(上篇)》),那在物理层之上的传输层理应也会断开,所以原本的 TCP 连接就不会存在的了。就好像我们拨打有线电话的时候,如果某一方的电话线被拔了,那么本次通话就彻底断了。

答案真的是这样吗?可能并非你理解的这样哦,一起跟随笔者来深入探讨一下。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

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

3、比较笼统的答案

3.1 答案

引言里我们说到:有人认为,网线都被拔掉了,那说明物理层被断开,那么物理层之上的传输层肯定也会断开,所以原来的 TCP 连接自然也就不存在了。(PS:计算机网络分层详解请见《史上最通俗计算机网络分层详解

上面这个逻辑是有问题的。

问题在于:错误的认为拔掉网线这个动作会影响传输层,事实上并不会影响!

实际上:TCP 连接在 Linux 内核中是一个名为 struct socket 的结构体,该结构体的内容包含 TCP 连接的状态等信息。

所以:当拔掉网线的时候,操作系统并不会变更该结构体的任何内容,所以 TCP 连接的状态也不会发生改变。

3.2 实验验证一下

我做了个小实验:我用 ssh 终端连接了我的云服务器,然后我通过断开 wifi 的方式来模拟拔掉网线的场景,此时查看 TCP 连接的状态没有发生变化,还是处于 ESTABLISHED 状态(如下图所示)。

通过上面实验结果可以验证我的结论:拔掉网线这个动作并不会影响 TCP 连接的状态。

不过,这个答案还是有点笼统。实际上,我们应该在更具体的场景中来看待这个问题,答案才更准确一些。

这个具体场景就是:

  • 1)当拔掉网线后,有数据传输时;
  • 2)当拔掉网线后,没有数据传输时。

针对上面这两种具体的场景,我来更具体地来分析一下。我们继续往下阅读。

4、具体场景1:拔掉网线后,有数据传输时

4.1 数据传输过程中,恰好又把网线插回去了

如果是客户端被拔掉网线后,服务端向客户端发送的数据报文会得不到任何的响应,在等待一定时长后,服务端就会触发TCP协议的超时重传机制(详见:《TCP/IP详解 - 第21章·TCP的超时与重传),然而此时重传并不能得到响应的数据报文。

如果在服务端重传报文的过程中,客户端恰好把网线插回去了,由于拔掉网线并不会改变客户端的 TCP 连接状态,并且还是处于 ESTABLISHED 状态,所以这时客户端是可以正常接收服务端发来的数据报文的,然后客户端就会回 ACK 响应报文。

此时:客户端和服务端的 TCP 连接将依然存在且工作状态不会受到影响,给应用层的感觉就像什么事情都没有发生。。。

4.2 数据传输过程中,网线一直没有插回去

上面这种情况下,如果在服务端TCP协议重传报文的过程中,客户端一直没有将网线插回去,那么服务端超时重传报文的次数达到一定阈值后,内核就会判定出该 TCP 有问题。然后就会通过 Socket 接口告诉应用程序该 TCP 连接出问题了,于是服务端的 TCP 连接就会断开。

接下来,如果客户端再插回网线,如果客户端向服务端发送了数据,由于服务端已经没有与客户端匹配的 TCP 连接信息了,因此服务端内核就会回复 RST 报文,客户端收到后就会释放该 TCP 连接。

此时:客户端和服务端的 TCP 连接已经明确被断开,原本的这个连接也就不存在了。

4.3 刨根问底:TCP数据报文到底重传几次?

本着知其然更应知其所以然的精神,我们来刨根问底一下:TCP 的数据报文到底有重传几次呢?

在 Linux 系统中,提供了一个叫 tcp_retries2 配置项,默认值是 15(如下图所示)。

如上图所示:这个内核参数是控制 TCP 连接建立的情况下,超时重传的最大次数。

不过 tcp_retries2 设置了 15 次,并不代表 TCP 超时重传了 15 次才会通知应用程序终止该 TCP 连接,内核还会基于“最大超时时间”来判定。

每一轮的超时时间都是倍数增长的,比如第一次触发超时重传是在 2s 后,第二次则是在 4s 后,第三次则是 8s 后,以此类推。

内核会根据 tcp_retries2 设置的值,计算出一个最大超时时间。

在重传报文且一直没有收到对方响应的情况时,先达到“最大重传次数”或者“最大超时时间”这两个的其中一个条件后,就会停止重传,然后就会断开 TCP 连接。

PS:有关TCP超时重传机制的详细情况,可以阅读浅析TCP协议中的疑难杂症(下篇)》。

5、具体场景2:拔掉网线后,有数据传输时

5.1 场景分析

针对拔掉网线后,没有数据传输的场景,还得具体看看是否开启了 TCP KeepAlive 机制 (详见《彻底搞懂TCP协议层的KeepAlive保活机制》)。

1)如果没有开启 TCP KeepAlive 机制:

在客户端拔掉网线后,并且双方都没有进行数据传输,那么客户端和服务端的 TCP 连接将会一直保持存在。

2)如果开启了 TCP KeepAlive 机制:

在客户端拔掉网线后,即使双方都没有进行数据传输,在持续一段时间后,TCP 就会发送KeepAlive探测报文。

根据KeepAlive探测报文响应情况,会有以下两种可能:

  • 1)如果对端正常工作:当探测报文被对端收到并正常响应, TCP 保活时间将被重置,等待下一个 TCP 保活时间的到来;
  • 2)如果对端主机崩溃或对端由于其他原因导致报文不可达:当探测报文发送给对端后,石沉大海、没有响应,连续几次,达到保活探测次数后,TCP 会报告该连接已经死亡。

所以:TCP 保活机制可以在双方没有数据交互的情况,通过TCP KeepAlive 机制的探测报文,来确定对方的 TCP 连接是否存活。

5.2 刨根问底:TCP KeepAlive 机制具体是什么样的?

TCP KeepAlive 机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文。该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔。

以下是 Linux 中的默认值:

net.ipv4.tcp_keepalive_time=7200

net.ipv4.tcp_keepalive_intvl=75 

net.ipv4.tcp_keepalive_probes=9

解释一下:

  • 1)tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制;
  • 2)tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
  • 3)tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个“死亡”连接。

计算公式是:

注意:应用程序若想使用 TCP 保活机制需要通过 socket 接口设置 SO_KEEPALIVE 选项才能够生效,如果没有设置,那么就无法使用 TCP 保活机制。

PS:关于TCP协议的KeepAlive 机制详见《彻底搞懂TCP协议层的KeepAlive保活机制》、《一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等》。

5.3 刨根问底:TCP KeepAlive 机制的探测时间也太长了吧?

没错,确实有点长。

TCP KeepAlive  机制是 TCP 层(内核态) 实现的,它是给所有基于 TCP 传输协议的程序一个兜底的方案。

实际上:我们通常在应用层自己实现一套探测机制,可以在较短的时间内,探测到对方是否存活。

比如:一般Web 服务器都会提供 keepalive_timeout 参数,用来指定 HTTP 长连接的超时时间。如果设置了 HTTP 长连接的超时时间是 60 秒,Web 服务软件就会启动一个定时器,如果客户端在完后一个 HTTP 请求后,在 60 秒内都没有再发起新的请求,定时器的时间一到,就会触发回调函数来释放该连接。

再比如:IM、消息推送系统里的心跳机制,通过应用层的心跳机制(由客户端发出,服务端回复响应包),来灵活控制和探测长连接的健康度。

为何基于TCP协议的移动端IM仍然需要心跳保活机制?》这篇文章解释了IM这类应用中应用层心跳保活的必要性,有兴趣可以读一读。

如果对应用层心跳的具体应用没什么概念,可以看看微信的这两篇文章:

  1. 微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
  2. 移动端IM实践:实现Android版微信的智能心跳机制

下面有几个针对im这类应用的心跳实现代码,可以具体感受学习一下:

  1. 正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)
  2. 一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)
  3. 自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)
  4. 手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

6、本文小结

下面简单总结一下文中的内容,本文开头的问题并不是简单一句话能够准确说清楚的,需要分情况对待。

也就是:客户端拔掉网线后,并不会直接影响 TCP 的连接状态。所以拔掉网线后,TCP 连接是否还会存在,关键要看拔掉网线之后,有没有进行数据传输。

1)有数据传输的情况:

在客户端拔掉网线后:如果服务端发送了数据报文,那么在服务端重传次数没有达到最大值之前,客户端恰好插回网线的话,那么双方原本的 TCP 连接还是能存在并正常工作,就好像什么事情都没有发生。

在客户端拔掉网线后:如果服务端发送了数据报文,在客户端插回网线之前,服务端重传次数达到了最大值时,服务端就会断开 TCP 连接。等到客户端插回网线后,向服务端发送了数据,因为服务端已经断开了与客户端相同四元组的 TCP 连接,所以就会回 RST 报文,客户端收到后就会断开 TCP 连接。至此, 双方的 TCP 连接都断开了。

2)没有数据传输的情况:

  • a. 如果双方都没有开启 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,那么客户端和服务端的 TCP 连接状态将会一直保持存在;
  • b. 如果双方都开启了 TCP keepalive 机制,那么在客户端拔掉网线后,如果客户端一直不插回网线,TCP keepalive 机制会探测到对方的 TCP 连接没有存活,于是就会断开 TCP 连接。而如果在 TCP 探测期间,客户端插回了网线,那么双方原本的 TCP 连接还是能正常存在。

除了客户端拔掉网线的场景,还有客户端“宕机和杀死进程”的两种场景。

第一个场景:客户端宕机这件事跟拔掉网线是一样无法被服务端的感知的,所以如果在没有数据传输,并且没有开启 TCP keepalive 机制时,,服务端的 TCP 连接将会一直处于 ESTABLISHED 连接状态,直到服务端重启进程。

所以:我们可以得知一个点——在没有使用 TCP 保活机制,且双方不传输数据的情况下,一方的 TCP 连接处在 ESTABLISHED 状态时,并不代表另一方的 TCP 连接还一定是正常的。

第二个场景:杀死客户端的进程后,客户端的内核就会向服务端发送 FIN 报文,与客户端进行四次挥手(见《跟着动画来学TCP三次握手和四次挥手》)。

所以:即使没有开启 TCP KeepAlive,且双方也没有数据交互的情况下,如果其中一方的进程发生了崩溃,这个过程操作系统是可以感知的到的,于是就会发送 FIN 报文给对方,然后与对方进行 TCP 四次挥手。

7、参考资料

[1] TCP/IP详解 - 第21章·TCP的超时与重传

[2] 通俗易懂-深入理解TCP协议(上):理论基础

[3] 网络编程懒人入门(三):快速理解TCP协议一篇就够

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

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

[6] 技术大牛陈硕的分享:由浅入深,网络编程学习经验干货总结

[7] 网络编程入门从未如此简单(二):假如你来设计TCP协议,会怎么做?

[8] 不为人知的网络编程(十):深入操作系统,从内核理解网络包的接收过程(Linux篇)

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

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

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

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

posted @ 2022-03-07 18:15 Jack Jiang 阅读(203) | 评论 (0)编辑 收藏

     摘要: 本文由微信开发团队工程师“ qiuwenchen”分享,发布于WeMobileDev公众号,有修订。1、引言全文搜索是使用倒排索引进行搜索的一种搜索方式。倒排索引也称为反向索引,是指对输入的内容中的每个Token建立一个索引,索引中保存了这个Token在内容中的具体位置。全文搜索技术主要应用在对大量文本内容进行搜索的场景。微信终端涉及到大量文本搜索的业务场景主要包括:im联...  阅读全文

posted @ 2022-02-28 17:48 Jack Jiang 阅读(107) | 评论 (0)编辑 收藏

本文由融云技术团队原创分享,有修订和改动。

1、引言

在视频直播场景中,弹幕交互、与主播的聊天、各种业务指令等等,组成了普通用户与主播之间的互动方式。

从技术的角度来看,这些实时互动手段,底层逻辑都是实时聊天消息或指令的分发,技术架构类比于IM应用的话,那就相当于IM聊天室功能。

本系列文章的上篇《百万人在线的直播间实时聊天消息分发技术实践》主要分享的是消息分发和丢弃策略。本文将主要从高可用、弹性扩缩容、用户管理、消息分发、客户端优化等角度,分享直播间海量聊天消息的架构设计技术难点的实践经验。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

本文是系列文章中的第7篇:

直播系统聊天技术(一):百万在线的美拍直播弹幕系统的实时推送技术实践之路

直播系统聊天技术(二):阿里电商IM消息平台,在群聊、直播场景下的技术实践

直播系统聊天技术(三):微信直播聊天室单房间1500万在线的消息架构演进之路

直播系统聊天技术(四):百度直播的海量用户实时消息系统架构演进实践

直播系统聊天技术(五):微信小游戏直播在Android端的跨进程渲染推流实践

直播系统聊天技术(六):百万人在线的直播间实时聊天消息分发技术实践

直播系统聊天技术(七):直播间海量聊天消息的架构设计难点实践》(* 本文

3、直播间的主要功能和技术特征

如今的视频直播间早已不单纯是视频流媒体技术问题,它还包含了用户可感知的多类型消息发送和管理、用户管理等任务。在万物皆可直播的当下,超大型直播场景屡见不鲜,甚至出现了人数无上限的场景,面对如此海量实时消息和指令的并发挑战,带来的技术难度已非常规手段所能解决。

我们先来归纳一下如今的典型视频直播间,相较于传统直播间所包含的主要功能特征、技术特征等。

丰富的消息类型和进阶功能:

  • 1)可发送文字、语音、图片等传统聊天功能;
  • 2)可实现点赞、礼物等非传统聊天功能的消息类型;
  • 3)可管理内容安全,包括敏感词设置,聊天内容反垃圾处理等。

聊天管理功能:

  • 1)用户管理:包括创建、加入、销毁、禁言、查询、封禁(踢人)等;
  • 2)用户白名单:白名单用户处于被保护状态不会被自动踢出,且发送消息优先级别最高;
  • 3)消息管理:包括消息优先级、消息分发控制等;
  • 4)实时统计及消息路由等能力。

人数上限和行为特征:

  • 1)人数没有上限:一些大型直播场景,如春晚、国庆大阅兵等,直播间累计观看动辄上千万人次,同时观看人数也可达数百万;
  • 2)用户进退行为:用户进出直播间非常频繁,高热度直播间的人员进出秒并发可能上万,这对服务支撑用户上下线以及用户管理的能力提出了非常大的挑战。

海量消息并发:

  • 1)消息并发量大:直播聊天室人数没有明显上限,带来了海量并发消息的问题(一个百万人数的聊天室,消息的上行已是巨量,消息分发量更是几何级上升);
  • 2)消息实时性高:如果服务器只做消息的消峰处理,峰值消息的堆积会造成整体消息延时增大。

针对上述第 2) 点,延时的累积效应会导致消息与直播视频流在时间线上产生偏差,进而影响用户观看直播时互动的实时性。所以,服务器的海量消息快速分发能力十分重要。

4、直播间聊天室的架构设计

高可用系统需要支持服务故障自动转移、服务精准熔断降级、服务治理、服务限流、服务可回滚、服务自动扩容 / 缩容等能力。

以服务高可用为目标的直播间聊天室系统架构如下:

如上图所示,系统架构主要分三层:

  • 1)连接层:主要管理服务跟客户端的长链接;
  • 2)存储层:当前使用的是 Redis,作为二级缓存,主要存储聊天室的信息(比如人员列表、黑白名单、封禁列表等,服务更新或重启时,可以从 Redis 中加载出聊天室的备份信息);
  • 3)业务层:这是整个聊天室的核心,为了实现跨机房容灾,将服务部署在多个可用区,并根据能力和职责,将其分为聊天室服务和消息服务。

聊天室服务和消息服务的具体职责:

  • 1)聊天室服务:主要负责处理管理类请求,比如聊天室人员的进出、封禁 / 禁言、上行消息处理审核等;
  • 2)消息服务:主要缓存本节点需要处理的用户信息以及消息队列信息,并负责聊天室消息的分发。

在海量用户高并发场景下,消息分发能力将决定着系统的性能。以一个百万级用户量的直播间聊天室为例,一条上行消息对应的是百万倍的分发。这种情况下,海量消息的分发,依靠单台服务器是无法实现的。

我们的优化思路是:将一个聊天室的人员分拆到不同的消息服务上,在聊天室服务收到消息后向消息服务扩散,再由消息服务分发给用户。

以百万在线的直播间聊天室为例:假设聊天室消息服务共 200 台,那平均每台消息服务管理 5000 人左右,每台消息服务在分发消息时只需要给落在本台服务器上的用户分发即可。

服务落点的选择逻辑:

  • 1)在聊天室服务中:聊天室的上行信令是依据聊天室 ID 使用一致性哈希算法来选择节点的;
  • 2)在消息服务中:依据用户 ID 使用一致性哈希算法来决定用户具体落在哪个消息服务。

一致性哈希选择的落点相对固定,可以将聊天室的行为汇聚到一个节点上,极大提升服务的缓存命中率。

聊天室人员进出、黑 / 白名单设置以及消息发送时的判断等处理直接访问内存即可,无须每次都访问第三方缓存,从而提高了聊天室的响应速度和分发速度。

最后:Zookeeper 在架构中主要用来做服务发现,各服务实例均注册到 Zookeeper。

5、直播间聊天室的扩缩容能力

5.1 概述

随着直播这种形式被越来越多人接受,直播间聊天室面对人数激增致使服务器压力逐步增大的情况越来越多。所以,在服务压力逐步增大 / 减少的过程中能否进行平滑的扩 / 缩容非常重要。

在服务的自动扩缩容方面,业内提供的方案大体一致:即通过压力测试了解单台服务器的瓶颈点  通过对业务数据的监控来判断是否需要进行扩缩 → 触发设定的条件后报警并自动进行扩缩容。

鉴于直播间聊天室的强业务性,具体执行中应该保证在扩缩容中整体聊天室业务不受影响。

5.2 聊天室服务扩缩容

聊天室服务在进行扩缩容时,我们通过 Redis 来加载成员列表、封禁 / 黑白名单等信息。

需要注意的是:在聊天室进行自动销毁时,需先判断当前聊天室是否应该是本节点的。如果不是,跳过销毁逻辑,避免 Redis 中的数据因为销毁逻辑而丢失。

聊天室服务扩缩容方案细节如下图所示:

5.3 消息服务扩缩容

消息服务在进行扩缩容时,大部分成员需要按照一致性哈希的原则路由到新的消息服务节点上。这个过程会打破当前的人员平衡,并做一次整体的人员转移。

1)在扩容时:我们根据聊天室的活跃程度逐步转移人员。

2)在有消息时:[消息服务会遍历缓存在本节点上的所有用户进行消息的通知拉取,在此过程中判断此用户是否属于这台节点(如果不是,将此用户同步加入到属于他的节点)。

3)在拉消息时:用户在拉取消息时,如果本机缓存列表中没有该用户,消息服务会向聊天室服务发送请求确认此用户是否在聊天室中(如果在则同步加入到消息服务,不在则直接丢掉)。

4)在缩容时:消息服务会从公共 Redis 获得全部成员,并根据落点计算将本节点用户筛选出来并放入用户管理列表中。

6、海量用户的上下线和管理

聊天室服务:管理了所有人员的进出,人员的列表变动也会异步存入 Redis 中。

消息服务:则维护属于自己的聊天室人员,用户在主动加入和退出房间时,需要根据一致性哈希算出落点后同步给对应的消息服务。

聊天室获得消息后:聊天室服务广播给所有聊天室消息服务,由消息服务进行消息的通知拉取。消息服务会检测用户的消息拉取情况,在聊天室活跃的情况下,30s 内人员没有进行拉取或者累计 30 条消息没有拉取,消息服务会判断当前用户已经离线,然后踢出此人,并且同步给聊天室服务对此成员做下线处理。

7、海量聊天消息的分发策略

直播间聊天室服务的消息分发及拉取方案如下图:

7.1 消息通知的拉取

在上图中:用户 A 在聊天室中发送一条消息,首先由聊天室服务处理,聊天室服务将消息同步到各消息服务节点,消息服务向本节点缓存的所有成员下发通知拉取(图中服务器向用户 B 和用户 Z 下发了通知)。

在消息分发过程中,server 做了通知合并。

通知拉取的详细流程为:

  • 1)客户端成功加入聊天,将所有成员加入到待通知队列中(如已存在则更新通知消息时间);
  • 2)下发线程,轮训获取待通知队列;
  • 3)向队列中用户下发通知拉取。

通过这个流程可保障下发线程一轮只会向同一用户发送一个通知拉取(即多个消息会合并为一个通知拉取),有效提升了服务端性能且降低了客户端与服务端的网络消耗。

7.2 消息的拉取

用户的消息拉取流程如下图:

 

如上图所示,用户 B 收到通知后向服务端发送拉取消息请求,该请求最终将由消息节点 1 进行处理,消息节点 1 将根据客户端传递的最后一条消息时间戳,从消息队列中返回消息列表(参考下图 )。

客户端拉取消息示例:

用户端本地最大时间为 1585224100000,从 server 端可以拉取到比这个数大的两条消息。

7.3 消息控速

服务器应对海量消息时,需要做消息的控速处理。

这是因为:在直播间聊天室中,大量用户在同一时段发送的海量消息,一般情况下内容基本相同。如果将所有消息全部分发给客户端,客户端很可能出现卡顿、消息延迟等问题,严重影响用户体验。

所以服务器对消息的上下行都做了限速处理。

消息控速原理:

具体的限速控制策略如下:

  • 1)服务器上行限速控制(丢弃)策略:针对单个聊天室的消息上行的限速控制,我们默认为 200 条 / 秒,可根据业务需要调整。达到限速后发送的消息将在聊天室服务丢弃,不再向各消息服务节点同步;
  • 2)服务器下行限速(丢弃)策略:服务端的下行限速控制,主要是根据消息环形队列的长度进行控制,达到最大值后最“老”的消息将被淘汰丢弃。

每次下发通知拉取后服务端将该用户标记为拉取中,用户实际拉取消息后移除该标记。

如果产生新消息时用户有拉取中标记:

  • 1)距设置标记时间在 2 秒内,则不会下发通知(降低客户端压力,丢弃通知未丢弃消息);
  • 2)超过 2 秒则继续下发通知(连续多次通知未拉取则触发用户踢出策略,不在此赘述)。

因此:消息是否被丢弃取决于客户端拉取速度(受客户端性能、网络影响),客户端及时拉取消息则没有被丢弃的消息。

8、直播间聊天室的消息优先级

消息控速的核心是对消息的取舍,这就需要对消息做优先级划分。

划分逻辑大致如下:

  • 1)白名单消息:这类消息最为重要,级别最高,一般系统类通知或者管理类信息会设置为白名单消息;
  • 2)高优先级消息:仅次于白名单消息,没有特殊设置过的消息都为高优先级;
  • 3)低优先级消息:最低优先级的消息,这类消息大多是一些文字类消息。

具体如何划分,应该是可以开放出方便的接口进行设置的。

服务器对三种消息执行不同的限速策略,在高并发时,低优先级消息被丢弃的概率最大。

服务器将三种消息分别存储在三个消息桶中:客户端在拉取消息时按照白名单消息  高优先级消息  低优先级消息的顺序拉取。

9、客户端针对大量消息的接收和渲染优化

9.1 消息的接收优化

在消息同步机制方面,如果直播间聊天室每收到一条消息都直接下发到客户端,无疑会给客户端带来极大性能挑战。特别是在每秒几千或上万条消息的并发场景下,持续的消息处理会占用客户端有限的资源,影响用户其它方面的互动。

考虑到以上问题,为聊天室单独设计了通知拉取机制,由服务端进行一系列分频限速聚合等控制后,再通知客户端拉取。

具体分为以下几步:

  • 1)客户端成功加入聊天室;
  • 2)服务端下发通知拉取信令;
  • 3)客户端根据本地存储的消息最大时间戳,去服务端拉取消息。

这里需要注意的是:首次加入直播间聊天室时,本地并没有有效时间戳,此时会传 0 给服务拉取最近 50 条消息并存库。后续再次拉取时才会传递数据库里存储的消息的最大时间戳,进行差量拉取。

客户端拉取到消息后:会进行排重处理,然后将排重后的数据上抛业务层,以避免上层重复显示。

另外:直播间聊天室中的消息即时性较强,直播结束或用户退出聊天室后,之前拉取的消息大部分不需要再次查看,因此在用户退出聊天室时,会清除数据库中该聊天室的所有消息,以节约存储空间。

9.2 消息的渲染优化

在消息渲染方面,客户端也通过一系列优化保证在直播间聊天室大量消息刷屏的场景下仍有不俗的表现。

以Andriod端为例,具体的措施有:

  • 1)采用 MVVM 机制:将业务处理和 UI 刷新严格区分。每收到一条消息,都在 ViewModel 的子线程将所有业务处理好,并将页面刷新需要的数据准备完毕后,才通知页面刷新;
  • 2)降低主线程负担:精确使用 LiveData 的 setValue() 和 postValue() 方法:已经在主线程的事件通过  setValue() 方式通知 View 刷新,以避免过多的 postValue() 造成主线程负担过重;
  • 3)减少非必要刷新:比如在消息列表滑动时,并不需要将接收到的新消息刷新出来,仅进行提示即可;
  • 4)识别数据的更新:通过谷歌的数据对比工具 DiffUtil 识别数据是否有更新,仅更新有变更的部分数据;
  • 5)控制全局刷新次数:尽量通过局部刷新进行 UI 更新。

通过以上机制:从压测结果看,在中端手机上,直播间聊天室中每秒 400 条消息时,消息列表仍然表现流畅,没有卡顿。

10、针对传统聊天消息外的自定义属性优化

10.1 概述

在直播间聊天室场景中,除了传统的聊天消息收发以外,业务层经常需要有自己的一些业务属性,如在语音直播聊天室场景中的主播麦位信息、角色管理等,还有狼人杀等卡牌类游戏场景中记录用户的角色和牌局状态等。

相对于传统聊天消息,自定义属性有必达和时效的要求,比如麦位、角色等信息需要实时同步给聊天室的所有成员,然后客户端再根据自定义属性刷新本地的业务。

10.2 自定义属性的存储

自定义属性是以 key 和 value 的形式进行传递和存储的。自定义属性的操作行为主要有两种:即设置和删除。

服务器存储自定义属性也分两部分:

  • 1)全量的自定义属性集合;
  • 2)自定义属性集合变更记录。

自定义属性存储结构如下图所示:

针对这两份数据,应该提供两种查询接口,分别是查询全量数据和查询增量数据。这两种接口的组合应用可以极大提升聊天室服务的属性查询响应和自定义分发能力。

10.3 自定义属性的拉取

内存中的全量数据,主要给从未拉取过自定义属性的成员使用。刚进入聊天室的成员,直接拉取全量自定义属性数据然后展示即可。

对于已经拉取过全量数据的成员来说,若每次都拉取全量数据,客户端想获得本次的修改内容,就需要比对客户端的全量自定义属性与服务器端的全量自定义属性,无论比对行为放在哪一端,都会增加一定的计算压力。

所以:为了实现增量数据的同步,构建一份属性变更记录集合十分必要。这样:大部分成员在收到自定义属性有变更来拉取时,都可以获得增量数据。

属性变更记录采用的是一个有序的 map 集合:key 为变更时间戳,value 里存着变更的类型以及自定义属性内容,这个有序的 map 提供了这段时间内所有的自定义属性的动作。

自定义属性的分发逻辑与消息一致:均为通知拉取。即客户端在收到自定义属性变更拉取的通知后,带着自己本地最大自定义属性的时间戳来拉取。比如:如果客户端传的时间戳为 4,则会拉取到时间戳为 5 和时间戳为 6 的两条记录。客户端拉取到增量内容后在本地进行回放,然后对自己本地的自定义属性进行修改和渲染。

11、多人群聊参考资料

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

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

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

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

[5] 关于IM即时通讯群聊消息的乱序问题讨论

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

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

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

[9] IM群聊机制,除了循环去发消息还有什么方式?如何优化?

[10] 网易云信技术分享:IM中的万人群聊技术方案实践总结

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

[12] IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

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

[14] 融云IM技术分享:万人群聊消息投递方案的思考和实践

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

posted @ 2022-02-23 12:50 Jack Jiang 阅读(133) | 评论 (0)编辑 收藏

本文由cxuan分享,原题“原来这才是 Socket”,有修订。

1、引言

本系列文章前面那些主要讲解的是计算机网络的理论基础,但对于即时通讯IM这方面的应用层开发者来说,跟计算机网络打道的其实是各种API接口。

本篇文章就来聊一下网络应用程序员最熟悉的Socket这个东西,抛开生涩的计算机网络理论,从应用层的角度来理解到底什么是Socket。

对于 Socket 的认识,本文将从以下几个方面着手介绍:

  • 1)Socket 是什么;
  • 2)Socket 是如何创建的;
  • 3)Socket 是如何连接的;
  • 4)Socket 是如何收发数据的;
  • 5)Socket 是如何断开连接的;
  • 6)Socket 套接字的删除等。

特别说明:本文中提到的“Socket”、“网络套接字”、“套接字”,如无特殊指明,指的都是同一个东西哦。

 

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、Socket 是什么

一个数据包经由应用程序产生,进入到协议栈中进行各种报文头的包装,然后操作系统调用网卡驱动程序指挥硬件,把数据发送到对端主机。

整个过程的大体的图示如下:

我们大家知道,协议栈其实是位于操作系统中的一些协议的堆叠,这些协议包括 TCP、UDP、ARP、ICMP、IP等。

通常某个协议的设计都是为了解决特定问题的,比如:

  • 1)TCP 的设计就负责安全可靠的传输数据;
  • 2)UDP 设计就是报文小,传输效率高;
  • 3)ARP 的设计是能够通过 IP 地址查询物理(Mac)地址;
  • 4)ICMP 的设计目的是返回错误报文给主机;
  • 5)IP 设计的目的是为了实现大规模主机的互联互通。

应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输。而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket

在上面这幅图中,应用程序包含 Socket 和解析器,解析器的作用就是向 DNS 服务器发起查询,查询目标 IP 地址(关于DNS请见《理论联系实际,全方位深入理解DNS)。

应用程序的下面:就是操作系统内部,操作系统内部包括协议栈,协议栈是一系列协议的堆叠。

操作系统下面:就是网卡驱动程序,网卡驱动程序负责控制网卡硬件,驱动程序驱动网卡硬件完成收发工作。

在操作系统内部有一块用于存放控制信息的存储空间,这块存储空间记录了用于控制通信的控制信息。其实这些控制信息就是 Socket 的实体,或者说存放控制信息的内存空间就是Socket的实体。

这里大家有可能不太清楚所以然,所以我用了一下 netstat 命令来给大伙看一下Socket是啥玩意。

我们在 Windows 的命令提示符中输入:

netstat-ano

# netstat 用于显示Socket内容 , -ano 是可选选项

# a 不仅显示正在通信的Socket,还显示包括尚未开始通信等状态的所有Socket

# n 显示 IP 地址和端口号

# o 显示Socket的程序 PID

我的计算机会出现下面结果:

如上图所示:

  • 1)每一行都相当于一个Socket;
  • 2)每一列也被称为一个元组。

所以,一个Socket就是五元组:

  • 1)协议;
  • 2)本地地址;
  • 3)外部地址;
  • 4)状态;
  • 5)PID。

PS:有的时候也被叫做四元组,四元组不包括协议。

我们来解读一下上图中的数据,比如图中的第一行:

1)它的协议就是 TCP,本地地址和远程地址都是 0.0.0.0这表示通信还没有开始,IP 地址暂时还未确定)。

2)而本地端口已知是 135,但是远程端口还未知,此时的状态是 LISTENING(LISTENING 表示应用程序已经打开,正在等待与远程主机建立连接。关于各种状态之间的转换,大家可以阅读《通俗易懂-深入理解TCP协议(上):理论基础)。

3)最后一个元组是 PID,即进程标识符,PID 就像我们的身份证号码,能够精确定位唯一的进程。

3、Socket 是如何创建的

通过上节的讲解,现在你可能对 Socket 有了一个基本的认识,先喝口水,休息一下,让我们继续探究 Socket。

现在我有个问题,Socket 是如何创建的呢?

Socket 是和应用程序一起创建的。

应用程序中有一个 socket 组件,在应用程序启动时,会调用 socket 申请创建Socket,协议栈会根据应用程序的申请创建Socket:首先分配一个Socket所需的内存空间,这一步相当于是为控制信息准备一个容器,但只有容器并没有实际作用,所以你还需要向容器中放入控制信息;如果你不申请创建Socket所需要的内存空间,你创建的控制信息也没有地方存放,所以分配内存空间,放入控制信息缺一不可。至此Socket的创建就已经完成了。

Socket创建完成后,会返回一个Socket描述符给应用程序,这个描述符相当于是区分不同Socket的号码牌。根据这个描述符,应用程序在委托协议栈收发数据时就需要提供这个描述符。

4、Socket 是如何连接的

Socket创建完成后,最终还是为数据收发服务的。但是,在数据收发之前,还需要进行一步“连接”(术语就是 connect),建立连接有一整套过程。

这个“连接”并不是真实的连接(用一根水管插在两个电脑之间?不是你想的这样。。。)。

实际上这个“连接”是应用程序通过 TCP/IP 协议标准从一个主机通过网络介质传输到另一个主机的过程。

Socket刚刚创建完成后,还没有数据,也不知道通信对象。

在这种状态下:即使你让客户端应用程序委托协议栈发送数据,它也不知道发送到哪里。所以浏览器需要根据网址来查询服务器的 IP 地址(做这项工作的协议是 DNS),查询到目标主机后,再把目标主机的 IP 告诉协议栈。至此,客户端这边就准备好了。

在服务器上:与客户端一样也需要创建Socket,但是同样的它也不知道通信对象是谁,所以我们需要让客户端向服务器告知客户端的必要信息:IP 地址和端口号

现在通信双方建立连接的必要信息已经具备,可以开始“连接”过程了。

首先:客户端应用程序需要调用 Socket 库中的 connect 方法,提供 socket 描述符和服务器 IP 地址、端口号。

以下是connect的伪码调用:

connect(<描述符>、<服务器IP地址和端口号>)

这些信息会传递给协议栈中的 TCP 模块,TCP 模块会对请求报文进行封装,再传递给 IP 模块,进行 IP 报文头的封装,然后传递给物理层,进行帧头封装。

之后通过网络介质传递给服务器,服务器上会对帧头、IP 模块、TCP 模块的报文头进行解析,从而找到对应的Socket。

Socket收到请求后,会写入相应的信息,并且把状态改为正在连接。

请求过程完成后:服务器的 TCP 模块会返回响应,这个过程和客户端是一样的(如果大家不太清楚报文头的封装过程,可以阅读《快速理解TCP协议一篇就够)。

在一个完整的请求和响应过程中,控制信息起到非常关键的作用:

  • 1)SYN 就是同步的缩写,客户端会首先发送 SYN 数据包,请求服务端建立连接;
  • 2)ACK 就是相应的意思,它是对发送 SYN 数据包的响应;
  • 3)FIN 是终止的意思,它表示客户端/服务器想要终止连接。

由于网络环境的复杂多变,经常会存在数据包丢失的情况,所以双方通信时需要相互确认对方的数据包是否已经到达,而判断的标准就是 ACK 的值。

上面的文字不够生动,动画可以更好的说明这个过程:

PS:这个“连接”的详细理论知识,可以阅读《理论经典:TCP协议的3次握手与4次挥手过程详解》、《跟着动画来学TCP三次握手和四次挥手》,这里不再赘述。

当所有建立连接的报文都能够正常收发之后,此时套接字就已经进入可收发状态了,此时可以认为用一根管理把两个套接字连接了起来。当然,实际上并不存在这个管子。建立连接之后,协议栈的连接操作就结束了,也就是说 connect 已经执行完毕,控制流程被交回给应用程序。

另外:如果你对Socket代码更熟悉的话,可以先读读这篇《手把手教你写基于TCP的Socket长连接》。

5、Socket 是如何收发数据的

当控制流程上节中的连接过程回到应用程序之后,接下来就会直接进入数据收发阶段。

数据收发操作是从应用程序调用 write 将要发送的数据交给协议栈开始的,协议栈收到数据之后执行发送操作。

协议栈不会关心应用程序传输过来的是什么数据,因为这些数据最终都会转换为二进制序列,协议栈在收到数据之后并不会马上把数据发送出去,而是会将数据放在发送缓冲区,再等待应用程序发送下一条数据。

为什么收到数据包不会直接发送出去,而是放在缓冲区中呢?

因为只要一旦收到数据就会发送,就有可能发送大量的小数据包,导致网络效率下降(所以协议栈需要将数据积攒到一定数量才能将其发送出去)。

至于协议栈会向缓冲区放多少数据,这个不同版本和种类的操作系统有不同的说法。

不过,所有的操作系统都会遵循下面这几个标准:

1)第一个判断要素:是每个网络包能够容纳的数据长度,判断的标准是 MTU,它表示的是一个网络包的最大长度。最大长度包含头部,所以如果单论数据区的话,就会用 MTU - 包头长度,由此的出来的最大数据长度被称为 MSS。

 

2)另一个判断标准:是时间,当应用程序产生的数据比较少,协议栈向缓冲区放置数据效率不高时,如果每次都等到 MSS 再发送的话,可能因为等待时间太长造成延迟。在这种情况下,即使数据长度没有到达 MSS,也应该把数据发送出去。

但协议栈并没有告诉我们怎样平衡这两个因素,如果数据长度优先,那么效率有可能比较低;如果时间优先,那又会降低网络的效率。

经过了一段时间。。。。。。

假设我们使用的是长度有限法则:此时缓冲区已满,协议栈要发送数据了,协议栈刚要把数据发送出去,却发现无法一次性传输这么大数据量(相对的)的数据,那怎么办呢?

在这种情况下,发送缓冲区中的数据就会超过 MSS 的长度,发送缓冲区中的数据会以 MSS 大小为一个数据包进行拆分,拆分出来的每块数据都会加上 TCP,IP,以太网头部,然后被放进单独的网络包中。

到现在,网络包已经准备好发往服务器了,但是数据发送操作还没有结束,因为服务器还未确认是否已经收到网络包。因此在客户端发送数据包之后,还需要服务器进行确认。

TCP 模块在拆分数据时,会计算出网络包偏移量,这个偏移量就是相对于数据从头开始计算的第几个字节,并将算好的字节数写在 TCP 头部,TCP 模块还会生成一个网络包的序号(SYN),这个序号是唯一的,这个序号就是用来让服务器进行确认的。

服务器会对客户端发送过来的数据包进行确认,确认无误之后,服务器会生成一个序号和确认号(ACK)并一起发送给客户端,客户端确认之后再发送确认号给服务器。

我们来看一下实际的工作过程:

首先:客户端在连接时需要计算出序号初始值,并将这个值发送给服务器。

接下来:服务器通过这个初始值计算出确认号并返回给客户端(初始值在通信过程中有可能会丢弃,因此当服务器收到初始值后需要返回确认号用于确认)。

同时:服务器也需要计算出从服务器到客户端方向的序号初始值,并将这个值发送给客户端。然后,客户端也需要根据服务器发来的初始值计算出确认号发送给服务器。

至此:连接建立完成,接下来就可以进入数据收发阶段了。

数据收发阶段中,通信双方可以同时发送请求和响应,双方也可以同时对请求进行确认。

请求 - 确认机制非常强大:通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,但凡网络中出现的任何错误,我们都可以即使发现并补救。

上面的文字不够生动,动画可以更好的理解请求 - 确认机制:

网卡、集线器、路由器(见《史上最通俗的集线器、交换机、路由器功能原理入门)都没有错误补救机制,一旦检测到错误就会直接丢弃数据包,应用程序也没有这种机制,起作用的只是 TCP/IP 模块。

由于网络环境复杂多变,所以数据包会存在丢失情况,因此发送序号和确认号也存在一定规则,TCP 会通过窗口管理确认号,我们这篇文章不再赘述,大家可以阅读《通俗易懂-深入理解TCP协议(下):RTT、滑动窗口、拥塞处理》来寻找答案。

PS:另一篇《我们在读写Socket时,究竟在读写什么?》中用动画详细说明了这个过程,有兴趣可以读一读。

6、Socket 是如何断开连接的

当通信双方不再需要收发数据时,需要断开连接。不同的应用程序断开连接的时机不同。

以 Web 为例:浏览器向 Web 服务器发送请求消息,Web 服务器再返回响应消息,这时收发数据就全部结束了,服务器可能会首先发起断开响应,当然客户端也有可能会首先发起(谁先断开连接是应用程序做出的判断),与协议栈无关。

无论哪一方发起断开连接的请求,都会调用 Socket 库的 close 程序。

我们以服务器断开连接为例:服务器发起断开连接请求,协议栈会生成断开连接的 TCP 头部,其实就是设置 FIN 位,然后委托 IP 模块向客户端发送数据,与此同时,服务器的Socket会记录下断开连接的相关信息。

收到服务器发来 FIN 请求后:客户端协议栈会将Socket标记为断开连接状态,然后,客户端会向服务器返回一个确认号,这是断开连接的第一步,在这一步之后,应用程序还会调用 read 来读取数据。等到服务器数据发送完成后,协议栈会通知客户端应用程序数据已经接收完毕。

只要收到服务器返回的所有数据,客户端就会调用 close 程序来结束收发操作,这时客户端会生成一个 FIN 发送给服务器,一段时间后服务器返回 ACK 号。至此,客户端和服务器的通信就结束了。

上面的文字不够生动,动画可以更好的说明这个过程:

PS:断开连接的详细理论知识,可以阅读《理论经典:TCP协议的3次握手与4次挥手过程详解》、《跟着动画来学TCP三次握手和四次挥手》,这里不再赘述。

7、Socket的删除

上述通信过程完成后,用来通信的Socket就不再会使用了,此时我们就可以删除这个Socket了。

不过,这时候Socket不会马上删除,而是等过一段时间再删除。

等待这段时间是为了防止误操作,最常见的误操作就是客户端返回的确认号丢失,至于等待多长时间,和数据包重传的方式有关,这里我们就深入展开讨论了。

关于Socket操作的全过程,如果从系统的角度来看,可能会更深入一些,建议可以深入阅读张彦飞的《深入操作系统,从内核理解网络包的接收过程(Linux篇)》一文。

8、系列文章

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

[1] 网络编程懒人入门(一):快速理解网络通信协议(上篇)

[2] 网络编程懒人入门(二):快速理解网络通信协议(下篇)

[3] 网络编程懒人入门(三):快速理解TCP协议一篇就够

[4] 网络编程懒人入门(四):快速理解TCP和UDP的差异

[5] 网络编程懒人入门(五):快速理解为什么说UDP有时比TCP更有优势

[6] 网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门

[7] 网络编程懒人入门(七):深入浅出,全面理解HTTP协议

[8] 网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

[9] 网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?

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

[11] 网络编程懒人入门(十一):一文读懂什么是IPv6

[12] 网络编程懒人入门(十二):快速读懂Http/3协议,一篇就够!

[13] 网络编程懒人入门(十三):一泡尿的时间,快速搞懂TCP和UDP的区别

[14] 网络编程懒人入门(十四):到底什么是Socket?一文即懂!* 本文)

9、参考资料

[1] TCP/IP详解 - 第17章·TCP:传输控制协议

[2] TCP/IP详解 - 第18章·TCP连接的建立与终止

[3] TCP/IP详解 - 第21章·TCP的超时与重传

[4] 快速理解网络通信协议(上篇)

[5] 快速理解网络通信协议(下篇)

[6] 面视必备,史上最通俗计算机网络分层详解

[7] 假如你来设计网络,会怎么做?

[8] 假如你来设计TCP协议,会怎么做?

[10] 浅析TCP协议中的疑难杂症(下篇)

[11] 关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT

[12] 从底层入手,深度分析TCP连接耗时的秘密

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

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

本文原题“搭建高性能的IM系统”,作者“刘莅”,内容有修订和改动。为了尊重原创,如需转载,请联系作者获得授权。

1、引言

相信很多朋友对微信、QQ等聊天软件的实现原理都非常感兴趣,笔者同样对这些软件有着深厚的兴趣。而且笔者在公司也是做IM的,公司的IM每天承载着上亿条消息的发送!

正好有这样的技术资源和条件,所以前段时间,笔者利用业余时间,基于Netty开发了一套基本功能比较完善的IM系统。该系统支持私聊、群聊、会话管理、心跳检测,支持服务注册、负载均衡,支持任意节点水平扩容。

这段时间,网上的一些读者,也希望笔者分享一些Netty或者IM相关的知识,所以今天笔者把开发的这套IM系统分享给大家。

本文将根据笔者这次的业余技术实践,为你讲述如何基于Netty+Zk+Redis来搭建一套高性能IM集群,包括本次实现IM集群的技术原理和实例代码,希望能带给你启发。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、本文源码

主地址:https://github.com/nicoliuli/chat

备地址:https://github.com/52im/chat

源码的目录结构,如下图所示:

3、知识准备

* 重要提示:本文不是一篇即时通讯理论文章,文章内容来自代码实战,如果你对即时通讯(IM)技术理论了解的太少,建议先详细阅读:《新手入门一篇就够:从零开发移动端IM》。

可能有人不知道 Netty 是什么,这里简单介绍下:

Netty 是一个 Java 开源框架。Netty 提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于 NIO 的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。

Netty 相当简化和流线化了网络应用的编程开发过程,例如,TCP 和 UDP 的 Socket 服务开发。

以下是有关Netty的入门文章:

如果你连Java的NIO都不知道是什么,下面的文章建议优先读:

Netty源码和API的在线查阅地址:

4、系统架构

系统的架构如上图所示:整个系统是一个C/S系统,客户端没有做复杂的图形化界面而是用Java终端开发的(黑窗口),服务端IM实例是Netty写的socket服务。

ZK作为服务注册中心,Redis用来做分布式会话的缓存,并保存用户信息和轻量级的消息队列。

对于整个系统架构中各部分的工作原理,我们将在接下来的各章节中一一介绍。

5、服务端的工作原理

在上述架构中:NettyServer启动,每启动一台Server节点,都会把自身的节点信息,如:ip、port等信息注册到ZK上(临时节点)。

正如上节架构图上启动了两台NettyServer,所以ZK上会保存两个Server的信息。

同时ZK将监听每台Server节点,如果Server宕机ZK就会删除当前机器所注册的信息(把临时节点删除),这样就完成了简单的服务注册的功能。

6、客户端的工作原理

Client启动时,会先从ZK上随机选择一个可用的NettyServer(随机表示可以实现负载均衡),拿到NettyServer的信息(IP和port)后与NettyServer建立链接。

链接建立起来后,NettyServer端会生成一个Session(即会话),用来把当前客户端的Channel等信息组装成一个Session对象,保存在一个SessionMap里,同时也会把这个Session保存在Redis中。

这个会话特别重要,通过会话,我们能获取当前Client和NettyServer的Channel等信息。

7、Session的作用

我们启动多个Client,由于每个Client启动,都会先从ZK上随机获取NettyServer的的信息,所以如果启动多个Client,就会连接到不同的NettyServer上。

熟悉Netty的朋友都知道,Client与Server建立接连后会产生一个Channel,通过Channel,Client和Server才能进行正常的网络数据传输。

如果Client1和Client2连接在同一个Server上:那么Server通过SessionMap分别拿到Client1和Client2的会话,会话中包含Channel信息,有了两个Client的Channel,Client1和Client2便可完成消息通信。

如果Client1和Client2连接到不同的NettyServer上:Client1和Client2要进行通信,该怎么办?这个问题放在后面解答。

8、高效的数据传输

无论是IM系统,还是分布式的RPC框架,高效的网络数据传输,无疑会极大的提升系统的性能。

数据通过网络传输时,一般把对象通序列化成二进制字节流数组,然后将数据通过socket传给对方服务器,对方服务器拿到二进制字节流后再反序列化成对象,达到远程通信的目的。

在Java领域,Java序列化对象的方式有严重的性能问题,业界常用谷歌的protobuf来实现序列化反序列化(见《Protobuf通信协议详解:代码演示、详细原理介绍等)。

protobuf支持不同的编程语言,可以实现跨语言的系统调用,并且有着极高的序列化反序列化性能,本系统也采用protobuf来做数据的序列化。

关于Protobuf的基本认之,下面这几篇可以深入读一读:

  1. 强列建议将Protobuf作为你的即时通讯应用数据传输格式
  2. 全方位评测:Protobuf性能到底有没有比JSON快5倍?
  3. 金蝶随手记团队分享:还在用JSON? Protobuf让数据传输更省更快(原理篇)

另外:一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》一文中,“3、协议设计”这一节有关于protobuf在IM中的实战设计和使用,可以一并学习一下。

9、聊天协议定义

我们在使用各种聊天APP时,会发各种各样的消息,每种消息都会对应不同的消息格式(即“聊天协议”)。

聊天协议中主要包含几种重要的信息:

  • 1)消息类型;
  • 2)发送时间;
  • 3)消息的收发人;
  • 4)聊天类型(群聊或私聊)。

我的这套IM系统中,聊天协议定义如下:

syntax = "proto3";

option java_package = "model.chat";

option java_outer_classname = "RpcMsg";

message Msg{

    string msg_id = 1;

    int64 from_uid = 2;

    int64 to_uid = 3;

    int32 format = 4;

    int32 msg_type = 5;

    int32 chat_type = 6;

    int64 timestamp = 7;

    string body = 8;

    repeated int64 to_uid_list = 9;

}

如上面的protobuf代码,字段的具体含义如下:

  • 1)msg_id:表示消息的唯一id,可以用UUID表示;
  • 2)from_uid:消息发送者的uid;
  • 3)to_uid:消息接收者的uid;
  • 4)format:消息格式,我们使用各种聊天软件时,会发送文字消息,语音消息,图片消息等等等等,每种消息有不同的消息格式,我们用format来表示(由于本系统是java终端,format字段没有太大含义,可有可无);
  • 5)msg_type:消息类型,比如登录消息、聊天消息、ack消息、ping、pong消息;
  • 6)chat_type:聊天类型,如群聊、私聊;
  • 7)timestamp:发送消息的时间戳;
  • 8)body:消息的具体内容,载体;
  • 9)to_uid_list:这个字段用户群聊消息提高群聊消息的性能,具体作用会在群聊原理部分详细解释。

10、私聊消息发送原理

Client1给Client2发消息时,我们需要构建上节中的消息体。

具体就是:from_uid是Client1的uid、to_uid是Client2的uid。

NettyServer收到消息后的处理逻辑是:

  • 1)解析到to_uid字段;
  • 2)从SessionMap或者Redis中保存的Session集合中获取to_uid即Client2的Session;
  • 3)从Session中取出Client2的Channel;
  • 4)然后将消息通过Client2的Channel发给Client2。

11、群聊消息发送原理

群聊消息的分发通常有两种技术实现方式,我们一一来看看。

方式一:假设一个群有100人,如果Client1给一个群的所有人发消息,其实相当于Client1分别给其余99人分别发一条消息。我们可以直接在Client端,通过循环,分别给群里的99人发消息即可,相当于Client发送给NettyServer发送了99次相同的消息(除了to_uid不同)。

上述方案有很严重的性能问题:Client1通过循环99次,分别把消息发给NettyServer,NettyServer收到这99条消息后,分别将消息发给群内其余的用户。先抛开移动端的特殊性(比如循环还没完成手机就有可能退到后台被系统挂起),显然Client1到NettyServer的99次循环存在明显不合理地方。

方式二:上节的消息体中to_uid_list字段就是为了解决这个方式一的性能问题的。Client1把群内其余99个Client的uid保存在to_uid_list中,然后NettyServer只发一条消息,NettyServer收到这一条消息后,通过to_uid_list字段解析群内其余99的Client的uid,再通过循环把消息分别发送给群内其余的Client。

可以看到:方式二的群聊时,Client1与NettyServer只进行1次消息传输,相比于方式一,效率提高了50%。

11、技术关键点1:客户端分别连接在不同IM实例时如何通信?

针对本文中的架构,如果多个Client分别连接在不同的Server上,Client之间应该如何通信呢?

为了回答这个问题,我们首先要明白Session的作用。

我们做过JavaWeb开发的朋友都知道,Session用来保存用户的登录信息。

在IM系统中也是如此:Session中保存用户的Channel信息。当Client与Server建立链接成功后,会产生一个Channel,Client和Server是通过Channel,实现数据传输。当两端链接建立起来后,Server会构建出一个Session对象,保存uid和Channel等信息,并把这个Session保存在一个SessionMap里(NettyServer的内存里),uid为key,我们可以通过uid就可以找到这个uid对应的Session。

但只有SessionMap还不够:我们需要利用Redis,它的作用是保存整个NettyServer集群全部链接成功的用户,这也是一种Session,但这种Session没有保存uid和Channel的对应关系,而是保存Client链接到NettyServer的信息,如Client链接到的这个NettyServer的ip、port等。通过uid,我们同样可以从Redis中拿到当前Client链接到的NettyServer的信息。正是有了这个信息,我们才能做到,NettyServer集群任意节点水平扩容。

当用户量少的时候:我们只需要一台NettyServer节点便可以扛住流量,所有的Client链接到同一个NettyServer上,并在NettyServer的SessionMap中保存每个Client的会话。Client1与Client2通信时,Client1把消息发给NettyServer,NettyServer从SessionMap中取出Client2的Session和Channel,将消息发给Client2。

随着用户量不断增多:一台NettyServer不够,我们增加了几台NettyServer,这时Client1链接到NettyServer1上并在SessionMap和Redis中保存了会话和Client1的链接信息,Client2链接到NettyServer2上并在SessionMap和Redis中保存了会话和Client2的链接信息。Client1给Client2发消息时,通过NettyServer1的SessionMap找不到Client2的会话,消息无法发送,于是便从Redis中获取Client2链接在哪台NettyServer上。获取到Client2所链接的NettyServer信息后,我们可以把消息转发给NettyServer2,NettyServer2收到消息后,从NettyServer2的SessionMap中获取Client2的Session和Channel,然后将消息发送给Client2。

那么:NettyServer1的消息如何转发给NettyServer2呢?答案是通过消息队列,如Redis中的list数据结构。每台NettyServer启动后都需要监听一个自己的Redis中的消息队列,这个队列用户接收其他NettyServer转发给当前NettyServer的消息。

* Jack Jiang点评:上述集群方案中,Redis既作为在线用户列表存储中心,又作为集群中不同IM长连接实例的消息中转服务(此时的Redis作用相当于MQ),那Redis不就成为了整个分布式集群的单点瓶颈了吗?

12、技术关键点2:链接断开,如何处理?

如果Client与NettyServer,由于某种原因(客户端退出、服务端重启、网络因素等)断开链接,我们必须要从SessionMap删除会话和Redis中保留的数据。

如果不清除这两类数据的话,很有可能Client1发送给Client2的消息,可能会发给其他用户,或者就算Client2处于登录状态,Client2也收到不到消息。

我们可以在Netty框架中的channelInactive方法里,处理链接断开后的会话清除操作。

13、技术关键点3:ping、pong的作用

当Client与NettyServer建立链接后,由于双端网络较差,Client与NettyServer断开链接后,如果NettyServer没有感知到,也就没有清除SessionMap和Redis中的数据,这将会造成严重的问题(对于服务端来说,这个Client的会话实际处于“假死”状态,消息是无法实时发送过去的)。

此时就需要一种ping/pong机制(也就是心跳机制啦)。

实现原理就是:通过定时任务,Client每隔一段时间给NettyServer发一个ping消息,NettyServer收到ping消息后给客户端回复一个pong消息,确保客户端和服务端能一直保持链接状态。如果Client与NettyServer断连了,NettyServer可以立即发现并清空会话数据。Netty中的我们可以在Pipeline中添加IdleStateHandler,可达到这样的目的。

如果你不明白心跳的作用,务必读以下文章:

  1. 为何基于TCP协议的移动端IM仍然需要心跳保活机制?
  2. 一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等

也可以学习一下主流IM的心跳逻辑:

  1. 微信团队原创分享:Android版微信后台保活实战分享(进程保活篇)
  2. 微信团队原创分享:Android版微信后台保活实战分享(网络保活篇)
  3. 移动端IM实践:实现Android版微信的智能心跳机制
  4. 移动端IM实践:WhatsApp、Line、微信的心跳策略分析

如果觉得理论不够直观,下面的代码实例可以直观地进行学习:

  1. 正确理解IM长连接的心跳及重连机制,并动手实现(有完整IM源码)
  2. 一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)
  3. 自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)
  4. 手把手教你用Netty实现网络通信程序的心跳机制、断线重连机制

其实,心跳算法的实际效果,还是有一些逻辑技巧的,以下两篇建议必读:

  1. Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?
  2. 融云技术分享:融云安卓端IM产品的网络链路保活技术实践

14、技术关键点4:为Server和Client添加Hook

如果NettyServer重启了或者进程被kill掉,我们需要清除当前节点的SessionMap(其实不用清理SessionMap,数据在内存里重启会自动删除的)和Redis保存的Client的链接信息。

我们需要遍历SessionMap找出所有的uid,然后一一清除Redis的数据,然后优雅退出。此时,我们就需要为我们的NettyServer添加一个Hook,来做数据清理。

15、技术关键点5:对方不在线该如何处理消息?

Client1给对方发消息,我们通过SessionMap或Redis拿不到对方的会话数据,这就表明对方不在线。

此时:我们需要把消息存储在离线消息表中,当对方下次登录时,NettyServer查离线消息表,把消息发给登录用户(最好是批量发送,提高性能)。

IM中的离线消息处理,也不是个简单的技术点,有兴趣可以深入学习一下:

  1. IM消息送达保证机制实现(二):保证离线消息的可靠投递
  2. 阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化
  3. IM开发干货分享:我是如何解决大量离线消息导致客户端卡顿的
  4. IM开发干货分享:如何优雅的实现大量离线消息的可靠投递
  5. 喜马拉雅亿级用户量的离线消息推送系统架构设计实践

16、写在最后

代码写成这样,也算是了确了自已手撸IM的心愿。唯一遗憾的是,时间比较紧张,还没来得及实现消息ack机制,保证消息一定会送达,这个笔者以后会补充上去的。

好了,这就是我开发的这个简易的聊天系统,麻雀虽小,五脏俱全,大家有什么不明白的地方,可以直接在下方留言,笔者会一一回复的,谢谢大家。

17、系列文章

18、参考资料

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

[2] 写给初学者:Java高性能NIO框架Netty的学习方法和进阶策略

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

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

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

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

[7] 浅谈IM系统的架构设计

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

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

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

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

[12] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[13] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[14] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[15] 基于实践:一套百万消息量小规模IM系统技术要点总结

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

posted @ 2022-01-18 16:45 Jack Jiang 阅读(170) | 评论 (0)编辑 收藏

关于RainbowChat-Web

► RainbowChat-Web详细介绍:http://www.52im.net/thread-2483-1-1.html
► 历史版本更新记录:http://www.52im.net/thread-2480-1-1.html

RainbowChat-Web是一套基于MobileIMSDK-Web的网页端IM系统。

▲ 主界面更多截图点此进入更多演示视频点此进入

▲ 主界面(聊天窗全屏时)更多截图点此进入更多演示视频点此进入

v4.0 版更新内容

此版更新内容:

  • 1)[前端] [新增增加了消息“撤回”功能,体验与微信保持一致(支持3种聊天模式,包含完整的前后端处理逻辑);
  • 2)[前端] [新增增加了删除聊天消息功能
  • 3)[前端] [新增增加了设置好友备注(及附属字段)的功能;
  • 4)[前端/服务端] [优化] 升级MobileIMSDK-Web库至v5.0版(支持完整消息送达保证机制);
  • 5)[前端] [优化] 将原UI各模块代码按js文件分拆,使得代码更易理解和阅读;
  • 6)[前端] [优化] 增强了表情功能,且可与APP产品互通;

增强版的表情功能效果截图:


消息“撤回”功能效果截图:

 

消息气泡右键功能效果截图:

设置好友备注功能效果截图:

posted @ 2022-01-15 22:54 Jack Jiang 阅读(50) | 评论 (0)编辑 收藏

     摘要: 本文由ELab技术团队分享,原题“浅谈WebRTC技术原理与应用”,有修订和改动。1、基本介绍WebRTC(全称 Web Real-Time Communication),即网页即时通信。 是一个支持网页浏览器进行实时语音对话或视频对话的技术方案。从前端技术开发的视角来看,是一组可调用的API标准。 在WebRTC发布之前,开发实时音视频交互应用的成本是非常昂贵,...  阅读全文

posted @ 2022-01-10 16:19 Jack Jiang 阅读(129) | 评论 (0)编辑 收藏

本文由融云技术团队原创分享,原题“聊天室海量消息分发之消息丢弃策略”,内容有修订。

1、引言

随着直播类应用的普及,尤其直播带货概念的风靡,大用户量的直播间场景已然常态化。

大用户量直播间中的实时互动是非常频繁的,具体体现在技术上就是各种用户聊天、弹幕、礼物、点赞、禁言、系统通知等实时消息(就像下图这样)。

▲ 某电商APP的卖货直播间

如此大量的实时消息,在分发时如何处理才能不至于把服务端搞垮,而到了客户端时也不至于让APP出现疯狂刷屏和卡顿(不至于影响用户体验),这显然需要特殊的技术手段和实现策略才能应对。

其实,直播间中的实时消息分发,在技术上是跟传统的在线聊天室这种概念是一样的,只是传统互联网时代,聊天室同时在线的用户量不会这么大而已,虽然量级不同,但技术模型是完全可以套用的。

本文将基于直播技术实践的背景,分享了单直播间百万用户在线量的实时消息分发的技术经验总结,希望带给你启发。

学习交流:

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

本文是系列文章中的第6篇:

3、技术挑战

我们以一个百万人观看的直播间为例进行分析,看看需要面临哪些技术挑战。

1)在直播中会有一波一波的消息高峰,比如直播中的“刷屏”消息,即大量用户在同一时段发送的海量实时消息,一般情况下此类“刷屏”消息的消息内容基本相同。如果将所有消息全部展示在客户端,则客户端很可能出现卡顿、消息延迟等问题,严重影响用户体验。

2)海量消息的情况下,如果服务端每条消息都长期存储将导致服务缓存使用量激增,使得内存、存储成为性能瓶颈。

3)在另外一些场景下,比如直播间的房间管理员进行操作后的通知消息或者系统通知,一般情况下这类消息是较为重要的,如何优先保障它的到达率。

基于这些挑战,我们的服务需要做一个基于业务场景的优化来应对。

4、架构模型

我们的架构模型图如下:

 

如上图所示,下面将针对主要服务进行简要说明。

1)直播间服务:

主要作用是:缓存直播间的基本信息。包括用户列表、禁言/封禁关系、白名单用户等。

2)消息服务:

主要作用是:缓存本节点需要处理的用户关系信息、消息队列信息等。

具体说是以下两个主要事情。

直播间用户关系同步:

  • a)成员主动加入退出时:直播间服务同步至==> 消息服务;
  • b)分发消息发现用户已离线时:消息服务同步至==> 直播间服务。

发送消息:   

  • a)直播间服务经过必要校验通过后将消息广播至消息服务;
  • b)直播间服务不缓存消息内容。

3)Zk(就是 Zookeeper 啦):

主要作用就是:将各服务实例均注册到 Zk,数据用于服务间流转时的落点计算。

具体就是:

  • a)直播间服务:按照直播间 ID 落点;
  • b)消息服务:按照用户 ID 落点。

4)Redis:

主要作为二级缓存,以及服务更新(重启)时内存数据的备份。

5、消息分发总体方案

直播间服务的消息分发完整逻辑主要包括:消息分发流程和消息拉取流程。

5.1 消息分发流程

如上图所示,我们的消息分发流程主要是以下几步:

  • 1)用户 A 在直播间中发送一条消息,首先由直播间服务处理;
  • 2)直播间服务将消息同步到各消息服务节点;
  • 3)消息服务向本节点缓存的所有成员下发通知拉取;
  • 4)如上图中的“消息服务-1”,将向用户 B 下发通知。

另外,因为消息量过大,我们在在分发的过程中,是具有通知合并机制的,通知合并机制主要提现在上述步骤 3 中。

上述步骤3的通知合并机制原理如下:

  • a)将所有成员加入到待通知队列中(如已存在则更新通知消息时间);
  • b)下发线程,轮训获取待通知队列;
  • c)向队列中用户下发通知拉取。

通过通知合并机制,我们可以可保障下发线程一轮只会向同一用户发送一个通知拉取,即多个消息会合并为一个通知拉取,从面有效提升了服务端性能且降低了客户端与服务端的网络消耗。

PS:以上通知合并机制,在大消息量的情况下,非常适合使用Actor分布式算法来实现,有兴趣的同学可以进一步学习《分布式高并发下Actor模型如此优秀》、《分布式计算技术之Actor计算模式》。

5.2 消息拉取流程

 

如上图所示,我们的消息拉取流程主要是以下几步:

  • 1)用户 B 收到通知后将向服务端发送拉取消息请求;
  • 2)该请求将由“消息服务-1”节点处理;
  • 3)“消息服务-1”将根据客户端传递的最后一条消息时间戳,从消息队列中返回消息列表(原理详见下图 ▼);
  • 4)用户 B 获取到新的消息。

上述步骤 3 中拉取消息的具体逻辑如下图所示:

6、消息分发的丢弃策略

对于直播间中的用户来说,很多消息其实并没有太多实际意义,比如大量重复的刷屏消息和动态通知等等,为了提升用户体验,这类消息是可以有策略地进行丢弃的(这是跟IM中的实时聊天消息最大的不同,IM中是不允许丢消息的)。

PS:直播间中消息分发的丢弃策略,跟上节中的通知合并机制一起,使得直接间海量消息的稳定、流畅分发得以成为可能。

我们的丢弃策略主要由以下3部分组成:

  • 1)上行限速控制(丢弃)策略;
  • 2)下行限速控制(丢弃)策略;
  • 3)重要消息防丢弃策略。

如下图所示:

我们来逐个解释一下。

1)上行限速控制(丢弃)策略:

针对上行的限速控制,我们默认是 200 条/秒,根据业务需要可调整。达到限速后发送的消息将在直播间服务丢弃,不再向各消息服务节点同步。

2)下行限速控制(丢弃)策略:

针对下行的限速控制,即对消息环形队列(见“5.2 消息拉取流程”中的拉取消息详细逻辑图)长度的控制,达到最大值后最“老”的消息将被淘汰丢弃。

每次下发通知拉取后服务端将该用户标记为“拉取中”,用户实际拉取消息后移除该标记。

拉取中标记的作用:例如产生新消息时用户具有拉取中标记,如果距设置标记时间在 2 秒内则不会下发通知(降低客户端压力,丢弃通知未丢弃消息),超过 2 秒则继续下发通知(连续多次通知未拉取则触发用户踢出策略,不在此赘述)。

因此消息是否被丢弃取决于客户端拉取速度(受客户端性能、网络影响),客户端及时拉取消息则没有被丢弃的消息。

3)重要消息防丢弃策略:

如前文所述:在直播间场景下对某些消息应具有较高优先级,不应丢弃。

例如:直播间的房间管理员进行操作后的通知消息或者系统通知。

针对此场景:我们设置了消息白名单、消息优先级的概念,保障不丢弃。如本节开始的图所示,消息环形队列可以为多个,与普通直播间消息分开则保障了重要消息不丢弃。

通过上述“1)上行限速控制(丢弃)策略”和“下行限速控制(丢弃)策略”保障了:

  • 1)客户端不会因为海量消息出现卡顿、延迟等问题;
  • 2)避免出现消息刷屏,肉眼无法查看的情况;
  • 3)同时降低了服务端存储压力,不会因为海量消息出现内存瓶颈从而影响服务。

7、写在最后

随着移动互联网的发展,直播间的实时消息业务模型和压力也在不停地扩展变化,后续可能还会遇到更多的挑战,我们的服务会与时俱进、跟进更优的方案策略进行应对。

附录:多人群聊天技术文章

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

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

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

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

[5]《关于IM即时通讯群聊消息的乱序问题讨论

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

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

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

[9]《IM群聊机制,除了循环去发消息还有什么方式?如何优化?

[10]《网易云信技术分享:IM中的万人群聊技术方案实践总结

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

[12]《IM群聊消息的已读未读功能在存储空间方面的实现思路探讨

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

[14]《融云IM技术分享:万人群聊消息投递方案的思考和实践

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

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

     摘要: 本文引用了作者Fundebug的“一文搞懂TCP与UDP的区别”一文的内容,感谢无私分享。1、引言网络协议是每个搞网络通信应用开发(比如IM、推送、网关等等)的程序员都必须要掌握的基础知识,TCP/IP协议簇中有两个最具有代表性的传输层协议——分别是 TCP 和 UDP。有过网络通信开发经验的同学们都知道,TCP和UDP协议是平时用的最多的两种协议,...  阅读全文

posted @ 2021-12-29 15:01 Jack Jiang 阅读(110) | 评论 (0)编辑 收藏

     摘要: 本文作者小傅哥,原题“使用DDD+Netty,开发一个分布式IM(即时通信)系统”。为了提升阅读体验,有大量修订和改动,感谢原作者。0、系列文章《跟着源码学IM(一):手把手教你用Netty实现心跳机制、断线重连机制》《跟着源码学IM(二):自已开发IM很难?手把手教你撸一个Andriod版IM》《跟着源码学IM(三):基于Netty,从零开发一个IM服务端》《跟着源码学I...  阅读全文

posted @ 2021-12-20 18:21 Jack Jiang 阅读(104) | 评论 (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.1.2更新内容 

【重要说明】:

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

【解决的Bug】:

  1. [Andior/iOS]解决了当网络断线后,重传队列中的包不增加重次数从而一直重传的问题;
  2. [iOS] 解决了RMMapper库中,因重写父类copyWithZone方法而导致某些工程里的动画效果不生效的问题!

【其它优化和提升】:

  1. [Andiord]Andriod端Demo中补全了完整的proguard混淆配置,否则真有人对Demo进行“realease”时,会运行报错哦;
  2. [iOS] 上一个版本中的Protocal类中忘记补上“sm”字段,现在补上了;
  3. [服务端] 服务端Demo同步为最新工程,之前提交的版本未正确合并最新lib等;
  4. [服务端] 升级log4j2至2.15.0,解决Log4j2远程代码执行高危漏洞;
  5. [Andiord]Andriod端SDK和Demo工程的targetSdkVersion提升为30;
  6. [Andriod]Andriod端TCP版协议Netty库加载方式改为gradle加载;

【版本地址】:

https://gitee.com/jackjiang/MobileIMSDK/releases/6.1.2

posted @ 2021-12-16 22:07 Jack Jiang 阅读(94) | 评论 (0)编辑 收藏

     摘要: 本文由探探服务端高级技术专家张凯宏分享,原题“探探长链接项目的Go语言实践”,因原文内容有较多错误,有修订和改动。1、引言即时通信长连接服务处于网络接入层,这个领域非常适合用Go语言发挥其多协程并行、异步IO的特点。探探自长连接项目上线以后,对服务进行了多次优化:GC从5ms降到100微秒(Go版本均为1.9以上),主要gRPC接口调用延时p999从300ms下降到5ms。...  阅读全文

posted @ 2021-12-14 14:55 Jack Jiang 阅读(100) | 评论 (0)编辑 收藏

     摘要: 本文由ELab团队技术团队分享,原题“Twitter和微博都在用的 @ 人的功能是如何设计与实现的?”,有修订。1、引言第一次使用@人功能到现在已经有差不多10年了,初次使用是通过微博体验的。@人的功能现在遍布各种应用,基本上涉及社交(IM、微博)、办公(钉钉、企业微信)等场景,就是一个必不可少的功能。最近正好在调研 IM 各种功能的技术实现方案,所以也详细地了解了下@人功...  阅读全文

posted @ 2021-12-08 17:23 Jack Jiang 阅读(116) | 评论 (0)编辑 收藏

     摘要: 本文由石墨文档技术杜旻翔分享,原题“石墨文档 Websocket 百万长连接技术实践”,有修订。1、引言在石墨文档的部分业务中,例如文档分享、评论、幻灯片演示和文档表格跟随等场景,涉及到多客户端数据实时同步和服务端批量数据在线推送的需求,一般的 HTTP 协议无法满足服务端主动 Push 数据的场景,因此选择采用 WebSocket 方案进行业务开发。随着石墨文档业务发展,...  阅读全文

posted @ 2021-12-01 12:55 Jack Jiang 阅读(147) | 评论 (0)编辑 收藏

     摘要: 本文由公众号“后台技术汇”分享,原题“基于实践,设计一个百万级别的高可用 & 高可靠的 IM 消息系统”,原文链接在文末。由于原文存在较多错误和不准确内容,有大量修订和改动。1、引言大家好,我是公众号“后台技术汇”的博主“一枚少年”。本人从事后台开发工作 3 年有余了,其中让我感触最深刻的一个项...  阅读全文

posted @ 2021-11-27 13:00 Jack Jiang 阅读(153) | 评论 (0)编辑 收藏

本文由阿里闲鱼技术团队逸昂分享,原题“消息链路优化之弱感知链路优化”,有修订和改动,感谢作者的分享。

1、引言

闲鱼的IM消息系统作为买家与卖家的沟通工具,增进理解、促进信任,对闲鱼的商品成交有重要的价值,是提升用户体验最关键的环节。

然而,随着业务体量的快速增长,当前这套消息系统正面临着诸多急待解决的问题。

以下几个问题典型最为典型:

  • 1)在线消息的体验提升;
  • 2)离线推送的到达率;
  • 3)消息玩法与消息底层系统的耦合过强。

经过评估,我们认为现阶段离线推送的到达率问题最为关键,对用户体验影响较大。

本文将要分享的是闲鱼IM消息在解决离线推送的到达率方面的技术实践,内容包括问题分析和技术优化思路等,希望能带给你启发。

学习交流:

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

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK 

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

2、系列文章

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

3、通信链路类型的划分

从数据通信链接的技术角度,我们根据闲鱼客户端是否在线,将整体消息链路大致分为强感知链路和弱感知链路。

强感知链路由以下子系统或模块:

  • 1)发送方客户端;
  • 2)idleapi-message(闲鱼的消息网关);
  • 3)heracles(闲鱼的消息底层服务);
  • 4)accs(阿里自研的长连接通道);
  • 5)接收方客户端组成。

整条链路的核心指标在于端到端延迟和消息到达率。

强感知链路中的双方都是在线的,消息到达客户端就可以保证接收方感知到。强感知链路的主要痛点在消息的端到端延迟。

弱感知链路与强感知链路的主要不同在于:弱感知链路的接收方是离线的,需要依赖离线推送这样的方式送达。

因此弱感知链路的用户感知度不强,其核心指标在于消息的到达率,而非延迟。

所以当前阶段,优化弱感知链路的重点也就是提升离线消息的到达率。换句话说,提升离线消息到达率问题,也就是优化弱感知链路本身。

4、消息系统架构概览

下图一张整个IM消息系统的架构图,感受下整体链路:

如上图所示,各主要组件和子系统分工如下:

  • 1)HSF是一个远程服务框架,是dubbo的内部版本;
  • 2)tair是阿里自研的分布式缓存框架,支持 memcached、Redis、LevelDB 等不同存储引擎;
  • 3)agoo是阿里的离线推送中台,负责整合不同厂商的离线推送通道,向集团用户提供一个统一的离线推送服务;
  • 4)accs是阿里自研的长连接通道,为客户端、服务端的实时双向交互提供便利;
  • 5)lindorm是阿里自研的NoSQL产品,与HBase有异曲同工之妙;
  • 6)域环是闲鱼消息优化性能的核心结构,用来存储用户最新的若干条消息。

强感知链路和弱感知链路在通道选择上是不同的:

  • 1)强感知链路使用accs这个在线通道;
  • 2)弱感知链路使用agoo这个离线通道。

5、弱感知链路到底怎么定义

通俗了说,弱感知链路指的就是离线消息推送系统。

相比较于在线消息和端内推送(也就是上面说的强感知链路),离线推送难以确保被用户感知到。

典型的情况包括:

  • 1)未发送到用户设备:即推送未送达用户设备,这种情况可以从通道的返回分析;
  • 2)发送到用户设备但没有展示到系统通知栏:闲鱼曾遇到通道返回成功,但是用户未看到推送的案例;
  • 3)展示到通知栏,并被系统折叠:不同安卓厂商对推送的折叠策略不同,被折叠后,需用户主动展开才能看到内容,触达效果明显变差;
  • 4)展示到通知栏,并被用户忽略:离线推送的点击率相比于在线推送更低。

针对“1)未发送到用户设备”,原因有:

  • 1)离线通道的token失效;
  • 2)参数错误;
  • 3)用户关闭应用通知;
  • 4)用户已卸载等。

针对“3)展示到通知栏,并被系统折叠”,原因有:

  • 1)通知的点击率;
  • 2)应用在厂商处的权重;
  • 3)推送的数量等。

针对“4)展示到通知栏,并被用户忽略”,原因有:

  • 1)用户不愿意查看推送;
  • 2)用户看到了推送,但是对内容不感兴趣;
  • 3)用户在忙别的事,无暇处理。

总之:以上这些离线消息推送场景,对于用户来说感知度不高,我们也便称之为弱感知链路。

6、弱感知链路的逻辑构成

我们的弱感知链路分为3部分,即:

  • 1)系统;
  • 2)通道;
  • 3)用户。

共包含了Hermes、agoo、厂商、设备、用户、承接页这几个环节。具体如下图所示。

从推送的产生到用户最终进入APP,共分为如下几个步骤:

  • 步骤1:Hermes是闲鱼的用户触达系统,负责人群管理、内容管理、时机把控,是整个弱感知链路的起点。;
  • 步骤2:agoo是阿里内部承接离线推送的中台,是闲鱼离线推送能力的基础;
  • 步骤3:agoo实现离线推送依靠的是厂商的推送通道(如:苹果的apns通道、Google的fcm通道、及国内各厂商的自建通道。;
  • 步骤4:通过厂商的通道,推送最终出现在用户的设备上,这是用户能感知到推送的前提条件;
  • 步骤5:如果用户刚巧看到这条推送,推送的内容也很有趣,在用户的主动点击下会唤起APP,打开承接页,进而给用户展示个性化的商品。

经过以上5个步骤,至此弱感知链路就完成了使命。

7、弱感知链路面临的具体问题

弱感知链路的核心问题在于:

  • 1)推送的消息是否投递给了用户;
  • 2)已投递到的消息用户是否有感知。

这对应推送的两个阶段:

  • 1)推送消息是否已到达设备;
  • 2)用户是否查看推送并点击。

其中:到达设备这个阶段是最基础的,也是本次优化的核心。

我们可以将每一步的消息处理量依次平铺,展开为一张漏斗图,从而直观的查看链路的瓶颈。

漏斗图斜率最大的地方是优化的重点,差异小的地方不需要优化:

通过分析以上漏斗图,弱感知链路的优化重点在三个方面:

  • 1)agoo受理率:是指我们发送推送请到的数量到可以通过agoo(阿里承接离线推送的中台)转发到厂商通道的数量之间的漏斗;
  • 2)厂商受理率:是指agoo中台受理的量到厂商返回成功的量之间的漏斗;
  • 3)Push点击率:也就通过以上通道最终已送到到用户终端的消息,是否最终转化为用户的主动“点击”。

有了优化方向,我们来看看优化手段吧。

8、我们的技术优化手段

跟随推送的视角,顺着链路看一下我们是如何进行优化的。

8.1 agoo受理率优化

用户的推送,从 Hermes 站点搭乘“班车”,驶向下一站: agoo。

这是推送经历的第一站。到站一看,傻眼了,只有不到一半的推送到站下车了。这是咋回事嘞?

这就要先说说 agoo 了,调用 agoo 有两种方式:

  • 1)指定设备和客户端,agoo直接将推送投递到相应的设备;
  • 2)指定用户和客户端,agoo根据内部的转换表,找到用户对应的设备,再进行投递。

我们的系统不保存用户的设备信息。因此,是按照用户来调用agoo的。

同时:由于没有用户的设备信息,并不知道用户是 iOS 客户端还是 Android 客户端。工程侧不得不向 iOS 和 Android 都发送一遍推送。虽然保证了到达,但是,一半的调用都是无效的。

为了解这个问题:我们使用了agoo的设备信息。将用户转换设备这一阶段提前到了调用 agoo 之前,先明确用户对应的设备,再指定设备调用 agoo,从而避免无效调用。

agoo调用方式优化后,立刻剔除了无效调用,agoo受理率有了明显提升。

至此:我们总算能对 agoo 受理失败的真正原因做一个高大上的分析了。

根据统计:推送被 agoo 拒绝的主要原因是——用户关闭了通知权限。同时,我们对 agoo 调用数据的进一步分析发现——有部分用户找不到对应的设备。 优化到此,我们猛然发现多了两个问题。

那就继续优化呗:

  • 1)通知体验优化,引导打开通知权限;
  • 2)与agoo共建设备库,解决设备转换失败的问题。

这两个优化方向又是一片新天地,我们择日再聊。

8.2 厂商推送通道受理率优化

推送到达 agoo ,分机型搭乘厂商“专列”,驶向下一站:用户设备。

这是推送经历的第二站。出站查票,发现竟然超员了。

于是乎:我们每天有大量推送因为超过厂商设定的限额被拦截。

为什么会这样呢?

实际上:提供推送通道的厂商(没错,各手机厂商的自家推送通道良莠不齐),为了保证用户体验,会对每个应用能够推送的消息总量进行限制。

对于厂商而言,这个限制会根据推送的类型和应用的用户规模设定——推送主要分为产品类的推送和营销类的推送。

厂商推送通道对于不同类型消息的限制是:

  • 1)对于产品类推送,厂商会保证到达;
  • 2)对于营销类推送,厂商会进行额度限制;
  • 3)未标记的推送,默认作为营销类推送对待。

我们刚好没有对推送进行标记,因此触发了厂商的推送限制。

这对我们的用户来说,会带来困扰。闲鱼的交易,很依赖买卖家之间的消息互动。这部分消息是需要确保到达的。

同样:订单类的消息、用户的关注,也需要保证推送给用户。

根据主流厂商的接口协议,我们将推送的消息分为以下几类,并进行相应标记:

  • 1)即时通讯消息;
  • 2)订单状态变化;
  • 3)用户关注内容;
  • 4)营销消息这几类。

同时,在业务上,我们也进行了推送的治理——将用户关注度不高的消息,取消推送,避免打扰。

经过这些优化,因为超过厂商限额而被拦截的推送实现了清零。

8.3 Push点击率优化

通过优化agoo受理率、厂商受理率,我们解决了推送到达量的瓶颈。但即使消息被最终送达,用户到底点击了没有?这才是消息推送的根本意义所在。

于是,在日常的开发测试过程中,我们发现了推送的两个体验问题:

  • 1)用户点击Push有开屏广告;
  • 2)营销Push也有权限校验,更换用户登陆后无法点击。

对于开屏广告功能,我们增加了Push点击跳过广告的能力。

针对Push的权限校验功能,闲鱼根据场景做了细分:

  • 1)涉及个人隐私的推送,保持权限校验不变;
  • 2)营销类的推送,放开权限校验。

以上是点击体验的优化,我们还需要考虑用户的点击意愿。

用户点击量与推送的曝光量、推送素材的有趣程度相关。推送的曝光量又和推送的到达量、推送的到达时机有关。

具体的优化手段是:

  • 1)在推送内容上:我们需要优化的是推送的时机和相应的素材;
  • 2)在推送时机上:算法会根据用户的偏好和个性化行为数据,计算每个用户的个性化推送时间,在用户空闲的时间推送(避免在不合适的时间打扰用户,同时也能提升用户看到推送的可能性)。
  • 3)在推送素材上:算法会根据素材的实时点击反馈,对素材做实时赛马。只发用户感兴趣的素材,提高用户点击意愿。

9、实际优化效果

通过以上我们的分析和技术优化手段,整体弱推送链路链路有了不错的提升,离线消息的到达率相对提升了两位数。

10、写在最后

本篇主要和大家聊的是只是IM消息系统链路中的一环——弱感知链路的优化,落地到到具体的业务也就是离线消息送达率问题。

整体IM消息系统,还是一个比较复杂的领域。

我们在消息系统的发展过程中,面临着如下问题:

  • 1)如何进行消息的链路追踪;
  • 2)如何保证IM消息的快速到达(见《闲鱼亿级IM消息系统的及时性优化实践》);
  • 3)如何将消息的玩法和底层能力分离;
  • 4)离线推送中如何通过用户找到对应的设备。

这些问题,我们在以前的文章中有所分享,以后也会陆续分享更多,敬请期待。

附录:相关资料

[1] Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

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

[3] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[4] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[5] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

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

[7] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

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

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

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

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

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

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

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

[15] 零基础IM开发入门(一):什么是IM系统?

[16] 零基础IM开发入门(二):什么是IM系统的实时性?

[17] 零基础IM开发入门(三):什么是IM系统的可靠性?

[18] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?

本文已同步发布于“即时通讯技术圈”公众号。

同步发布链接是:http://www.52im.net/thread-3748-1-1.html 

posted @ 2021-11-17 12:24 Jack Jiang 阅读(147) | 评论 (0)编辑 收藏

     摘要: 本文由公众号“水滴与银弹”号主Kaito原创分享,原题“搞懂异地多活,看这篇就够了”,为使文章更好理解,即时通讯网收录时有修订。1、引言前几天技术群里有群友问我52im社区里有没有IM分布式系统异地多活方面的文章,我仔细想了想,除了微信分享的几篇文章里有提到容灾和异地多活(只是大致提过,没有详细展开),确实目前还没有系统性的异地多活技术资料可供参考。...  阅读全文

posted @ 2021-11-10 15:18 Jack Jiang 阅读(140) | 评论 (0)编辑 收藏

     摘要: 本文引用了ELab团队、腾讯大讲堂两个公众号分享的文章内容,引用内容见文末参考资料,感谢原作者的无私分享。1、引言对于市面上主流的IM来说,跟二维码有关的功能,比如扫码加好友、扫码登陆、扫码加群等,都是很常见的。这是微信的扫码登录功能:这是微信的扫码加好友功能: 二维码技术使用起来很简单,本系列的前三篇文章也专门针对IM扫码登录这个功能做了详细的分享,但本着学习技术不留死角的习惯,我认为...  阅读全文

posted @ 2021-11-01 19:49 Jack Jiang 阅读(102) | 评论 (0)编辑 收藏

     摘要: 本文由融云技术团队原创分享,原题“万字干货:IM “消息”列表卡顿优化实践”,为使文章更好理解,内容有修订。1、引言随着移动互联网的普及,无论是IM开发者还是普通用户,IM即时通讯应用在日常使用中都是必不可少的,比如:熟人社交的某信、IM活化石的某Q、企业场景的某钉等,几乎是人人必装。以下就是几款主流的IM应用(看首页就知道是哪款,我就不废话了):正...  阅读全文

posted @ 2021-10-26 14:41 Jack Jiang 阅读(98) | 评论 (0)编辑 收藏

本文由阿里闲鱼技术团队有攸分享,原题“向消息延迟说bybye:闲鱼消息及时到达方案”,有修订和改动,感谢作者的分享。

1、引言

IM消息作为闲鱼用户重要的交易咨询工具,核心目标有两点:

  • 1)第一是保证用户的消息不丢失;
  • 2)第二是保证用户的消息及时送达接收方。

IM消息根据消息的接收方设备是否在线,分为离线和在线推送。数据显示目前闲鱼每天有超过一半以上的IM消息是走在线通道的,而在线消息的到达率、及时性是直接影响用户体验的。

本文将根据闲鱼IM消息系统在消息及时性方面的优化实践,详细分析了IM在线通道面临的各种技术问题,并通过相应的技术手段来优化从而保证用户消息的及时到达。

PS:如果您对IM消息可靠性还没有概念,建议先阅读这篇入门文章《零基础IM开发入门(二):什么是IM系统的实时性?》。

学习交流:

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

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

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

2、系列文章

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

3、当前面临的问题

3.1 端内长连接中断

在IM场景中,用户与云端通信频繁,且为了实现用户的消息及时到达,往往采用云端下推消息的方式触达用户,所以用户在线时设备与云端会维持一条TCP长连接通道,可以更轻量级的与服务端进行交互,现代IM即时通讯的下行消息都是通过长连下发的。

当前闲鱼IM消息系统使用的是ACCS长连接,ACCS是淘宝无线提供的全双工、低延时、高安全的通道服务。

但由于用户设备网络状态的不确定性,可能会发生各种各样的网络异常情况导致ACCS长连接通道中断。而长连接一旦意外中断,就会导致用户无法及时收到在线消息。

针对这个问题,我们需要尽可能及时的感知到长连中断并尝试重连。具体的优化思路会在本文后面的内容中分享。

3.2 下推的消息未达

感知长连中断并重连只能在大多数时间保证长连接的有效性,但是在长连接无效或不稳定期间下推的消息客户端可能根本收不到。

简单说就是仅仅有重连机制无法保证下行消息必达,可能有以下场景导致下行消息失败:

  • 1)服务端发送下行消息时长连畅通,消息在传输路上通道断掉,客户端无法收到;
  • 2)设备的在线状态存在延迟,服务端下行消息时认为设备在线,实际上设备已经离线,无法收到;
  • 3)客户端收到了下行消息,但端上后续处理失败(比如落库失败,消息没有成功展示给用户)。

我们通过数据埋点统计得出,ACCS长连接的下行成功率在97%左右。

ACCS长连接的下行成功率的统计方法如下:

ACCS下行成功率 = 通过ACCS成功下行且客户端收到的消息量 / 服务端认为通过ACCS成功下行的消息量

有心急的同学就要问了,丢了3%的消息吗?

并没有!这3%的消息不会丢失,只是不保证及时触达给用户。

我们的消息同步模型是推拉结合模式,在用户拉取消息时会拉取到设备当前位点与服务端最新位点的所有消息,ACCS下行失败的消息会通过主动拉模式获取到,但客户端主动拉取消息的触发时机有限。

当前客户端主动拉取消息的触发时机主要有以下几个:

  • 1)用户冷启动app,主动同步消息;
  • 2)用户主动下拉刷新;
  • 3)app后台切换前台;
  • 4)收到一条推送消息,客户端发现新消息的位点跟本地最新的位点有gap,触发同步。

可见:上述主动同步消息的触发很大程度上依赖用户行为或者有没有收到新消息,难以保证消息及时到达。

如果是用户高频打开的IM软件,这样也不会有太大的问题。但是闲鱼app的活跃度较低,有时候甚至依赖IM消息拉活,而且一条延迟的消息触达可能导致用户错过一笔交易,闲鱼消息不允许有这样的延迟发生。

基于上述分析,我们先描述一个数据指标来反映现状。

通过上面的描述可知:ACCS消息并不全都是推下来的,也可能是主动拉下来的。如果是推,必定可以及时到达;如果是拉,则受限于用户行为。

拉的这部分消息,我们定义为ACCS消息补偿到达,然后计算ACCS消息补偿到达耗时,消息范围限定为服务端ACCS成功下行但是客户端通过主动拉取同步到的消息,以往的版本这个数据在60分钟左右。

注意:这个数据并不是消息触达到用户的耗时,因为如果在线转离线触达,拉取到消息的时间取决于用户行为(用户何时打开了app),但这个数据也能大致反映在线消息的到达延迟状况。

ACCS长连接的消息补偿到达耗时的统计方法如下:

ACCS消息补偿到达耗时 = 客户端通过拉获取到ACCS消息的时间 - 服务端ACCS下行时间

接下来本文将从长连接的重连和未达消息重发两个方面详细讲述我们是如何优化在线通道稳定性的,从而优化并保证消息的及时到达。

4、优化手段1:增加长连接重连机制

4.1 长连接为什么会中断?

有因必有果,我们先来分析下有哪些原因会导致连接中断。

对于IM这种场景下来说,通常可能有以下原因:

  • 1)用户设备断网;
  • 2)设备发生了网络切换;
  • 3)设备处于弱网环境,网络不稳定;
  • 4)设备网络正常,TCP连接由于NAT超时导致连接被运营商中断。

对于APP来说,如果是用户操作导致网络状态变化的情况,会有网络状态变化事件通知,这种情况可以监听事件并主动尝试重连。但现实中的大多数情况都是“意料之外”(正如上面列举的这些断网可能性一样)。

那么既然“意料之外”的断网无法预知,技术上可以如何有效的感知到各种异常状况呢?

PS:如果要透彻理解断网、弱网、TCP链接有效性,并不是本文能讲的清楚的,可以参照下面的资料深入理解一下,值得好好学习。

关于TCP链接本身的有效性问题,可以读以下两篇:

  1. 为何基于TCP协议的移动端IM仍然需要心跳保活机制?
  2. 不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制

关于移动网络的复杂性问题,可以从以下几篇入门的科普文章学习一下:

  1. IM开发者的零基础通信技术入门(十一):为什么WiFi信号差?一文即懂!
  2. IM开发者的零基础通信技术入门(十二):上网卡顿?网络掉线?一文即懂!
  3. IM开发者的零基础通信技术入门(十三):为什么手机信号差?一文即懂!
  4. IM开发者的零基础通信技术入门(十四):高铁上无线上网有多难?一文即懂!

关于移动弱网带来的各种问题、优化方案等,可以通过以下几篇系统学习一下:

  1. 现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障
  2. 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”
  3. 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结
  4. 百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

4.2 心跳检测机制

像大多数链路保活场景一样,IM这种场景下最有效的检测手段就是心跳检测(如果你对TCP链路保活还没有什么概念,建议先读《为何基于TCP协议的移动端IM仍然需要心跳保活机制?)。

原理就是:客户端通过定时发送心跳包,服务端收到心跳包后再反馈给客户端,通过客户端和服务端这一来一去的配合,就可以实现客户服和服务端各自都能感知到连接是否中断。

从及时性效果来看:心跳间隔越短越好,而频繁的心跳检测势必会带来用户流量以及电量的损耗,所以我们的实现目标是如何尽可能少的心跳检测而又尽量及时地感知到长连中断的意外情况。

状态机+消息心跳队列:

在心跳协议设计上,要注意心跳包的核心目标是检测长连通道是否畅通,客户端主动上行心跳包且能收到服务端回包,就认为长连通道健康。所以心跳的上行消息以及回包的数据包应尽可能小。一般来说,通过协议头标识心跳包及响应即可(这样就能节省协议包大小)。

PS:关于心跳机制的入门文章可以详读《一文读懂即时通讯应用中的网络心跳包机制:作用、原理、实现思路等》。

4.3 心跳策略

心跳策略是实现我们上述目标的核心机制,本文仅简单列举几种心跳策略。

比如以下这几种:

  • 1)短心跳检测 初始状态连续 ping 3次 收到 ACK 后,可以认为进入稳定状态;
  • 2)常规固定时长心跳(根据app状态不同,频率可调Mid+,Mid-, Long);
  • 3)自适应心跳 根据设备网络状态变化自动适应的心跳间隔;
  • 4)冗余心跳,app后台切前台,主动心跳一次。

关于心跳策略的详细设计甚至可以单独写一篇文章,有兴趣的同学可以阅读以下推荐的文章继续深入研究。

5、优化手段2:消息ACK应答与重发机制

5.1 概述

为了解决上面的问题,我们同时也引入了消息ACK应答与重发机制。

整体思路是:客户端在收到ACCS消息并处理成功后,给服务端回一个ACK应答包,服务端下发ACCS消息时将消息加入重试队列,收到ACK应答包后更新消息到达状态,并终止重试。

整体设计流程图如下:

该方案的难点即重试处理器的实现设计,接下来我们将重点讲述这部分的详细设计。

5.2 重试队列存储设计

我们采用阿里云表格存储TimeLine模型来存储下行消息的到达状态。Timeline 模型是针对消息数据场景所设计的数据模型,它能满足消息数据场景对消息保序、海量消息存储、实时同步的特殊需求,在IM、Feed流等消息场景应用广泛。(关于TimeLine模型,这里有篇详细的文章可以学习一下《现代IM系统中聊天消息的同步和存储方案探讨

我们给每个用户设备定义一个TimeLine,timeline-id定义为userId_deviceId,sequenceId自定义为消息位点。

存储结构如下:

每通过ACCS成功下行一条消息,则插入到接收用户设备的TimeLine中,收到ACK后根据消息id更新消息到达状态。

同时由于重试动作只发生在下行消息后较短的一段时间内,所以我们设置一个比较短的全局过期时间即可,避免数据膨胀。

5.3 延迟重试设计

如上图所示:

  • 1)每通过ACCS下发一条消息,先插入到Timeline中,初始状态为未达,然后生产一条延迟N秒的延迟消息;
  • 2)每次消费到延迟消息后,读取tablestore中该消息的到达状态,如到达则终止延迟,否则继续;
  • 3)每次重试先判断设备是否在线,如果设备不在线,转发离线通道并终止重试,如果设备在线,则重推未到达的消息,并再次延迟N秒消费;
  • 4)每条消息的重试生命周期中用的同一条延迟消息,最多重试消费M次,超过次数不再重试并打日志埋点(后续可以监控这种情况并基于这个数据进行优化)。

5.4 延迟重发策略

延迟重发策略是指在重发流程中,如何选择合适的延迟时间来使得重发的效率最高。

不同用户在不同时间、地点所处的网络环境差别较大,网络恢复到稳定态所需要的时间也有差异,需要选用合适的延迟策略来保证重发效率。

最优的延迟策略的目标是在最短的时间内,使用最少的重发次数将消息投递成功。以下是几种可选的方案。

5.4.1)固定延迟时间:

要想找到最优的延迟策略,必须从数据中通过分析得到答案,天马行空的想象往往离实际相差甚远。

我们先采用固定的延迟时间(10s)最大重试6次来分析一波数据:

通过这组数据可以看到:有约85%的消息在40s内重发可以投递成功,还有12%的消息在达到最大重试次数后依旧没有收到ACK。在4次重试之后,第5次成功只有2.03%,第6次只有0.92%,继续重发的收益已经变得很低。

6次以后还有部分消息没有收到ACK,这部分消息如果用固定延迟时间策略,性价比很低,频繁重发浪费系统资源,我们需要继续改进策略。

5.4.2)固定延迟+固定步长递增:

考虑到部分用户的网络短时间无法恢复,频繁的短间隔重发价值不大,我们采用4次固定短间隔延迟N秒后,每次延迟时间都是上一次延迟时间递增固定步长M秒的策略。直到收到ACK、用户设备离线或者达到了最大延迟时间MAX(N)。

这种策略一定程度上可以解决固定延迟时间重发策略的问题,但如果用户短时间网络无法恢复,每次重发都要重新递增,也不是一种最优解。

5.4.3)自适应延迟:

设计流程图:

如上图:我们最终衍生出了自适应延迟策略。

自适应延迟是指:根据用户的网络状况,采取自动调整的延迟时间,以期望达到最高的重发效率。

具体是:新消息先通过4次固定N秒的短延迟来探测设备的网络状况,一旦网络恢复,我们将设备的N值清空(设备N值是指根据上几次重发经验,当前设备网络能回复ACK所需要的最短时间,默认情况该值为空,代表用户设备网络正常)。4次重发后依旧收不到ACK,我们尝试读取设备N值,如果为空,则取初始值,以后每次延迟都递增固定步长M,并在重发后更新当前设备的N值,直到消息收到ACK或者达到了最大延迟时间MAX(N)。

5.5 新老版本兼容性

需要注意的是老版本的app是不会回ACK的,如果下发给老版本设备的消息也加入重试队列,那此类消息将一直重试到最大次数才会终止,无端消耗资源。

所以我们设计在ACCS长连建立之后,客户端主动上行一条设备信息,其中包含app的版本号,服务端存储一定时间,在将消息加入重试队列之前,先校验接收者设备app的版本号,符合要求再加入重试队列。

6、 最终优化后的效果

消息重连重发方案上线后,我们上面定义的指标 ACCS补偿到达时间 从60分钟大幅降低至15分钟,降幅达75%。

从而印证了我们的技术分析,同时用户有关消息延迟的舆情反馈大幅下降,可见消息重发机制对保证用户消息及时到达成效显著。

7、未来展望

消息在线通道的稳定性优化至此已告一段落,未来我们将继续优化闲鱼消息的使用体验,包括基础功能的完善以及基础体验的提升。

基础功能方面:我们在近期的版本中已经支持了消息撤回、草稿功能,后续将逐步支持发送定位,会话分组、备注,消息搜索等功能。

基础体验方面:我们对消息的UI样式做了优化升级,并优化了app消息tab页的cpu及内存使用,后续将继续从流量、电量、性能方面继续优化消息的使用体验。

附录:参考资料

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

[2] 不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制

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

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

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

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

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

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

[9] 融云技术分享:融云安卓端IM产品的网络链路保活技术实践

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

本文已同步发布于“即时通讯技术圈”公众号。

同步发布链接是:http://www.52im.net/thread-3726-1-1.html

posted @ 2021-10-19 23:05 Jack Jiang 阅读(156) | 评论 (0)编辑 收藏

     摘要: 本文由作者“阿宝哥”分享,原题“你不知道的 WebSocket”,有修订和改动。1、引言本文将从基本概念、技术原理、常见易错常识、动手实践等多个方面入手,万字长文,带你一起全方位探索 WebSocket 技术。阅读完本文,你将了解以下内容:1)了解 WebSocket 的诞生背景、WebSocket 是什么及它的优点;2)了解 WebSocket 含...  阅读全文

posted @ 2021-10-11 14:35 Jack Jiang 阅读(128) | 评论 (0)编辑 收藏

     摘要: 本文由阿里闲鱼技术团队景松分享,原题“到达率99.9%:闲鱼消息在高速上换引擎(集大成)”,有修订和改动,感谢作者的分享。1、引言在2020年年初的时候接手了闲鱼的IM即时消息系统,当时的消息存在各种问题,网上的用户舆情也是接连不断。典型的问题,比如:1)“聊天消息经常丢失”;2)“消息用户头像乱了”;3)“订单状...  阅读全文

posted @ 2021-09-26 14:47 Jack Jiang 阅读(152) | 评论 (0)编辑 收藏

本文由阿里闲鱼技术团队今朝、有攸分享,本次有修订。

1、引言

闲鱼即时消息系统历经数代迭代,目前已能稳定的支撑亿级消息体量。

在此消息系统的建设过程中,我们经历了从简单到复杂、从困扰到破局,每一次的技术改变都是为了更好的解决当下业务所面临的问题。

本文分享的是闲鱼即时消息系统架构从零开始的技术变迁之路,以期更多的同行们在此基础上汲取经验,得到有价值的启发。

学习交流:

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

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

- 开源IM框架源码:https://github.com/JackJiang2011/MobileIMSDK

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

2、系列文章

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

  1. 阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处
  2. 阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践
  3. 阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路》(* 本文
  4. 阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠性投递技术实践》(* 稍后发布

3、1.0版:业务初创期、最小化可用

3.1 技术背景

2014年启动闲置交易独立APP “闲鱼”,一期构建完成APP主链路,包含商品:发布→搜索→商品详情→IM会话→交易

作为初创app,业务需尽快上线验证效果,技术建设上需要完成闲鱼消息从无到有的系统搭建。

3.2 技术方案

作为即时通讯系统,最小化能力包含:

  • 1)消息存储:会话、摘要、消息;
  • 2)消息同步:推、拉;
  • 3)消息通道:长连接、厂商推送。

与一般IM会话模型不同的是,闲鱼会话以商品为主体,“人+人+商品”为要素构建会话。

因会话模型的差异,淘系已有的消息系统,短期内无法满足业务需求,而闲鱼完全自建消息系统耗时巨大。

为了保障业务高效上线,技术选型上最大化复用已有系统能力,避免重复造轮子。

所以,我们的技术方案是:

  • 1)数据模型与底层存储依赖淘系私信体系进行建设;
  • 2)消息数据获取上,客户端全量从服务端拉取消息数据;
  • 3)通讯协议使用来往SDK及mtop。

总体架构如下图,以此模式完成快速交付保障了业务最小化可用:

4、2.0版:用户量增速快、需要重建消息系统

4.1 技术背景

闲鱼用户量正快速突破100万,即时消息服务的调用量暴涨。在这样的背景下,用户反馈消息数据获取的卡顿、白屏成为常态,大量的消息Push发送下,系统告警频发。

造成这些问题的原因:1.0版的架构模式下,获取消息数据全量拉模式,客户端纯UI不做数据存储。

具体就是:

  • 1)当用户需要查看消息数据时,数据拉取成功与否取决于网络、数据访问速度,偶发性的造成卡顿、白屏;
  • 2)中心化的数据存储,读远大于写,高并发下,服务端负载过大。

针对第2)点:比如1W个用户同时在线聊天,按照当前架构并发拉取全量消息,估算5万QPS。不妨假设,同时在线聊天用户数10万时,对服务端压力可想而知。

4.2 技术方案

基于上述问题,我们决定重建消息系统架构,以应对未来更大的用户增量。

回归到IM系统的核心功能:

4.2.1)消息存储模型:

  • 1)会话模型:由owner、itemid、user、sessionType 来标识唯一会话,增加扩展属性支持个性化;
  • 2)摘要模型:作为用户会话视图,同一会话的不同用户可个性化呈现,由userid、sid标识唯一摘要;
  • 3)消息模型:由sender、消息内容、消息版本、sid组成。sid+消息版本唯一确定一条消息;
  • 4)指令模型:是一种双端约定,由服务端下发,客户端执行的指令集。如免打扰指令、删除指令等。

4.2.2)消息通道:

1)在线通道:使用淘宝无线ACCS长连接提供的全双工、低延时、高安全的通道服务;

2)离线通道:使用淘宝消息推送平台AGOO. 其屏蔽了各主流厂商对接的复杂度,直接对业务系统提供服务。

4.2.3)消息同步模型:

1)客户端建立数据库,存储消息数据:当消息数据存储在本地设备上,消息同步从全量拉取优化为全量+增量同步结合的模式。

增量和全量同步具体指的是:

  • a. 增量同步:客户端存储消息位点信息,通过与服务端最新位点比较,仅同步增量消息;
  • b. 全量同步:当用户卸载重装或位点gap过大时,客户端全量拉取历史消息数据,进行端上数据重建。

2)服务端建设个人消息域环(收件箱模型):以和客户端进行增量数据同步。同时,1.0版本架构中存在的读多写少的问题,通过个人域环的写扩散来平衡读写压力。

下图为一条消息从发送到接收的过程以及服务端和客户端的执行流程:

如上图所示:假设Ua给Ub发送一条消息,消息写扩散至Ua和Ub的各自的域环中:

  • 1)当客户端online时,接收到推送的消息位点=当前端上域版本+1,本地消息数据库merge即可;
  • 2)当客户端offline时,仅进行离线推送通知,当用户重新上线时,进行数据同步,由服务端判断触发增量同步还是全量同步。

针对第2)点,具体逻辑是:

  • 1)如果域环版本差值小于阀值,增量同步后,进行本地消息数据库merge;
  • 2)当域环版本差值大于阀值,进行全量消息拉取,做端上数据重建。

整个同步逻辑基于闲鱼的即时消息域环,域环可以看作是有着固定容量的用户消息收件箱,给一个用户发送的所有消息都会同步到他的域环中。

具体就是:

  • 1)域环存储:域环需要支持高并发数据读写,使用阿里分布式KV存储系统tair来实现;
  • 2)域环容量:为减少全量消息同步,以用户下次进入闲鱼需要同步的平均消息量来规划个人域环容量。同时利用FIFO循环覆盖历史数据;
  • 3)域环版本:用户当前消息位点,在消息进入个人域环时通过tair的counter实现域环版本严格连续递增,用于全量、增量同步判断。

上述建设完成后,闲鱼具备了自己独立的即时消息系统,当下遇到的问题得到了缓解,用户体验度有大幅提升。

5、3.0版:随着业务快速发展,系统稳定性需得到保障

5.1 技术背景

随着闲鱼业务生态的丰富,IM会话与消息内容类型不断扩展,同时在用户量的快速增长下,用户反馈消息收不到、消息延迟等舆情问题日渐突出。

5.2 问题分析

问题1:闲鱼app进程无有效保活机制,app退到后台后进程很快就会被系统挂起,导致长连接中断。此时消息推送走厂商通道,而厂商通道的实时性较差,且对消息推送的优先级设定有差异,从而造成用户感知消息延迟。

问题2:accs在线消息推送时,平均延时较短,但存在假连情况。而且目前的消息推送链路无ack机制,造成服务端以为消息发出去了但实际上客户端并没有收到,用户下次打开app后才能看到消息,用户感知消息延迟。

PS:造成假连接的原因主要是用户退到后台,accs长连中断,但是设备状态更新有延时。

问题3:目前消息同步的推模式(accs push)、拉模式(mtop),客户端未做隔离,异步进行处理,导致在某些极端情况下消息数据库处理异常,引发消息丢失。

如:某用户上线后连续收到多条消息,其中一条触发域黑洞,在进行消息同步端上数据重建时,小概率处理出错。

问题4:大部分线上消息问题发现靠舆情反馈,如消息错乱,出问题后系统无感知、无补救措施且排查困难,仅能跟随版本做修复。

问题5:业务不断丰富,孵化出基于消息系统的服务号及小程序内容营销、消息群组等,各类消息发送链路共用域环与数据存储,造成稳定性问题。

如:个人域环的消息包括IM聊天和营销消息,IM聊天由用户触发,需要保证强到达;而营销消息一般是由系统通过班车等方式批量发送,消息量级大,tps高,影响IM服务稳定性。

5.3 解案决方案

基于上述分析,我们逐个问题进行专项解决。

1)消息重发与推拉隔离:

如上图所示:

  • a. ACK:保障消息及时到达。服务端下行accs消息时,将消息加入重试队列并延迟重试,客户端在收到accs消息并处理成功后,给服务端回一个ack,服务端收到ack后更新消息到达状态,并终止重试,以此避免设备假连或网络不稳定的情况;
  • b. 重发:根据延迟重发策略决定何时重发消息,保障消息确定性到达。自适应延迟重发策略是指新消息先通过4次固定N秒的短延迟来探测设备的网络状况,然后根据网络状况来递增固定步长M的延迟策略,这种策略可以保障在最短的时间内,使用最少的重发次数将消息投递成功;
  • c. 消息队列:端上引入消息队列,按顺序处理消息,保证消息处理的准确性。同时进行推拉隔离,保障队列有序消费,解决了复杂状况下并发处理消息数据合并出错的问题。

2)数据存储拆分:

闲鱼每天发送的即时消息中有一半以上是营销消息,营销消息的发送具有明显的波峰波谷流量,高峰期会导致消息数据库抖动,影响IM消息。我来对消息、摘要、域环存储做业务隔离,以适应不同业务场景对稳定性不同的要求。

具体做法是:

  • 1)IM消息需要极高的稳定性保证,其消息及摘要继续使用mysql存储;
  • 2)营销消息存储周期短,稳定性要求低于IM,采用Lindorm存储;
  • 3)域环做实例级别隔离,保证IM域环的容量不会被其他消息占用,从而影响到消息同步。

PS:Lindorm是一种多模型的云原生数据库服务,具有成本低、自定义TTL、容量横向扩展等优势。

3)线上问题发现与恢复:

保障稳定性的关键要素是做好各种核心指标的监控,而监控首先要有数据来源,对服务端+客户端的关键链路节点埋点,基于集团UT、SLS,通过blink进行实时清洗、计算,最终形成统一规范的日志数据落至SLS,以供实时监控及链路排查。

消息系统的核心目标是保障用户消息发的出、收得到且及时收到,所以我们通过计算发送成功率、到达率、消息延迟来监控系统的稳定性。

此外,为了解决用户舆情排查困难的问题:

  • 1)我们设计了一套指令集,通过约定指令协议,服务端向指定用户下发指令,客户端执行对应指令进行异常数据上报,提高排查效率;
  • 2)扩展了强制全量同步、数据校正等指令,定向修复用户消息数据问题,相较以往出现严重bug只能让用户卸载重装解决,这种方式显然对用户是更友好的。

经过一系列专项治理,技术类舆情下降50%,从0到1建设了消息稳定性体系,用户体验进一步提升。

6、展望未来

闲鱼作为电商交易APP, 其中IM是交易的前置链路,IM的产品体验极大影响用户交易效率。

前段时间进行用户调研,从闲鱼IM的NPS低于预期(NPS是用户忠诚度衡量指标 = 推荐者%-贬损者%)。

从用户反馈来看:

  • 1)部分用户对产品功能有较强烈的诉求,诸如消息搜索、分组等;
  • 2)大部分用户对发送消息过程中的违规问题难以理解;
  • 3)仍有较多舆情反馈消息收不到或延迟。

映射到目前闲鱼的即时消息系统上,我们的系统架构依然有很多需要持续改进的地方。

典型的如:同步协议冗余,在需求迭代过程中容易引发问题、有效保活机制的缺失对消息即时送达的影响、小众机型离线消息收不到、多年的数据积累在线库臃肿等问题,影响着闲鱼业务迭代速度与NPS。

作为技术团队,下一步将提升NPS作为核心技术目标,闲鱼的即时消息系统4.0版架构正在路上 ......

附录:更多相关文章

[1] 更多阿里巴巴的技术资源:

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

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