【直播回顾】华为云×环信,强强联手实现用户增长,降本增效加快企业转型!

1月28日晚上19:00,华为云云市场新生态直播邀请到环信产品总监王璨老师,以《浅谈增长秘籍:看APP如何利用IM云拉新促活》为题做了分享,他提到互联网的特点是以用户为中心,企业要靠不断提升客户体验,帮助客户“定义”产品,累积形成用户规模优势,从而形成突破性增长。同时结合环信即时通讯云和客服云详细讲解了用户增长、利用IM做拉新促活、降本增效的具体方法及策略,也分享了环信联合华为云助力企业数字化转型的成功实践,现在让我们来一起回顾一下要点吧!
f449e79bfab08b16b5e46893046090b.jpg

王璨总监在直播中谈到,环信一共有两条主要产品线——即时通讯云及客服云,在用户增长方面,环信结合产品给出了有竞争力的解决方案。
首先是关于APP增长之分析。当前企业营销运营环节中遇到的突出问题包括流量红利褪去,公域流量获取成本高等,疫情之下,企业加快了数字转型,需要严格控制投入产出比,所以如何获取私域流量成了众多企业能否良好生存及发展的关键因素。
结合当前微信生态,微信私域流量的红利也终将褪去,企业触达用户的渠道更加分散,针对此,环信推出即时通讯云帮助客户打造全渠道的私域流量运营体系,在用户获取方面使用5G富媒体消息、邮件、外呼机器人等工具主动触达,利用微信公众号、小程序、客服等进行公域流量转化,获取用户后,及时与用户建立连接并激活。
在智能客服助力企业降本增效方面,环信推出智能客服机器人和外呼机器人,极大的节省了人力成本,提升客服效率。
根据目前产品,王璨老师也深入分析了环信是如何利用IM云拉新促活,使用客服云降本增效。
环信即时通讯云(IM)将资源部署在华为云上,利用多集群的方案,为客户提供更稳定、更高等级的服务,基于国内开发者客户需求,提供深度优化的SDK。同时环信即时通讯云产品提供包括直播、视频会议、语音连麦聊天室、小程序、小游戏、企业内部IM、智能硬件等多种解决方案。
环信客服云(CEC)提供全渠道互动与全媒体接入,来自不同媒体的服务请求均可以统一接入,一键回复。帮助用户打造跨网、跨界、跨平台的极致客户体验。
环信智能问答机器人为用户降低人工成本,通过大量数据消息训练机器人,优化参数,最终提升工作效率;外呼机器人系统结合云呼叫中心,呼出频率远高于人工;同时环信客服云提供视频客服,全平台支持、灵活接入,极大提升用户使用体验;统一融合智慧工单,不仅能实现工单的有效整合和高效流转,更能快速发现产品功能缺失,提升内部运营效率;提供开放平台能力,可以通过自由组合实现客户的个性化客服需求。
环信即时通讯云及客服云产品通过了华为云相关安全认证,将服务部署在华为云上,稳定性和技术支持能够得到保障,多年来与众多行业头部企业合作,在跨境电商、在线教育、医疗、制造、金融、智能硬件等行业都有成功实践!
获取更多详情请点击https://0x7.me/NaqQm,进入华为云云市场直播间观看精彩回放!
继续阅读 »
1月28日晚上19:00,华为云云市场新生态直播邀请到环信产品总监王璨老师,以《浅谈增长秘籍:看APP如何利用IM云拉新促活》为题做了分享,他提到互联网的特点是以用户为中心,企业要靠不断提升客户体验,帮助客户“定义”产品,累积形成用户规模优势,从而形成突破性增长。同时结合环信即时通讯云和客服云详细讲解了用户增长、利用IM做拉新促活、降本增效的具体方法及策略,也分享了环信联合华为云助力企业数字化转型的成功实践,现在让我们来一起回顾一下要点吧!
f449e79bfab08b16b5e46893046090b.jpg

王璨总监在直播中谈到,环信一共有两条主要产品线——即时通讯云及客服云,在用户增长方面,环信结合产品给出了有竞争力的解决方案。
首先是关于APP增长之分析。当前企业营销运营环节中遇到的突出问题包括流量红利褪去,公域流量获取成本高等,疫情之下,企业加快了数字转型,需要严格控制投入产出比,所以如何获取私域流量成了众多企业能否良好生存及发展的关键因素。
结合当前微信生态,微信私域流量的红利也终将褪去,企业触达用户的渠道更加分散,针对此,环信推出即时通讯云帮助客户打造全渠道的私域流量运营体系,在用户获取方面使用5G富媒体消息、邮件、外呼机器人等工具主动触达,利用微信公众号、小程序、客服等进行公域流量转化,获取用户后,及时与用户建立连接并激活。
在智能客服助力企业降本增效方面,环信推出智能客服机器人和外呼机器人,极大的节省了人力成本,提升客服效率。
根据目前产品,王璨老师也深入分析了环信是如何利用IM云拉新促活,使用客服云降本增效。
环信即时通讯云(IM)将资源部署在华为云上,利用多集群的方案,为客户提供更稳定、更高等级的服务,基于国内开发者客户需求,提供深度优化的SDK。同时环信即时通讯云产品提供包括直播、视频会议、语音连麦聊天室、小程序、小游戏、企业内部IM、智能硬件等多种解决方案。
环信客服云(CEC)提供全渠道互动与全媒体接入,来自不同媒体的服务请求均可以统一接入,一键回复。帮助用户打造跨网、跨界、跨平台的极致客户体验。
环信智能问答机器人为用户降低人工成本,通过大量数据消息训练机器人,优化参数,最终提升工作效率;外呼机器人系统结合云呼叫中心,呼出频率远高于人工;同时环信客服云提供视频客服,全平台支持、灵活接入,极大提升用户使用体验;统一融合智慧工单,不仅能实现工单的有效整合和高效流转,更能快速发现产品功能缺失,提升内部运营效率;提供开放平台能力,可以通过自由组合实现客户的个性化客服需求。
环信即时通讯云及客服云产品通过了华为云相关安全认证,将服务部署在华为云上,稳定性和技术支持能够得到保障,多年来与众多行业头部企业合作,在跨境电商、在线教育、医疗、制造、金融、智能硬件等行业都有成功实践!
获取更多详情请点击https://0x7.me/NaqQm,进入华为云云市场直播间观看精彩回放! 收起阅读 »

【华为云生态直播】浅谈APP增长秘籍,看直播抽取华为手环、蓝牙音箱

5G时代,企业如何提升用户体验、实现增长?众所周知IM是APP拉新促活的标配服务,环信作为全球最大的即时通讯云服务商如何做到亿级高并发助力全民直播获客?小程序IM如何赋能APP客户?如何在全球200多个国家和地区服务社交、游戏等出海企业?以及面对互联网新规,如何提供敏感词过滤、智能反垃圾、鉴黄、图片合规等服务,让APP远离不良信息侵扰。

同样,作为领先的全渠道智能在线客服,环信如何为企业降低60%客服成本,提高80%线索转化?看直播,抽取华为手环、蓝牙音箱、体脂秤、双肩包等惊喜礼物,1月28日晚7点,华为云云市场新生态直播邀您不见不散!

带二维码_副本.jpg

 
继续阅读 »
5G时代,企业如何提升用户体验、实现增长?众所周知IM是APP拉新促活的标配服务,环信作为全球最大的即时通讯云服务商如何做到亿级高并发助力全民直播获客?小程序IM如何赋能APP客户?如何在全球200多个国家和地区服务社交、游戏等出海企业?以及面对互联网新规,如何提供敏感词过滤、智能反垃圾、鉴黄、图片合规等服务,让APP远离不良信息侵扰。

同样,作为领先的全渠道智能在线客服,环信如何为企业降低60%客服成本,提高80%线索转化?看直播,抽取华为手环、蓝牙音箱、体脂秤、双肩包等惊喜礼物,1月28日晚7点,华为云云市场新生态直播邀您不见不散!

带二维码_副本.jpg

  收起阅读 »

程序员如何写出高质量的代码程序

编码是程序员最重要的工作,每个程序员都希望自己可以写出优雅,高性能,高质量的代码,对于大师级别的程序员,他们的写的代码就和艺术品一样,你会忍不住发出惊叹,他们怎么可以创造出如此惊艳的作品出来。
下面笔者就以自己的浅薄学识和一些经验来总结下优秀的程序应该具有的特点。

每个变量的命名都深思熟虑

普通程序员的变量命名很随便,以至于随便到abcd都会出来,而高质量的代码的命名则很规范,既不长,也不短,既可以读出它们的含义,又不至于显得啰嗦,总之,从变量命名你就能读出一个程序是否优雅。

从配置文件中读取变量

很多人喜欢在程序中通过注释来修改变量值,这样的做法非常不对,首先不说无用地注释影响了代码的整洁,就通过修改代码来修改变量的值就是不优雅的。

一个优秀的程序,一定是从配置文件中读取所需要的变量的,而修改配置文件对于一个人来说远远比去源代码中修改变量值要方便的多得多。

当你学会从配置文件中读取配置,修改配置的时候,你的程序才是优秀的。

一定要有测试代码

一个高质量的程序一定会有测试代码,记住无论程序功能多么简单,我们都要写测试代码。为什么TDD会流行,因为很多人懒得写代码,而TDD就是强迫你写测试代码,因为这样可以让代码更加健壮,同时,其它人修改代码也可以不会造成更重大影响。

我们不一定使用TDD进行程序开发,但是一定要写测试代码,有了测试代码,你的程序才经得起折腾,记住,有时候你会犯迷糊,但是测试代码不会,跑通过测试用例的代码至少可以让你减少很多错误。

一定要写日志

一个程序开发之后,你是没有办法预测它的使用环境和使用方式的,你能做的就是在它出现错误的时候记录下日志,这样你才可能进行分析。同时,在程序开发的过程中,通过记录日志也可以方便我们进行代码的调试,日志也是调试分析的一种方式。

永远不要重复写代码

古人云事不过三,写代码也一样,当你在很多地方写了重复代码的时候,你要记得将它们重构,永远不要写重复的代码,发现重复的时候,记得使用函数将它抽象出来。

很多人喜欢拷贝代码,然后你会发现他的程序中好多代码是一样的,而当他要修改代码的时候,不得不每一处都需要修改,这不仅浪费时间,还可能造成代码的遗漏。

代码格式要统一

记得以前听过一个笑话,我们中国人写的代码,一个人写的像一千个人写的一样,而印度人写的代码,一千人像一个人写的一样。

我们不要求所有人写的代码风格都一模一样,但是我们需要你写的代码前后要统一,同时要遵循代码推荐分隔。

现在所有的语言都有自己的代码格式风格,你只要按照规则来写就好。

总结

优秀的代码每一个变量的命名都是反复斟酌的,每一个函数都是力求最精简的,每一个方法都是尽力是最高效的。

自己写完的代码一定要复审,有时候很多明显的错误一定要避免。

代码之道永无止境,我们只有不断地总结,才能写出接近优秀的程序,而优秀的程序永远都不会存在。
继续阅读 »
编码是程序员最重要的工作,每个程序员都希望自己可以写出优雅,高性能,高质量的代码,对于大师级别的程序员,他们的写的代码就和艺术品一样,你会忍不住发出惊叹,他们怎么可以创造出如此惊艳的作品出来。
下面笔者就以自己的浅薄学识和一些经验来总结下优秀的程序应该具有的特点。

每个变量的命名都深思熟虑

普通程序员的变量命名很随便,以至于随便到abcd都会出来,而高质量的代码的命名则很规范,既不长,也不短,既可以读出它们的含义,又不至于显得啰嗦,总之,从变量命名你就能读出一个程序是否优雅。

从配置文件中读取变量

很多人喜欢在程序中通过注释来修改变量值,这样的做法非常不对,首先不说无用地注释影响了代码的整洁,就通过修改代码来修改变量的值就是不优雅的。

一个优秀的程序,一定是从配置文件中读取所需要的变量的,而修改配置文件对于一个人来说远远比去源代码中修改变量值要方便的多得多。

当你学会从配置文件中读取配置,修改配置的时候,你的程序才是优秀的。

一定要有测试代码

一个高质量的程序一定会有测试代码,记住无论程序功能多么简单,我们都要写测试代码。为什么TDD会流行,因为很多人懒得写代码,而TDD就是强迫你写测试代码,因为这样可以让代码更加健壮,同时,其它人修改代码也可以不会造成更重大影响。

我们不一定使用TDD进行程序开发,但是一定要写测试代码,有了测试代码,你的程序才经得起折腾,记住,有时候你会犯迷糊,但是测试代码不会,跑通过测试用例的代码至少可以让你减少很多错误。

一定要写日志

一个程序开发之后,你是没有办法预测它的使用环境和使用方式的,你能做的就是在它出现错误的时候记录下日志,这样你才可能进行分析。同时,在程序开发的过程中,通过记录日志也可以方便我们进行代码的调试,日志也是调试分析的一种方式。

永远不要重复写代码

古人云事不过三,写代码也一样,当你在很多地方写了重复代码的时候,你要记得将它们重构,永远不要写重复的代码,发现重复的时候,记得使用函数将它抽象出来。

很多人喜欢拷贝代码,然后你会发现他的程序中好多代码是一样的,而当他要修改代码的时候,不得不每一处都需要修改,这不仅浪费时间,还可能造成代码的遗漏。

代码格式要统一

记得以前听过一个笑话,我们中国人写的代码,一个人写的像一千个人写的一样,而印度人写的代码,一千人像一个人写的一样。

我们不要求所有人写的代码风格都一模一样,但是我们需要你写的代码前后要统一,同时要遵循代码推荐分隔。

现在所有的语言都有自己的代码格式风格,你只要按照规则来写就好。

总结

优秀的代码每一个变量的命名都是反复斟酌的,每一个函数都是力求最精简的,每一个方法都是尽力是最高效的。

自己写完的代码一定要复审,有时候很多明显的错误一定要避免。

代码之道永无止境,我们只有不断地总结,才能写出接近优秀的程序,而优秀的程序永远都不会存在。 收起阅读 »

环信大学:2021漫游指南,PC Web、Uni-App、小程序集成环信IM都在这里了

与其说这是一篇集成攻略,其实这更多是对于官网文档的一篇解释说明,相信很多的小伙伴在准备将环信的IM即时通讯能力集成或移植在自己的项目上都会出现一头雾水或无从下手的感觉,即使看遍了官方文档也不免心生疑问:
“啥?!啥?!啥?!这是啥?!文档写的是个啥?!”
图片1.jpg

所以我决定更加直白且详细的描述一下如何集成web端的IM SDK,(小程序、Uni-app通用)以下是概览:
一、 注册环信console,创建自己的应用,拿到Appkey。
二、 粗略浏览官方文档,熟悉大致流程及接口。
三、 Clone Demo,挑出必选引入文件。
四、 Hello World! (从注册登陆开始)。
常见问题以及error报错查询入口:
http://docs-im.easemob.com/faq
http://docs-im.easemob.com/im/other/errorcode/web
http://docs-im.easemob.com/im/ ... stapi
以下为较为详细的描述一些集成步骤,如一些步骤已非常了解且已操作完成请自行跳过。
一、注册环信console,创建自己的应用,拿到Appkey
PS:这一步骤,各端通用。
1. 注册console 管理后台
相信经常使用第三方服务的小伙伴都非常熟悉这一步(不熟悉?没关系,照着葫芦画瓢就行),打开开发者平台进行注册登录创建等操作,这一点我就不过多赘述,直接上注册平台链接:https://console.easemob.com/user/login
图片2.png

注册成功之后会跳转到这样的一个界面:
图片3.jpg

这个就是console后台管理页面,这个实际就属于一个后台管理系统,创建自己的项目应用,监测日活,统计注册用户数,常见的一些增删改查操作都可以执行,没事可以捣鼓捣鼓看看有哪些功能,这里我就不多介绍了,咱们接着往下进行。
2. 创建应用
创建应用这一步搁咱们JavaScript其实可以理解为 new 了一个空对象,var了一个空变量等等随意怎么比喻都可以。如果放在现实当中其实更像是创建了一个公司,这个公司有公司名也就是Appname,而每个公司肯定有一个唯一的公司代码(公司的身份证ID号),也就是Appkey。公司人数也就是创建出来的应用下注册的用户数量等等。
点击添加应用开始创建:
图片4.png

创建应用的详情页:
图片5.png

这里我特意框住是因为经常有人告诉我:“创建出来的appkey在客户端测试时候点击注册没法注册,报错描述是401无权限是怎么回事?”
最后发现是创建应用的时候注册模式选的是授权注册,你选个授权注册,看眼描述就知道这种注册模式相当于注册是要经过审批的,如果测试阶段可以使用开放注册,后期还可以修改注册模式。
这个是创建成功之后点击进入应用的基本信息描述,分别对应的是什么功能也有描述,Appkey也就成功拿到了,由orgname +“#”+appname组成的appkey。如图:
图片6.png

这里特别说下这个Client ID和Client Secret ,有了Client ID和Client Secret就可以获取到管理员token从而可以在应用下随意增删改查,发消息,之前有遇到泄露导致客户端用户莫名其妙收到一些骚扰信息,当然这都算轻的,所以正式环境下一定要保管好Client ID和Client Secret !!!
二、粗略浏览官方文档,熟悉大致流程及接口
PS:这一步骤,各端通用
可能有的小伙伴觉得这一步我再拉出来讲意义不是很大没什么用,其实不然,因为在协助一些小伙伴集成SDK中发现有的朋友对于在自己项目中要用到的接口都还不知道在哪,更谈不上如何使用。文档通篇一字不落看下来也不太现实,但是大致知道一些后期咱们的项目中有哪些接口可能会用到,知道大概在哪能找到调用方式还是很有必要的。
一篇文档中我们可以从这些方向看:
图片7.png

SDK功能分类,更新日志(了解SDK更新周期,新增功能,修复问题),Demo的使用介绍,兼容性以及常见问题。
Uni-app打包双端的介绍,运行平台描述,源码的GitHub地址。
图片8.png

而且文档中也有描述一些可能会遇到的坑,一些解决方案。例如:发送URL类型的图片消息需要在config配置文件中配置。
图片9.png

图片10.png

通过“WebIM.conn.mr_cache =  ”方法可以重置拉取历史消息的游标。
图片11.png

常用的新消息提示如何去处理,等等都会有一些描述,磨刀不误砍柴工,因此我强烈建议各位在正式开始集成之前首先浏览一下咱们对应端的文档。
在此我也正好有几个常用的功能场景提供给大家,文档中有介绍,为了方便各位也还是直接上链接。
消息回执(涉及到web端已读未读的处理):
http://docs-im.easemob.com/im/ ... %25A7
消息漫游(涉及到web端拉取历史SDK接口):
http://docs-im.easemob.com/im/ ... %25B8
昵称与头像(涉及到昵称与头像的处理):
http://docs-im.easemob.com/im/ ... kname
获取管理员token(一些服务端rest接口的调用需获取管理员token):
http://docs-im.easemob.com/im/ ... %2590
拉取历史消息文件(将聊天产生的历史消息保存到本地服务器):
http://docs-im.easemob.com/im/ ... %25B6
更多常用实现场景可以参考文档常见方案一栏,里面可能有些只有IOS 和 Android 解决方案,但是web端实际也是相似的处理方式,瞜一眼相信会有些启发。
图片12.png

http://docs-im.easemob.com/im/ ... troom
文档已浏览完毕,接着往下进行。
三、Clone Demo、挑选出必要文件
其实这一步主要面向于直接下载了官网Demo,但是不知道如何把将Demo往里引入的同学,在协助集成的过程中,我经常遇到有些朋友启动Demo测试完成之后,直接就把所有Demo文件拷贝到了原有项目当中,我们知道前端的部署打包的性能优化是与压缩精简代码,减少请求数量,页面结构,浏览器缓存等因素是相关的,我并不建议完全将Demo拷贝到咱们的原项目当中。
首先我想说下现有PCweb-Demo uni-appDemo 微信小程序Demo等Demo 的意义是为我们开发者小伙伴展示功能接口的实际运用,即时通讯能力的接口测验,各接口组合使用的运用逻辑及调用顺序 ,当然更是给了咱们在实际项目开发当中有了一定的参考作用。由于其功能性的考虑并没有按照线上产品的标准去做,所以其代码内部接口中有些接口的调用以及请求并未考虑性能因素,功能并未完全实现展示。
相信通过我的描述,也就明白了为什么不建议将Demo完全拷贝到原有项目中,因为可能会对咱们的性能优化造成一定的影响,并且可能会有很多代码对项目中的功能实现并无任何帮助,有些组件关联,运行可能会有更多难以排查的报错,更多是建议可以参考一下Demo当中的写法。
下面我先介绍一下uni-app-Demo集成 IMSDK的操作:
Uni-App-Demo GitHub地址:
https://github.com/easemob/webim-uniapp-demo
1. 将clone下来Demo于HbuilderX中打开,首先咱们不急着运行先了解一下目录下各个文件的作用。
<!-- 截图来自uni-appDemo中的README.md文件,详细可以自行查看 -->
图片13.png

2. 将核心文件导入到自己的测试项目或正式项目中。
随着环信3.x IM SDK的更新,现在引入依赖文件相对之前2.x IM SDK的集成引数个文件无疑是简单了许多。
Uni-app-Demo中需要引用的文件截图:
图片14.png

其实除SDK必须引入之外,WebIMConfig.js 以及WebIM.js也可以不引用,但是在项目中必须要存在,听起来很矛盾,但实际含义就是WebIMConfig.js该有的配置你必须要配置,WebIM.js中的初始化这一步你也必须要进行初始化,至于你要命名为其他文件名,放在其他位置也不是不可以的。
SDK包:PCWeb端:websdk3.x.js 小程序/Uni-app:wxsdk3.x.js
WebIMConfig.js:这个文件的作用就是将引入的WebIMSDK进行自定义配置:
图片15.png

如图文档上有对各个配置项的作用详细介绍,建议在集成过程中的配置页可以参考Demo中的配置进行配置,但是各个配置项的开启与否,参数设置请自行按需设置。
依然是在协助小伙伴集成环信IM的过程经常会遇到有小伙伴问配置项中的:“socket Server地址:socketServer: '//im-api-v2.easemob.com/ws',    // ”和“rest Server地址:restServer: '//a1.easemob.com' ”,“需不需要配置成自己项目地址,或者自己去定义这两个参数?”,对于这个问题,我只能回答说千万别!这个是定义好了的,你要做的基本就只需要将appkey:改成自己申请的,其他几乎不用动。
WebIM.js:将SDK的配置以及引入的SDK进一步做处理简称:初始化。
导出SDK 并引入WebIMConfig.js文件,将SDK重命名为WebIM挂载到window或uni或wx下,视情况决定。
图片16.png

同样这一步文档也有详细的描述:
图片17.png

3. 完成文件导入挂载,开始配置并添加监听回调。
如图,监听仅截图一部分:
图片18.jpg

在集成Web端(包括小程序以及Uni-app)IM中,所有的事件监听都是要靠WebIM.conn.listen( ),由此可以看出其重要性。
所以在初始化阶段结束后我们首要将监听配置添加上。
至于监听的具体位置应该添加在哪里这个实际并没有严格的要求,PCwebDemo中是将监听写在了WebIM.js中,微信小程序Demo是将监听写在了app.js,uni-app是将监听写在了App.vue根组件中,多数是写在了全局。但其实监听也可以写在局部,前提局部能够访问到WebIM,然后在局部添加上监听回调,不管是全局监听还是局部监听都要在SDK接口调用之前布置好监听并开启监听。
下面我们再来聊一下监听中的个别监听回调的作用以及实际运用,其监听的作用文档已有详细描写且较为好了解,这里就不再一一赘述。
onOpened: function ( ) {},         //连接成功回调 
此监听为与环信连接成功之后触发的监听回调,经常会出现一个问题就是明明环信conn.open登陆接口都已经返回success都已经返回token了,可是发送消息依然报错,接收消息也接收不到。那么我们首要检查的就是该监听有没有触发,放个console.log( )看下登陆之后的该回调有没有成功的打印,如果打印了那么说明成功与环信建立了连接!也就可以进行其他的操作了。
经常有朋友问:“登陆成功之后打印message形参为什么是undefined?是不是登陆有问题?”,其实不是这个message并不会有任何返回,登陆是否成功一切以onOpened监听回调是否执行为准!
图片19.png

在该监听的实际运用中Demo中其实也做了很好的演示,就是登陆之后的页面跳转,这个非常常用,因为IM聊天功能是否可以使用首要的判断必然是与环信是否建立连接成功,成功那么跳转进入聊天页面。
onClosed: function ( message ) {},        //连接关闭回调
此监听为与环信连接关闭的监听回调,其实也没有具体要描述的地方,更多的是该监听经常用来处理在与环信断开连接时候的一些UI层面的提示,或者再意外断开连接之后退回到login页面,以及有些场景下进行手动重连。
onMessage:function ( message ) {},   //监听收到消息的回调
这里的onMessage是对所有收到消息的回调的一个简称,例如:onTextMessage 监听文本消息、onPictureMessage 监听图片消息、onAudioMessage 监听音频消息,等等... 这个监听相信不用过多描述就可以清楚其功能,消息监听回调非常的核心,所有的消息都是仰仗这几个监听回调拿到的。测试的时候可以在监听回调中定义形参并控制台打印参数查看消息监听是否正常触发并接收数据。
截图为onTextMessage监听收到的单聊消息
图片20.png

onError: function ( message ) {},          //失败回调
此监听回调可监听SDK中大多数的error抛出,在集成开发过程中帮助更快定位问题原因,并且可在回调中根据error信息从UI层面进行提示,因此其实际运用相对也常用。
截图为Uni-appDemo利用onError监听做出的提示以及一些操作。
图片21.png

当然有些error出现之后大家可能不知道其具体含义下面为分析一下error异常的判断思路,
首先onError监听回调的message字段就已经给出了大致error原因:
例图中onError回调返回的对象中的message字段value的值中给出了“login failed”登陆异常,接着我们看data字段下给出的具体原因,不难看出invalid password 不打开百度翻译也能猜出密码有问题,那么我们也就可以以此判断,登陆密码错误,或者对用户做出弹窗提示。
图片22.png

同样的type:1,同样的message:“login failed”,error详情就又不一样了,error介绍:“user not found” 用户找不到,所以具体的判断error原因还是要以data下的error_description给出的原因去定位。
图片23.png

当然有些error也会出现一些其他的情况,并不返回message 参数,data也是空的,只给出了一个type类型,像这种情况其实就是在没有登陆的情况下调用了SDK接口,那么这时首先要判断的就是是否登录了环信,或者已经登陆但是又断开了与环信的连接。
截图为未登录调用了获取群组详情返回的error。
图片24.jpg

在已登录的情况下查询群组详情,error详情直接给出了找不到 group ID为XXXXXX的群组,此时就可以以此判断,或给出提示查找不到群ID为...的详情
图片25.png

onPresence: function ( message ) {},       //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息。
此监听正如描述所讲,处理“广播”或“发布-订阅消息”,至于发布-订阅消息可以去了解一下设计模式,发布订阅者模式,这里就不再进行解释了,这里主要讲的是该监听如果在实际运用中的作用是什么。
我们先看下Uni-appDemo中的示例:
图片26.png

通过观察我们看到了示例中运用到了switch case 语句通过回调里message中不同的type类型从而对应执行不同的提示或其他逻辑操作。
在实际SDK集成过程中,例如:“申请添加好友、删除好友,邀请入群,他人退群,新增群组管理员、他人加入聊天室,退出聊天室”等操作均会触发此监听的执行并回调出对应行为类型的type字段,根据type类型我们从而可以给出对应的UI层面的提示,或发布-订阅事件,uni-appDemo中的disp.fire(‘XXXX’),正是发布-订阅模式中的发布事件。
如果我们需要详细的类型,则可以在文档中的事件监听找到:
好友状态事件监听:
现有的好友事件监听,已新增对应的回调监听:
图片27.png

群组事件监听:
文档地址:
http://docs-im.easemob.com/im/ ... %25AC
图片28.png

聊天室事件监听:
文档地址:
http://docs-im.easemob.com/im/ ... %25AC

图片29.png


以上变为常用的几个监听的介绍,具体的使用还是以咱们实际项目中的逻辑为准。
SDK,config配置,以及初始化,监听都已配置完毕,下面我们就开始注册、登陆环信ID,发出我们的第一条消息吧!
四、Hello World ! (从注册登录开始)
环信所有的功能性SDK接口调用都是要从注册及登陆成功方可有效使用,如已注册,登陆则是客户端调用的第一个接口。
下面我将从注册接口开始讲起:
注册,web端注册接口如图:
图片30.png

客户端是否能够直接调用注册接口,需要先去环信IM管理后台检查该Appkey的注册模式是否为开放注册,授权模式的话,此接口调用注册会直接出现下图情况
图片31.png

Open registration doesn't allow, so register user need token
开放注册不允许,所以注册用户需要token
注册成功会触发注册接口中的success回调,并有注册信息的返回如图:返回字段包含创建时间,appname,orgname等信息。
图片32.png

注册完成之后便可以开始调用登陆接口完成登录操作。
下面我们来了解SDK提供的两种登陆方式:
1. 用户名密码登陆(环信ID、密码登陆),截图为文档——登陆接口:
图片33.png

user: String //环信ID
pwd:String //注册时的密码
appKey  // appkey 初始化后appkey
2. token登陆(使用token进行免密登陆)
图片34.png

该登陆方式可以进行免密登陆,常见用于刷新页面之后的二次登陆。
比较常用到的场景,例如从login页面跳转到chat页面,用户在当前页面刷新之后不再进行login页面登陆直接正常进行聊天。(刷新页面是会断开与环信的连接,如不登陆则无法调用SDK接口)。
token获取:token的获取是在用户名密码登陆成功后的success回调中返回获取。
图片35.png

access_token字段为token。
expires_in字段为token有效期,单位为秒,有效期内token可重复使用。
uni-app-demo中的用户名密码登陆的接口调用
图片36.png

Uni-app-demo中的token登陆:
图片37.png

有时我们会遇到success成功的返回了token,可是发送消息或者调用其他接口还是会出现报错,或者提示未登录的error,这时我们首要排查的情况还是检查一下onOpened监听是否成功触发,与环信的连接建立还是要以onOpened监听触发为准!
图片38.png

图片39.png

监听成功打印,意味着登陆成功!
这里顺便给大家两个检验是否登录的方法。
1. WebIM.conn方法下有一个logOut字段,该字段为true时表明未登录状态,该字段为false时表明登陆:
图片40.png

2. WebIM.conn.isOpened () 方法有三个状态,undefined为未登录状态,true为已登录状态,false为未登录状态,可以根据这三个状态去判断是否登录:
图片41.png

到这里登陆就说完了,至于为什么没有讲下去也是因为只要登录成功,其他接口就可以按需调用,相信以各位小伙伴的能力接着往下进行也不是很困难了,当然也说不定有些奇葩问题搞不定,那么建议咱们可以去环信官网去寻求我们支持同事的帮助,将遇到的问题描述清楚,问题将在24H以内得到回复。
图片42.png

图片43.png

最后愿JavaScript之父布兰登·艾克
保佑各位项目顺利上线!
继续阅读 »
与其说这是一篇集成攻略,其实这更多是对于官网文档的一篇解释说明,相信很多的小伙伴在准备将环信的IM即时通讯能力集成或移植在自己的项目上都会出现一头雾水或无从下手的感觉,即使看遍了官方文档也不免心生疑问:
“啥?!啥?!啥?!这是啥?!文档写的是个啥?!”
图片1.jpg

所以我决定更加直白且详细的描述一下如何集成web端的IM SDK,(小程序、Uni-app通用)以下是概览:
一、 注册环信console,创建自己的应用,拿到Appkey。
二、 粗略浏览官方文档,熟悉大致流程及接口。
三、 Clone Demo,挑出必选引入文件。
四、 Hello World! (从注册登陆开始)。
常见问题以及error报错查询入口:
http://docs-im.easemob.com/faq
http://docs-im.easemob.com/im/other/errorcode/web
http://docs-im.easemob.com/im/ ... stapi
以下为较为详细的描述一些集成步骤,如一些步骤已非常了解且已操作完成请自行跳过。
一、注册环信console,创建自己的应用,拿到Appkey
PS:这一步骤,各端通用。
1. 注册console 管理后台
相信经常使用第三方服务的小伙伴都非常熟悉这一步(不熟悉?没关系,照着葫芦画瓢就行),打开开发者平台进行注册登录创建等操作,这一点我就不过多赘述,直接上注册平台链接:https://console.easemob.com/user/login
图片2.png

注册成功之后会跳转到这样的一个界面:
图片3.jpg

这个就是console后台管理页面,这个实际就属于一个后台管理系统,创建自己的项目应用,监测日活,统计注册用户数,常见的一些增删改查操作都可以执行,没事可以捣鼓捣鼓看看有哪些功能,这里我就不多介绍了,咱们接着往下进行。
2. 创建应用
创建应用这一步搁咱们JavaScript其实可以理解为 new 了一个空对象,var了一个空变量等等随意怎么比喻都可以。如果放在现实当中其实更像是创建了一个公司,这个公司有公司名也就是Appname,而每个公司肯定有一个唯一的公司代码(公司的身份证ID号),也就是Appkey。公司人数也就是创建出来的应用下注册的用户数量等等。
点击添加应用开始创建:
图片4.png

创建应用的详情页:
图片5.png

这里我特意框住是因为经常有人告诉我:“创建出来的appkey在客户端测试时候点击注册没法注册,报错描述是401无权限是怎么回事?”
最后发现是创建应用的时候注册模式选的是授权注册,你选个授权注册,看眼描述就知道这种注册模式相当于注册是要经过审批的,如果测试阶段可以使用开放注册,后期还可以修改注册模式。
这个是创建成功之后点击进入应用的基本信息描述,分别对应的是什么功能也有描述,Appkey也就成功拿到了,由orgname +“#”+appname组成的appkey。如图:
图片6.png

这里特别说下这个Client ID和Client Secret ,有了Client ID和Client Secret就可以获取到管理员token从而可以在应用下随意增删改查,发消息,之前有遇到泄露导致客户端用户莫名其妙收到一些骚扰信息,当然这都算轻的,所以正式环境下一定要保管好Client ID和Client Secret !!!
二、粗略浏览官方文档,熟悉大致流程及接口
PS:这一步骤,各端通用
可能有的小伙伴觉得这一步我再拉出来讲意义不是很大没什么用,其实不然,因为在协助一些小伙伴集成SDK中发现有的朋友对于在自己项目中要用到的接口都还不知道在哪,更谈不上如何使用。文档通篇一字不落看下来也不太现实,但是大致知道一些后期咱们的项目中有哪些接口可能会用到,知道大概在哪能找到调用方式还是很有必要的。
一篇文档中我们可以从这些方向看:
图片7.png

SDK功能分类,更新日志(了解SDK更新周期,新增功能,修复问题),Demo的使用介绍,兼容性以及常见问题。
Uni-app打包双端的介绍,运行平台描述,源码的GitHub地址。
图片8.png

而且文档中也有描述一些可能会遇到的坑,一些解决方案。例如:发送URL类型的图片消息需要在config配置文件中配置。
图片9.png

图片10.png

通过“WebIM.conn.mr_cache =  ”方法可以重置拉取历史消息的游标。
图片11.png

常用的新消息提示如何去处理,等等都会有一些描述,磨刀不误砍柴工,因此我强烈建议各位在正式开始集成之前首先浏览一下咱们对应端的文档。
在此我也正好有几个常用的功能场景提供给大家,文档中有介绍,为了方便各位也还是直接上链接。
消息回执(涉及到web端已读未读的处理):
http://docs-im.easemob.com/im/ ... %25A7
消息漫游(涉及到web端拉取历史SDK接口):
http://docs-im.easemob.com/im/ ... %25B8
昵称与头像(涉及到昵称与头像的处理):
http://docs-im.easemob.com/im/ ... kname
获取管理员token(一些服务端rest接口的调用需获取管理员token):
http://docs-im.easemob.com/im/ ... %2590
拉取历史消息文件(将聊天产生的历史消息保存到本地服务器):
http://docs-im.easemob.com/im/ ... %25B6
更多常用实现场景可以参考文档常见方案一栏,里面可能有些只有IOS 和 Android 解决方案,但是web端实际也是相似的处理方式,瞜一眼相信会有些启发。
图片12.png

http://docs-im.easemob.com/im/ ... troom
文档已浏览完毕,接着往下进行。
三、Clone Demo、挑选出必要文件
其实这一步主要面向于直接下载了官网Demo,但是不知道如何把将Demo往里引入的同学,在协助集成的过程中,我经常遇到有些朋友启动Demo测试完成之后,直接就把所有Demo文件拷贝到了原有项目当中,我们知道前端的部署打包的性能优化是与压缩精简代码,减少请求数量,页面结构,浏览器缓存等因素是相关的,我并不建议完全将Demo拷贝到咱们的原项目当中。
首先我想说下现有PCweb-Demo uni-appDemo 微信小程序Demo等Demo 的意义是为我们开发者小伙伴展示功能接口的实际运用,即时通讯能力的接口测验,各接口组合使用的运用逻辑及调用顺序 ,当然更是给了咱们在实际项目开发当中有了一定的参考作用。由于其功能性的考虑并没有按照线上产品的标准去做,所以其代码内部接口中有些接口的调用以及请求并未考虑性能因素,功能并未完全实现展示。
相信通过我的描述,也就明白了为什么不建议将Demo完全拷贝到原有项目中,因为可能会对咱们的性能优化造成一定的影响,并且可能会有很多代码对项目中的功能实现并无任何帮助,有些组件关联,运行可能会有更多难以排查的报错,更多是建议可以参考一下Demo当中的写法。
下面我先介绍一下uni-app-Demo集成 IMSDK的操作:
Uni-App-Demo GitHub地址:
https://github.com/easemob/webim-uniapp-demo
1. 将clone下来Demo于HbuilderX中打开,首先咱们不急着运行先了解一下目录下各个文件的作用。
<!-- 截图来自uni-appDemo中的README.md文件,详细可以自行查看 -->
图片13.png

2. 将核心文件导入到自己的测试项目或正式项目中。
随着环信3.x IM SDK的更新,现在引入依赖文件相对之前2.x IM SDK的集成引数个文件无疑是简单了许多。
Uni-app-Demo中需要引用的文件截图:
图片14.png

其实除SDK必须引入之外,WebIMConfig.js 以及WebIM.js也可以不引用,但是在项目中必须要存在,听起来很矛盾,但实际含义就是WebIMConfig.js该有的配置你必须要配置,WebIM.js中的初始化这一步你也必须要进行初始化,至于你要命名为其他文件名,放在其他位置也不是不可以的。
SDK包:PCWeb端:websdk3.x.js 小程序/Uni-app:wxsdk3.x.js
WebIMConfig.js:这个文件的作用就是将引入的WebIMSDK进行自定义配置:
图片15.png

如图文档上有对各个配置项的作用详细介绍,建议在集成过程中的配置页可以参考Demo中的配置进行配置,但是各个配置项的开启与否,参数设置请自行按需设置。
依然是在协助小伙伴集成环信IM的过程经常会遇到有小伙伴问配置项中的:“socket Server地址:socketServer: '//im-api-v2.easemob.com/ws',    // ”和“rest Server地址:restServer: '//a1.easemob.com' ”,“需不需要配置成自己项目地址,或者自己去定义这两个参数?”,对于这个问题,我只能回答说千万别!这个是定义好了的,你要做的基本就只需要将appkey:改成自己申请的,其他几乎不用动。
WebIM.js:将SDK的配置以及引入的SDK进一步做处理简称:初始化。
导出SDK 并引入WebIMConfig.js文件,将SDK重命名为WebIM挂载到window或uni或wx下,视情况决定。
图片16.png

同样这一步文档也有详细的描述:
图片17.png

3. 完成文件导入挂载,开始配置并添加监听回调。
如图,监听仅截图一部分:
图片18.jpg

在集成Web端(包括小程序以及Uni-app)IM中,所有的事件监听都是要靠WebIM.conn.listen( ),由此可以看出其重要性。
所以在初始化阶段结束后我们首要将监听配置添加上。
至于监听的具体位置应该添加在哪里这个实际并没有严格的要求,PCwebDemo中是将监听写在了WebIM.js中,微信小程序Demo是将监听写在了app.js,uni-app是将监听写在了App.vue根组件中,多数是写在了全局。但其实监听也可以写在局部,前提局部能够访问到WebIM,然后在局部添加上监听回调,不管是全局监听还是局部监听都要在SDK接口调用之前布置好监听并开启监听。
下面我们再来聊一下监听中的个别监听回调的作用以及实际运用,其监听的作用文档已有详细描写且较为好了解,这里就不再一一赘述。
onOpened: function ( ) {},         //连接成功回调 
此监听为与环信连接成功之后触发的监听回调,经常会出现一个问题就是明明环信conn.open登陆接口都已经返回success都已经返回token了,可是发送消息依然报错,接收消息也接收不到。那么我们首要检查的就是该监听有没有触发,放个console.log( )看下登陆之后的该回调有没有成功的打印,如果打印了那么说明成功与环信建立了连接!也就可以进行其他的操作了。
经常有朋友问:“登陆成功之后打印message形参为什么是undefined?是不是登陆有问题?”,其实不是这个message并不会有任何返回,登陆是否成功一切以onOpened监听回调是否执行为准!
图片19.png

在该监听的实际运用中Demo中其实也做了很好的演示,就是登陆之后的页面跳转,这个非常常用,因为IM聊天功能是否可以使用首要的判断必然是与环信是否建立连接成功,成功那么跳转进入聊天页面。
onClosed: function ( message ) {},        //连接关闭回调
此监听为与环信连接关闭的监听回调,其实也没有具体要描述的地方,更多的是该监听经常用来处理在与环信断开连接时候的一些UI层面的提示,或者再意外断开连接之后退回到login页面,以及有些场景下进行手动重连。
onMessage:function ( message ) {},   //监听收到消息的回调
这里的onMessage是对所有收到消息的回调的一个简称,例如:onTextMessage 监听文本消息、onPictureMessage 监听图片消息、onAudioMessage 监听音频消息,等等... 这个监听相信不用过多描述就可以清楚其功能,消息监听回调非常的核心,所有的消息都是仰仗这几个监听回调拿到的。测试的时候可以在监听回调中定义形参并控制台打印参数查看消息监听是否正常触发并接收数据。
截图为onTextMessage监听收到的单聊消息
图片20.png

onError: function ( message ) {},          //失败回调
此监听回调可监听SDK中大多数的error抛出,在集成开发过程中帮助更快定位问题原因,并且可在回调中根据error信息从UI层面进行提示,因此其实际运用相对也常用。
截图为Uni-appDemo利用onError监听做出的提示以及一些操作。
图片21.png

当然有些error出现之后大家可能不知道其具体含义下面为分析一下error异常的判断思路,
首先onError监听回调的message字段就已经给出了大致error原因:
例图中onError回调返回的对象中的message字段value的值中给出了“login failed”登陆异常,接着我们看data字段下给出的具体原因,不难看出invalid password 不打开百度翻译也能猜出密码有问题,那么我们也就可以以此判断,登陆密码错误,或者对用户做出弹窗提示。
图片22.png

同样的type:1,同样的message:“login failed”,error详情就又不一样了,error介绍:“user not found” 用户找不到,所以具体的判断error原因还是要以data下的error_description给出的原因去定位。
图片23.png

当然有些error也会出现一些其他的情况,并不返回message 参数,data也是空的,只给出了一个type类型,像这种情况其实就是在没有登陆的情况下调用了SDK接口,那么这时首先要判断的就是是否登录了环信,或者已经登陆但是又断开了与环信的连接。
截图为未登录调用了获取群组详情返回的error。
图片24.jpg

在已登录的情况下查询群组详情,error详情直接给出了找不到 group ID为XXXXXX的群组,此时就可以以此判断,或给出提示查找不到群ID为...的详情
图片25.png

onPresence: function ( message ) {},       //处理“广播”或“发布-订阅”消息,如联系人订阅请求、处理群组、聊天室被踢解散等消息。
此监听正如描述所讲,处理“广播”或“发布-订阅消息”,至于发布-订阅消息可以去了解一下设计模式,发布订阅者模式,这里就不再进行解释了,这里主要讲的是该监听如果在实际运用中的作用是什么。
我们先看下Uni-appDemo中的示例:
图片26.png

通过观察我们看到了示例中运用到了switch case 语句通过回调里message中不同的type类型从而对应执行不同的提示或其他逻辑操作。
在实际SDK集成过程中,例如:“申请添加好友、删除好友,邀请入群,他人退群,新增群组管理员、他人加入聊天室,退出聊天室”等操作均会触发此监听的执行并回调出对应行为类型的type字段,根据type类型我们从而可以给出对应的UI层面的提示,或发布-订阅事件,uni-appDemo中的disp.fire(‘XXXX’),正是发布-订阅模式中的发布事件。
如果我们需要详细的类型,则可以在文档中的事件监听找到:
好友状态事件监听:
现有的好友事件监听,已新增对应的回调监听:
图片27.png

群组事件监听:
文档地址:
http://docs-im.easemob.com/im/ ... %25AC
图片28.png

聊天室事件监听:
文档地址:
http://docs-im.easemob.com/im/ ... %25AC

图片29.png


以上变为常用的几个监听的介绍,具体的使用还是以咱们实际项目中的逻辑为准。
SDK,config配置,以及初始化,监听都已配置完毕,下面我们就开始注册、登陆环信ID,发出我们的第一条消息吧!
四、Hello World ! (从注册登录开始)
环信所有的功能性SDK接口调用都是要从注册及登陆成功方可有效使用,如已注册,登陆则是客户端调用的第一个接口。
下面我将从注册接口开始讲起:
注册,web端注册接口如图:
图片30.png

客户端是否能够直接调用注册接口,需要先去环信IM管理后台检查该Appkey的注册模式是否为开放注册,授权模式的话,此接口调用注册会直接出现下图情况
图片31.png

Open registration doesn't allow, so register user need token
开放注册不允许,所以注册用户需要token
注册成功会触发注册接口中的success回调,并有注册信息的返回如图:返回字段包含创建时间,appname,orgname等信息。
图片32.png

注册完成之后便可以开始调用登陆接口完成登录操作。
下面我们来了解SDK提供的两种登陆方式:
1. 用户名密码登陆(环信ID、密码登陆),截图为文档——登陆接口:
图片33.png

user: String //环信ID
pwd:String //注册时的密码
appKey  // appkey 初始化后appkey
2. token登陆(使用token进行免密登陆)
图片34.png

该登陆方式可以进行免密登陆,常见用于刷新页面之后的二次登陆。
比较常用到的场景,例如从login页面跳转到chat页面,用户在当前页面刷新之后不再进行login页面登陆直接正常进行聊天。(刷新页面是会断开与环信的连接,如不登陆则无法调用SDK接口)。
token获取:token的获取是在用户名密码登陆成功后的success回调中返回获取。
图片35.png

access_token字段为token。
expires_in字段为token有效期,单位为秒,有效期内token可重复使用。
uni-app-demo中的用户名密码登陆的接口调用
图片36.png

Uni-app-demo中的token登陆:
图片37.png

有时我们会遇到success成功的返回了token,可是发送消息或者调用其他接口还是会出现报错,或者提示未登录的error,这时我们首要排查的情况还是检查一下onOpened监听是否成功触发,与环信的连接建立还是要以onOpened监听触发为准!
图片38.png

图片39.png

监听成功打印,意味着登陆成功!
这里顺便给大家两个检验是否登录的方法。
1. WebIM.conn方法下有一个logOut字段,该字段为true时表明未登录状态,该字段为false时表明登陆:
图片40.png

2. WebIM.conn.isOpened () 方法有三个状态,undefined为未登录状态,true为已登录状态,false为未登录状态,可以根据这三个状态去判断是否登录:
图片41.png

到这里登陆就说完了,至于为什么没有讲下去也是因为只要登录成功,其他接口就可以按需调用,相信以各位小伙伴的能力接着往下进行也不是很困难了,当然也说不定有些奇葩问题搞不定,那么建议咱们可以去环信官网去寻求我们支持同事的帮助,将遇到的问题描述清楚,问题将在24H以内得到回复。
图片42.png

图片43.png

最后愿JavaScript之父布兰登·艾克
保佑各位项目顺利上线! 收起阅读 »

双旦有“礼”狂欢季

今天有收到圣诞礼物吗?
如果没有,也没关系哦。
因为这次双旦节,
我们给大家准备了非常丰厚的礼品,
快来扫码参加活动吧!
微信图片_20201225105015.jpg

 
今天有收到圣诞礼物吗?
如果没有,也没关系哦。
因为这次双旦节,
我们给大家准备了非常丰厚的礼品,
快来扫码参加活动吧!
微信图片_20201225105015.jpg

 

公开课 | 移动开发助力在线教育增长实战

2020年,线上教育大火!受用户需求的影响,学校、教育培训机构都把线下的课程搬到线上,在线教育行业随之开启一场“新基建”。

然而,这是一项巨大且缓慢的工程,线上教学平台的开发、营销获客方法的探索、运营工具的管理、教学内容的研发……教育行业究竟怎样实现线上和线下的融合?教育OMO(Online-Merge-Offline)模式又该怎么跑通?教育增长到底该如何实现?

环信联合MobTech袤博及保利威,结合当前教育行业的最新趋势,给出一系列全新产品和技术解决方案,全方位助力教育行业平滑转型、降本增效。

干货1:从在线教育app全生命周期精准分析运营之道

干货2:通过稳定安全的PaaS+SaaS+云服务,打造教学、管理、营销一体化解决方案

干货3:通过直播平台助力教育机构建立私域流量

干货4:资深在线教育专家独家分享爆发式增长秘诀


更有精美礼品与价值万元的充电大礼包等你来领!

如果你还在为在线教育的运营突破口而困扰

那就抓紧报名参与!一起来碰撞出灵感的火花吧!
 
直播观看链接:https://live.polyv.cn/watch/2047367 

1.jpg

2.jpg

环信冬冬_副本.jpg

添加小助手微信【huanxin-hh】进入活动直播群
继续阅读 »
2020年,线上教育大火!受用户需求的影响,学校、教育培训机构都把线下的课程搬到线上,在线教育行业随之开启一场“新基建”。

然而,这是一项巨大且缓慢的工程,线上教学平台的开发、营销获客方法的探索、运营工具的管理、教学内容的研发……教育行业究竟怎样实现线上和线下的融合?教育OMO(Online-Merge-Offline)模式又该怎么跑通?教育增长到底该如何实现?

环信联合MobTech袤博及保利威,结合当前教育行业的最新趋势,给出一系列全新产品和技术解决方案,全方位助力教育行业平滑转型、降本增效。

干货1:从在线教育app全生命周期精准分析运营之道

干货2:通过稳定安全的PaaS+SaaS+云服务,打造教学、管理、营销一体化解决方案

干货3:通过直播平台助力教育机构建立私域流量

干货4:资深在线教育专家独家分享爆发式增长秘诀


更有精美礼品与价值万元的充电大礼包等你来领!

如果你还在为在线教育的运营突破口而困扰

那就抓紧报名参与!一起来碰撞出灵感的火花吧!
 
直播观看链接:https://live.polyv.cn/watch/2047367 

1.jpg

2.jpg

环信冬冬_副本.jpg

添加小助手微信【huanxin-hh】进入活动直播群 收起阅读 »

活动 | 2020TOP100全球软件案例研究峰会

1.jpg
今年的TOP100全球软件案例研究峰会(简称TOP100Summit)将于12月17-20日在北京举行,100+全球优秀“技术大脑”,将帮助大家在互联网时代开放、自由地汲取案例带来的独特价值,转化为所有研发中心可用的促进成长的案例。
作为科技界颇具影响力的案例研究峰会,TOP100Summit在往届举办中也备受各行业关注,与往年不同的是,今年大会选题、质量及特色会更加突出,下面让我们一起来看看,本届大会有哪些值得关注的亮点!

开拓视野,感知技术脉动
不同于媒体的追逐热点和新奇概念,TOP100在案例评选时,更崇尚专业的力量和案例落地实践,案例提交者需要从案例目标、成功要点与背后教训、ROI 分析、案例启示、案例对组织的意义等多个维度进行结构化提炼,从而达到让听众有所收益的目的,保证了每年发布的案例学习榜单是最有学习价值的。
2.jpeg

业界大咖,联袂出品
在 TOP100Summit 上,众多产业互联网领袖将与各位参会者见面。小米集团副总裁崔宝秋, 美团副总裁、首席科学家夏华夏,美图秀秀运营副总裁陈辉,快狗打车CTO沈剑,快手产品副总裁徐欣,CNCF中国区总裁Keith Chan等18位国内外享誉盛名的专家、各大公司技术委员会与设计委员会负责人担任本届大会的联席主席,将共同为大家呈现本届大会的最新洞见与思路,并详解本年度的最新落地实践及最新的行业趋势。
3.jpg

18门必修课,打造团队组织力
当今时代,企业是驱动创新的主要动力。创新不是易事,通过别人案例看清创新方法,对避免重复犯错或少走弯路都不无裨益。
通过收集往届参会人员的需求,本届峰会将分为六大技术角色论坛、十八大专题。关于产品、运营、效能、大前端、工程实践、AI、工具链、DevOps等领域都设立了专题,分别覆盖游戏、软件、电商、金融、能源、交通、石油、游戏、医疗、工业、零售、出行、教育等行业。
4.jpeg

目前已确定有AWS、Facebook、阿里、百度、滴滴等技术型标杆公司精英人士参与。

60分钟深度解读,探索发现与实践心得
区别于其他的大会,TOP100Summit不含任何形式的广告,50分钟为团队带头人深度解读,10分钟为联席主席梳理启示。
5.jpeg

TOP100Summit借助于纯学术和实际案例研究方面的结合显现出巨大推动力,帮助越来越多的高校在产学研结合上投入了更多的精力,越来越多的教授投入到了基于企业实践的实境研究之中,通过扎实、有深度的案例研究,为教学和研究提供源源不断的智慧。

精选案例解读,洞见行业未来
在大会上,100位讲师所讲的内容均以解决企业级研发实际存在的问题为出发点,解决真实出现的难题,选题委员会不选取那些「炫技」的案例。同时,案例涉及的技术均为软件应用周期内处于核心位置的技术,是90%以上的企业都会应用到的技术。
我们甄选了一部分具有代表性的案例,希望可以帮助大家解决当下工作中的痛点:
6.jpeg

7.jpeg

8.jpeg

9.jpeg

壹佰案例甄选年度100分案例教学
全球软件案例研究峰会(简称“壹佰案例”)是科技界一年一度的案例研究榜单,旨在发现有案例教学意义的项目或方法论,如同商业领域的哈佛案例,科技界的壹佰案例榜单每年精选100件案例题材,有别于媒体的追逐热点和新奇概念,壹佰案例榜单更崇尚专业的力量和案例落地实践,通过邀请国内外享誉盛名的专家、各大公司技术委员会与设计委员会负责人担任联席主席,向领先公司和早期实践者征集年度里程碑或杰出成果背后的案例故事。组委会通过专有的案例开发流程、案例评审机制,以及数字化案例评审平台,帮助案例提交者从案例目标、成功要点与背后教训、ROI分析、案例启示、案例对组织的意义等多个维度进行结构化提炼,从而达到让听众有所收益的目的,保证了每年发布的壹佰案例学习榜单是最有学习价值的案例。

自2012年创办至今,壹佰案例榜单涵盖了Facebook、Google、Airbnb、Apple、LinkedIn、Amazon、Microsoft、Walmart、Oracle、Lyft、Uber、Twitter、Zynga、Adobe、Autodesk、SAP、淘宝、天猫、58、京东、360、小米 、中兴、腾讯游戏、华为、网易、微博、东软、广发、百度、搜狐、携程、苏宁、魅族、美团、乐视、迅雷、网龙、途牛、滴滴、猎豹、移动、海信、浪潮、金山等国内外知名软件、互联网企业。

媒体咨询:景景 13920859305

赞助咨询:sissi  13743218801
继续阅读 »
1.jpg
今年的TOP100全球软件案例研究峰会(简称TOP100Summit)将于12月17-20日在北京举行,100+全球优秀“技术大脑”,将帮助大家在互联网时代开放、自由地汲取案例带来的独特价值,转化为所有研发中心可用的促进成长的案例。
作为科技界颇具影响力的案例研究峰会,TOP100Summit在往届举办中也备受各行业关注,与往年不同的是,今年大会选题、质量及特色会更加突出,下面让我们一起来看看,本届大会有哪些值得关注的亮点!

开拓视野,感知技术脉动
不同于媒体的追逐热点和新奇概念,TOP100在案例评选时,更崇尚专业的力量和案例落地实践,案例提交者需要从案例目标、成功要点与背后教训、ROI 分析、案例启示、案例对组织的意义等多个维度进行结构化提炼,从而达到让听众有所收益的目的,保证了每年发布的案例学习榜单是最有学习价值的。
2.jpeg

业界大咖,联袂出品
在 TOP100Summit 上,众多产业互联网领袖将与各位参会者见面。小米集团副总裁崔宝秋, 美团副总裁、首席科学家夏华夏,美图秀秀运营副总裁陈辉,快狗打车CTO沈剑,快手产品副总裁徐欣,CNCF中国区总裁Keith Chan等18位国内外享誉盛名的专家、各大公司技术委员会与设计委员会负责人担任本届大会的联席主席,将共同为大家呈现本届大会的最新洞见与思路,并详解本年度的最新落地实践及最新的行业趋势。
3.jpg

18门必修课,打造团队组织力
当今时代,企业是驱动创新的主要动力。创新不是易事,通过别人案例看清创新方法,对避免重复犯错或少走弯路都不无裨益。
通过收集往届参会人员的需求,本届峰会将分为六大技术角色论坛、十八大专题。关于产品、运营、效能、大前端、工程实践、AI、工具链、DevOps等领域都设立了专题,分别覆盖游戏、软件、电商、金融、能源、交通、石油、游戏、医疗、工业、零售、出行、教育等行业。
4.jpeg

目前已确定有AWS、Facebook、阿里、百度、滴滴等技术型标杆公司精英人士参与。

60分钟深度解读,探索发现与实践心得
区别于其他的大会,TOP100Summit不含任何形式的广告,50分钟为团队带头人深度解读,10分钟为联席主席梳理启示。
5.jpeg

TOP100Summit借助于纯学术和实际案例研究方面的结合显现出巨大推动力,帮助越来越多的高校在产学研结合上投入了更多的精力,越来越多的教授投入到了基于企业实践的实境研究之中,通过扎实、有深度的案例研究,为教学和研究提供源源不断的智慧。

精选案例解读,洞见行业未来
在大会上,100位讲师所讲的内容均以解决企业级研发实际存在的问题为出发点,解决真实出现的难题,选题委员会不选取那些「炫技」的案例。同时,案例涉及的技术均为软件应用周期内处于核心位置的技术,是90%以上的企业都会应用到的技术。
我们甄选了一部分具有代表性的案例,希望可以帮助大家解决当下工作中的痛点:
6.jpeg

7.jpeg

8.jpeg

9.jpeg

壹佰案例甄选年度100分案例教学
全球软件案例研究峰会(简称“壹佰案例”)是科技界一年一度的案例研究榜单,旨在发现有案例教学意义的项目或方法论,如同商业领域的哈佛案例,科技界的壹佰案例榜单每年精选100件案例题材,有别于媒体的追逐热点和新奇概念,壹佰案例榜单更崇尚专业的力量和案例落地实践,通过邀请国内外享誉盛名的专家、各大公司技术委员会与设计委员会负责人担任联席主席,向领先公司和早期实践者征集年度里程碑或杰出成果背后的案例故事。组委会通过专有的案例开发流程、案例评审机制,以及数字化案例评审平台,帮助案例提交者从案例目标、成功要点与背后教训、ROI分析、案例启示、案例对组织的意义等多个维度进行结构化提炼,从而达到让听众有所收益的目的,保证了每年发布的壹佰案例学习榜单是最有学习价值的案例。

自2012年创办至今,壹佰案例榜单涵盖了Facebook、Google、Airbnb、Apple、LinkedIn、Amazon、Microsoft、Walmart、Oracle、Lyft、Uber、Twitter、Zynga、Adobe、Autodesk、SAP、淘宝、天猫、58、京东、360、小米 、中兴、腾讯游戏、华为、网易、微博、东软、广发、百度、搜狐、携程、苏宁、魅族、美团、乐视、迅雷、网龙、途牛、滴滴、猎豹、移动、海信、浪潮、金山等国内外知名软件、互联网企业。

媒体咨询:景景 13920859305

赞助咨询:sissi  13743218801 收起阅读 »

【直播】小程序客服使用场景及集成方案

时间:12月25日下午 15:00-15:30
时长:半小时
课程主题:小程序客服解决方案
主讲人:客服云产品经理 王芳

1、课程介绍:
小程序客服使用场景及集成方案

2、受众人群:
环信客服云用户

3、培训内容:
小程序客服适用的业务场景
各种场景对应的集成方案
            
点击报名直播链接:
https://ke.qq.com/course/42352 ... 22305
扫码加入环信客服云公开课企业微信群

008e39785613a515bb45420eed40f15.jpg


 
继续阅读 »
时间:12月25日下午 15:00-15:30
时长:半小时
课程主题:小程序客服解决方案
主讲人:客服云产品经理 王芳

1、课程介绍:
小程序客服使用场景及集成方案

2、受众人群:
环信客服云用户

3、培训内容:
小程序客服适用的业务场景
各种场景对应的集成方案
            
点击报名直播链接:
https://ke.qq.com/course/42352 ... 22305
扫码加入环信客服云公开课企业微信群

008e39785613a515bb45420eed40f15.jpg


  收起阅读 »

环信2020年终大促,转发送京东卡抽IPhone12!

环信年终盛典拉开帷幕,涵盖即时通讯云、音视频云、客服云、机器人四大产品线。多重好礼等你来拿:一重礼超值赠费,低成本高续航;二重礼感恩回馈,拼手气抽大奖,iPhone12等终极大奖等你来拿;三重礼纪念盲盒,探索未知惊喜;四重礼雨露均沾,你体验我买单。
参与环信年终盛典海报裂变活动,即有机会获得1000元京东卡并同时参与微信群抽取实物大奖。赶紧上车,错过等一年!
01
交个朋友,扫码参与转发就抽奖


3.jpg

 
02
环信2020年终大促盛典详情


5fcda7aba2a44.jpg

 
继续阅读 »
环信年终盛典拉开帷幕,涵盖即时通讯云、音视频云、客服云、机器人四大产品线。多重好礼等你来拿:一重礼超值赠费,低成本高续航;二重礼感恩回馈,拼手气抽大奖,iPhone12等终极大奖等你来拿;三重礼纪念盲盒,探索未知惊喜;四重礼雨露均沾,你体验我买单。
参与环信年终盛典海报裂变活动,即有机会获得1000元京东卡并同时参与微信群抽取实物大奖。赶紧上车,错过等一年!
01
交个朋友,扫码参与转发就抽奖


3.jpg

 
02
环信2020年终大促盛典详情


5fcda7aba2a44.jpg

  收起阅读 »

【公开课】全渠道客服在教育场景下的适用介绍

时间:11月20日下午 15:00-15:30
时长:半小时
课程主题:全渠道客服在教育场景下的适用介绍
主讲人:环信 解决方案专家 王林

 
课程介绍:
1.面向对象:教育行业负责人、运营、客服或信息部门
2.课程方向:解决方案
3.培训要达成的目标:
A.帮助客户拓宽教育场景下的业务实现思路
B.提供场景快速搭建的解决方案     

点击报名直播链接:
http://s.easemob.com/eY1Teb
扫码加入环信客服云公开课企业微信群:

微信图片_20201118161039.png

 
继续阅读 »
时间:11月20日下午 15:00-15:30
时长:半小时
课程主题:全渠道客服在教育场景下的适用介绍
主讲人:环信 解决方案专家 王林

 
课程介绍:
1.面向对象:教育行业负责人、运营、客服或信息部门
2.课程方向:解决方案
3.培训要达成的目标:
A.帮助客户拓宽教育场景下的业务实现思路
B.提供场景快速搭建的解决方案     

点击报名直播链接:
http://s.easemob.com/eY1Teb
扫码加入环信客服云公开课企业微信群:

微信图片_20201118161039.png

  收起阅读 »

Uni-app:(不通用)HbuilderX启动别人uni-app项目运行到小程序,提示打开了小程序开发者工具但是不进页面

前段时间下载了环信uni-app Demo,启动HBuilderX,运行到了小程序,但是死活打不开页面不知道什么原因。开发者工具打开了但是页面进不去。
不是什么大问题,但是也很耽误时间,Hello world,连Hello都Hello不出来,最后研究了半天才解决了这个问题下面是解决方法:
除了查看对应版本和开启端口外还需注意,因为微信开发者工具是和对应的微信账号绑定的,所以需要设置对应账号的AppID才行,如果使用的是别人的需要用自己的AppID,但是启动别人的项目只用在,mainifest.json下的微信小程序配置,找到微信小程序APPID 删除掉然后再重新启动运行就可以了。




由于大小限制上传的图片是个压缩包,大家不清楚位置的可以下载下来瞜一眼。
 
继续阅读 »
前段时间下载了环信uni-app Demo,启动HBuilderX,运行到了小程序,但是死活打不开页面不知道什么原因。开发者工具打开了但是页面进不去。
不是什么大问题,但是也很耽误时间,Hello world,连Hello都Hello不出来,最后研究了半天才解决了这个问题下面是解决方法:
除了查看对应版本和开启端口外还需注意,因为微信开发者工具是和对应的微信账号绑定的,所以需要设置对应账号的AppID才行,如果使用的是别人的需要用自己的AppID,但是启动别人的项目只用在,mainifest.json下的微信小程序配置,找到微信小程序APPID 删除掉然后再重新启动运行就可以了。




由于大小限制上传的图片是个压缩包,大家不清楚位置的可以下载下来瞜一眼。
  收起阅读 »

环信双十一狂欢月:优惠不只五折,充值拿iphone12,参与推荐赢千元京卡

参与裂变拿千元京东卡
6.jpg

IM云+音视频云专场
1.jpg

2.jpg

3.jpg

4.jpg

5.jpg

IM云+音视频云专场​
1.jpg

2.jpg

3.jpg

 
继续阅读 »
参与裂变拿千元京东卡
6.jpg

IM云+音视频云专场
1.jpg

2.jpg

3.jpg

4.jpg

5.jpg

IM云+音视频云专场​
1.jpg

2.jpg

3.jpg

  收起阅读 »

【扫码进群抽大奖】小程序&Uniapp跨平台开发实战[环信直播公开课48期]

华为AI音箱、运动手环、猫爪杯~~

10月21日下午3:00关注环信线上公开课《小程序&Uniapp跨平台开发实战》,各种大奖等你来拿哦~

抽奖&直播通道:http://mudu.tv/watch/7866499 

IMhere备份_副本_副本.jpg

 
继续阅读 »
华为AI音箱、运动手环、猫爪杯~~

10月21日下午3:00关注环信线上公开课《小程序&Uniapp跨平台开发实战》,各种大奖等你来拿哦~

抽奖&直播通道:http://mudu.tv/watch/7866499 

IMhere备份_副本_副本.jpg

  收起阅读 »

2020 中国开源年会(COSCon'20)再启程:开源向善(Open Source for Good)


1.jpg

 时间:2020年10月24-25日

线上直播地址:bilibili & Youtube

讲师互动平台:Zoom

 时间弹指飞逝,转眼即过去了一年。不知道各位在这多舛的半年间又和开源这二字摩擦出了多少火花呢?COSCon'2020已经迫不及待地想要邀请你们加入了!
 
尽管今年由于疫情的影响,无法在线下建立大舞台,和大家一起面对面交流开源方面的心得,但是我们依旧准备了丰富多彩的线上直播活动,并且将会在上海、北京、深圳和成都设立分会场,广邀当地的小伙伴们一起加入到这场开源盛事中来。尽管今年由于特殊原因变成了线上直播,但是我们相信推广开源的热情并不会减少。去年在线图文直播总量达到了13,400人次,我们相信今年这个人数将会变得更多。
 
本次活动依旧由开源社主办,而今年的主题是开源向善(Open Source for Good)。这一主题在今年疫情肆虐的大背景下被提出,同时借由 wuhan2020 项目的成功被广泛认可:开源拥有巨大的力量。之后我们还将推出讲师征集令。
2.png


本次大会将和去年一样,为期两天,具体时间为:2020年10月24-25日。在这两天时间中,将会有多位导师进行各种各样的主题演讲,为大家带来不一样的开源之旅。
 
最后,在 COSCon'20 筹办之际,我们开源社正式官宣自己的吉祥物啦!就是TA:小O。大家有没有觉得TA的形象很符合广大程序员们智慧、呆萌的人设呢?
3.jpg

*本形象由开源社设计组朱亿钦设计 
继续阅读 »

1.jpg

 时间:2020年10月24-25日

线上直播地址:bilibili & Youtube

讲师互动平台:Zoom

 时间弹指飞逝,转眼即过去了一年。不知道各位在这多舛的半年间又和开源这二字摩擦出了多少火花呢?COSCon'2020已经迫不及待地想要邀请你们加入了!
 
尽管今年由于疫情的影响,无法在线下建立大舞台,和大家一起面对面交流开源方面的心得,但是我们依旧准备了丰富多彩的线上直播活动,并且将会在上海、北京、深圳和成都设立分会场,广邀当地的小伙伴们一起加入到这场开源盛事中来。尽管今年由于特殊原因变成了线上直播,但是我们相信推广开源的热情并不会减少。去年在线图文直播总量达到了13,400人次,我们相信今年这个人数将会变得更多。
 
本次活动依旧由开源社主办,而今年的主题是开源向善(Open Source for Good)。这一主题在今年疫情肆虐的大背景下被提出,同时借由 wuhan2020 项目的成功被广泛认可:开源拥有巨大的力量。之后我们还将推出讲师征集令。
2.png


本次大会将和去年一样,为期两天,具体时间为:2020年10月24-25日。在这两天时间中,将会有多位导师进行各种各样的主题演讲,为大家带来不一样的开源之旅。
 
最后,在 COSCon'20 筹办之际,我们开源社正式官宣自己的吉祥物啦!就是TA:小O。大家有没有觉得TA的形象很符合广大程序员们智慧、呆萌的人设呢?
3.jpg

*本形象由开源社设计组朱亿钦设计  收起阅读 »

全球顶级开源大神们现身 COSCon'20

业界最具影响力的开源年度盛会2020中国开源年会 (COSCon'20) 将于 10月24-25日由开源社举办。COSCon 以其独特定位及日益增加的影响力,吸引越来越多的顶级企业和国际基金会的大力支持。
1.jpg

COSCon 的独特定位就是成为一个最理想、最走心的对接“优秀的开源项目社区”与“有志于学习及参与贡献开源的开发者与志愿者”的园地。为此,中国开源年会每年都从国内外请来顶级开源大神及重磅开源嘉宾,和大家直接面对面交流互动。

今年的大会,我们将以开源向善(Open Source for Good)为主题!在我们看来,科技应该是一股向善的力量,而开源天生就是一股向善的力量。面对一个急速变幻、急速下坠,甚至急速撕裂的世界,开源更应发挥自己的协作优势,凝聚众人的向善之力,汇聚众人的向善之智,为这个世界变得更好,做出贡献。

这次的中国开源年会,将采取线上线下相结合的形式。大会将持续两天,我们策划的主题包括:教育与公益、社区与治理、社会/经济/法律/文化、女性半边天、人工智能、云原生与微服务、数据技术、开源硬件与操作系统等方向。接下来小编就为大家剧透 COSCon'20 大神讲师阵容吧!
2.jpg

(以上按照姓氏字母排序,不分先后)

秉承从 2015 Apache 中国路演(开源社主办)到 2016-2019 中国开源年会(COSCon'16 - COSCon'19)的传统,本次Apache 中国路演再度与开源社联手举办,我们也将和 Apache 软件基金会(ASF)及其 Apache Local Community - Beijing 携手邀请 Apache 国内外大神、大咖们,今年继续参与分享精彩话题!
3.jpg

全球最大的开源基金会 Apache 软件基金会(ASF),今年再度有多位重磅大咖参加 ASF 与 开源社 联合举办的 Apache 中国路演。由非常关注和投入中国开源运动并多次来华访问和演讲的 ASF 董事(前任主席)Craig Russell,作为大会首位主题演讲嘉宾,为大会拉开序幕,和大家分享 Building Communities The Apache Way (Apache 的社区建设之道),同时还有多位 Apache 项目大神在接下来二天的大会里,和大家分享各种精彩的开源话题。

精彩预告
Jono Bacon, "社区运营的艺术" 作者。若是对开源社区运营及治理感兴趣者,推荐阅读!我们更荣幸地邀请到作者本人现身大会开讲。
4.jpg

GitHub 的 COO Erica Brescia 女士将会给大家带来全球领先的代码托管与协作平台的最新发展方向以及在中国的发展战略。

激动人心的是,以太坊创始人 V 神 Vitalik Buterin 现身和大家分享区块链发展的趋势。

中国首个开源基金会“开放原子开源基金会”已于近日亮相,我们也特地邀请到基金会 TOC 主席堵俊平与副主席谭中意给大家第一时间解读这家备受关注的基金会的情况。

作为国内开源与新技术的思潮洞察者与引领者,中国信通院云计算与大数据研究所所长何宝宏博士(笔名何所思,相信很多人都读过他的文章或是书),也将继 COSCon'17、  COSCon'19 之后,再度现身 COSCon'20。他的主题演讲《开源法则》绝对值得期待。

Linux 基金会 AI 基金会执行董事以及 Linux 基金会战略计划副总裁 Ibrahim Haddad 将给大家带来题为 “Accelerating Innovation in the AI Market” 的报告,分享人工智能领域中的开源创新之道。

还有开源社联合发起人、CSDN 创始人/CEO 蒋涛也将再度现身 COSCon'20,和观众分享精彩洞见。

还有一些主会场主题演讲神秘大咖将陆续披露,请拭目以待。

您一定有许多问题想问这些大神们,欢迎在本文评论区中提出,小编精选之后会直接提交给他们哦!

除了顶级的开源大神们,本次年会还汇集了国内外众多的开源人士,在十多个不同的方向上展开为期两天的开源狂欢节,一起来分享开源的方方面面,交流全球的开源趋势,探索中国的开源发展之路,期待大家的到来。
5.jpg

各个分论坛的最新信息将于近期公布,敬请关注!

报名方式
对 COSCon'20 内容感兴趣的你可以通过下方的链接或点击阅读原文进行报名,期待你的参与!

报名链接:https://www.bagevent.com/event/6840909

 


 
继续阅读 »
业界最具影响力的开源年度盛会2020中国开源年会 (COSCon'20) 将于 10月24-25日由开源社举办。COSCon 以其独特定位及日益增加的影响力,吸引越来越多的顶级企业和国际基金会的大力支持。
1.jpg

COSCon 的独特定位就是成为一个最理想、最走心的对接“优秀的开源项目社区”与“有志于学习及参与贡献开源的开发者与志愿者”的园地。为此,中国开源年会每年都从国内外请来顶级开源大神及重磅开源嘉宾,和大家直接面对面交流互动。

今年的大会,我们将以开源向善(Open Source for Good)为主题!在我们看来,科技应该是一股向善的力量,而开源天生就是一股向善的力量。面对一个急速变幻、急速下坠,甚至急速撕裂的世界,开源更应发挥自己的协作优势,凝聚众人的向善之力,汇聚众人的向善之智,为这个世界变得更好,做出贡献。

这次的中国开源年会,将采取线上线下相结合的形式。大会将持续两天,我们策划的主题包括:教育与公益、社区与治理、社会/经济/法律/文化、女性半边天、人工智能、云原生与微服务、数据技术、开源硬件与操作系统等方向。接下来小编就为大家剧透 COSCon'20 大神讲师阵容吧!
2.jpg

(以上按照姓氏字母排序,不分先后)

秉承从 2015 Apache 中国路演(开源社主办)到 2016-2019 中国开源年会(COSCon'16 - COSCon'19)的传统,本次Apache 中国路演再度与开源社联手举办,我们也将和 Apache 软件基金会(ASF)及其 Apache Local Community - Beijing 携手邀请 Apache 国内外大神、大咖们,今年继续参与分享精彩话题!
3.jpg

全球最大的开源基金会 Apache 软件基金会(ASF),今年再度有多位重磅大咖参加 ASF 与 开源社 联合举办的 Apache 中国路演。由非常关注和投入中国开源运动并多次来华访问和演讲的 ASF 董事(前任主席)Craig Russell,作为大会首位主题演讲嘉宾,为大会拉开序幕,和大家分享 Building Communities The Apache Way (Apache 的社区建设之道),同时还有多位 Apache 项目大神在接下来二天的大会里,和大家分享各种精彩的开源话题。

精彩预告
Jono Bacon, "社区运营的艺术" 作者。若是对开源社区运营及治理感兴趣者,推荐阅读!我们更荣幸地邀请到作者本人现身大会开讲。
4.jpg

GitHub 的 COO Erica Brescia 女士将会给大家带来全球领先的代码托管与协作平台的最新发展方向以及在中国的发展战略。

激动人心的是,以太坊创始人 V 神 Vitalik Buterin 现身和大家分享区块链发展的趋势。

中国首个开源基金会“开放原子开源基金会”已于近日亮相,我们也特地邀请到基金会 TOC 主席堵俊平与副主席谭中意给大家第一时间解读这家备受关注的基金会的情况。

作为国内开源与新技术的思潮洞察者与引领者,中国信通院云计算与大数据研究所所长何宝宏博士(笔名何所思,相信很多人都读过他的文章或是书),也将继 COSCon'17、  COSCon'19 之后,再度现身 COSCon'20。他的主题演讲《开源法则》绝对值得期待。

Linux 基金会 AI 基金会执行董事以及 Linux 基金会战略计划副总裁 Ibrahim Haddad 将给大家带来题为 “Accelerating Innovation in the AI Market” 的报告,分享人工智能领域中的开源创新之道。

还有开源社联合发起人、CSDN 创始人/CEO 蒋涛也将再度现身 COSCon'20,和观众分享精彩洞见。

还有一些主会场主题演讲神秘大咖将陆续披露,请拭目以待。

您一定有许多问题想问这些大神们,欢迎在本文评论区中提出,小编精选之后会直接提交给他们哦!

除了顶级的开源大神们,本次年会还汇集了国内外众多的开源人士,在十多个不同的方向上展开为期两天的开源狂欢节,一起来分享开源的方方面面,交流全球的开源趋势,探索中国的开源发展之路,期待大家的到来。
5.jpg

各个分论坛的最新信息将于近期公布,敬请关注!

报名方式
对 COSCon'20 内容感兴趣的你可以通过下方的链接或点击阅读原文进行报名,期待你的参与!

报名链接:https://www.bagevent.com/event/6840909

 


  收起阅读 »

交个朋友!环信IM+音视频云金秋大促

金秋九月、金九银十,环信新用户活动专场大幕拉开。环信IM+音视频云三款精选大礼包任意选,近十种增值功能永久免费体验(包括:多端多设备同步、消息回调、消息撤回、消息漫游、敏感词过滤、智能反垃圾、音视频服务等);秋天是一个收获的季节,只为交个朋友!!!

微信图片_20200831141615.jpg

 
继续阅读 »
金秋九月、金九银十,环信新用户活动专场大幕拉开。环信IM+音视频云三款精选大礼包任意选,近十种增值功能永久免费体验(包括:多端多设备同步、消息回调、消息撤回、消息漫游、敏感词过滤、智能反垃圾、音视频服务等);秋天是一个收获的季节,只为交个朋友!!!

微信图片_20200831141615.jpg

  收起阅读 »

环信客服云公开课0814


客服云公开课0819期.png

 hi 邀请您或您同事参与环信客服云公开课
时间:8 月 14 日 15:00-15:30
时长:半小时
课程主题:环信客服云使用常见问题介绍及排查
主讲人:环信 CSM 吴敏
                
课程介绍:
1.课程名称
 环信客服云系统使用常见问题介绍及排查
2.受众人群:
 环信客服云系统使用者(坐席&管理员)
3.培训目标
 a.了解客服云系统基本功能使用
 b.掌握客服云系统常见问题现象和排查步骤
点击链接入直播:
https://ke.qq.com/course/42352 ... 3f9c6
扫码加入环信客服云企业微信群:

微信图片_20200810111906.jpg

 
继续阅读 »

客服云公开课0819期.png

 hi 邀请您或您同事参与环信客服云公开课
时间:8 月 14 日 15:00-15:30
时长:半小时
课程主题:环信客服云使用常见问题介绍及排查
主讲人:环信 CSM 吴敏
                
课程介绍:
1.课程名称
 环信客服云系统使用常见问题介绍及排查
2.受众人群:
 环信客服云系统使用者(坐席&管理员)
3.培训目标
 a.了解客服云系统基本功能使用
 b.掌握客服云系统常见问题现象和排查步骤
点击链接入直播:
https://ke.qq.com/course/42352 ... 3f9c6
扫码加入环信客服云企业微信群:

微信图片_20200810111906.jpg

  收起阅读 »

安卓 企业欢迎语集成

1.后台配置企业欢迎语
  管理员模式---设置---系统开关  企业欢迎语 
  
企业欢迎语.png
2.代码
  
ChatClient.getInstance().chatManager().getEnterpriseWelcome(new ValueCallBack<String>() {
@Override
public void onSuccess(String value) {
Message message = Message.createReceiveMessage(Message.Type.TXT);
EMTextMessageBody body = null;
body = new EMTextMessageBody(value);
message.setFrom(toChatUsername);//toChatUsername替换为自己的IM服务号
message.addBody(body);
message.setMsgTime(System.currentTimeMillis());
message.setStatus(Message.Status.SUCCESS);
message.setMsgId(UUID.randomUUID().toString());
ChatClient.getInstance().chatManager().saveMessage(message);
messageList.refresh();
}

@Override
public void onError(int i, String s) {

}
});
继续阅读 »
1.后台配置企业欢迎语
  管理员模式---设置---系统开关  企业欢迎语 
  
企业欢迎语.png
2.代码
  
ChatClient.getInstance().chatManager().getEnterpriseWelcome(new ValueCallBack<String>() {
@Override
public void onSuccess(String value) {
Message message = Message.createReceiveMessage(Message.Type.TXT);
EMTextMessageBody body = null;
body = new EMTextMessageBody(value);
message.setFrom(toChatUsername);//toChatUsername替换为自己的IM服务号
message.addBody(body);
message.setMsgTime(System.currentTimeMillis());
message.setStatus(Message.Status.SUCCESS);
message.setMsgId(UUID.randomUUID().toString());
ChatClient.getInstance().chatManager().saveMessage(message);
messageList.refresh();
}

@Override
public void onError(int i, String s) {

}
});
收起阅读 »

客服工作量分配不均

1.管理员模式---设置---系统开关  页面 打开后,找 
  *熟客优先开关,如果之前开启,建议关闭,这个对新会话调度有影响
1_熟客.png


  *允许客服手动接入会话 ,这个也需要关闭
2_手动接起.png


2.如果上述两个都是关闭状态,之后检查 工作量不均的客服所在技能组,最大接待量,某个时间段或者某天内 上线空闲时长,这三个因素是否一致
*客服所在技能组
 管理员模式--成员管理--在线技能组  可以查看技能组内成员
3_所在技能组.png


 
*客服最大接待量
管理员模式--成语管理--客服列表可以查看最大接待量,最好保持一致,并且不要让客服手动修改
4_最大接待量.png



最大接待量建议所有统一由管理员调整,管理员模式---设置---系统开关  客服自定义最大接待人数   关闭
5_自定义最大接待量.png



*客服指定时间段内上线空闲时长
管理员模式--会话统计--时长统计  最好当前接线的客服上线空闲时长基本一致
6_上线时长.png


3.如果上述条件均符合,最后看  管理员模式--会话统计--工作量 页面底部的客服工作量报表 -->会话时长  客服平均会话时长数据
7_平均会话时长.png


如果接起会话数量少的客服,平均会话时长比接起会话多的客服较长,建议该客服处理完会话后手动关闭结束,避免占位影响新会话调度(如果占位会影响空闲率的计算)
注意:
另外您可以配置系统自动结束会话避免因为客服没有手动关闭会话影响会话调度
    可以到  管理员模式----设置--系统开关 页面  
   访客超时未回复自动结束会话
   不活跃会话超时自动结束   ,配置后针对的是新会话生效,旧的会话还是需要手动结束
配置文档参考
http://docs.easemob.com/cs/200 ... %259D

8_访客超时未回复.png


9_不活跃.png


如果排查以上因素均无异常,到 管理员模式---设置---系统开关   
分配会话时检测坐席在线状态  如果当前为开启,建议先关闭后 再观察看下

分配会话检测坐席在线状态.png


(此开关为预调度。目前预调度的分配原则是这样的:访客发起咨询后,系统会给技能组中前10个,没有技能组就是10个客服,发送通知消息,哪个客服回复的ACK比较快,就认为这个客服的接待能力是比较好的,会把新来的访客的直接调度给这个客服,不会考虑客服系统现有的分配原则:空闲率+已结束会话。
  影响客服回复ACK的因素:网络原因+客服电脑的硬件设备的性能)
    此开关关闭 ,会话仍然是调度给在线空闲的客服。不影响会话进线。
如有其他疑问,可以工作时间联系在线客服咨询解决。您需要登录客服系统:https://kefu.easemob.com  登陆后,切换到管理员模式,点击右上角 技术支持,之后点击页面中的联系客服 发起会话。
继续阅读 »
1.管理员模式---设置---系统开关  页面 打开后,找 
  *熟客优先开关,如果之前开启,建议关闭,这个对新会话调度有影响
1_熟客.png


  *允许客服手动接入会话 ,这个也需要关闭
2_手动接起.png


2.如果上述两个都是关闭状态,之后检查 工作量不均的客服所在技能组,最大接待量,某个时间段或者某天内 上线空闲时长,这三个因素是否一致
*客服所在技能组
 管理员模式--成员管理--在线技能组  可以查看技能组内成员
3_所在技能组.png


 
*客服最大接待量
管理员模式--成语管理--客服列表可以查看最大接待量,最好保持一致,并且不要让客服手动修改
4_最大接待量.png



最大接待量建议所有统一由管理员调整,管理员模式---设置---系统开关  客服自定义最大接待人数   关闭
5_自定义最大接待量.png



*客服指定时间段内上线空闲时长
管理员模式--会话统计--时长统计  最好当前接线的客服上线空闲时长基本一致
6_上线时长.png


3.如果上述条件均符合,最后看  管理员模式--会话统计--工作量 页面底部的客服工作量报表 -->会话时长  客服平均会话时长数据
7_平均会话时长.png


如果接起会话数量少的客服,平均会话时长比接起会话多的客服较长,建议该客服处理完会话后手动关闭结束,避免占位影响新会话调度(如果占位会影响空闲率的计算)
注意:
另外您可以配置系统自动结束会话避免因为客服没有手动关闭会话影响会话调度
    可以到  管理员模式----设置--系统开关 页面  
   访客超时未回复自动结束会话
   不活跃会话超时自动结束   ,配置后针对的是新会话生效,旧的会话还是需要手动结束
配置文档参考
http://docs.easemob.com/cs/200 ... %259D

8_访客超时未回复.png


9_不活跃.png


如果排查以上因素均无异常,到 管理员模式---设置---系统开关   
分配会话时检测坐席在线状态  如果当前为开启,建议先关闭后 再观察看下

分配会话检测坐席在线状态.png


(此开关为预调度。目前预调度的分配原则是这样的:访客发起咨询后,系统会给技能组中前10个,没有技能组就是10个客服,发送通知消息,哪个客服回复的ACK比较快,就认为这个客服的接待能力是比较好的,会把新来的访客的直接调度给这个客服,不会考虑客服系统现有的分配原则:空闲率+已结束会话。
  影响客服回复ACK的因素:网络原因+客服电脑的硬件设备的性能)
    此开关关闭 ,会话仍然是调度给在线空闲的客服。不影响会话进线。
如有其他疑问,可以工作时间联系在线客服咨询解决。您需要登录客服系统:https://kefu.easemob.com  登陆后,切换到管理员模式,点击右上角 技术支持,之后点击页面中的联系客服 发起会话。 收起阅读 »

港真,这些语言并不是你以为的那样!

今天在Quora上看到的问答:

If programming languages had honest titles, what would they be?


看看如下的回答,看来是对这些语言有了深入了解和亲身使用后才会有的答案:
1. C++ — A Force of Nature
源自自然的原力:确实,当你掌握了C++,是不是有种君临天下的感觉,除了灭霸的响指,估计找不到其它更有力的武器了。
2. Ruby —The Slow Scripting Language
可能是为了告诉你世事不完美,当你拥有简洁、优雅的编程语法的同时,你不得不忍受它运行时蜗牛一般的速度。
3. Haskell — Academic Hardon
4. Python — 21st Century Basic
目测马上就要“Python从娃娃抓起”了,趁着AI和机器学习的大热,Python已经走上人生巅峰。
[b]5. Erlang — The Dying Language[/b]
将死?不会,顶多半死。
6. Elixir — It ain’t Ruby!
7. C# — Java for Microsoft
8. Java — You will object, even if you object!

你终将厌恶,即使你现在反对!呵呵,多么痛的领悟!
[b]9.Kotlin — Java could never be so cool![/b]
二进制兼容Java,超过Java一个身位的语法,确实是语言工程实践的极佳范例!
10. Rust — The Be Safe Language
创立以后的几年一直默默无闻,可能是最近这世界越来越不安全,卧薪尝胆后逐渐要出人头地的赶脚?
11. Lisp — Parentitis
一层一层的括号能把你看晕,但是Emacs的名气又让你不得不对它肃然起敬。加上《黑客与画家》作者Paul Graham的吹捧,俨然是编程语言届的高富帅!
12. Clojure — Parentitis with Style!
13. C — Assembler for Fraidycats
一切荣誉归于谭浩强教授和计算机二级水平考试。
14. Assembler — The Bit Twiddler Language
15. Perl — Mean and Lean Scripting Machine
只记得所有的正则表达式语法都以兼容Perl为荣!
16. PHP — The Ewwww Language
宇宙最佳,不接受反驳。
17. Forth — Stack’em Up
18. BASIC — Useless
19. Visual Basic — Mostly Useless, except for the 3rd world.
20. Go — A Google Orgy
Google = Go olge? 反正挺佩服这帮人利用20%自由时间创造出来的语言,那80%时间算是浪费了。
21. Javascript — Prototyping Nightmare
原型噩梦可能是Javascript设计之初最草率的决定,但是也不影响它成为如今的Web编程王者。
22. R — A data scientist’s Wet Dream
没有深入使用不易理解,但是怕这春梦多半也是搞基的!
23. Julia — Whoops! We forgot Concurrency!
24. Fortran — BASIC done right!
25. Lua — The “tuck me in anywhere” language.
随便塞到哪里都能用,这个真不是吹的。
26. Ada — Where Real Programmers just got Real about Real Time.
27. COBOL — It won’t die because it can’t die because it still runs your payroll.
28. Pascal — Teacher’s old time favourite to learn you a useless language.
29. PL/1 — If you know this, you worked at IBM and are now retired.
30. ALGOL — Who’s your daddy? Who’s your dinosaur?
31. Dart — Yet another “compiled to JavaScript” language
高仿版JavaScript,经常写着写着就会问自己:我是谁,我在哪?

    继续阅读 »
    今天在Quora上看到的问答:

    If programming languages had honest titles, what would they be?


    看看如下的回答,看来是对这些语言有了深入了解和亲身使用后才会有的答案:
    1. C++ — A Force of Nature
    源自自然的原力:确实,当你掌握了C++,是不是有种君临天下的感觉,除了灭霸的响指,估计找不到其它更有力的武器了。
    2. Ruby —The Slow Scripting Language
    可能是为了告诉你世事不完美,当你拥有简洁、优雅的编程语法的同时,你不得不忍受它运行时蜗牛一般的速度。
    3. Haskell — Academic Hardon
    4. Python — 21st Century Basic
    目测马上就要“Python从娃娃抓起”了,趁着AI和机器学习的大热,Python已经走上人生巅峰。
    [b]5. Erlang — The Dying Language[/b]
    将死?不会,顶多半死。
    6. Elixir — It ain’t Ruby!
    7. C# — Java for Microsoft
    8. Java — You will object, even if you object!

    你终将厌恶,即使你现在反对!呵呵,多么痛的领悟!
    [b]9.Kotlin — Java could never be so cool![/b]
    二进制兼容Java,超过Java一个身位的语法,确实是语言工程实践的极佳范例!
    10. Rust — The Be Safe Language
    创立以后的几年一直默默无闻,可能是最近这世界越来越不安全,卧薪尝胆后逐渐要出人头地的赶脚?
    11. Lisp — Parentitis
    一层一层的括号能把你看晕,但是Emacs的名气又让你不得不对它肃然起敬。加上《黑客与画家》作者Paul Graham的吹捧,俨然是编程语言届的高富帅!
    12. Clojure — Parentitis with Style!
    13. C — Assembler for Fraidycats
    一切荣誉归于谭浩强教授和计算机二级水平考试。
    14. Assembler — The Bit Twiddler Language
    15. Perl — Mean and Lean Scripting Machine
    只记得所有的正则表达式语法都以兼容Perl为荣!
    16. PHP — The Ewwww Language
    宇宙最佳,不接受反驳。
    17. Forth — Stack’em Up
    18. BASIC — Useless
    19. Visual Basic — Mostly Useless, except for the 3rd world.
    20. Go — A Google Orgy
    Google = Go olge? 反正挺佩服这帮人利用20%自由时间创造出来的语言,那80%时间算是浪费了。
    21. Javascript — Prototyping Nightmare
    原型噩梦可能是Javascript设计之初最草率的决定,但是也不影响它成为如今的Web编程王者。
    22. R — A data scientist’s Wet Dream
    没有深入使用不易理解,但是怕这春梦多半也是搞基的!
    23. Julia — Whoops! We forgot Concurrency!
    24. Fortran — BASIC done right!
    25. Lua — The “tuck me in anywhere” language.
    随便塞到哪里都能用,这个真不是吹的。
    26. Ada — Where Real Programmers just got Real about Real Time.
    27. COBOL — It won’t die because it can’t die because it still runs your payroll.
    28. Pascal — Teacher’s old time favourite to learn you a useless language.
    29. PL/1 — If you know this, you worked at IBM and are now retired.
    30. ALGOL — Who’s your daddy? Who’s your dinosaur?
    31. Dart — Yet another “compiled to JavaScript” language
    高仿版JavaScript,经常写着写着就会问自己:我是谁,我在哪?

      收起阅读 »

      科普:QUIC协议原理分析

      作者介绍:罗成,腾讯资深研发工程师。目前主要负责腾讯 stgw(腾讯安全云网关)的相关工作,整体推进腾讯内部及腾讯公有云,混合云的七层负载均衡及全站 HTTPS 接入。对 HTTPS,SPDY,HTTP2,QUIC 等应用层协议、高性能服务器技术、云网络技术、用户访问速度、分布式文件传输等有较深的理解。

      本文主要介绍 QUIC 协议产生的背景和核心特性。

      写在前面

      如果你的 App,在不需要任何修改的情况下就能提升 15% 以上的访问速度。特别是弱网络的时候能够提升 20% 以上的访问速度。

      如果你的 App,在频繁切换 4G 和 WIFI 网络的情况下,不会断线,不需要重连,用户无任何感知。如果你的 App,既需要 TLS 的安全,也想实现 HTTP2 多路复用的强大。

      如果你刚刚才听说 HTTP2 是下一代互联网协议,如果你刚刚才关注到 TLS1.3 是一个革命性具有里程碑意义的协议,但是这两个协议却一直在被另一个更新兴的协议所影响和挑战。

      如果这个新兴的协议,它的名字就叫做“快”,并且正在标准化为新一代的互联网传输协议。

      你愿意花一点点时间了解这个协议吗?你愿意投入精力去研究这个协议吗?你愿意全力推动业务来使用这个协议吗?

      QUIC 概述

      Quic 全称 quick udp internet connection [1],“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 google 提出的使用 udp 进行多路并发传输的协议。

      Quic 相比现在广泛应用的 http2+tcp+tls 协议有如下优势 [2]:

      减少了 TCP 三次握手及 TLS 握手时间。

      改进的拥塞控制。

      避免队头阻塞的多路复用。

      连接迁移。

      前向冗余纠错。

      为什么需要 QUIC

      从上个世纪 90 年代互联网开始兴起一直到现在,大部分的互联网流量传输只使用了几个网络协议。使用 IPv4 进行路由,使用 TCP 进行连接层面的流量控制,使用 SSL/TLS 协议实现传输安全,使用 DNS 进行域名解析,使用 HTTP 进行应用数据的传输。

      而且近三十年来,这几个协议的发展都非常缓慢。TCP 主要是拥塞控制算法的改进,SSL/TLS 基本上停留在原地,几个小版本的改动主要是密码套件的升级,TLS1.3[3] 是一个飞跃式的变化,但截止到今天,还没有正式发布。IPv4 虽然有一个大的进步,实现了 IPv6,DNS 也增加了一个安全的 DNSSEC,但和 IPv6 一样,部署进度较慢。

      随着移动互联网快速发展以及物联网的逐步兴起,网络交互的场景越来越丰富,网络传输的内容也越来越庞大,用户对网络传输效率和 WEB 响应速度的要求也越来越高。

      一方面是历史悠久使用广泛的古老协议,另外一方面用户的使用场景对传输性能的要求又越来越高。如下几个由来已久的问题和矛盾就变得越来越突出。

      协议历史悠久导致中间设备僵化。

      依赖于操作系统的实现导致协议本身僵化。

      建立连接的握手延迟大。

      队头阻塞。

      这里分小节简单说明一下:

      中间设备的僵化

      可能是 TCP 协议使用得太久,也非常可靠。所以我们很多中间设备,包括防火墙、NAT 网关,整流器等出现了一些约定俗成的动作。

      比如有些防火墙只允许通过 80 和 443,不放通其他端口。NAT 网关在转换网络地址时重写传输层的头部,有可能导致双方无法使用新的传输格式。整流器和中间代理有时候出于安全的需要,会删除一些它们不认识的选项字段。

      TCP 协议本来是支持端口、选项及特性的增加和修改。但是由于 TCP 协议和知名端口及选项使用的历史太悠久,中间设备已经依赖于这些潜规则,所以对这些内容的修改很容易遭到中间环节的干扰而失败。

      而这些干扰,也导致很多在 TCP 协议上的优化变得小心谨慎,步履维艰。

      依赖于操作系统的实现导致协议僵化

      TCP 是由操作系统在内核西方栈层面实现的,应用程序只能使用,不能直接修改。虽然应用程序的更新迭代非常快速和简单。但是 TCP 的迭代却非常缓慢,原因就是操作系统升级很麻烦。

      现在移动终端更加流行,但是移动端部分用户的操作系统升级依然可能滞后数年时间。PC 端的系统升级滞后得更加严重,windows xp 现在还有大量用户在使用,尽管它已经存在快 20 年。

      服务端系统不依赖用户升级,但是由于操作系统升级涉及到底层软件和运行库的更新,所以也比较保守和缓慢。

      这也就意味着即使 TCP 有比较好的特性更新,也很难快速推广。比如 TCP Fast Open。它虽然 2013 年就被提出了,但是 Windows 很多系统版本依然不支持它。

      建立连接的握手延迟大

      不管是 HTTP1.0/1.1 还是 HTTPS,HTTP2,都使用了 TCP 进行传输。HTTPS 和 HTTP2 还需要使用 TLS 协议来进行安全传输。这就出现了两个握手延迟:

      1.TCP 三次握手导致的 TCP 连接建立的延迟。

      2.TLS 完全握手需要至少 2 个 RTT 才能建立,简化握手需要 1 个 RTT 的握手延迟。

      对于很多短连接场景,这样的握手延迟影响很大,且无法消除。

      队头阻塞

      队头阻塞主要是 TCP 协议的可靠性机制引入的。TCP 使用序列号来标识数据的顺序,数据必须按照顺序处理,如果前面的数据丢失,后面的数据就算到达了也不会通知应用层来处理。

      另外 TLS 协议层面也有一个队头阻塞,因为 TLS 协议都是按照 record 来处理数据的,如果一个 record 中丢失了数据,也会导致整个 record 无法正确处理。

      概括来讲,TCP 和 TLS1.2 之前的协议存在着结构性的问题,如果继续在现有的 TCP、TLS 协议之上实现一个全新的应用层协议,依赖于操作系统、中间设备还有用户的支持。部署成本非常高,阻力非常大。

      所以 QUIC 协议选择了 UDP,因为 UDP 本身没有连接的概念,不需要三次握手,优化了连接建立的握手延迟,同时在应用程序层面实现了 TCP 的可靠性,TLS 的安全性和 HTTP2 的并发性,只需要用户端和服务端的应用程序支持 QUIC 协议,完全避开了操作系统和中间设备的限制。

      QUIC 核心特性连接建立延时低

      0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。那什么是 0RTT 建连呢?这里面有两层含义。

      传输层 0RTT 就能建立连接。
      加密层 0RTT 就能建立加密连接。

      1.jpg

       
      图 1 HTTPS 及 QUIC 建连过程
      比如上图左边是 HTTPS 的一次完全握手的建连过程,需要 3 个 RTT。就算是 Session Resumption[14],也需要至少 2 个 RTT。
      而 QUIC 呢?由于建立在 UDP 的基础上,同时又实现了 0RTT 的安全握手,所以在大部分情况下,只需要 0 个 RTT 就能实现数据发送,在实现前向加密 [15] 的基础上,并且 0RTT 的成功率相比 TLS 的 Sesison Ticket[13] 要高很多。

      改进的拥塞控制

      TCP 的拥塞控制实际上包含了四个算法:慢启动,拥塞避免,快速重传,快速恢复 [22]。

      QUIC 协议当前默认使用了 TCP 协议的 Cubic 拥塞控制算法 [6],同时也支持 CubicBytes, Reno, RenoBytes, BBR, PCC 等拥塞控制算法。

      从拥塞算法本身来看,QUIC 只是按照 TCP 协议重新实现了一遍,那么 QUIC 协议到底改进在哪些方面呢?主要有如下几点:

      可插拔

      什么叫可插拔呢?就是能够非常灵活地生效,变更和停止。体现在如下方面:

      应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,这在产品快速迭代,网络爆炸式增长的今天,显然有点满足不了需求。

      即使是单个应用程序的不同连接也能支持配置不同的拥塞控制。就算是一台服务器,接入的用户网络环境也千差万别,结合大数据及人工智能处理,我们能为各个用户提供不同的但又更加精准更加有效的拥塞控制。比如 BBR 适合,Cubic 适合。

      应用程序不需要停机和升级就能实现拥塞控制的变更,我们在服务端只需要修改一下配置,reload 一下,完全不需要停止服务就能实现拥塞控制的切换。

      STGW 在配置层面进行了优化,我们可以针对不同业务,不同网络制式,甚至不同的 RTT,使用不同的拥塞控制算法。

      单调递增的 Packet Number

      TCP 为了保证可靠性,使用了基于字节序号的 Sequence Number 及 Ack 来确认消息的有序到达。
      QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 sequence number,并且每个 Packet Number 都严格递增,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。而 TCP 呢,重传 segment 的 sequence number 和原始的 segment 的 Sequence Number 保持不变,也正是由于这个特性,引入了 Tcp 重传的歧义问题。

      2.jpg

       
      图 2 Tcp 重传歧义性
      如上图所示,超时事件 RTO 发生后,客户端发起重传,然后接收到了 Ack 数据。由于序列号一样,这个 Ack 数据到底是原始请求的响应还是重传请求的响应呢?不好判断。
      如果算成原始请求的响应,但实际上是重传请求的响应(上图左),会导致采样 RTT 变大。如果算成重传请求的响应,但实际上是原始请求的响应,又很容易导致采样 RTT 过小。
      由于 Quic 重传的 Packet 和原始 Packet 的 Pakcet Number 是严格递增的,所以很容易就解决了这个问题。

      3.jpg

       
      图 3 Quic 重传没有歧义性
      如上图所示,RTO 发生后,根据重传的 Packet Number 就能确定精确的 RTT 计算。如果 Ack 的 Packet Number 是 N+M,就根据重传请求计算采样 RTT。如果 Ack 的 Pakcet Number 是 N,就根据原始请求的时间计算采样 RTT,没有歧义性。
      但是单纯依靠严格递增的 Packet Number 肯定是无法保证数据的顺序性和可靠性。QUIC 又引入了一个 Stream Offset 的概念。

      即一个 Stream 可以经过多个 Packet 传输,Packet Number 严格递增,没有依赖。但是 Packet 里的 Payload 如果是 Stream 的话,就需要依靠 Stream 的 Offset 来保证应用数据的顺序。如错误! 未找到引用源。所示,发送端先后发送了 Pakcet N 和 Pakcet N+1,Stream 的 Offset 分别是 x 和 x+y。
      假设 Packet N 丢失了,发起重传,重传的 Packet Number 是 N+2,但是它的 Stream 的 Offset 依然是 x,这样就算 Packet N + 2 是后到的,依然可以将 Stream x 和 Stream x+y 按照顺序组织起来,交给应用程序处理。

      4.jpg

       
      图 4 Stream Offset 保证有序性
       
      不允许 Reneging
      什么叫 Reneging 呢?就是接收方丢弃已经接收并且上报给 SACK 选项的内容 [8]。TCP 协议不鼓励这种行为,但是协议层面允许这样的行为。主要是考虑到服务器资源有限,比如 Buffer 溢出,内存不够等情况。

      Reneging 对数据重传会产生很大的干扰。因为 Sack 都已经表明接收到了,但是接收端事实上丢弃了该数据。

      QUIC 在协议层面禁止 Reneging,一个 Packet 只要被 Ack,就认为它一定被正确接收,减少了这种干扰。

      更多的 Ack 块

      TCP 的 Sack 选项能够告诉发送方已经接收到的连续 Segment 的范围,方便发送方进行选择性重传。

      由于 TCP 头部最大只有 60 个字节,标准头部占用了 20 字节,所以 Tcp Option 最大长度只有 40 字节,再加上 Tcp Timestamp option 占用了 10 个字节 [25],所以留给 Sack 选项的只有 30 个字节。

      每一个 Sack Block 的长度是 8 个,加上 Sack Option 头部 2 个字节,也就意味着 Tcp Sack Option 最大只能提供 3 个 Block。

      但是 Quic Ack Frame 可以同时提供 256 个 Ack Block,在丢包率比较高的网络下,更多的 Sack Block 可以提升网络的恢复速度,减少重传量。

      Ack Delay 时间

      Tcp 的 Timestamp 选项存在一个问题 [25],它只是回显了发送方的时间戳,但是没有计算接收端接收到 segment 到发送 Ack 该 segment 的时间。这个时间可以简称为 Ack Delay。
      这样就会导致 RTT 计算误差。如下图:

      5.jpg

       可以认为 TCP 的 RTT 计算:

      6.jpg


      而 Quic 计算如下:

      7.jpg


      当然 RTT 的具体计算没有这么简单,需要采样,参考历史数值进行平滑计算,参考如下公式 [9]。

      8.jpg


      基于 stream 和 connecton 级别的流量控制

      QUIC 的流量控制 [22] 类似 HTTP2,即在 Connection 和 Stream 级别提供了两种流量控制。为什么需要两类流量控制呢?主要是因为 QUIC 支持多路复用。

      Stream 可以认为就是一条 HTTP 请求。

      Connection 可以类比一条 TCP 连接。多路复用意味着在一条 Connetion 上会同时存在多条 Stream。既需要对单个 Stream 进行控制,又需要针对所有 Stream 进行总体控制。

      QUIC 实现流量控制的原理比较简单:

      通过 window_update 帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据。

      通过 BlockFrame 告诉对端由于流量控制被阻塞了,无法发送数据。

      QUIC 的流量控制和 TCP 有点区别,TCP 为了保证可靠性,窗口左边沿向右滑动时的长度取决于已经确认的字节数。如果中间出现丢包,就算接收到了更大序号的 Segment,窗口也无法超过这个序列号。
      但 QUIC 不同,就算此前有些 packet 没有接收到,它的滑动只取决于接收到的最大偏移字节数。

      9.jpg

       
      图 5 Quic Flow Control针对 Stream:
      10.jpg


      针对 Connection:

      11.jpg


      同样地,STGW 也在连接和 Stream 级别设置了不同的窗口数。

      最重要的是,我们可以在内存不足或者上游处理性能出现问题时,通过流量控制来限制传输速率,保障服务可用性。

      没有队头阻塞的多路复用

      QUIC 的多路复用和 HTTP2 类似。在一条 QUIC 连接上可以并发发送多个 HTTP 请求 (stream)。但是 QUIC 的多路复用相比 HTTP2 有一个很大的优势。

      QUIC 一个连接上的多个 stream 之间没有依赖。这样假如 stream2 丢了一个 udp packet,也只会影响 stream2 的处理。不会影响 stream2 之前及之后的 stream 的处理。

      这也就在很大程度上缓解甚至消除了队头阻塞的影响。
      多路复用是 HTTP2 最强大的特性 [7],能够将多条请求在一条 TCP 连接上同时发出去。但也恶化了 TCP 的一个问题,队头阻塞 [11],如下图示:

      12.jpg

       
      图 6 HTTP2 队头阻塞
      HTTP2 在一个 TCP 连接上同时发送 4 个 Stream。其中 Stream1 已经正确到达,并被应用层读取。但是 Stream2 的第三个 tcp segment 丢失了,TCP 为了保证数据的可靠性,需要发送端重传第 3 个 segment 才能通知应用层读取接下去的数据,虽然这个时候 Stream3 和 Stream4 的全部数据已经到达了接收端,但都被阻塞住了。 
      不仅如此,由于 HTTP2 强制使用 TLS,还存在一个 TLS 协议层面的队头阻塞 [12]。

      13.jpg

       
      图 7 TLS 队头阻塞
      Record 是 TLS 协议处理的最小单位,最大不能超过 16K,一些服务器比如 Nginx 默认的大小就是 16K。由于一个 record 必须经过数据一致性校验才能进行加解密,所以一个 16K 的 record,就算丢了一个字节,也会导致已经接收到的 15.99K 数据无法处理,因为它不完整。
      那 QUIC 多路复用为什么能避免上述问题呢?

      QUIC 最基本的传输单元是 Packet,不会超过 MTU 的大小,整个加密和认证过程都是基于 Packet 的,不会跨越多个 Packet。这样就能避免 TLS 协议存在的队头阻塞。
      Stream 之间相互独立,比如 Stream2 丢了一个 Pakcet,不会影响 Stream3 和 Stream4。不存在 TCP 队头阻塞。

      14.jpg

       
      图 8 QUIC 多路复用时没有队头阻塞的问题
      当然,并不是所有的 QUIC 数据都不会受到队头阻塞的影响,比如 QUIC 当前也是使用 Hpack 压缩算法 [10],由于算法的限制,丢失一个头部数据时,可能遇到队头阻塞。
      总体来说,QUIC 在传输大量数据时,比如视频,受到队头阻塞的影响很小。

      加密认证的报文

      TCP 协议头部没有经过任何加密和认证,所以在传输过程中很容易被中间网络设备篡改,注入和窃听。比如修改序列号、滑动窗口。这些行为有可能是出于性能优化,也有可能是主动攻击。

      但是 QUIC 的 packet 可以说是武装到了牙齿。除了个别报文比如 PUBLIC_RESET 和 CHLO,所有报文头部都是经过认证的,报文 Body 都是经过加密的。

      这样只要对 QUIC 报文任何修改,接收端都能够及时发现,有效地降低了安全风险。

      如下图所示,红色部分是 Stream Frame 的报文头部,有认证。绿色部分是报文内容,全部经过加密。

      15.jpg


      连接迁移

      一条 TCP 连接 [17] 是由四元组标识的(源 IP,源端口,目的 IP,目的端口)。什么叫连接迁移呢?就是当其中任何一个元素发生变化时,这条连接依然维持着,能够保持业务逻辑不中断。当然这里面主要关注的是客户端的变化,因为客户端不可控并且网络环境经常发生变化,而服务端的 IP 和端口一般都是固定的。

      比如大家使用手机在 WIFI 和 4G 移动网络切换时,客户端的 IP 肯定会发生变化,需要重新建立和服务端的 TCP 连接。

      又比如大家使用公共 NAT 出口时,有些连接竞争时需要重新绑定端口,导致客户端的端口发生变化,同样需要重新建立 TCP 连接。

      针对 TCP 的连接变化,MPTCP[5] 其实已经有了解决方案,但是由于 MPTCP 需要操作系统及网络协议栈支持,部署阻力非常大,目前并不适用。

      所以从 TCP 连接的角度来讲,这个问题是无解的。

      那 QUIC 是如何做到连接迁移呢?很简单,任何一条 QUIC 连接不再以 IP 及端口四元组标识,而是以一个 64 位的随机数作为 ID 来标识,这样就算 IP 或者端口发生变化时,只要 ID 不变,这条连接依然维持着,上层业务逻辑感知不到变化,不会中断,也就不需要重连。

      由于这个 ID 是客户端随机产生的,并且长度有 64 位,所以冲突概率非常低。

      其他亮点

      此外,QUIC 还能实现前向冗余纠错,在重要的包比如握手消息发生丢失时,能够根据冗余信息还原出握手消息。

      QUIC 还能实现证书压缩,减少证书传输量,针对包头进行验证等。

      限于篇幅,本文不再详细介绍,有兴趣的可以参考文档 [23] 和文档 [4] 和文档 [26]。

      参考线索

      [1]. https://www.chromium.org/quic

      [2]. https://docs.google.com/docume ... /edit

      [3]. E. Rescorla, “The Transport Layer Security (TLS) Protocol Version 1.3”, draft-ietf-tls-tls13-21, https://tools.ietf.org/html/dr ... 13-21, July 03, 2017

      [4]. Adam Langley,Wan-Teh Chang, “QUIC Crypto”,https://docs.google.com/docume ... /edit, 20161206

      [5]. https://www.multipath-tcp.org/

      [6]. Ha, S., Rhee, I., and L. Xu, "CUBIC: A New TCP-Friendly High-Speed TCP Variant", ACM SIGOPS Operating System Review , 2008.

      [7]. M. Belshe,BitGo, R. Peon, “Hypertext Transfer Protocol Version 2 (HTTP/2)”, RFC 7540, May 2015

      [8]. M. Mathis,J. Mahdavi,S. Floyd,A. Romanow,“TCP Selective Acknowledgment Options”, rfc2018, https://tools.ietf.org/html/rfc2018, October 1996

      [9]. V. Paxson,M. Allman,J. Chu,M. Sargent,“Computing TCP's Retransmission Timer”, rfc6298, https://tools.ietf.org/html/rfc6298, June 2011

      [10]. R. Peon,H. Ruellan,“HPACK: Header Compression for HTTP/2”,RFC7541,May 2015

      [11]. M. Scharf, Alcatel-Lucent Bell Labs, S. Kiesel, “Quantifying Head-of-Line Blocking in TCP and SCTP”, https://tools.ietf.org/id/draf ... .html, July 15, 2013

      [12]. Ilya Grigorik,“Optimizing TLS Record Size & Buffering Latency”, https://www.igvita.com/2013/10 ... ency/, October 24, 2013

      [13]. J. Salowey,H. Zhou,P. Eronen,H. Tschofenig, “Transport Layer Security (TLS) Session Resumption without Server-Side State”, RFC5077, January 2008

      [14]. Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008, .

      [15]. Shirey, R., "Internet Security Glossary, Version 2", FYI , RFC 4949, August 2007

      [16]. 罗成,“HTTPS性能优化”, http://www.infoq.com/cn/presen ... https,February.2017

      [17]. Postel, J., "Transmission Control Protocol", STD 7, RFC793, September 1981.

      [18]. J. Postel,“User Datagram Protocol”, RFC768,August 1980

      [19]. Q. Dang, S. Santesson,K. Moriarty,D. Brown.T. Polk, “Internet X.509 Public Key Infrastructure: Additional Algorithms and Identifiers for DSA and ECDSA”,RFC5758, January 2010

      [20]. Bassham, L., Polk, W., and R. Housley, "Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 3279, April 2002

      [21]. D.Cooper,S.Santesson, S.Farrell,S. Boeyen,R. Housley,W.Polk, “Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile”, RFC5280, May 2008

      [22]. M. Allman,V. Paxson,E. Blanton, "TCP Congestion Control”,RFC5681, September 2009

      [23]. Robbie Shade, “Flow control in QUIC”, https://docs.google.com/docume ... it%23, May, 2016,

      [24]. ianswett , “QUIC fec v1”, https://docs.google.com/docume ... tytjt, 2016-02-19

      [25]. D.Borman,B.Braden,V.Jacobson,R.Scheffenegger, Ed. “TCP Extensions for High Performance”,rfc7323, https://tools.ietf.org/html/rfc7323,September 2014

      [26]. 罗成,“WEB加速,协议先行”, https://zhuanlan.zhihu.com/p/27938635,july, 2017
      继续阅读 »
      作者介绍:罗成,腾讯资深研发工程师。目前主要负责腾讯 stgw(腾讯安全云网关)的相关工作,整体推进腾讯内部及腾讯公有云,混合云的七层负载均衡及全站 HTTPS 接入。对 HTTPS,SPDY,HTTP2,QUIC 等应用层协议、高性能服务器技术、云网络技术、用户访问速度、分布式文件传输等有较深的理解。

      本文主要介绍 QUIC 协议产生的背景和核心特性。

      写在前面

      如果你的 App,在不需要任何修改的情况下就能提升 15% 以上的访问速度。特别是弱网络的时候能够提升 20% 以上的访问速度。

      如果你的 App,在频繁切换 4G 和 WIFI 网络的情况下,不会断线,不需要重连,用户无任何感知。如果你的 App,既需要 TLS 的安全,也想实现 HTTP2 多路复用的强大。

      如果你刚刚才听说 HTTP2 是下一代互联网协议,如果你刚刚才关注到 TLS1.3 是一个革命性具有里程碑意义的协议,但是这两个协议却一直在被另一个更新兴的协议所影响和挑战。

      如果这个新兴的协议,它的名字就叫做“快”,并且正在标准化为新一代的互联网传输协议。

      你愿意花一点点时间了解这个协议吗?你愿意投入精力去研究这个协议吗?你愿意全力推动业务来使用这个协议吗?

      QUIC 概述

      Quic 全称 quick udp internet connection [1],“快速 UDP 互联网连接”,(和英文 quick 谐音,简称“快”)是由 google 提出的使用 udp 进行多路并发传输的协议。

      Quic 相比现在广泛应用的 http2+tcp+tls 协议有如下优势 [2]:

      减少了 TCP 三次握手及 TLS 握手时间。

      改进的拥塞控制。

      避免队头阻塞的多路复用。

      连接迁移。

      前向冗余纠错。

      为什么需要 QUIC

      从上个世纪 90 年代互联网开始兴起一直到现在,大部分的互联网流量传输只使用了几个网络协议。使用 IPv4 进行路由,使用 TCP 进行连接层面的流量控制,使用 SSL/TLS 协议实现传输安全,使用 DNS 进行域名解析,使用 HTTP 进行应用数据的传输。

      而且近三十年来,这几个协议的发展都非常缓慢。TCP 主要是拥塞控制算法的改进,SSL/TLS 基本上停留在原地,几个小版本的改动主要是密码套件的升级,TLS1.3[3] 是一个飞跃式的变化,但截止到今天,还没有正式发布。IPv4 虽然有一个大的进步,实现了 IPv6,DNS 也增加了一个安全的 DNSSEC,但和 IPv6 一样,部署进度较慢。

      随着移动互联网快速发展以及物联网的逐步兴起,网络交互的场景越来越丰富,网络传输的内容也越来越庞大,用户对网络传输效率和 WEB 响应速度的要求也越来越高。

      一方面是历史悠久使用广泛的古老协议,另外一方面用户的使用场景对传输性能的要求又越来越高。如下几个由来已久的问题和矛盾就变得越来越突出。

      协议历史悠久导致中间设备僵化。

      依赖于操作系统的实现导致协议本身僵化。

      建立连接的握手延迟大。

      队头阻塞。

      这里分小节简单说明一下:

      中间设备的僵化

      可能是 TCP 协议使用得太久,也非常可靠。所以我们很多中间设备,包括防火墙、NAT 网关,整流器等出现了一些约定俗成的动作。

      比如有些防火墙只允许通过 80 和 443,不放通其他端口。NAT 网关在转换网络地址时重写传输层的头部,有可能导致双方无法使用新的传输格式。整流器和中间代理有时候出于安全的需要,会删除一些它们不认识的选项字段。

      TCP 协议本来是支持端口、选项及特性的增加和修改。但是由于 TCP 协议和知名端口及选项使用的历史太悠久,中间设备已经依赖于这些潜规则,所以对这些内容的修改很容易遭到中间环节的干扰而失败。

      而这些干扰,也导致很多在 TCP 协议上的优化变得小心谨慎,步履维艰。

      依赖于操作系统的实现导致协议僵化

      TCP 是由操作系统在内核西方栈层面实现的,应用程序只能使用,不能直接修改。虽然应用程序的更新迭代非常快速和简单。但是 TCP 的迭代却非常缓慢,原因就是操作系统升级很麻烦。

      现在移动终端更加流行,但是移动端部分用户的操作系统升级依然可能滞后数年时间。PC 端的系统升级滞后得更加严重,windows xp 现在还有大量用户在使用,尽管它已经存在快 20 年。

      服务端系统不依赖用户升级,但是由于操作系统升级涉及到底层软件和运行库的更新,所以也比较保守和缓慢。

      这也就意味着即使 TCP 有比较好的特性更新,也很难快速推广。比如 TCP Fast Open。它虽然 2013 年就被提出了,但是 Windows 很多系统版本依然不支持它。

      建立连接的握手延迟大

      不管是 HTTP1.0/1.1 还是 HTTPS,HTTP2,都使用了 TCP 进行传输。HTTPS 和 HTTP2 还需要使用 TLS 协议来进行安全传输。这就出现了两个握手延迟:

      1.TCP 三次握手导致的 TCP 连接建立的延迟。

      2.TLS 完全握手需要至少 2 个 RTT 才能建立,简化握手需要 1 个 RTT 的握手延迟。

      对于很多短连接场景,这样的握手延迟影响很大,且无法消除。

      队头阻塞

      队头阻塞主要是 TCP 协议的可靠性机制引入的。TCP 使用序列号来标识数据的顺序,数据必须按照顺序处理,如果前面的数据丢失,后面的数据就算到达了也不会通知应用层来处理。

      另外 TLS 协议层面也有一个队头阻塞,因为 TLS 协议都是按照 record 来处理数据的,如果一个 record 中丢失了数据,也会导致整个 record 无法正确处理。

      概括来讲,TCP 和 TLS1.2 之前的协议存在着结构性的问题,如果继续在现有的 TCP、TLS 协议之上实现一个全新的应用层协议,依赖于操作系统、中间设备还有用户的支持。部署成本非常高,阻力非常大。

      所以 QUIC 协议选择了 UDP,因为 UDP 本身没有连接的概念,不需要三次握手,优化了连接建立的握手延迟,同时在应用程序层面实现了 TCP 的可靠性,TLS 的安全性和 HTTP2 的并发性,只需要用户端和服务端的应用程序支持 QUIC 协议,完全避开了操作系统和中间设备的限制。

      QUIC 核心特性连接建立延时低

      0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势。那什么是 0RTT 建连呢?这里面有两层含义。

      传输层 0RTT 就能建立连接。
      加密层 0RTT 就能建立加密连接。

      1.jpg

       
      图 1 HTTPS 及 QUIC 建连过程
      比如上图左边是 HTTPS 的一次完全握手的建连过程,需要 3 个 RTT。就算是 Session Resumption[14],也需要至少 2 个 RTT。
      而 QUIC 呢?由于建立在 UDP 的基础上,同时又实现了 0RTT 的安全握手,所以在大部分情况下,只需要 0 个 RTT 就能实现数据发送,在实现前向加密 [15] 的基础上,并且 0RTT 的成功率相比 TLS 的 Sesison Ticket[13] 要高很多。

      改进的拥塞控制

      TCP 的拥塞控制实际上包含了四个算法:慢启动,拥塞避免,快速重传,快速恢复 [22]。

      QUIC 协议当前默认使用了 TCP 协议的 Cubic 拥塞控制算法 [6],同时也支持 CubicBytes, Reno, RenoBytes, BBR, PCC 等拥塞控制算法。

      从拥塞算法本身来看,QUIC 只是按照 TCP 协议重新实现了一遍,那么 QUIC 协议到底改进在哪些方面呢?主要有如下几点:

      可插拔

      什么叫可插拔呢?就是能够非常灵活地生效,变更和停止。体现在如下方面:

      应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,这在产品快速迭代,网络爆炸式增长的今天,显然有点满足不了需求。

      即使是单个应用程序的不同连接也能支持配置不同的拥塞控制。就算是一台服务器,接入的用户网络环境也千差万别,结合大数据及人工智能处理,我们能为各个用户提供不同的但又更加精准更加有效的拥塞控制。比如 BBR 适合,Cubic 适合。

      应用程序不需要停机和升级就能实现拥塞控制的变更,我们在服务端只需要修改一下配置,reload 一下,完全不需要停止服务就能实现拥塞控制的切换。

      STGW 在配置层面进行了优化,我们可以针对不同业务,不同网络制式,甚至不同的 RTT,使用不同的拥塞控制算法。

      单调递增的 Packet Number

      TCP 为了保证可靠性,使用了基于字节序号的 Sequence Number 及 Ack 来确认消息的有序到达。
      QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 sequence number,并且每个 Packet Number 都严格递增,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。而 TCP 呢,重传 segment 的 sequence number 和原始的 segment 的 Sequence Number 保持不变,也正是由于这个特性,引入了 Tcp 重传的歧义问题。

      2.jpg

       
      图 2 Tcp 重传歧义性
      如上图所示,超时事件 RTO 发生后,客户端发起重传,然后接收到了 Ack 数据。由于序列号一样,这个 Ack 数据到底是原始请求的响应还是重传请求的响应呢?不好判断。
      如果算成原始请求的响应,但实际上是重传请求的响应(上图左),会导致采样 RTT 变大。如果算成重传请求的响应,但实际上是原始请求的响应,又很容易导致采样 RTT 过小。
      由于 Quic 重传的 Packet 和原始 Packet 的 Pakcet Number 是严格递增的,所以很容易就解决了这个问题。

      3.jpg

       
      图 3 Quic 重传没有歧义性
      如上图所示,RTO 发生后,根据重传的 Packet Number 就能确定精确的 RTT 计算。如果 Ack 的 Packet Number 是 N+M,就根据重传请求计算采样 RTT。如果 Ack 的 Pakcet Number 是 N,就根据原始请求的时间计算采样 RTT,没有歧义性。
      但是单纯依靠严格递增的 Packet Number 肯定是无法保证数据的顺序性和可靠性。QUIC 又引入了一个 Stream Offset 的概念。

      即一个 Stream 可以经过多个 Packet 传输,Packet Number 严格递增,没有依赖。但是 Packet 里的 Payload 如果是 Stream 的话,就需要依靠 Stream 的 Offset 来保证应用数据的顺序。如错误! 未找到引用源。所示,发送端先后发送了 Pakcet N 和 Pakcet N+1,Stream 的 Offset 分别是 x 和 x+y。
      假设 Packet N 丢失了,发起重传,重传的 Packet Number 是 N+2,但是它的 Stream 的 Offset 依然是 x,这样就算 Packet N + 2 是后到的,依然可以将 Stream x 和 Stream x+y 按照顺序组织起来,交给应用程序处理。

      4.jpg

       
      图 4 Stream Offset 保证有序性
       
      不允许 Reneging
      什么叫 Reneging 呢?就是接收方丢弃已经接收并且上报给 SACK 选项的内容 [8]。TCP 协议不鼓励这种行为,但是协议层面允许这样的行为。主要是考虑到服务器资源有限,比如 Buffer 溢出,内存不够等情况。

      Reneging 对数据重传会产生很大的干扰。因为 Sack 都已经表明接收到了,但是接收端事实上丢弃了该数据。

      QUIC 在协议层面禁止 Reneging,一个 Packet 只要被 Ack,就认为它一定被正确接收,减少了这种干扰。

      更多的 Ack 块

      TCP 的 Sack 选项能够告诉发送方已经接收到的连续 Segment 的范围,方便发送方进行选择性重传。

      由于 TCP 头部最大只有 60 个字节,标准头部占用了 20 字节,所以 Tcp Option 最大长度只有 40 字节,再加上 Tcp Timestamp option 占用了 10 个字节 [25],所以留给 Sack 选项的只有 30 个字节。

      每一个 Sack Block 的长度是 8 个,加上 Sack Option 头部 2 个字节,也就意味着 Tcp Sack Option 最大只能提供 3 个 Block。

      但是 Quic Ack Frame 可以同时提供 256 个 Ack Block,在丢包率比较高的网络下,更多的 Sack Block 可以提升网络的恢复速度,减少重传量。

      Ack Delay 时间

      Tcp 的 Timestamp 选项存在一个问题 [25],它只是回显了发送方的时间戳,但是没有计算接收端接收到 segment 到发送 Ack 该 segment 的时间。这个时间可以简称为 Ack Delay。
      这样就会导致 RTT 计算误差。如下图:

      5.jpg

       可以认为 TCP 的 RTT 计算:

      6.jpg


      而 Quic 计算如下:

      7.jpg


      当然 RTT 的具体计算没有这么简单,需要采样,参考历史数值进行平滑计算,参考如下公式 [9]。

      8.jpg


      基于 stream 和 connecton 级别的流量控制

      QUIC 的流量控制 [22] 类似 HTTP2,即在 Connection 和 Stream 级别提供了两种流量控制。为什么需要两类流量控制呢?主要是因为 QUIC 支持多路复用。

      Stream 可以认为就是一条 HTTP 请求。

      Connection 可以类比一条 TCP 连接。多路复用意味着在一条 Connetion 上会同时存在多条 Stream。既需要对单个 Stream 进行控制,又需要针对所有 Stream 进行总体控制。

      QUIC 实现流量控制的原理比较简单:

      通过 window_update 帧告诉对端自己可以接收的字节数,这样发送方就不会发送超过这个数量的数据。

      通过 BlockFrame 告诉对端由于流量控制被阻塞了,无法发送数据。

      QUIC 的流量控制和 TCP 有点区别,TCP 为了保证可靠性,窗口左边沿向右滑动时的长度取决于已经确认的字节数。如果中间出现丢包,就算接收到了更大序号的 Segment,窗口也无法超过这个序列号。
      但 QUIC 不同,就算此前有些 packet 没有接收到,它的滑动只取决于接收到的最大偏移字节数。

      9.jpg

       
      图 5 Quic Flow Control针对 Stream:
      10.jpg


      针对 Connection:

      11.jpg


      同样地,STGW 也在连接和 Stream 级别设置了不同的窗口数。

      最重要的是,我们可以在内存不足或者上游处理性能出现问题时,通过流量控制来限制传输速率,保障服务可用性。

      没有队头阻塞的多路复用

      QUIC 的多路复用和 HTTP2 类似。在一条 QUIC 连接上可以并发发送多个 HTTP 请求 (stream)。但是 QUIC 的多路复用相比 HTTP2 有一个很大的优势。

      QUIC 一个连接上的多个 stream 之间没有依赖。这样假如 stream2 丢了一个 udp packet,也只会影响 stream2 的处理。不会影响 stream2 之前及之后的 stream 的处理。

      这也就在很大程度上缓解甚至消除了队头阻塞的影响。
      多路复用是 HTTP2 最强大的特性 [7],能够将多条请求在一条 TCP 连接上同时发出去。但也恶化了 TCP 的一个问题,队头阻塞 [11],如下图示:

      12.jpg

       
      图 6 HTTP2 队头阻塞
      HTTP2 在一个 TCP 连接上同时发送 4 个 Stream。其中 Stream1 已经正确到达,并被应用层读取。但是 Stream2 的第三个 tcp segment 丢失了,TCP 为了保证数据的可靠性,需要发送端重传第 3 个 segment 才能通知应用层读取接下去的数据,虽然这个时候 Stream3 和 Stream4 的全部数据已经到达了接收端,但都被阻塞住了。 
      不仅如此,由于 HTTP2 强制使用 TLS,还存在一个 TLS 协议层面的队头阻塞 [12]。

      13.jpg

       
      图 7 TLS 队头阻塞
      Record 是 TLS 协议处理的最小单位,最大不能超过 16K,一些服务器比如 Nginx 默认的大小就是 16K。由于一个 record 必须经过数据一致性校验才能进行加解密,所以一个 16K 的 record,就算丢了一个字节,也会导致已经接收到的 15.99K 数据无法处理,因为它不完整。
      那 QUIC 多路复用为什么能避免上述问题呢?

      QUIC 最基本的传输单元是 Packet,不会超过 MTU 的大小,整个加密和认证过程都是基于 Packet 的,不会跨越多个 Packet。这样就能避免 TLS 协议存在的队头阻塞。
      Stream 之间相互独立,比如 Stream2 丢了一个 Pakcet,不会影响 Stream3 和 Stream4。不存在 TCP 队头阻塞。

      14.jpg

       
      图 8 QUIC 多路复用时没有队头阻塞的问题
      当然,并不是所有的 QUIC 数据都不会受到队头阻塞的影响,比如 QUIC 当前也是使用 Hpack 压缩算法 [10],由于算法的限制,丢失一个头部数据时,可能遇到队头阻塞。
      总体来说,QUIC 在传输大量数据时,比如视频,受到队头阻塞的影响很小。

      加密认证的报文

      TCP 协议头部没有经过任何加密和认证,所以在传输过程中很容易被中间网络设备篡改,注入和窃听。比如修改序列号、滑动窗口。这些行为有可能是出于性能优化,也有可能是主动攻击。

      但是 QUIC 的 packet 可以说是武装到了牙齿。除了个别报文比如 PUBLIC_RESET 和 CHLO,所有报文头部都是经过认证的,报文 Body 都是经过加密的。

      这样只要对 QUIC 报文任何修改,接收端都能够及时发现,有效地降低了安全风险。

      如下图所示,红色部分是 Stream Frame 的报文头部,有认证。绿色部分是报文内容,全部经过加密。

      15.jpg


      连接迁移

      一条 TCP 连接 [17] 是由四元组标识的(源 IP,源端口,目的 IP,目的端口)。什么叫连接迁移呢?就是当其中任何一个元素发生变化时,这条连接依然维持着,能够保持业务逻辑不中断。当然这里面主要关注的是客户端的变化,因为客户端不可控并且网络环境经常发生变化,而服务端的 IP 和端口一般都是固定的。

      比如大家使用手机在 WIFI 和 4G 移动网络切换时,客户端的 IP 肯定会发生变化,需要重新建立和服务端的 TCP 连接。

      又比如大家使用公共 NAT 出口时,有些连接竞争时需要重新绑定端口,导致客户端的端口发生变化,同样需要重新建立 TCP 连接。

      针对 TCP 的连接变化,MPTCP[5] 其实已经有了解决方案,但是由于 MPTCP 需要操作系统及网络协议栈支持,部署阻力非常大,目前并不适用。

      所以从 TCP 连接的角度来讲,这个问题是无解的。

      那 QUIC 是如何做到连接迁移呢?很简单,任何一条 QUIC 连接不再以 IP 及端口四元组标识,而是以一个 64 位的随机数作为 ID 来标识,这样就算 IP 或者端口发生变化时,只要 ID 不变,这条连接依然维持着,上层业务逻辑感知不到变化,不会中断,也就不需要重连。

      由于这个 ID 是客户端随机产生的,并且长度有 64 位,所以冲突概率非常低。

      其他亮点

      此外,QUIC 还能实现前向冗余纠错,在重要的包比如握手消息发生丢失时,能够根据冗余信息还原出握手消息。

      QUIC 还能实现证书压缩,减少证书传输量,针对包头进行验证等。

      限于篇幅,本文不再详细介绍,有兴趣的可以参考文档 [23] 和文档 [4] 和文档 [26]。

      参考线索

      [1]. https://www.chromium.org/quic

      [2]. https://docs.google.com/docume ... /edit

      [3]. E. Rescorla, “The Transport Layer Security (TLS) Protocol Version 1.3”, draft-ietf-tls-tls13-21, https://tools.ietf.org/html/dr ... 13-21, July 03, 2017

      [4]. Adam Langley,Wan-Teh Chang, “QUIC Crypto”,https://docs.google.com/docume ... /edit, 20161206

      [5]. https://www.multipath-tcp.org/

      [6]. Ha, S., Rhee, I., and L. Xu, "CUBIC: A New TCP-Friendly High-Speed TCP Variant", ACM SIGOPS Operating System Review , 2008.

      [7]. M. Belshe,BitGo, R. Peon, “Hypertext Transfer Protocol Version 2 (HTTP/2)”, RFC 7540, May 2015

      [8]. M. Mathis,J. Mahdavi,S. Floyd,A. Romanow,“TCP Selective Acknowledgment Options”, rfc2018, https://tools.ietf.org/html/rfc2018, October 1996

      [9]. V. Paxson,M. Allman,J. Chu,M. Sargent,“Computing TCP's Retransmission Timer”, rfc6298, https://tools.ietf.org/html/rfc6298, June 2011

      [10]. R. Peon,H. Ruellan,“HPACK: Header Compression for HTTP/2”,RFC7541,May 2015

      [11]. M. Scharf, Alcatel-Lucent Bell Labs, S. Kiesel, “Quantifying Head-of-Line Blocking in TCP and SCTP”, https://tools.ietf.org/id/draf ... .html, July 15, 2013

      [12]. Ilya Grigorik,“Optimizing TLS Record Size & Buffering Latency”, https://www.igvita.com/2013/10 ... ency/, October 24, 2013

      [13]. J. Salowey,H. Zhou,P. Eronen,H. Tschofenig, “Transport Layer Security (TLS) Session Resumption without Server-Side State”, RFC5077, January 2008

      [14]. Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008, .

      [15]. Shirey, R., "Internet Security Glossary, Version 2", FYI , RFC 4949, August 2007

      [16]. 罗成,“HTTPS性能优化”, http://www.infoq.com/cn/presen ... https,February.2017

      [17]. Postel, J., "Transmission Control Protocol", STD 7, RFC793, September 1981.

      [18]. J. Postel,“User Datagram Protocol”, RFC768,August 1980

      [19]. Q. Dang, S. Santesson,K. Moriarty,D. Brown.T. Polk, “Internet X.509 Public Key Infrastructure: Additional Algorithms and Identifiers for DSA and ECDSA”,RFC5758, January 2010

      [20]. Bassham, L., Polk, W., and R. Housley, "Algorithms and Identifiers for the Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile", RFC 3279, April 2002

      [21]. D.Cooper,S.Santesson, S.Farrell,S. Boeyen,R. Housley,W.Polk, “Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile”, RFC5280, May 2008

      [22]. M. Allman,V. Paxson,E. Blanton, "TCP Congestion Control”,RFC5681, September 2009

      [23]. Robbie Shade, “Flow control in QUIC”, https://docs.google.com/docume ... it%23, May, 2016,

      [24]. ianswett , “QUIC fec v1”, https://docs.google.com/docume ... tytjt, 2016-02-19

      [25]. D.Borman,B.Braden,V.Jacobson,R.Scheffenegger, Ed. “TCP Extensions for High Performance”,rfc7323, https://tools.ietf.org/html/rfc7323,September 2014

      [26]. 罗成,“WEB加速,协议先行”, https://zhuanlan.zhihu.com/p/27938635,july, 2017 收起阅读 »

      Netty实现长连接服务的各种难点和可优化点

      推送服务

      还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。

      所以之前在 Android 上做推送大部分只能靠轮询。而我们之前在技术调研的时候,搜到了 jPush 的博客,上面介绍了一些他们的技术特点,他们主要做的其实就是移动网络下的长连接服务。单机 50W-100W 的连接的确是吓我一跳!后来我们也采用了他们的免费方案,因为是一个受众面很小的产品,所以他们的免费版够我们用了。一年多下来,运作稳定,非常不错!

      时隔两年,换了部门后,竟然接到了一项任务,优化公司自己的长连接服务端。

      再次搜索网上技术资料后才发现,相关的很多难点都被攻破,网上也有了很多的总结文章,单机 50W-100W 的连接完全不是梦,其实人人都可以做到。但是光有连接还不够,QPS 也要一起上去。

      所以,这篇文章就是汇总一下利用 Netty 实现长连接服务过程中的各种难点和可优化点。

      Netty 是什么

      Netty: http://netty.io/

      Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

      官方的解释最精准了,期中最吸引人的就是高性能了。但是很多人会有这样的疑问:直接用 NIO 实现的话,一定会更快吧?就像我直接手写 JDBC 虽然代码量大了点,但是一定比 iBatis 快!

      但是,如果了解 Netty 后你才会发现,这个还真不一定!

      利用 Netty 而不用 NIO 直接写的优势有这些:

      高性能高扩展的架构设计,大部分情况下你只需要关注业务而不需要关注架构

      Zero-Copy 技术尽量减少内存拷贝

      为 Linux 实现 Native 版 Socket

      写同一份代码,兼容 java 1.7 的 NIO2 和 1.7 之前版本的 NIO

      Pooled Buffers 大大减轻 Buffer 和释放 Buffer 的压力

      ……

      特性太多,大家可以去看一下《Netty in Action》这本书了解更多。

      另外,Netty 源码是一本很好的教科书!大家在使用的过程中可以多看看它的源码,非常棒!

       
      瓶颈是什么

      想要做一个长链服务的话,最终的目标是什么?而它的瓶颈又是什么?

      其实目标主要就两个:

      更多的连接

      更高的 QPS

      所以,下面就针对这连个目标来说说他们的难点和注意点吧。

      更多的连接
      非阻塞 IO


      其实无论是用 Java NIO 还是用 Netty,达到百万连接都没有任何难度。因为它们都是非阻塞的 IO,不需要为每个连接创建一个线程了。

      欲知详情,可以搜索一下BIO,NIO,AIO的相关知识点。

      Java NIO 实现百万连接ServerSocketChannel ssc = ServerSocketChannel.open(); Selector sel = Selector.open(); ssc.configureBlocking(false); ssc.socket().bind(new InetSocketAddress(8080)); SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT); while(true) {     sel.select();     Iterator it = sel.selectedKeys().iterator();     while(it.hasNext()) {         SelectionKey skey = (SelectionKey)it.next();         it.remove();         if(skey.isAcceptable()) {             ch = ssc.accept();         }     } }这段代码只会接受连过来的连接,不做任何操作,仅仅用来测试待机连接数极限。

      大家可以看到这段代码是 NIO 的基本写法,没什么特别的。


      Netty 实现百万连接NioEventLoopGroup bossGroup =  new NioEventLoopGroup(); NioEventLoopGroup workerGroup= new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup); bootstrap.channel( NioServerSocketChannel.class); bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {     @Override protected void initChannel(SocketChannel ch) throws Exception {         ChannelPipeline pipeline = ch.pipeline();         //todo: add handler     }}); bootstrap.bind(8080).sync();这段其实也是非常简单的 Netty 初始化代码。同样,为了实现百万连接根本没有什么特殊的地方。


      瓶颈到底在哪

      上面两种不同的实现都非常简单,没有任何难度,那有人肯定会问了:实现百万连接的瓶颈到底是什么?

      其实只要 java 中用的是非阻塞 IO(NIO 和 AIO 都算),那么它们都可以用单线程来实现大量的 Socket 连接。 不会像 BIO 那样为每个连接创建一个线程,因为代码层面不会成为瓶颈。

      其实真正的瓶颈是在 Linux 内核配置上,默认的配置会限制全局最大打开文件数(Max Open Files)还会限制进程数。 所以需要对 Linux 内核配置进行一定的修改才可以。

      这个东西现在看似很简单,按照网上的配置改一下就行了,但是大家一定不知道第一个研究这个人有多难。

      这里直接贴几篇文章,介绍了相关配置的修改方式:

      构建C1000K的服务器

      100万并发连接服务器笔记之1M并发连接目标达成

      淘宝技术分享 HTTP长连接200万尝试及调优


      如何验证

      让服务器支持百万连接一点也不难,我们当时很快就搞定了一个测试服务端,但是最大的问题是,我怎么去验证这个服务器可以支撑百万连接呢?

      我们用 Netty 写了一个测试客户端,它同样用了非阻塞 IO ,所以不用开大量的线程。 但是一台机器上的端口数是有限制的,用root权限的话,最多也就 6W 多个连接了。 所以我们这里用 Netty 写一个客户端,用尽单机所有的连接吧。NioEventLoopGroup workerGroup =  new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(workerGroup); b.channel( NioSocketChannel.class); b.handler(new ChannelInitializer<SocketChannel>() {     @Override     public void initChannel(SocketChannel ch) throws Exception {         ChannelPipeline pipeline = ch.pipeline();         //todo:add handler     }     }); for (int k = 0; k < 60000; k++) {     //请自行修改成服务端的IP     b.connect(127.0.0.1, 8080); }代码同样很简单,只要连上就行了,不需要做任何其他的操作。

      这样只要找到一台电脑启动这个程序即可。这里需要注意一点,客户端最好和服务端一样,修改一下 Linux 内核参数配置。


      怎么去找那么多机器

      按照上面的做法,单机最多可以有 6W 的连接,百万连接起码需要17台机器!

      如何才能突破这个限制呢?其实这个限制来自于网卡。 我们后来通过使用虚拟机,并且把虚拟机的虚拟网卡配置成了桥接模式解决了问题。

      根据物理机内存大小,单个物理机起码可以跑4-5个虚拟机,所以最终百万连接只要4台物理机就够了。


      讨巧的做法

      除了用虚拟机充分压榨机器资源外,还有一个非常讨巧的做法,这个做法也是我在验证过程中偶然发现的。

      根据 TCP/IP 协议,任何一方发送FIN后就会启动正常的断开流程。而如果遇到网络瞬断的情况,连接并不会自动断开。

      那我们是不是可以这样做?

      启动服务端,千万别设置 Socket 的keep-alive属性,默认是不设置的

      用虚拟机连接服务器

      强制关闭虚拟机

      修改虚拟机网卡的 MAC 地址,重新启动并连接服务器

      服务端接受新的连接,并保持之前的连接不断

      我们要验证的是服务端的极限,所以只要一直让服务端认为有那么多连接就行了,不是吗?

      经过我们的试验后,这种方法和用真实的机器连接服务端的表现是一样的,因为服务端只是认为对方网络不好罢了,不会将你断开。

      另外,禁用keep-alive是因为如果不禁用,Socket 连接会自动探测连接是否可用,如果不可用会强制断开。


      更高的 QPS

      由于 NIO 和 Netty 都是非阻塞 IO,所以无论有多少连接,都只需要少量的线程即可。而且 QPS 不会因为连接数的增长而降低(在内存足够的前提下)。

      而且 Netty 本身设计得足够好了,Netty 不是高 QPS 的瓶颈。那高 QPS 的瓶颈是什么?

      是数据结构的设计!


      如何优化数据结构

      首先要熟悉各种数据结构的特点是必需的,但是在复杂的项目中,不是用了一个集合就可以搞定的,有时候往往是各种集合的组合使用。

      既要做到高性能,还要做到一致性,还不能有死锁,这里难度真的不小…

      我在这里总结的经验是,不要过早优化。优先考虑一致性,保证数据的准确,然后再去想办法优化性能。

      因为一致性比性能重要得多,而且很多性能问题在量小和量大的时候,瓶颈完全会在不同的地方。 所以,我觉得最佳的做法是,编写过程中以一致性为主,性能为辅;代码完成后再去找那个 TOP1,然后去解决它!


      解决 CPU 瓶颈

      在做这个优化前,先在测试环境中去狠狠地压你的服务器,量小量大,天壤之别。

      有了压力测试后,就需要用工具来发现性能瓶颈了!

      我喜欢用的是 VisualVM,打开工具后看抽样器(Sample),根据自用时间(Self Time (CPU))倒序,排名第一的就是你需要去优化的点了!
      备注:Sample 和 Profiler 有什么区别?前者是抽样,数据不是最准但是不影响性能;后者是统计准确,但是非常影响性能。 如果你的程序非常耗 CPU,那么尽量用 Sample,否则开启 Profiler 后降低性能,反而会影响准确性。

      1.png

       还记得我们项目第一次发现的瓶颈竟然是ConcurrentLinkedQueue这个类中的size()方法。 量小的时候没有影响,但是Queue很大的时候,它每次都是从头统计总数的,而这个size()方法我们又是非常频繁地调用的,所以对性能产生了影响。

      size()的实现如下:public int size() {     int count = 0;     for (Node<E> p = first(); p != null; p = succ(p))     if (p.item != null)     // Collection.size() spec says to max out     if (++count == Integer.MAX_VALUE)     break;     return count; }后来我们通过额外使用一个AtomicInteger来计数,解决了问题。但是分离后岂不是做不到高一致性呢? 没关系,我们的这部分代码关心最终一致性,所以只要保证最终一致就可以了。

      总之,具体案例要具体分析,不同的业务要用不同的实现。


      解决 GC 瓶颈

      GC 瓶颈也是 CPU 瓶颈的一部分,因为不合理的 GC 会大大影响 CPU 性能。

      这里还是在用 VisualVM,但是你需要装一个插件:VisualGC

      2.png


      有了这个插件后,你就可以直观的看到 GC 活动情况了。

      按照我们的理解,在压测的时候,有大量的 New GC 是很正常的,因为有大量的对象在创建和销毁。

      但是一开始有很多 Old GC 就有点说不过去了!

      后来发现,在我们压测环境中,因为 Netty 的 QPS 和连接数关联不大,所以我们只连接了少量的连接。内存分配得也不是很多。

      而 JVM 中,默认的新生代和老生代的比例是1:2,所以大量的老生代被浪费了,新生代不够用。

      通过调整 -XX:NewRatio 后,Old GC 有了显著的降低。

      但是,生产环境又不一样了,生产环境不会有那么大的 QPS,但是连接会很多,连接相关的对象存活时间非常长,所以生产环境更应该分配更多的老生代。

      总之,GC 优化和 CPU 优化一样,也需要不断调整,不断优化,不是一蹴而就的。


      其他优化

      如果你已经完成了自己的程序,那么一定要看看《Netty in Action》作者的这个网站:Netty Best Practices a.k.a Faster == Better。

      相信你会受益匪浅,经过里面提到的一些小小的优化后,我们的整体 QPS 提升了很多。

      最后一点就是,java 1.7 比 java 1.6 性能高很多!因为 Netty 的编写风格是事件机制的,看似是 AIO。 可 java 1.6 是没有 AIO 的,java 1.7 是支持 AIO 的,所以如果用 java 1.7 的话,性能也会有显著提升。


      最后成果

      经过几周的不断压测和不断优化了,我们在一台16核、120G内存(JVM只分配8G)的机器上,用 java 1.6 达到了60万的连接和20万的QPS。

      其实这还不是极限,JVM 只分配了8G内存,内存配置再大一点连接数还可以上去;

      QPS 看似很高,System Load Average 很低,也就是说明瓶颈不在 CPU 也不在内存,那么应该是在 IO 了! 上面的 Linux 配置是为了达到百万连接而配置的,并没有针对我们自己的业务场景去做优化。

      因为目前性能完全够用,线上单机 QPS 最多才 1W,所以我们先把精力放在了其他地方。 相信后面我们还会去继续优化这块的性能,期待 QPS 能有更大的突破!

      本作品由 Dozer 创作,采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。
      继续阅读 »
      推送服务

      还记得一年半前,做的一个项目需要用到 Android 推送服务。和 iOS 不同,Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging ,但是连国外都没统一,更别说国内了,直接被墙。

      所以之前在 Android 上做推送大部分只能靠轮询。而我们之前在技术调研的时候,搜到了 jPush 的博客,上面介绍了一些他们的技术特点,他们主要做的其实就是移动网络下的长连接服务。单机 50W-100W 的连接的确是吓我一跳!后来我们也采用了他们的免费方案,因为是一个受众面很小的产品,所以他们的免费版够我们用了。一年多下来,运作稳定,非常不错!

      时隔两年,换了部门后,竟然接到了一项任务,优化公司自己的长连接服务端。

      再次搜索网上技术资料后才发现,相关的很多难点都被攻破,网上也有了很多的总结文章,单机 50W-100W 的连接完全不是梦,其实人人都可以做到。但是光有连接还不够,QPS 也要一起上去。

      所以,这篇文章就是汇总一下利用 Netty 实现长连接服务过程中的各种难点和可优化点。

      Netty 是什么

      Netty: http://netty.io/

      Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

      官方的解释最精准了,期中最吸引人的就是高性能了。但是很多人会有这样的疑问:直接用 NIO 实现的话,一定会更快吧?就像我直接手写 JDBC 虽然代码量大了点,但是一定比 iBatis 快!

      但是,如果了解 Netty 后你才会发现,这个还真不一定!

      利用 Netty 而不用 NIO 直接写的优势有这些:

      高性能高扩展的架构设计,大部分情况下你只需要关注业务而不需要关注架构

      Zero-Copy 技术尽量减少内存拷贝

      为 Linux 实现 Native 版 Socket

      写同一份代码,兼容 java 1.7 的 NIO2 和 1.7 之前版本的 NIO

      Pooled Buffers 大大减轻 Buffer 和释放 Buffer 的压力

      ……

      特性太多,大家可以去看一下《Netty in Action》这本书了解更多。

      另外,Netty 源码是一本很好的教科书!大家在使用的过程中可以多看看它的源码,非常棒!

       
      瓶颈是什么

      想要做一个长链服务的话,最终的目标是什么?而它的瓶颈又是什么?

      其实目标主要就两个:

      更多的连接

      更高的 QPS

      所以,下面就针对这连个目标来说说他们的难点和注意点吧。

      更多的连接
      非阻塞 IO


      其实无论是用 Java NIO 还是用 Netty,达到百万连接都没有任何难度。因为它们都是非阻塞的 IO,不需要为每个连接创建一个线程了。

      欲知详情,可以搜索一下BIO,NIO,AIO的相关知识点。

      Java NIO 实现百万连接ServerSocketChannel ssc = ServerSocketChannel.open(); Selector sel = Selector.open(); ssc.configureBlocking(false); ssc.socket().bind(new InetSocketAddress(8080)); SelectionKey key = ssc.register(sel, SelectionKey.OP_ACCEPT); while(true) {     sel.select();     Iterator it = sel.selectedKeys().iterator();     while(it.hasNext()) {         SelectionKey skey = (SelectionKey)it.next();         it.remove();         if(skey.isAcceptable()) {             ch = ssc.accept();         }     } }这段代码只会接受连过来的连接,不做任何操作,仅仅用来测试待机连接数极限。

      大家可以看到这段代码是 NIO 的基本写法,没什么特别的。


      Netty 实现百万连接NioEventLoopGroup bossGroup =  new NioEventLoopGroup(); NioEventLoopGroup workerGroup= new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup); bootstrap.channel( NioServerSocketChannel.class); bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {     @Override protected void initChannel(SocketChannel ch) throws Exception {         ChannelPipeline pipeline = ch.pipeline();         //todo: add handler     }}); bootstrap.bind(8080).sync();这段其实也是非常简单的 Netty 初始化代码。同样,为了实现百万连接根本没有什么特殊的地方。


      瓶颈到底在哪

      上面两种不同的实现都非常简单,没有任何难度,那有人肯定会问了:实现百万连接的瓶颈到底是什么?

      其实只要 java 中用的是非阻塞 IO(NIO 和 AIO 都算),那么它们都可以用单线程来实现大量的 Socket 连接。 不会像 BIO 那样为每个连接创建一个线程,因为代码层面不会成为瓶颈。

      其实真正的瓶颈是在 Linux 内核配置上,默认的配置会限制全局最大打开文件数(Max Open Files)还会限制进程数。 所以需要对 Linux 内核配置进行一定的修改才可以。

      这个东西现在看似很简单,按照网上的配置改一下就行了,但是大家一定不知道第一个研究这个人有多难。

      这里直接贴几篇文章,介绍了相关配置的修改方式:

      构建C1000K的服务器

      100万并发连接服务器笔记之1M并发连接目标达成

      淘宝技术分享 HTTP长连接200万尝试及调优


      如何验证

      让服务器支持百万连接一点也不难,我们当时很快就搞定了一个测试服务端,但是最大的问题是,我怎么去验证这个服务器可以支撑百万连接呢?

      我们用 Netty 写了一个测试客户端,它同样用了非阻塞 IO ,所以不用开大量的线程。 但是一台机器上的端口数是有限制的,用root权限的话,最多也就 6W 多个连接了。 所以我们这里用 Netty 写一个客户端,用尽单机所有的连接吧。NioEventLoopGroup workerGroup =  new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(workerGroup); b.channel( NioSocketChannel.class); b.handler(new ChannelInitializer<SocketChannel>() {     @Override     public void initChannel(SocketChannel ch) throws Exception {         ChannelPipeline pipeline = ch.pipeline();         //todo:add handler     }     }); for (int k = 0; k < 60000; k++) {     //请自行修改成服务端的IP     b.connect(127.0.0.1, 8080); }代码同样很简单,只要连上就行了,不需要做任何其他的操作。

      这样只要找到一台电脑启动这个程序即可。这里需要注意一点,客户端最好和服务端一样,修改一下 Linux 内核参数配置。


      怎么去找那么多机器

      按照上面的做法,单机最多可以有 6W 的连接,百万连接起码需要17台机器!

      如何才能突破这个限制呢?其实这个限制来自于网卡。 我们后来通过使用虚拟机,并且把虚拟机的虚拟网卡配置成了桥接模式解决了问题。

      根据物理机内存大小,单个物理机起码可以跑4-5个虚拟机,所以最终百万连接只要4台物理机就够了。


      讨巧的做法

      除了用虚拟机充分压榨机器资源外,还有一个非常讨巧的做法,这个做法也是我在验证过程中偶然发现的。

      根据 TCP/IP 协议,任何一方发送FIN后就会启动正常的断开流程。而如果遇到网络瞬断的情况,连接并不会自动断开。

      那我们是不是可以这样做?

      启动服务端,千万别设置 Socket 的keep-alive属性,默认是不设置的

      用虚拟机连接服务器

      强制关闭虚拟机

      修改虚拟机网卡的 MAC 地址,重新启动并连接服务器

      服务端接受新的连接,并保持之前的连接不断

      我们要验证的是服务端的极限,所以只要一直让服务端认为有那么多连接就行了,不是吗?

      经过我们的试验后,这种方法和用真实的机器连接服务端的表现是一样的,因为服务端只是认为对方网络不好罢了,不会将你断开。

      另外,禁用keep-alive是因为如果不禁用,Socket 连接会自动探测连接是否可用,如果不可用会强制断开。


      更高的 QPS

      由于 NIO 和 Netty 都是非阻塞 IO,所以无论有多少连接,都只需要少量的线程即可。而且 QPS 不会因为连接数的增长而降低(在内存足够的前提下)。

      而且 Netty 本身设计得足够好了,Netty 不是高 QPS 的瓶颈。那高 QPS 的瓶颈是什么?

      是数据结构的设计!


      如何优化数据结构

      首先要熟悉各种数据结构的特点是必需的,但是在复杂的项目中,不是用了一个集合就可以搞定的,有时候往往是各种集合的组合使用。

      既要做到高性能,还要做到一致性,还不能有死锁,这里难度真的不小…

      我在这里总结的经验是,不要过早优化。优先考虑一致性,保证数据的准确,然后再去想办法优化性能。

      因为一致性比性能重要得多,而且很多性能问题在量小和量大的时候,瓶颈完全会在不同的地方。 所以,我觉得最佳的做法是,编写过程中以一致性为主,性能为辅;代码完成后再去找那个 TOP1,然后去解决它!


      解决 CPU 瓶颈

      在做这个优化前,先在测试环境中去狠狠地压你的服务器,量小量大,天壤之别。

      有了压力测试后,就需要用工具来发现性能瓶颈了!

      我喜欢用的是 VisualVM,打开工具后看抽样器(Sample),根据自用时间(Self Time (CPU))倒序,排名第一的就是你需要去优化的点了!
      备注:Sample 和 Profiler 有什么区别?前者是抽样,数据不是最准但是不影响性能;后者是统计准确,但是非常影响性能。 如果你的程序非常耗 CPU,那么尽量用 Sample,否则开启 Profiler 后降低性能,反而会影响准确性。

      1.png

       还记得我们项目第一次发现的瓶颈竟然是ConcurrentLinkedQueue这个类中的size()方法。 量小的时候没有影响,但是Queue很大的时候,它每次都是从头统计总数的,而这个size()方法我们又是非常频繁地调用的,所以对性能产生了影响。

      size()的实现如下:public int size() {     int count = 0;     for (Node<E> p = first(); p != null; p = succ(p))     if (p.item != null)     // Collection.size() spec says to max out     if (++count == Integer.MAX_VALUE)     break;     return count; }后来我们通过额外使用一个AtomicInteger来计数,解决了问题。但是分离后岂不是做不到高一致性呢? 没关系,我们的这部分代码关心最终一致性,所以只要保证最终一致就可以了。

      总之,具体案例要具体分析,不同的业务要用不同的实现。


      解决 GC 瓶颈

      GC 瓶颈也是 CPU 瓶颈的一部分,因为不合理的 GC 会大大影响 CPU 性能。

      这里还是在用 VisualVM,但是你需要装一个插件:VisualGC

      2.png


      有了这个插件后,你就可以直观的看到 GC 活动情况了。

      按照我们的理解,在压测的时候,有大量的 New GC 是很正常的,因为有大量的对象在创建和销毁。

      但是一开始有很多 Old GC 就有点说不过去了!

      后来发现,在我们压测环境中,因为 Netty 的 QPS 和连接数关联不大,所以我们只连接了少量的连接。内存分配得也不是很多。

      而 JVM 中,默认的新生代和老生代的比例是1:2,所以大量的老生代被浪费了,新生代不够用。

      通过调整 -XX:NewRatio 后,Old GC 有了显著的降低。

      但是,生产环境又不一样了,生产环境不会有那么大的 QPS,但是连接会很多,连接相关的对象存活时间非常长,所以生产环境更应该分配更多的老生代。

      总之,GC 优化和 CPU 优化一样,也需要不断调整,不断优化,不是一蹴而就的。


      其他优化

      如果你已经完成了自己的程序,那么一定要看看《Netty in Action》作者的这个网站:Netty Best Practices a.k.a Faster == Better。

      相信你会受益匪浅,经过里面提到的一些小小的优化后,我们的整体 QPS 提升了很多。

      最后一点就是,java 1.7 比 java 1.6 性能高很多!因为 Netty 的编写风格是事件机制的,看似是 AIO。 可 java 1.6 是没有 AIO 的,java 1.7 是支持 AIO 的,所以如果用 java 1.7 的话,性能也会有显著提升。


      最后成果

      经过几周的不断压测和不断优化了,我们在一台16核、120G内存(JVM只分配8G)的机器上,用 java 1.6 达到了60万的连接和20万的QPS。

      其实这还不是极限,JVM 只分配了8G内存,内存配置再大一点连接数还可以上去;

      QPS 看似很高,System Load Average 很低,也就是说明瓶颈不在 CPU 也不在内存,那么应该是在 IO 了! 上面的 Linux 配置是为了达到百万连接而配置的,并没有针对我们自己的业务场景去做优化。

      因为目前性能完全够用,线上单机 QPS 最多才 1W,所以我们先把精力放在了其他地方。 相信后面我们还会去继续优化这块的性能,期待 QPS 能有更大的突破!

      本作品由 Dozer 创作,采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。 收起阅读 »

      如何构建分布式SFU/MCU媒体服务器?

      本文来自英特尔实时通信解决方案架构师 段先德在LiveVideoStackCon2019上海大会的分享,详细介绍了英特尔在进行分布式SFU/MCU媒体服务器的架构设计中秉持的一些设计原则以及关键问题的解决思路。
      大家好,我是来自英特尔上海研发中心的段先德。从2014年开始主要做基于WebRTC的实时通信和统一通信解决方案。对于实时通讯来说WebRTC技术是一个革命性的存在。2014年4月英特尔发布了Intel® Collaboration Suite for WebRTC,这是一款可免费使用的包含服务器侧程序和客户端SDK的完整解决方案。经过多年的迭代更新,当前最新发布的是4.2版本。

      1. Requirements and Design Principles

      本次分享的内容主要分为三个部分,首先介绍英特尔ICS for WebRTC项目中要解决的问题;其次介绍我们在解决这些问题的时候的指导思想和整体设计原则;最后介绍我们的解决方案目前的状态以及当下和近期要做的一些事情。

      1.1 Functional Requirements

      1.jpg


      我们项目团队最初的出发点是希望能做一套够达到一般功能性要求的基于互联网的视频会议解决方案。譬如可以支持WebRTC和SIP终端,实现接入到同一个会议中。SIP主要针对的是存量设备,重点是对WebRTC终端的支持。WebRTC接入相比于很多以前存量的企业视频会议解决方案有很多的突破,从2011年以后Chrome在端多媒体系统,弱网对抗方面以及音视频处理这方面一直在持续的改进。

      英特尔很早就注意到在WebRTC时代,亟需一个统一的终端和服务器侧的解决方案。我们需要把企业内外的一些移动终端、桌面应用、浏览器、传统的SIP终端设备都支持起来,需要支持NAT穿越和屏幕共享,需要支持服务器侧音视频录制,等等。这里面很多功能性需求通过传统SIP的解决方案做起来很不方便或者成本很高,但是在WebRTC时代,在基于互联网应用的技术思路下,可以很便捷、很优雅地解决这些问题,于是我们在2014年做了ICS for WebRTC v1.0。之后在2016年和2017年之间直播类的应用大爆发使得有些客户希望我们的解决方案里面能够支持直播类场景,把实时互动场景下的音视频流通过RTMP/RTSP/HLS/Dash推送到现有的CDN网络里面去。基于这类需求,我们在功能性方面增加了互动Streaming的能力。

      2018年到现在,直播的用户体验要求越来越高,客户希望主播和粉丝或者观众之间的互动能够非常平滑的切换,同时端到端的时延也能够做得更好,也就是希望做到保证端到端的实时性的前提下,在单个呼叫里支持海量的用户连接。这就要求服务器侧系统既要有非常大的“扇出”能力,要支持终端连接在“发布者”和“订阅者”之间非常平滑地进行切换。我们目前正在做的就是把目前的解决方案扩展到这种能够支持大规模并发的“实时互动广播”,初步目标是单个呼叫里达到百万以上的并发连接,而且端到端的时延能够全球控制在300毫秒以内。关于端到端时延,我们在国内互联网上做过一些小规模的测试,测试结果的时延是150毫秒以内。我们还希望这个解决方案能够很方便封装成类似于CDN的服务访问接口或者形式,以便集成到客户现有的直播解决方案中去。

      我们当前的解决方案已经具备了非常灵活的服务器侧媒体处理,服务器端可以做音视频的混音混流,比如说当前的一个呼叫里面有十几个参与方,有的参与方希望订阅呼叫中其他参与方发布的原始流,有的参与方希望订阅所有或部分参与方的mix流,有的参与方希望订阅符合自己对codec、分辨率、帧率、码率等定制化要求的转发流,我们当前的解决方案已经可以很好地支持这些需求。

      1.2 Nonfunctional Requirements

      2.jpg


      如果仅仅是为了达到前面所讲的各种功能性需求,随便选择一个现有的开源框架去改改,再自己从头写一些功能模块拼凑一下,总可以整出一个PoC的版本或可以初步走向产品的东西。如果是要严肃地做一个打算把它放到生产环境去运营的产品级别的东西,真正考验这个解决方案的生命力的其实是它在非功能性需求方面的取舍和功力。即使是选择现有的开源框架去做产品,这个框架对非功能性方面的考量也是最重要的决定因素。

      在非功能性方面主要关注的点有三个方面。

      一是系统的可扩展性,它的服务部署规模可大可小,可以小到在一台英特尔®️ 酷睿™️i7的PC上部署使用,大到一个集群几百台甚至上千台机器组成一个大的cluster上部署使用。另外呼叫的参与方式可以是两三个人的讨论会,或者十几个人一般视频会议,又或者是几十人的在线课堂。部署时可以在当前的系统容量不足时在不中断业务的前提下增加或者删减当前部署的规模,达到很灵活的Scale in/Scale out。

      二是容错性,容错能力大多描述都比较抽象,但是落实到系统在做设计的时候要考虑的东西就是非常具体的设计决策,在系统设计里面我们会强调甚至固执的坚持每一个部件都可能会出错,运行时都会发生crash,这就需要在流程设计或者一般逻辑里面handle这些问题,在系统发生部分失效的时候,要能够做到自动恢复或服务优雅降级。

      三是分布式部署,单台机器上单实例的部署是不可能做容错的,只有分布式的部署才能够做到。我们要求允许把任何部件部署在数据中心的多台机器上面。我们现在进一步的要求是要能够把任何部件部署在多个数据中心,进行跨数据中心的分布式部署。

      2.Unified Media Spread Model UMSM)
      2.1 Modularization at Runtime

      3.jpg


      要满足上述的各种功能性和非功能性需求,就需要在概念模型上对系统的各个部件进行足够的抽象,将逻辑上独立的部件封装到运行时独立的模块里面——即模块化。不管是从单一职责的角度来说,还是从系统的可组合性来说,模块化是自始至终不能打破的一个原则,是我们当前系统——也是很多复杂系统进行架构的第一原则。在我们的系统设计中,对于跟客户端交互的部件来说,要把信令和媒体分开。对于媒体部分来说,媒体的接入部分和处理部分一定是分开的,直接和用户打交道的部分和后台内部的一些处理部件,不管是从单一职责角度来讲还是从面向接口的健壮性要求来讲都必须把它们分开。

      我们的服务器侧系统在运行时可以分成五大块。

      第一块就是跟客户端进行信令交互的部件,即图中的WebRTC Portal和SIP Portal。他们跟WebRTC客户端和SIP终端进行信令交互。值得注意的一点是WebRTC标准对信令交互的格式和通道没有规定,我们采用的是一种承载在socket.io通道中的私有协议。

      第二块是跟客户端进行音视频媒体交互的部件,即图中的WebRTC Agent、Streaming Agent、SIP Agent和Recording Agent。其中WebRTC Agent负责跟客户端之间建立PeerConnection连接,SIP Agent跟SIP终端RTP流进行传输,Streaming Agent是针对RTSP/RTMP/HLS/Dash流,我们可以把IPCamera的RTSP流作为输入直接拉到系统里面来,也可以把系统里面任何一个输入流/合成流/转码后的流作为输出推送到RTMP Server上去,Recording虽然是完全发生在服务器侧的行为,但实际上在概念层次上面是更接近于流的输出。所以在概念模型里我们也把Recording Agent当做媒体接出部件,以达到概念模型的一致性。

      第三块是媒体处理的部件,即图中的Audio Agent和Video Agent。Audio Agent是进行音频混音转码工作的部件,Video Agent是视频的合屏和转码的部件,这些所有的部件都是单独部署独立进程在运行。

      第四块是呼叫控制的部件,即图中的Conference Agent。我们的系统还是将多方实时音视频通信作为场景基础,Conference Agent就是一通呼叫的总控制部件,它负责room中的参与者、流、订阅关系的控制和管理。对于像远程教育、远程医疗、远程协助之类的其他场景,我们主要是通过对Conference Agent来进行拓展和增强去支持。

      第五块就是一些支持部件。整个服务器系统在运行和单机运行时都是cluster形式,Cluster Manager就是一个简单的cluster管理器。视频会议场景中会有一些room的预配置和管理,room的配置数据存放在MongoDB中,管理员都是通过OAM UI通过RESTful API访问Management API部件实现数据访问并受理REST请求。另外各个部件之间的rpc是架设在RabbitMQ消息队列上的。

      2.2 Strong Isolation

      4.jpg


      第二个原则就是要做强隔离。在系统里面坚持执行的原则就是要做强隔离,运行时一定是把看到的逻辑上面独立部件,把它在物理上也做成完全独立的运行时进程。比如像信令受理部件和信令执行部件就是分别独立的进程。这样做使得信令受理部件可以独立于呼叫控制里面的业务逻辑而存在。同理媒体接入部件和媒体处理部件也是分别独立进程。这里的进程就是OS语义上面进程,是我们服务器侧系统构建的基本元素,是生命体的细胞,不同的部件之间进行通讯唯一的方式就是message passing(消息传递)。在概念模型里面看的得到部件都是用单独的Worker进程来处理一个独立的Job。比方说一个Video Agent生成出来的Video Node,它的职责要么是做一个视频混流器,要么是做一个视频转码器,单独运行,独立工作。这样做一方面是进行错误隔离一个部件中产生的异常不会传染影响其他部件,一方面是各个运行时部件可以进行运行时单独进行升级替换。

      2.3 Hierarchy in Media Accessing/Processing

      5.jpg


      第三个原则就是层次化。具体体现在在媒体接入和媒体处理的一些部件的设计和实现上,这些部件在南北(纵)向上面有明确的层次划分,自下而上分为包交互层、帧交互层和内容操作层。以媒体接入部件为例,我们服务器侧系统需要跟各种外围系统和终端进行媒体交互,有的媒体是通过RTP/SRTP包的形式输入、输出,有的媒体是直接以AVStream的行书输出、输出。当媒体进入到我们服务器侧系统内部以后,我们希望有一个统一的格式让它在所有的媒体相关部件之间自由流转,所以我们就定义了统一的MediaFrame格式,所有输入的媒体在媒体接入部件上被组装成MediaFrame。处理MediaFrame的逻辑我们把它放在帧交互层,与客户端进行RTP/SRTP交互的逻辑我们放在包交互层。另外,MediaFrame进入媒体处理部件后,如果涉及到raw格式的操作——譬如合屏、色彩调整、添加水印、替换背景等——我们就把相关逻辑放在内容操作层。

      2.4 Media Pipeline in WebRTC Node

      6.jpg


      设计原则讲起来太枯燥,举两个例子。

      第一个是WebRTC Node中的Pipeline结构。在WebRTCNode上面有一个明确的一个界限,广为人知的一些开源的框架里面有一些SFU框架是直接做RTP包的高级转发,而在我们的解决方案里于所有的外部媒体进入到系统里面会先将它们整理成统一的媒体(帧集的封装)之后在各个结点之间进行传输。除了使得层次分明便于系统横向扩展以外,另外一大好处就是把RTP传输相关的事务都终结在媒体接入部件(节点)上,RTP传输中的丢包、乱序等问题的处理不会扩散到系统其它部件。

      2.5 Media Pipeline in Video Node (Video Mixer)

      7.jpg


      第二个例子是视频混流器内部的Pipeline结构。视频混流的部件在Pipeline上面进出都是视频帧,图上紫颜色的模块进出的都是视频已编码的帧,在视频处理部件的内部可以是一些已编码的帧,也可以是一些Scaler和Convertor。使得各个层次的处理器接口非常清楚,便于做成plugable。

      2.6 Unified Media Spread Model (UMSM)

      8.jpg


      前面我们根据系统的功能性和非功能性需求,把系统拆成了一个个松散的部件。那么,怎么把这些部件捏合到一起成为一个有机的系统呢?特别是针对各个媒体接入部件和媒体处理部件之间的媒体交互,我们需要定义一个统一的内部媒体交互模型——我们称之为UMSM。

      音视频媒体在系统内部流动,我们采用的是一个“发布-订阅”结构的流基本拓扑。如图所示,系统有一个发布者发布一个流进入到系统里,此时有两个订阅者,其中一个订阅者希望订阅发布的原始流的直接转发流,另外一个订阅者希望订阅房间里面所有的原始流合成流合屏以后的mix流,流的发布者和订阅者的PeerConnection连接建立在不同的WebRTC Node上面,通过PeerConnection进入WebRTC Node的SRTP包流,经过解密,被整理封装成MediaFrame(Audioframe/Videoframe),之后再在不同的部件之间进行传递,如果有订阅者需要的是直接转发流,就把它封装好的音频和视频的帧直接扩散到订阅者所连接的WebRTC Node上面来,如果有订阅者需要合成的流(合屏和混音的流),那么就把混流和混音以后的MediaFrame从AudioNode(Audio Mixer)和VideoNode(Video Mixer)扩散到订阅者所连接的WebRTC Node上。

      有了这样一个足够松散的系统内部流扩散结构,无论这些媒体接入部件和媒体处理部件是运行在同一台机器上还是运行在一个数据中心内的不同机器上——甚至运行在位于不同数据中心的不同机器上,都有统一的、一致的流拓扑结构。

      2.7 Media Spread Protocol

      9.jpg


      要实现这样一个流扩散模型,重点要解决两个方面的问题,一个是媒体节点间的传输,另一个是媒体节点的控制。

      媒体节点间的传输是面向连接的,因为扩散链路都可能持续比较长的时间,且一般服务器侧部件的部署环境的网络条件是可控的,有利于保障传输质量。另外每一个连接结点间的扩散链路的连接是双向的,因为有可能两个媒体流的接入结点之间存在双向的扩散,以及与媒体流相关的一些feedback信息需要被反向传递,我们希望它能够复用在同一个扩散链路上面。另外我们需要它是可靠的,在以前跟合作伙伴做技术交流的时候他们对于要求流扩散链路必须是可靠的这一点有疑惑。实际上这是一个实时性和可靠性的取舍问题,我们选择在这个环节保证可靠性,而把实时性推给底层去解决,因为如果要在流扩散链路的所有环节处理信号损失,将给上层逻辑带来巨大的复杂性。

      2.8 MSP - Transport Control Primitives(WIP)

      传输控制就是对于节点间扩散传输链路的控制,目前为了方便在采用的是TCP,在同一数据中心内进行流扩散问题不大,在应用到跨数据中心的部署场景中时,特别是tts和delay比较大的情况下,实际可用的throughput会受比较大的影响,目前仍有一些改进的工作还在进行当中,我们也在调研SCTP和QUIC。

      2.9 MSP - Underlying Transport Protocols(TCP vs.QUIC under weak network)

      11.jpg


      我们在节点间扩散时加一些网损的情况下用TCP和QUIC有做过一些对比测试。QUIC和TCP都是可靠传输,在有网损的时候都会产生一些重传或者是冗余,但是他们不同的拥塞控制策略会对端到端的媒体传递的质量产生不同的影响。我们的对比测试中,发送端是以恒定的码率和帧率(24fps)向服务器侧发送视频流,服务器侧在节点间分别采用TCP和QUIC进行节点间媒体流扩散,图中截取的是相同的网损条件下接收端收到的实际帧率,在5%的丢包和30ms delay时, TCP的帧率就会抖动的非常厉害,在接收端体验就会看到点不流畅,能明显地看到它的卡顿。当加上10%的丢包时波动就跟家剧烈,有时甚至降低到0fps,接收端的用户体验就是非常明的卡顿。相比而言,在QUIC上面还能够看到,接收端的帧率能够更好地坚持在24fps上下,接收端的流畅度更好。总体来看,QUIC是在弱网环境下进行节点间流扩散的一个不错的备选传输。

      2.10 MSP - Media Control Primitives

      12.jpg


      媒体控制的操作对于媒体节点来说,一个publish就是往媒体结点上面发布一路流,给它增加一个input,一个subscribe就是在它上面去增添一个output,linkup就是把一个input和output接续起来,cutoff就把一个input和一个output拆开。对于媒体处理的结点有一些内生的流,generate就是让它产生一路流指定规格(codec、分辨率、帧率、码率、关键帧间隔等),degenerate就是让它取消正在生成中的一个流。

      3.Cross DC Media Spread
      3.1 Cross DC Media Spread:Relay Node (WIP)

      13.jpg


      做TCP和QUIC的对比调研目的就是解决跨数据中心通过Internet进行节点间媒体流扩散的实时性(本质是throughput)问题。由于在跨数据中心媒体扩散的时候需要在Internet上面做流扩散,Internet在传输质量上讲没有在数据中心里的效果那么满意,需要找一些基于UDP改进的可靠传输协议去尝试,我们调研过SCTP和QUIC,总体来看QUIC的表现是相当不错的。

      同时为了减少同一条流在两个数据中心的多个节点间传输,我们增加了一个Relay Agent(Node)的部件,使得同一条流在两个数据中心之间只需要扩散一次。Relay Agent的另一个作用是进行流扩散的时候的路由控制,譬如一个集团公司的很多分支机房并不是BGP的,需要将流汇聚到指定的BGP机房才能更好地向其他地区数据中心扩散。

      3.2 Access Node(Agent) Scheduling

      14.jpg


      在部署了多个接入节点以后,除了通过增加接入节点来扩充系统的scalability,我们还希望能够利用接入节点的不同地理位置给靠近它的终端用户做就近接入。以WebRTC Agent为例,在部署WebRTC Agent的时候可以指定它的capacity(能力),capacity上面有两个标签,一个是isp,一个是region。用户在进行通信连接请求的时候,它带上isp和region的preference(喜好),系统在进行WebRTC Agent调度的时候会对所有可用的WebRTC Agent的capacity与用户指定的preference进行匹配,找到最满意的接入结点,最后达到就近接入的目的。

      在符合preference的候选不止一个时,系统还提供基于work load和历史使用记录进行last-used、least-used、round-robin、random等调度策略,选取符合指定策略的接入节点。

      3.3 CDN alike Service

      15.jpg


      解决了跨数据中心部署的媒体流扩散和调度问题后,我们的解决方案就可以提供更广阔的实时多方音视频通信服务。特别是有了Relay Agent的级联能力后,我们服务器侧系统就可以得到极大的提升,譬如假设单个媒体接入节点的扇出能力是1:1000的话,经过一级级联后就能达到1:100万,经过两级级联后就能达到1:10亿,已经堪比一般CDN的扇出能力了。而CDN的就是本质是一个分布式的cache系统,cache是实时应用的天敌。许多既要求海量扇出比,又要求实时性,并且要随时平滑进行流拓扑切换的场景下,CDN就显得无能为力了,而我们的解决方案将覆盖这些场景,特别是在5G和IoT的时代。

      原文发布于微信公众号 - LiveVideoStack(livevideostack)
      继续阅读 »
      本文来自英特尔实时通信解决方案架构师 段先德在LiveVideoStackCon2019上海大会的分享,详细介绍了英特尔在进行分布式SFU/MCU媒体服务器的架构设计中秉持的一些设计原则以及关键问题的解决思路。
      大家好,我是来自英特尔上海研发中心的段先德。从2014年开始主要做基于WebRTC的实时通信和统一通信解决方案。对于实时通讯来说WebRTC技术是一个革命性的存在。2014年4月英特尔发布了Intel® Collaboration Suite for WebRTC,这是一款可免费使用的包含服务器侧程序和客户端SDK的完整解决方案。经过多年的迭代更新,当前最新发布的是4.2版本。

      1. Requirements and Design Principles

      本次分享的内容主要分为三个部分,首先介绍英特尔ICS for WebRTC项目中要解决的问题;其次介绍我们在解决这些问题的时候的指导思想和整体设计原则;最后介绍我们的解决方案目前的状态以及当下和近期要做的一些事情。

      1.1 Functional Requirements

      1.jpg


      我们项目团队最初的出发点是希望能做一套够达到一般功能性要求的基于互联网的视频会议解决方案。譬如可以支持WebRTC和SIP终端,实现接入到同一个会议中。SIP主要针对的是存量设备,重点是对WebRTC终端的支持。WebRTC接入相比于很多以前存量的企业视频会议解决方案有很多的突破,从2011年以后Chrome在端多媒体系统,弱网对抗方面以及音视频处理这方面一直在持续的改进。

      英特尔很早就注意到在WebRTC时代,亟需一个统一的终端和服务器侧的解决方案。我们需要把企业内外的一些移动终端、桌面应用、浏览器、传统的SIP终端设备都支持起来,需要支持NAT穿越和屏幕共享,需要支持服务器侧音视频录制,等等。这里面很多功能性需求通过传统SIP的解决方案做起来很不方便或者成本很高,但是在WebRTC时代,在基于互联网应用的技术思路下,可以很便捷、很优雅地解决这些问题,于是我们在2014年做了ICS for WebRTC v1.0。之后在2016年和2017年之间直播类的应用大爆发使得有些客户希望我们的解决方案里面能够支持直播类场景,把实时互动场景下的音视频流通过RTMP/RTSP/HLS/Dash推送到现有的CDN网络里面去。基于这类需求,我们在功能性方面增加了互动Streaming的能力。

      2018年到现在,直播的用户体验要求越来越高,客户希望主播和粉丝或者观众之间的互动能够非常平滑的切换,同时端到端的时延也能够做得更好,也就是希望做到保证端到端的实时性的前提下,在单个呼叫里支持海量的用户连接。这就要求服务器侧系统既要有非常大的“扇出”能力,要支持终端连接在“发布者”和“订阅者”之间非常平滑地进行切换。我们目前正在做的就是把目前的解决方案扩展到这种能够支持大规模并发的“实时互动广播”,初步目标是单个呼叫里达到百万以上的并发连接,而且端到端的时延能够全球控制在300毫秒以内。关于端到端时延,我们在国内互联网上做过一些小规模的测试,测试结果的时延是150毫秒以内。我们还希望这个解决方案能够很方便封装成类似于CDN的服务访问接口或者形式,以便集成到客户现有的直播解决方案中去。

      我们当前的解决方案已经具备了非常灵活的服务器侧媒体处理,服务器端可以做音视频的混音混流,比如说当前的一个呼叫里面有十几个参与方,有的参与方希望订阅呼叫中其他参与方发布的原始流,有的参与方希望订阅所有或部分参与方的mix流,有的参与方希望订阅符合自己对codec、分辨率、帧率、码率等定制化要求的转发流,我们当前的解决方案已经可以很好地支持这些需求。

      1.2 Nonfunctional Requirements

      2.jpg


      如果仅仅是为了达到前面所讲的各种功能性需求,随便选择一个现有的开源框架去改改,再自己从头写一些功能模块拼凑一下,总可以整出一个PoC的版本或可以初步走向产品的东西。如果是要严肃地做一个打算把它放到生产环境去运营的产品级别的东西,真正考验这个解决方案的生命力的其实是它在非功能性需求方面的取舍和功力。即使是选择现有的开源框架去做产品,这个框架对非功能性方面的考量也是最重要的决定因素。

      在非功能性方面主要关注的点有三个方面。

      一是系统的可扩展性,它的服务部署规模可大可小,可以小到在一台英特尔®️ 酷睿™️i7的PC上部署使用,大到一个集群几百台甚至上千台机器组成一个大的cluster上部署使用。另外呼叫的参与方式可以是两三个人的讨论会,或者十几个人一般视频会议,又或者是几十人的在线课堂。部署时可以在当前的系统容量不足时在不中断业务的前提下增加或者删减当前部署的规模,达到很灵活的Scale in/Scale out。

      二是容错性,容错能力大多描述都比较抽象,但是落实到系统在做设计的时候要考虑的东西就是非常具体的设计决策,在系统设计里面我们会强调甚至固执的坚持每一个部件都可能会出错,运行时都会发生crash,这就需要在流程设计或者一般逻辑里面handle这些问题,在系统发生部分失效的时候,要能够做到自动恢复或服务优雅降级。

      三是分布式部署,单台机器上单实例的部署是不可能做容错的,只有分布式的部署才能够做到。我们要求允许把任何部件部署在数据中心的多台机器上面。我们现在进一步的要求是要能够把任何部件部署在多个数据中心,进行跨数据中心的分布式部署。

      2.Unified Media Spread Model UMSM)
      2.1 Modularization at Runtime

      3.jpg


      要满足上述的各种功能性和非功能性需求,就需要在概念模型上对系统的各个部件进行足够的抽象,将逻辑上独立的部件封装到运行时独立的模块里面——即模块化。不管是从单一职责的角度来说,还是从系统的可组合性来说,模块化是自始至终不能打破的一个原则,是我们当前系统——也是很多复杂系统进行架构的第一原则。在我们的系统设计中,对于跟客户端交互的部件来说,要把信令和媒体分开。对于媒体部分来说,媒体的接入部分和处理部分一定是分开的,直接和用户打交道的部分和后台内部的一些处理部件,不管是从单一职责角度来讲还是从面向接口的健壮性要求来讲都必须把它们分开。

      我们的服务器侧系统在运行时可以分成五大块。

      第一块就是跟客户端进行信令交互的部件,即图中的WebRTC Portal和SIP Portal。他们跟WebRTC客户端和SIP终端进行信令交互。值得注意的一点是WebRTC标准对信令交互的格式和通道没有规定,我们采用的是一种承载在socket.io通道中的私有协议。

      第二块是跟客户端进行音视频媒体交互的部件,即图中的WebRTC Agent、Streaming Agent、SIP Agent和Recording Agent。其中WebRTC Agent负责跟客户端之间建立PeerConnection连接,SIP Agent跟SIP终端RTP流进行传输,Streaming Agent是针对RTSP/RTMP/HLS/Dash流,我们可以把IPCamera的RTSP流作为输入直接拉到系统里面来,也可以把系统里面任何一个输入流/合成流/转码后的流作为输出推送到RTMP Server上去,Recording虽然是完全发生在服务器侧的行为,但实际上在概念层次上面是更接近于流的输出。所以在概念模型里我们也把Recording Agent当做媒体接出部件,以达到概念模型的一致性。

      第三块是媒体处理的部件,即图中的Audio Agent和Video Agent。Audio Agent是进行音频混音转码工作的部件,Video Agent是视频的合屏和转码的部件,这些所有的部件都是单独部署独立进程在运行。

      第四块是呼叫控制的部件,即图中的Conference Agent。我们的系统还是将多方实时音视频通信作为场景基础,Conference Agent就是一通呼叫的总控制部件,它负责room中的参与者、流、订阅关系的控制和管理。对于像远程教育、远程医疗、远程协助之类的其他场景,我们主要是通过对Conference Agent来进行拓展和增强去支持。

      第五块就是一些支持部件。整个服务器系统在运行和单机运行时都是cluster形式,Cluster Manager就是一个简单的cluster管理器。视频会议场景中会有一些room的预配置和管理,room的配置数据存放在MongoDB中,管理员都是通过OAM UI通过RESTful API访问Management API部件实现数据访问并受理REST请求。另外各个部件之间的rpc是架设在RabbitMQ消息队列上的。

      2.2 Strong Isolation

      4.jpg


      第二个原则就是要做强隔离。在系统里面坚持执行的原则就是要做强隔离,运行时一定是把看到的逻辑上面独立部件,把它在物理上也做成完全独立的运行时进程。比如像信令受理部件和信令执行部件就是分别独立的进程。这样做使得信令受理部件可以独立于呼叫控制里面的业务逻辑而存在。同理媒体接入部件和媒体处理部件也是分别独立进程。这里的进程就是OS语义上面进程,是我们服务器侧系统构建的基本元素,是生命体的细胞,不同的部件之间进行通讯唯一的方式就是message passing(消息传递)。在概念模型里面看的得到部件都是用单独的Worker进程来处理一个独立的Job。比方说一个Video Agent生成出来的Video Node,它的职责要么是做一个视频混流器,要么是做一个视频转码器,单独运行,独立工作。这样做一方面是进行错误隔离一个部件中产生的异常不会传染影响其他部件,一方面是各个运行时部件可以进行运行时单独进行升级替换。

      2.3 Hierarchy in Media Accessing/Processing

      5.jpg


      第三个原则就是层次化。具体体现在在媒体接入和媒体处理的一些部件的设计和实现上,这些部件在南北(纵)向上面有明确的层次划分,自下而上分为包交互层、帧交互层和内容操作层。以媒体接入部件为例,我们服务器侧系统需要跟各种外围系统和终端进行媒体交互,有的媒体是通过RTP/SRTP包的形式输入、输出,有的媒体是直接以AVStream的行书输出、输出。当媒体进入到我们服务器侧系统内部以后,我们希望有一个统一的格式让它在所有的媒体相关部件之间自由流转,所以我们就定义了统一的MediaFrame格式,所有输入的媒体在媒体接入部件上被组装成MediaFrame。处理MediaFrame的逻辑我们把它放在帧交互层,与客户端进行RTP/SRTP交互的逻辑我们放在包交互层。另外,MediaFrame进入媒体处理部件后,如果涉及到raw格式的操作——譬如合屏、色彩调整、添加水印、替换背景等——我们就把相关逻辑放在内容操作层。

      2.4 Media Pipeline in WebRTC Node

      6.jpg


      设计原则讲起来太枯燥,举两个例子。

      第一个是WebRTC Node中的Pipeline结构。在WebRTCNode上面有一个明确的一个界限,广为人知的一些开源的框架里面有一些SFU框架是直接做RTP包的高级转发,而在我们的解决方案里于所有的外部媒体进入到系统里面会先将它们整理成统一的媒体(帧集的封装)之后在各个结点之间进行传输。除了使得层次分明便于系统横向扩展以外,另外一大好处就是把RTP传输相关的事务都终结在媒体接入部件(节点)上,RTP传输中的丢包、乱序等问题的处理不会扩散到系统其它部件。

      2.5 Media Pipeline in Video Node (Video Mixer)

      7.jpg


      第二个例子是视频混流器内部的Pipeline结构。视频混流的部件在Pipeline上面进出都是视频帧,图上紫颜色的模块进出的都是视频已编码的帧,在视频处理部件的内部可以是一些已编码的帧,也可以是一些Scaler和Convertor。使得各个层次的处理器接口非常清楚,便于做成plugable。

      2.6 Unified Media Spread Model (UMSM)

      8.jpg


      前面我们根据系统的功能性和非功能性需求,把系统拆成了一个个松散的部件。那么,怎么把这些部件捏合到一起成为一个有机的系统呢?特别是针对各个媒体接入部件和媒体处理部件之间的媒体交互,我们需要定义一个统一的内部媒体交互模型——我们称之为UMSM。

      音视频媒体在系统内部流动,我们采用的是一个“发布-订阅”结构的流基本拓扑。如图所示,系统有一个发布者发布一个流进入到系统里,此时有两个订阅者,其中一个订阅者希望订阅发布的原始流的直接转发流,另外一个订阅者希望订阅房间里面所有的原始流合成流合屏以后的mix流,流的发布者和订阅者的PeerConnection连接建立在不同的WebRTC Node上面,通过PeerConnection进入WebRTC Node的SRTP包流,经过解密,被整理封装成MediaFrame(Audioframe/Videoframe),之后再在不同的部件之间进行传递,如果有订阅者需要的是直接转发流,就把它封装好的音频和视频的帧直接扩散到订阅者所连接的WebRTC Node上面来,如果有订阅者需要合成的流(合屏和混音的流),那么就把混流和混音以后的MediaFrame从AudioNode(Audio Mixer)和VideoNode(Video Mixer)扩散到订阅者所连接的WebRTC Node上。

      有了这样一个足够松散的系统内部流扩散结构,无论这些媒体接入部件和媒体处理部件是运行在同一台机器上还是运行在一个数据中心内的不同机器上——甚至运行在位于不同数据中心的不同机器上,都有统一的、一致的流拓扑结构。

      2.7 Media Spread Protocol

      9.jpg


      要实现这样一个流扩散模型,重点要解决两个方面的问题,一个是媒体节点间的传输,另一个是媒体节点的控制。

      媒体节点间的传输是面向连接的,因为扩散链路都可能持续比较长的时间,且一般服务器侧部件的部署环境的网络条件是可控的,有利于保障传输质量。另外每一个连接结点间的扩散链路的连接是双向的,因为有可能两个媒体流的接入结点之间存在双向的扩散,以及与媒体流相关的一些feedback信息需要被反向传递,我们希望它能够复用在同一个扩散链路上面。另外我们需要它是可靠的,在以前跟合作伙伴做技术交流的时候他们对于要求流扩散链路必须是可靠的这一点有疑惑。实际上这是一个实时性和可靠性的取舍问题,我们选择在这个环节保证可靠性,而把实时性推给底层去解决,因为如果要在流扩散链路的所有环节处理信号损失,将给上层逻辑带来巨大的复杂性。

      2.8 MSP - Transport Control Primitives(WIP)

      传输控制就是对于节点间扩散传输链路的控制,目前为了方便在采用的是TCP,在同一数据中心内进行流扩散问题不大,在应用到跨数据中心的部署场景中时,特别是tts和delay比较大的情况下,实际可用的throughput会受比较大的影响,目前仍有一些改进的工作还在进行当中,我们也在调研SCTP和QUIC。

      2.9 MSP - Underlying Transport Protocols(TCP vs.QUIC under weak network)

      11.jpg


      我们在节点间扩散时加一些网损的情况下用TCP和QUIC有做过一些对比测试。QUIC和TCP都是可靠传输,在有网损的时候都会产生一些重传或者是冗余,但是他们不同的拥塞控制策略会对端到端的媒体传递的质量产生不同的影响。我们的对比测试中,发送端是以恒定的码率和帧率(24fps)向服务器侧发送视频流,服务器侧在节点间分别采用TCP和QUIC进行节点间媒体流扩散,图中截取的是相同的网损条件下接收端收到的实际帧率,在5%的丢包和30ms delay时, TCP的帧率就会抖动的非常厉害,在接收端体验就会看到点不流畅,能明显地看到它的卡顿。当加上10%的丢包时波动就跟家剧烈,有时甚至降低到0fps,接收端的用户体验就是非常明的卡顿。相比而言,在QUIC上面还能够看到,接收端的帧率能够更好地坚持在24fps上下,接收端的流畅度更好。总体来看,QUIC是在弱网环境下进行节点间流扩散的一个不错的备选传输。

      2.10 MSP - Media Control Primitives

      12.jpg


      媒体控制的操作对于媒体节点来说,一个publish就是往媒体结点上面发布一路流,给它增加一个input,一个subscribe就是在它上面去增添一个output,linkup就是把一个input和output接续起来,cutoff就把一个input和一个output拆开。对于媒体处理的结点有一些内生的流,generate就是让它产生一路流指定规格(codec、分辨率、帧率、码率、关键帧间隔等),degenerate就是让它取消正在生成中的一个流。

      3.Cross DC Media Spread
      3.1 Cross DC Media Spread:Relay Node (WIP)

      13.jpg


      做TCP和QUIC的对比调研目的就是解决跨数据中心通过Internet进行节点间媒体流扩散的实时性(本质是throughput)问题。由于在跨数据中心媒体扩散的时候需要在Internet上面做流扩散,Internet在传输质量上讲没有在数据中心里的效果那么满意,需要找一些基于UDP改进的可靠传输协议去尝试,我们调研过SCTP和QUIC,总体来看QUIC的表现是相当不错的。

      同时为了减少同一条流在两个数据中心的多个节点间传输,我们增加了一个Relay Agent(Node)的部件,使得同一条流在两个数据中心之间只需要扩散一次。Relay Agent的另一个作用是进行流扩散的时候的路由控制,譬如一个集团公司的很多分支机房并不是BGP的,需要将流汇聚到指定的BGP机房才能更好地向其他地区数据中心扩散。

      3.2 Access Node(Agent) Scheduling

      14.jpg


      在部署了多个接入节点以后,除了通过增加接入节点来扩充系统的scalability,我们还希望能够利用接入节点的不同地理位置给靠近它的终端用户做就近接入。以WebRTC Agent为例,在部署WebRTC Agent的时候可以指定它的capacity(能力),capacity上面有两个标签,一个是isp,一个是region。用户在进行通信连接请求的时候,它带上isp和region的preference(喜好),系统在进行WebRTC Agent调度的时候会对所有可用的WebRTC Agent的capacity与用户指定的preference进行匹配,找到最满意的接入结点,最后达到就近接入的目的。

      在符合preference的候选不止一个时,系统还提供基于work load和历史使用记录进行last-used、least-used、round-robin、random等调度策略,选取符合指定策略的接入节点。

      3.3 CDN alike Service

      15.jpg


      解决了跨数据中心部署的媒体流扩散和调度问题后,我们的解决方案就可以提供更广阔的实时多方音视频通信服务。特别是有了Relay Agent的级联能力后,我们服务器侧系统就可以得到极大的提升,譬如假设单个媒体接入节点的扇出能力是1:1000的话,经过一级级联后就能达到1:100万,经过两级级联后就能达到1:10亿,已经堪比一般CDN的扇出能力了。而CDN的就是本质是一个分布式的cache系统,cache是实时应用的天敌。许多既要求海量扇出比,又要求实时性,并且要随时平滑进行流拓扑切换的场景下,CDN就显得无能为力了,而我们的解决方案将覆盖这些场景,特别是在5G和IoT的时代。

      原文发布于微信公众号 - LiveVideoStack(livevideostack) 收起阅读 »

      Golang实现单机百万长连接服务 - 美图的三年优化经验

      美图长连接服务简介
      随着科技的飞速发展,技术的日新月异,长连接的运用场景日益增多。不仅在后端服务中被广泛运用,比较常见的有数据库的访问、服务内部状态的协调等,而且在 App 端的消息推送、聊天信息、直播弹字幕等场景长连接服务也是优选方案。长连接服务的重要性也在各个场合被业界专家不断提及,与此同时也引起了更为广泛地关注和讨论,各大公司也开始构建自己的长连接服务。
      6.jpg


      美图公司于2016 年初开始构建长连接服务,与此同时, Go 在编程语言领域异军突起,考虑到其丰富的编程库,完善的工具链,简单高效的并发模型等优势,使我们最终选择 Go 去作为实现长连接服务的语言。在通信协议的选择上,考虑到 MQTT 协议的轻量、简单、易于实现的优点,选择了 MQTT 协议作为数据交互的载体。其整体的架构会在下文中做相应地介绍。

      美图长连接服务(项目内部代号为bifrost )已经历时三年,在这三年的时间里,长连接服务经过了业务的检验,同时也经历了服务的重构,存储的升级等,长连接服务从之前支持单机二十几万连接到目前可以支撑单机百万连接。在大多数长连接服务中存在一个共性问题,那就是内存占用过高,我们经常发现单个节点几十万的长连接,内存却占用十几G 甚至更多,有哪些手段能降低内存呢?

      本文将从多个角度介绍长连接服务在内存优化路上的探索,首先会先通过介绍当前服务的架构模型,Go 语言的内存管理,让大家清晰地了解我们内存优化的方向和关注的重要数据。后面会重点介绍我们在内存优化上做的一些尝试以及具体的优化手段,希望对大家有一定的借鉴意义。

      架构模型

      一个好的架构模型设计不仅能让系统有很好的可扩展性,同时也能在服务能力上有很好的体现。除此之外,在设计上多考虑数据的抽象、模块的划分、工具链的完善,这样不仅能让软件具有更灵活的扩展能力、服务能力更高,也提高系统的稳定性和健壮性以及可维护性。

      在数据抽象层面抽象pubsub 数据集合,用于消息的分发和处理。模块划分层面我们将服务一分为三:内部通讯(grpcsrv)、外部服务(mqttsrv)、连接管理(session)。工具链的方面我们构建了自动化测试,系统 mock ,压测工具。美图长连接服务架构设计如下:图一架构图从架构图中我们可以清晰地看到由7 个模块组成,分别是:conf 、grpcsrv 、mqttsrv、session、pubsub、packet、util ,每个模块的作用如下:

      1.jpg


      conf :配置管理中心,负责服务配置的初始化,基本字段校验。

      grpcsrv :grpc 服务,集群内部信息交互协调。

      mqttsrv :mqtt 服务,接收客户端连接,同时支持单进程多端口 MQTT 服务。

      session :会话模块,管理客户端状态变化,MQTT 信息的收发。

      pubsub :发布订阅模块,按照 Topic 维度保存 session 并发布 Topic 通知给 session。

      packet:协议解析模块,负责 MQTT 协议包解析。

      util :工具包,目前集成监控、日志、grpc 客户端、调度上报四个子模块。

      Go 的内存管理

      众所周知,Go 是一门自带垃圾回收机制的语言,内存管理参照 tcmalloc 实现,使用连续虚拟地址,以页( 8k )为单位、多级缓存进行管理。针对小于16 byte 直接使用Go的上下文P中的mcache分配,大于 32 kb 直接在 mheap 申请,剩下的先使用当前 P 的 mcache 中对应的 size class 分配 ,如果 mcache 对应的 size class 的 span 已经没有可用的块,则向 mcentral 请求。如果 mcentral 也没有可用的块,则向 mheap 申请,并切分。如果 mheap 也没有合适的 span,则向操作系统申请。

      Go 在内存统计方面做的也是相当出色,提供细粒度的内存分配、GC 回收、goroutine 管理等统计数据。在优化过程中,一些数据能帮助我们发现和分析问题,在介绍优化之前,我们先来看看哪些参数需要关注,其统计参数如下:

      go_memstats_sys_bytes :进程从操作系统获得的内存的总字节数 ,其中包含 Go 运行时的堆、栈和其他内部数据结构保留的虚拟地址空间。

      go_memstats_heap_inuse_bytes:在 spans 中正在使用的字节。其中不包含可能已经返回到操作系统,或者可以重用进行堆分配,或者可以将作为堆栈内存重用的字节。

      go_memstats_heap_idle_bytes:在 spans 中空闲的字节。

      go_memstats_stack_sys_bytes:栈内存字节,主要用于 goroutine 栈内存的分配。

      在内存监控中根据Go 将堆的虚拟地址空间划分为 span ,即对内存8K或更大的连续区域进行统计。span 可能处于以下三种状态之一 :

      idle 不包含对象或其他数据,空闲空间的物理内存可以释放回 OS (但虚拟地址空间永远不会释放),或者可以将其转换为使用中或栈空间;

      inuse 至少包含一个堆对象,并且可能有空闲空间来分配更多的堆对象;

      stack span 用于 goroutine 栈,栈不被认为是堆的一部分。span 可以在堆和堆栈内存之间更改,但它从来不会同时用于两者。

      此外有一部分统计没有从堆内存中分配的运行时内部结构(通常因为它们是实现堆的一部分),与堆栈内存不同,分配给这些结构的任何内存都专用于这些结构,这些主要用于调试运行时内存开销。

      虽然Go 拥有了丰富的标准库、语言层面支持并发、内置runtime,但相比C/C++ 完成相同逻辑的情况下 Go 消耗内存相对增多。在程序的运行过程中,它的 stack 内存会随着使用而自动扩容,但在 stack 内存回收采用惰性回收方式,一定程度的导致内存消耗增多,此外还有GC 机制也会带来额外内存的消耗。

      Go 提供了三种内存回收机制:定时触发,按量触发,手动触发。在内存垃圾少量的情况下,Go 可以良好的运行。但是无论采用哪种触发方式,由于在海量用户服务的情况下造成的垃圾内存是巨大的,在 GC 执行过程中服务都会感觉明显的卡顿。这些也是目前长连接服务面对的难题,在下文中我将会逐一介绍我们如何减少和解决问题的产生的具体实践。

      优化之路

      在了解架构设计、Go 的内存管理、基础监控后,相信大家已经对当前系统有了一个大致的认识,先给大家展示一下内存优化的成果,下表一是内存优化前后的对比表,在线连接数基本相同的情况下,进程内存占用大幅度降低,其中 stack 申请内存降低约 5.9 G,其次 heap 使用内存降低 0.9 G,other 申请内存也小幅下降。那么我们是如何做到内存降低的呢?那接下来我将会把我们团队关于进行内存优化的探索和大家聊一聊。

      2.jpg


      在优化前随机抽取线上一台机器进行分析内存,通过监控发现当前节点进程占用虚拟内存为22.3 G,堆区使用的内存占用 5.2 G ,堆区未归还内存为 8.9 G,栈区内存为 7.25 G,其它约占用 0.9 G,连接数为 225 K。

      我们简单进行换算,可以看出平均一个链接占用的内存分别为:堆:23K,栈:32K。通过对比业内长连接服务的数据可以看出单个链接占用的内存偏大,根据监控数据和内存分配原理分析主要原因在:goroutine 占用、session 状态信息、pubsub 模块占用,我们打算从业务、程序、网络模式三个方面进行优化。

      业务优化

      上文中提到 session 模块主要是用于处理消息的收发,在实现时考虑到在通常场景中业务的消息生产大于客户端消息的消费速度的情况,为了缓解这种状况,设计时引入消息的缓冲队列,这种做法同样也有助于做客户端消息的流控。

      缓冲消息队列借助chan 实现 ,chan 大小根据经验将初始化默认配置为 128 。但在目前线上推送的场景中,我们发现,消息的生产一般小于消费的速度,128 缓冲大小明显偏大,因此我们把长度调整为 16 ,减少内存的分配。

      在设计中按照topic 对客户端进行分组管理的算法中,采用空间换时间的方式,组合 map 和 list 两种数据结构对于客户端集合操作提供O(1)的删除、O(1)的添加、O(n)的遍历。数据的删除采用标记删除方式,使用辅助 slice 结构进行记录,只有到达预设阈值才会进行真正的删除。虽然标记删除提高了遍历和添加的性能,但也同样带来了内存损耗问题。

      大家一定好奇什么样的场景需要提供这样的复杂度,在实际中其场景有以下两种情况:

      在实际的网络场景中,客户端随时都可能由于网络的不稳定断开或者重新建联,因此集合的增加和删除需要在常数范围内。

      在消息发布的流程中,采用遍历集合逐一发布通知方式,但随着单个topic 上的用户量的增加,经常会出现单个 topic 用户集合消息过热的问题,耗时太久导致消息挤压,因此针对集合的遍历当然也要求尽量快。

      通过benchamrk 数据分析,在标记回收 slice 长度在 1000 时,可以提供最佳的性能,因此默认配置阈值为 1000。在线上服务中,无特殊情况都是采用默认配置。但在当前推送服务的使用中,发现标记删除和延迟回收机制好处甚微,主要是因为 topic 和客户端为 1 : 1 方式,也就是不存在客户端集合,因此调整回收阈值大小为 2,减少无效内存占用。

      上述所有优化,只要简单调整配置后服务灰度上线即可,在设计实现时通过conf 模块动态配置,降低了服务的开发和维护成本。通过监控对比优化效果如下表,在优化后在线连接数比优化的在线连接更多的情况下, heap 使用内存使用数量由原来的 4.16G 下降到了 3.5G ,降低了约 0.66 G。

      3.jpg


      golang 代码优化

      在实现上面展示的架构的时候发现在session 模块 和 mqttsrv 模块之间存在很多共享变量,目前实现方式都是采用指针或者值拷贝的,由于 session的数量和客户端数据量成正比也就导致消耗大量内存用于共享数据,这不仅仅增加 GC 压力,同样对于内存的消耗也是巨大的。就此问题思考再三,参考系统的库 context 的设计在架构中也抽象 context 包负责模块之间交互信息传递,统一分配内存。此外还参考他人减少临时变量的分配的优化方式,提高系统运行效率。主要优化角度参考如下:

      在频繁申请内存的地方,使用pool 方式进行内存管理

      小对象合并成结构体一次分配,减少内存分配次数

      缓存区内容一次分配足够大小空间,并适当复用

      slice 和 map 采 make 创建时,预估大小指定容量

      调用栈避免申请较多的临时对象

      减少[]byte 与 string 之间转换,尽量采用 []byte 来字符串处理

      目前系统具被完备的单元测试、集成测试,因此经过一周的快速的开发重构后灰度上线监控数据对比如下表:在基本相同的连接数上,heap 使用内存约占用降低 0.27G,stack 申请内存占用降低 3.81G。为什么 stack 会大幅度降低呢?

      通过设置stackDebug 重新编译程序追查程序运行过程,优化前 goroutine 栈的大多数在内存为 16K,通过减少临时变量的分配,拆分大函数处理逻辑,有效的减少触发栈的内存扩容(详细分析见参考文章),优化后 goroutine 栈内存降低到 8 K。一个连接需要启动两个 goroutine 负责数据的读和写,粗略计算一个连接减少约 16 K 的内存,23 w 连接约降低 3.68 G 内存。

      4.jpg


      网络模型优化

      在Go 语言的网络编程中经典的实现都是采用同步处理方式,启动两个 goroutine 分别处理读和写请求,goroutine 也不像 thread ,它是轻量级的。但对于一百万连接的情况,这种设计模式至少要启动两百万的 goroutine,其中一个 goroutine 使用栈的大小在 2 KB 到 8KB, 对于资源的消耗也是极大的。在大多数场景中,只有少数连接是有数据处理,大部分 goroutine 阻塞 IO 处理中。在因此可以借鉴 C 语言的设计,在程序中使用 epoll 模型做事件分发,只有活跃连接才会启动 goroutine 处理业务,基于这种思想修改网络处理流程。

      网络模型修改测试完成后开始灰度上线,通过监控数据对比如下表:在优化后比优化前的连接数多10 K的情况下,heap 使用内存降低 0.33 G,stack 申请内存降低 2.34 G,优化效果显著。

      5.jpg


      总结

      在经过业务优化,临时内存优化,网络模型优化操作后,线上服务保证21w 长连接在线实际内存占用约为 5.1 G。简单进行压测 100w 连接只完成建立连接,不进行其他操作约占用 10 G。长连接服务内存优化已经取得阶段性的成功,但是这仅仅是我们团队的一小步,未来还有更多的工作要做:网络链路、服务能力,存储优化等,这些都是亟待探索的方向。如果大家有什么好的想法,欢迎与我们团队分享,共同探讨。

      bifrost项目目前我们有开源计划,敬请大家期待。

      参考文章

      go tool pprof 使用介绍 :https://segmentfault.com/a/1190000016412013

      Go 内存监控介绍:https://golang.org/src/runtime/mstats.go

      Go 内存优化介绍:https://blog.golang.org/profiling-go-programs

      高性能Go服务内存分配:https://segment.com/blog/alloc ... vices

      Go stack 优化分析:https://studygolang.com/article
      继续阅读 »
      美图长连接服务简介
      随着科技的飞速发展,技术的日新月异,长连接的运用场景日益增多。不仅在后端服务中被广泛运用,比较常见的有数据库的访问、服务内部状态的协调等,而且在 App 端的消息推送、聊天信息、直播弹字幕等场景长连接服务也是优选方案。长连接服务的重要性也在各个场合被业界专家不断提及,与此同时也引起了更为广泛地关注和讨论,各大公司也开始构建自己的长连接服务。
      6.jpg


      美图公司于2016 年初开始构建长连接服务,与此同时, Go 在编程语言领域异军突起,考虑到其丰富的编程库,完善的工具链,简单高效的并发模型等优势,使我们最终选择 Go 去作为实现长连接服务的语言。在通信协议的选择上,考虑到 MQTT 协议的轻量、简单、易于实现的优点,选择了 MQTT 协议作为数据交互的载体。其整体的架构会在下文中做相应地介绍。

      美图长连接服务(项目内部代号为bifrost )已经历时三年,在这三年的时间里,长连接服务经过了业务的检验,同时也经历了服务的重构,存储的升级等,长连接服务从之前支持单机二十几万连接到目前可以支撑单机百万连接。在大多数长连接服务中存在一个共性问题,那就是内存占用过高,我们经常发现单个节点几十万的长连接,内存却占用十几G 甚至更多,有哪些手段能降低内存呢?

      本文将从多个角度介绍长连接服务在内存优化路上的探索,首先会先通过介绍当前服务的架构模型,Go 语言的内存管理,让大家清晰地了解我们内存优化的方向和关注的重要数据。后面会重点介绍我们在内存优化上做的一些尝试以及具体的优化手段,希望对大家有一定的借鉴意义。

      架构模型

      一个好的架构模型设计不仅能让系统有很好的可扩展性,同时也能在服务能力上有很好的体现。除此之外,在设计上多考虑数据的抽象、模块的划分、工具链的完善,这样不仅能让软件具有更灵活的扩展能力、服务能力更高,也提高系统的稳定性和健壮性以及可维护性。

      在数据抽象层面抽象pubsub 数据集合,用于消息的分发和处理。模块划分层面我们将服务一分为三:内部通讯(grpcsrv)、外部服务(mqttsrv)、连接管理(session)。工具链的方面我们构建了自动化测试,系统 mock ,压测工具。美图长连接服务架构设计如下:图一架构图从架构图中我们可以清晰地看到由7 个模块组成,分别是:conf 、grpcsrv 、mqttsrv、session、pubsub、packet、util ,每个模块的作用如下:

      1.jpg


      conf :配置管理中心,负责服务配置的初始化,基本字段校验。

      grpcsrv :grpc 服务,集群内部信息交互协调。

      mqttsrv :mqtt 服务,接收客户端连接,同时支持单进程多端口 MQTT 服务。

      session :会话模块,管理客户端状态变化,MQTT 信息的收发。

      pubsub :发布订阅模块,按照 Topic 维度保存 session 并发布 Topic 通知给 session。

      packet:协议解析模块,负责 MQTT 协议包解析。

      util :工具包,目前集成监控、日志、grpc 客户端、调度上报四个子模块。

      Go 的内存管理

      众所周知,Go 是一门自带垃圾回收机制的语言,内存管理参照 tcmalloc 实现,使用连续虚拟地址,以页( 8k )为单位、多级缓存进行管理。针对小于16 byte 直接使用Go的上下文P中的mcache分配,大于 32 kb 直接在 mheap 申请,剩下的先使用当前 P 的 mcache 中对应的 size class 分配 ,如果 mcache 对应的 size class 的 span 已经没有可用的块,则向 mcentral 请求。如果 mcentral 也没有可用的块,则向 mheap 申请,并切分。如果 mheap 也没有合适的 span,则向操作系统申请。

      Go 在内存统计方面做的也是相当出色,提供细粒度的内存分配、GC 回收、goroutine 管理等统计数据。在优化过程中,一些数据能帮助我们发现和分析问题,在介绍优化之前,我们先来看看哪些参数需要关注,其统计参数如下:

      go_memstats_sys_bytes :进程从操作系统获得的内存的总字节数 ,其中包含 Go 运行时的堆、栈和其他内部数据结构保留的虚拟地址空间。

      go_memstats_heap_inuse_bytes:在 spans 中正在使用的字节。其中不包含可能已经返回到操作系统,或者可以重用进行堆分配,或者可以将作为堆栈内存重用的字节。

      go_memstats_heap_idle_bytes:在 spans 中空闲的字节。

      go_memstats_stack_sys_bytes:栈内存字节,主要用于 goroutine 栈内存的分配。

      在内存监控中根据Go 将堆的虚拟地址空间划分为 span ,即对内存8K或更大的连续区域进行统计。span 可能处于以下三种状态之一 :

      idle 不包含对象或其他数据,空闲空间的物理内存可以释放回 OS (但虚拟地址空间永远不会释放),或者可以将其转换为使用中或栈空间;

      inuse 至少包含一个堆对象,并且可能有空闲空间来分配更多的堆对象;

      stack span 用于 goroutine 栈,栈不被认为是堆的一部分。span 可以在堆和堆栈内存之间更改,但它从来不会同时用于两者。

      此外有一部分统计没有从堆内存中分配的运行时内部结构(通常因为它们是实现堆的一部分),与堆栈内存不同,分配给这些结构的任何内存都专用于这些结构,这些主要用于调试运行时内存开销。

      虽然Go 拥有了丰富的标准库、语言层面支持并发、内置runtime,但相比C/C++ 完成相同逻辑的情况下 Go 消耗内存相对增多。在程序的运行过程中,它的 stack 内存会随着使用而自动扩容,但在 stack 内存回收采用惰性回收方式,一定程度的导致内存消耗增多,此外还有GC 机制也会带来额外内存的消耗。

      Go 提供了三种内存回收机制:定时触发,按量触发,手动触发。在内存垃圾少量的情况下,Go 可以良好的运行。但是无论采用哪种触发方式,由于在海量用户服务的情况下造成的垃圾内存是巨大的,在 GC 执行过程中服务都会感觉明显的卡顿。这些也是目前长连接服务面对的难题,在下文中我将会逐一介绍我们如何减少和解决问题的产生的具体实践。

      优化之路

      在了解架构设计、Go 的内存管理、基础监控后,相信大家已经对当前系统有了一个大致的认识,先给大家展示一下内存优化的成果,下表一是内存优化前后的对比表,在线连接数基本相同的情况下,进程内存占用大幅度降低,其中 stack 申请内存降低约 5.9 G,其次 heap 使用内存降低 0.9 G,other 申请内存也小幅下降。那么我们是如何做到内存降低的呢?那接下来我将会把我们团队关于进行内存优化的探索和大家聊一聊。

      2.jpg


      在优化前随机抽取线上一台机器进行分析内存,通过监控发现当前节点进程占用虚拟内存为22.3 G,堆区使用的内存占用 5.2 G ,堆区未归还内存为 8.9 G,栈区内存为 7.25 G,其它约占用 0.9 G,连接数为 225 K。

      我们简单进行换算,可以看出平均一个链接占用的内存分别为:堆:23K,栈:32K。通过对比业内长连接服务的数据可以看出单个链接占用的内存偏大,根据监控数据和内存分配原理分析主要原因在:goroutine 占用、session 状态信息、pubsub 模块占用,我们打算从业务、程序、网络模式三个方面进行优化。

      业务优化

      上文中提到 session 模块主要是用于处理消息的收发,在实现时考虑到在通常场景中业务的消息生产大于客户端消息的消费速度的情况,为了缓解这种状况,设计时引入消息的缓冲队列,这种做法同样也有助于做客户端消息的流控。

      缓冲消息队列借助chan 实现 ,chan 大小根据经验将初始化默认配置为 128 。但在目前线上推送的场景中,我们发现,消息的生产一般小于消费的速度,128 缓冲大小明显偏大,因此我们把长度调整为 16 ,减少内存的分配。

      在设计中按照topic 对客户端进行分组管理的算法中,采用空间换时间的方式,组合 map 和 list 两种数据结构对于客户端集合操作提供O(1)的删除、O(1)的添加、O(n)的遍历。数据的删除采用标记删除方式,使用辅助 slice 结构进行记录,只有到达预设阈值才会进行真正的删除。虽然标记删除提高了遍历和添加的性能,但也同样带来了内存损耗问题。

      大家一定好奇什么样的场景需要提供这样的复杂度,在实际中其场景有以下两种情况:

      在实际的网络场景中,客户端随时都可能由于网络的不稳定断开或者重新建联,因此集合的增加和删除需要在常数范围内。

      在消息发布的流程中,采用遍历集合逐一发布通知方式,但随着单个topic 上的用户量的增加,经常会出现单个 topic 用户集合消息过热的问题,耗时太久导致消息挤压,因此针对集合的遍历当然也要求尽量快。

      通过benchamrk 数据分析,在标记回收 slice 长度在 1000 时,可以提供最佳的性能,因此默认配置阈值为 1000。在线上服务中,无特殊情况都是采用默认配置。但在当前推送服务的使用中,发现标记删除和延迟回收机制好处甚微,主要是因为 topic 和客户端为 1 : 1 方式,也就是不存在客户端集合,因此调整回收阈值大小为 2,减少无效内存占用。

      上述所有优化,只要简单调整配置后服务灰度上线即可,在设计实现时通过conf 模块动态配置,降低了服务的开发和维护成本。通过监控对比优化效果如下表,在优化后在线连接数比优化的在线连接更多的情况下, heap 使用内存使用数量由原来的 4.16G 下降到了 3.5G ,降低了约 0.66 G。

      3.jpg


      golang 代码优化

      在实现上面展示的架构的时候发现在session 模块 和 mqttsrv 模块之间存在很多共享变量,目前实现方式都是采用指针或者值拷贝的,由于 session的数量和客户端数据量成正比也就导致消耗大量内存用于共享数据,这不仅仅增加 GC 压力,同样对于内存的消耗也是巨大的。就此问题思考再三,参考系统的库 context 的设计在架构中也抽象 context 包负责模块之间交互信息传递,统一分配内存。此外还参考他人减少临时变量的分配的优化方式,提高系统运行效率。主要优化角度参考如下:

      在频繁申请内存的地方,使用pool 方式进行内存管理

      小对象合并成结构体一次分配,减少内存分配次数

      缓存区内容一次分配足够大小空间,并适当复用

      slice 和 map 采 make 创建时,预估大小指定容量

      调用栈避免申请较多的临时对象

      减少[]byte 与 string 之间转换,尽量采用 []byte 来字符串处理

      目前系统具被完备的单元测试、集成测试,因此经过一周的快速的开发重构后灰度上线监控数据对比如下表:在基本相同的连接数上,heap 使用内存约占用降低 0.27G,stack 申请内存占用降低 3.81G。为什么 stack 会大幅度降低呢?

      通过设置stackDebug 重新编译程序追查程序运行过程,优化前 goroutine 栈的大多数在内存为 16K,通过减少临时变量的分配,拆分大函数处理逻辑,有效的减少触发栈的内存扩容(详细分析见参考文章),优化后 goroutine 栈内存降低到 8 K。一个连接需要启动两个 goroutine 负责数据的读和写,粗略计算一个连接减少约 16 K 的内存,23 w 连接约降低 3.68 G 内存。

      4.jpg


      网络模型优化

      在Go 语言的网络编程中经典的实现都是采用同步处理方式,启动两个 goroutine 分别处理读和写请求,goroutine 也不像 thread ,它是轻量级的。但对于一百万连接的情况,这种设计模式至少要启动两百万的 goroutine,其中一个 goroutine 使用栈的大小在 2 KB 到 8KB, 对于资源的消耗也是极大的。在大多数场景中,只有少数连接是有数据处理,大部分 goroutine 阻塞 IO 处理中。在因此可以借鉴 C 语言的设计,在程序中使用 epoll 模型做事件分发,只有活跃连接才会启动 goroutine 处理业务,基于这种思想修改网络处理流程。

      网络模型修改测试完成后开始灰度上线,通过监控数据对比如下表:在优化后比优化前的连接数多10 K的情况下,heap 使用内存降低 0.33 G,stack 申请内存降低 2.34 G,优化效果显著。

      5.jpg


      总结

      在经过业务优化,临时内存优化,网络模型优化操作后,线上服务保证21w 长连接在线实际内存占用约为 5.1 G。简单进行压测 100w 连接只完成建立连接,不进行其他操作约占用 10 G。长连接服务内存优化已经取得阶段性的成功,但是这仅仅是我们团队的一小步,未来还有更多的工作要做:网络链路、服务能力,存储优化等,这些都是亟待探索的方向。如果大家有什么好的想法,欢迎与我们团队分享,共同探讨。

      bifrost项目目前我们有开源计划,敬请大家期待。

      参考文章

      go tool pprof 使用介绍 :https://segmentfault.com/a/1190000016412013

      Go 内存监控介绍:https://golang.org/src/runtime/mstats.go

      Go 内存优化介绍:https://blog.golang.org/profiling-go-programs

      高性能Go服务内存分配:https://segment.com/blog/alloc ... vices

      Go stack 优化分析:https://studygolang.com/article 收起阅读 »

      【源码下载】一款使用环信SDK实现的开源--社交demo

      React webIm demo简介   --(集成环信SDK)

      webIm demo 是基于环信sdk开发的一款具有单聊、群聊、聊天室、音视频等功能的应用,为了react用户能够快速集成环信 im sdk和音视频sdk,我们特使用了react全家桶,为大家提供参考。

      同时我们也提供了[Vue版demo]  (https://github.com/easemob/webim-vue-demo)。

      项目截图:

      16df69ea7a91facd.jpg


      16df6a2287ccb151.jpg


      16df6a550a6bd226.jpg


      16df6a87eaa8c068.jpg


      16df6aef07a80553.jpg


      16df6b33eaa4edfd.jpg


      项目地址:https://github.com/easemob/webim


      开发环境

      完全基于React + Redux的单向数据流,引入ant-design组件库。 

      响应式布局, 一套Demo同时支持PC和H5,自适应不同终端屏幕尺寸

      支持所有的现代浏览器(不支持IE6-11)




      初始化安装
      - 在/demo目录下执行  
      npm i

      - 运行demo
      - cd demo && npm start (requires node@>=6)

      http://localhost:3001
      - cd demo && HTTPS=true npm start (webrtc supports HTTPS only)

      https://localhost:3001

      注意:只有在https的情况才支持语音视频功能
       

      打包发布demo
      cd demo && npm run build 
      /demo/build 目录下的就是可以运行和部署的版本


      可能遇见的问题:

      1. 如果在npm i的过程中遇到
      > phantomjs-prebuilt@2.1.14 install /Users/will/work/my-project/node_modules/phantomjs-prebuilt> node install.jsPhantomJS not found on PATHDownloading https://github.com/Medium/phan ... aving to /var/folders/mh/2ptfthxj2qb49jscj1b0gjsm0000gn/T/phantomjs/phantomjs-2.1.1-macosx.zipReceiving...Error making request.Error: connect ETIMEDOUT 54.231.113.227:443    at Object.exports._errnoException (util.js:1018:11) at exports._exceptionWithHostPort (util.js:1041:20) at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1090:14)

      FIX: 这个问题,可以尝试 PHANTOMJS_CDNURL=https://npm.taobao.org/mirrors/phantomjs/ npm install --save-dev phantomjs-prebuilt 来解决

      2. 执行npm start时如果出现
      > node scripts/start.js/Users/wenke/www/web-im/demo/scripts/start.js:23const { ^SyntaxError: Unexpected token { at exports.runInThisContext (vm.js:53:16) at Module._compile (module.js:373:25) at Object.Module._extensions..js (module.js:416:10)    at Module.load (module.js:343:32) at Function.Module._load (module.js:300:12) at Function.Module.runMain (module.js:441:10) at startup (node.js:139:18) at node.js:974:3

      FIX: 请检查node版本是否是v6.0+ 

      项目模块

      本项目包含两部分:
      一部分是项目主模块,这部分主要包含了项目的业务逻辑,比如增,删好友、音视频聊天、信息修改、群设置等

      另一部分是 环信sdk集成(包含音视频sdk)


      16e1ba1c27a529fe.jpg


      src项目结构

      16e1bd9c61944e5f.jpg



      更多关于环信sdk[集成文档]
      http://docs-im.easemob.com/im/web/intro/start




      参与贡献
      如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
      1.Fork本仓库
      2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
      3.提交代码
      4.新建Pull Request
      5.等待我们的Review & Merge


      最后的最后如果你有更好的建议,或者你的疑惑,请随时给我留言。

       
      继续阅读 »
      React webIm demo简介   --(集成环信SDK)

      webIm demo 是基于环信sdk开发的一款具有单聊、群聊、聊天室、音视频等功能的应用,为了react用户能够快速集成环信 im sdk和音视频sdk,我们特使用了react全家桶,为大家提供参考。

      同时我们也提供了[Vue版demo]  (https://github.com/easemob/webim-vue-demo)。

      项目截图:

      16df69ea7a91facd.jpg


      16df6a2287ccb151.jpg


      16df6a550a6bd226.jpg


      16df6a87eaa8c068.jpg


      16df6aef07a80553.jpg


      16df6b33eaa4edfd.jpg


      项目地址:https://github.com/easemob/webim


      开发环境

      完全基于React + Redux的单向数据流,引入ant-design组件库。 

      响应式布局, 一套Demo同时支持PC和H5,自适应不同终端屏幕尺寸

      支持所有的现代浏览器(不支持IE6-11)




      初始化安装
      - 在/demo目录下执行  
      npm i

      - 运行demo
      - cd demo && npm start (requires node@>=6)

      http://localhost:3001
      - cd demo && HTTPS=true npm start (webrtc supports HTTPS only)

      https://localhost:3001

      注意:只有在https的情况才支持语音视频功能
       

      打包发布demo
      cd demo && npm run build 
      /demo/build 目录下的就是可以运行和部署的版本


      可能遇见的问题:

      1. 如果在npm i的过程中遇到
      > phantomjs-prebuilt@2.1.14 install /Users/will/work/my-project/node_modules/phantomjs-prebuilt> node install.jsPhantomJS not found on PATHDownloading https://github.com/Medium/phan ... aving to /var/folders/mh/2ptfthxj2qb49jscj1b0gjsm0000gn/T/phantomjs/phantomjs-2.1.1-macosx.zipReceiving...Error making request.Error: connect ETIMEDOUT 54.231.113.227:443    at Object.exports._errnoException (util.js:1018:11) at exports._exceptionWithHostPort (util.js:1041:20) at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1090:14)

      FIX: 这个问题,可以尝试 PHANTOMJS_CDNURL=https://npm.taobao.org/mirrors/phantomjs/ npm install --save-dev phantomjs-prebuilt 来解决

      2. 执行npm start时如果出现
      > node scripts/start.js/Users/wenke/www/web-im/demo/scripts/start.js:23const { ^SyntaxError: Unexpected token { at exports.runInThisContext (vm.js:53:16) at Module._compile (module.js:373:25) at Object.Module._extensions..js (module.js:416:10)    at Module.load (module.js:343:32) at Function.Module._load (module.js:300:12) at Function.Module.runMain (module.js:441:10) at startup (node.js:139:18) at node.js:974:3

      FIX: 请检查node版本是否是v6.0+ 

      项目模块

      本项目包含两部分:
      一部分是项目主模块,这部分主要包含了项目的业务逻辑,比如增,删好友、音视频聊天、信息修改、群设置等

      另一部分是 环信sdk集成(包含音视频sdk)


      16e1ba1c27a529fe.jpg


      src项目结构

      16e1bd9c61944e5f.jpg



      更多关于环信sdk[集成文档]
      http://docs-im.easemob.com/im/web/intro/start




      参与贡献
      如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
      1.Fork本仓库
      2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
      3.提交代码
      4.新建Pull Request
      5.等待我们的Review & Merge


      最后的最后如果你有更好的建议,或者你的疑惑,请随时给我留言。

        收起阅读 »