即时通信
▐ 单聊信息可靠传输
TCP保证消息可靠传输三板斧:超时、重传、确认。服务端和客户端通信MSG和ACK的共计6个报文。
- 请求报文(request,后简称为R),客户端主动发送给服务端。
- 应答报文(acknowledge,后简称为A),服务器被动应答客户端的报文。
- 通知报文(notify,后简称为N),服务器主动发送给客户端的报文
在线消息流程:A 消息请求 MSG:R => S 消息应答 MSG:A => S 消息通知B MSG:NS 确认通知 ACK:N <= S 确认应答 ACK:A <= B确认请求S ACK:R
超时与重传、确认和去重:A发出了 MSG:R ,收到了MSG:A之后,在一个期待的时间内,如果没有收到ACK:N,A会尝试将 MSG:R 重发。可能A同时发出了很多消息,所以A需要在本地维护一个等待ack队列,并配合timer超时机制,来记录哪些消息没有收到ACK:N,定时重发。确认ACK保证必达,去重保证唯一
离线消息流程原方案:根据离线好友的标识,交互拉取指定的消息。
优化的方案:
- 如用户勾选全量则返回计数,在用户点击时拉取。
- 如用户未勾选全量则返回最近全部离线消息,客户端针对用户id进行计算。
- 全量离线信息可以通过客户端异步线程分页拉取,减少卡顿
- 将ACK和分页第二次拉取的报文重合,可以较少离线消息拉取交互的次数
▐ 群聊消息如何保证不丢不重
在线的群友能第一时间收到消息;
离线的群友能在登陆后收到消息。
- 群消息发送者x向server发出群消息;
- server去db中查询群中有多少用户(x,A,B,C,D);
- server去cache中查询这些用户的在线状态;
- 对于群中在线的用户A与B,群消息server进行实时推送;
- 对于群中离线的用户C与D,群消息server进行离线存储。
对于同一份群消息的内容,多个离线用户存储了很多份。假设群中有200个用户离线,离线消息则冗余了200份,这极大的增加了数据库的存储压力
- 离线消息表只存储用户的群离线消息msg_id,降低数据库的冗余存储量;
- 加入应用层的ACK,才能保证群消息一定到达,服务端幂等性校验及客户端去重,保证不重复;
- 每条群消息都ACK,会给服务器造成巨大的冲击,通过批量ACK减少消息风暴扩散系数的影响;
- 群离线消息过多:拉取过慢,可以通过分页懒拉取改善。
▐ 如何保证消息的时序性
方案:
- Id通过借鉴微信号段+跳跃的方式保证趋势递增;
- 单聊借鉴数据库设计,单点序列化同步到其他节点保证多机时序;
- 群聊消息使用单点序列化保证各个发送者的消息相对时序;
优化:
- 利用服务器单点序列化时序,可能出现服务端收到消息的时序,与发出序列不一致;
- 在A往B发出的消息中,加上发送方A本地的一个绝对时序,来表示接收方B的展现时序;
- 群聊消息保证一个群聊落在一个service上然后通过本地递增解决全局递增的瓶颈问题;
▐ 推拉结合
历史方案:
- 服务器在缓存集群里存储所有用户的在线状态 -> 保证状态可查;
- 用户状态实时变更,任何用户登录/登出时,需要推送所有好友更新状态;
- A登录时,先去数据库拉取自己的好友列表,再去缓存获取所有好友的在线状态;
“消息风暴扩散系数”是指一个消息发出时,变成N个消息的扩散系数,这个系数与业务及数据相关,一定程度上它的大小决定了技术采用推送还是拉取。
优化方案:
- 好友状态推拉结合,首页置顶亲密、当前群聊,采用推送,否则可以采用轮询拉取的方式同步;
- 群友的状态,由于消息风暴扩散系数过大,可以采用按需拉取,延时拉取的方式同步;
- 系统消息/开屏广告等这种实时产生的消息,可以采用推送的方式获取消息;
▐ 好友推荐
Neo4j 图谱数据库
智慧社区
18年初,针对我们Dubbo框架的智慧楼宇项目的单体服务显得十分笨重,需要采用微服务的形式进行架构的重新设计,当时,我阅读了Eric Evans 写的《领域驱动设计:软件核心复杂性应对之道》和Martin fowler的《微服务架构:Microservice》两本重量级书籍,书中了解到转型微服务的重要原因之一就是利用分治的思想减少系统的复杂性,是一种针对复杂问题的宏观设计,来应对系统后来规模越来越大,维护越来越困难的问题。然而,拆分成微服务以后,并不意味着每个微服务都是各自独立地运行,而是彼此协作地组织在一起。这就好像一个团队,规模越大越需要一些方法来组织,这正是我们需要DDD模型为我们的架构设计提供理论并实践的方法。
当时每次版本更新迭代动辄十几个微服务同时修改,有时一个简单的数据库字段变更,也需要同时变更多个微服务,引起了团队的反思:微服务化看上去并没有减少我们的工作量。《企业架构设计》中对于微服务的定义是小而专,但在起初的设计时,我们只片面的理解了小却忽视了专,此时我们才意识到拆分的关键是要保证微服务内高内聚,微服务间低耦合。
▐ 物联网架构
物联网是互联网的外延。将用户端延伸和扩展到物与人的连接。物联网模式中,所有物品与网络连接,并进行通信和场景联动。互联网通过电脑、移动终端等设备将参与者联系起来,形成的一种全新的信息互换方式。
- DCM系统架构
- 设备感知层(Device):利用射频识别、二维码、传感器等技术进行数据采集;
- 网络传输层(Connect):依托通信网络和协议,实现可信的信息交互和共享;
- 应用控制层(Manage):分析和处理海量数据和信息,实现智能化的决策和控制;
- 三要素
- 设备联网:通过不同的网络协议和通信标准,实现设备与控制端的连接;
- 云端分析:提供监控、存储、分析等数据服务,以及保障客户的业务数据安全;
- 云边协同:云端接受设备上报数据,下发设备管控指令;
- 云 / 边 / 端协同
云端计算、终端计算和边缘计算是一个协同的系统,根据用户场景、资源约束程度、业务实时性等进行动态调 配,形成可靠、低成本的应用方案。从过去几年的发展积累来看,AI 已在物联网多个层面进行融合,比我们合作的海康威视、旷视宇视、商汤科技等纷纷发布了物联网AI相关平台和产品,进行了紧密的融合。
- 物联网平台接入
向下连接海量设备,支撑设备数据采集上云;向上通过调用云端API将指令下发至设备端,实现远程控制。上行数据链路
- 设备建立MQTT长连接,上报数据(发布Topic和Payload)到物联网平台;
- 物联网平台通过配置规则,通过RocketMQ、AMQP等队列转发到业务平台;
下行指令链路
- 业务服务器基于HTTPS协议调用的API接口,发布Topic指令到物联网平台;
- 物联网平台通过MQTT协议,使用发布(指定Topic和Payload)到设备端;
- 门锁接入
WIFI门锁:非保活 平常处于断电休眠状态,需要MCU 唤醒才能传输和发送数据;蓝牙门锁:MCU串口对接和SDK对接,近距离单点登录和远距离网关登录;Zigbee门锁:非保活 但是保持心跳,MCU对接,Zigbee协议控制;NB-Iot门锁:可以通过公网连接,把门禁变成SAAS服务,MCU;
- 各种协议
HTTP协议(CS用户上网)HTTP协议是典型的CS通讯模式,由客户端主动发起连接,向服务器请求XML或JSON数据。该协议最早是为了适用web浏览器的上网浏览场景和设计的,目前在PC、手机、pad等终端上都应用广泛,但并不适用于物联网场景。
- 由于必须由设备主动向服务器发送数据,难以主动向设备推送数据;
- 物联网场景中的设备多样,运算受限的设备,难以实现JSON数据格式的解析;
RESTAPI(松耦合调用)REST/HTTP主要为了简化互联网中的系统架构,快速实现客户端和服务器之间交互的松耦合,降低了客户端和服务器之间的交互延迟。因此适合在物联网的应用层面,通过REST开放物联网中资源,实现服务被其他应用所调用。
CoAP协议(无线传感)
简化了HTTP协议的RESTful API,它适用于在资源受限的通信的IP网络。
MQTT协议(低带宽)
MQTT协议采用发布/订阅模式,物联网终端都通过TCP连接到云端,云端通过主题的方式管理各个设备关注的通讯内容,负责将设备与设备之间消息的转发。
适用范围:在低带宽、不可靠的集中星型网络架构(hub-and-spoke),不适用设备与设备之间通信,设备控制能力弱,另外实时性较差,一般都在秒级。协议要足够轻量,方便嵌入式设备去快速地解析和响应。具备足够的灵活性,使其足以为 IoT 设备和服务的多样化提供支持。应该设计为异步消息协议,这么做是因为大多数 IoT 设备的网络延迟很可能非常不稳定,若使用同步消息协议,IoT 设备需要等待服务器的响应,必须是双向通信,服务器和客户端应该可以互相发送消息。
AMQP协议(互操作性)
用于业务系统例如PLM,ERP,MES等进行数据交换。
适用范围:最早应用于金融系统之间的交易消息传递,在物联网应用中,主要适用于移动手持设备与后台数据中心的通信和分析。
XMPP协议(即时通信)
开源形式组织产生的网络即时通信协议。被IETF国际标准组织完成了标准化工作。
适用范围:即时通信的应用程序,还能用在协同工具、游戏等。XMPP在通讯的业务流程上是更适合物联网系统的,开发者不用花太多心思去解决设备通讯时的业务通讯流程,相对开发成本会更低。但是HTTP协议中的安全性以及计算资源消耗的硬伤并没有得到本质的解决。
JMS (Java消息服务)Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
Zigbee协议低功耗,它保持IEEE 802.15.4(2003)标准
▐ IOT流量洪峰
智慧社区IOT领域,不管是嵌入式芯片还是应用服务器都需要传递消息,常见上行的消息有:人脸识别开门、烟感雾感告警、共享充电桩充电,下行的广告下发、NB门禁开门指令、超级门板显示等,由于物联网设备时不时会故障和断网导致大量的流量洪峰,传统消息队列需要针对性优化。
- 上下行拆分上行消息特征:并发量高、可靠性和时延性要求低下行消息特征:并发量低、控制指令的成功率要求高
- 海量Topic下性能Kafka海量Topic性能会急剧下降,Zookeeper协调也有瓶颈多泳道消息队列可以实现IoT消息队列的故障隔离
- 实时消息优先处理NB门禁实时产生的开门指令必须第一优先级处理,堆积的消息降级设计成无序、不持久化的,并与传统的FIFO队列隔离
- 连接、计算、存储分离Broker只做流转分发,实现无状态和水平扩展计算交给Flink,存储交给nosqlDB,实现高吞吐写
- 消息策略-推拉结合MQTT针对电池类物联网设备,AMQP针对安全性较高的门禁设备消费端离线时存到queue,在线时将实时消息和从queue中拉取的消息一起推送
如果解决海量Topic首先要做的就是分区、分组等水平拆分的方式,接下来考虑单实例如何处理更多Topic,传统消息队列在海量Topic下顺序写会退化成随机写,性能大幅下降
- 人工Sharding:部署多个Kafka集群,通过不同mq连接来隔离;
- 合并Topic,客户端封装subTopic。比如一个服务的N个统计项,会消费到无关消息;基于这个思路,使用Kafka Streams或者Hbase列存储来聚合;
针对单个Topic海量订阅的问题,可以在上层封装广播组件来协调批量发送。
图片来源于网络
▐ 社区直播带货
使用端 / 边 / 云三级架构,客户端加密传输,边缘节点转发、云侧转码并持久化。
- 产品的背景
上线时间,从调研到正式上线用了 3个月时间,上线后一个月内就要经历双十二挑战。在这么紧的上线时间要求下,需要用到公司提供的所有优势,包括cdn网络,直播牌照等。
- 面临的挑战
- 直播数据是实时生成的,所有不能够进行预缓存;
- 直播随时会发生,举办热点活动,相关服务器资源需要动态分配;
- 直播的延迟对于用户体验影响很大,需要控制在秒级;
- 直播sdk是内嵌在社区应用里的,整体要求不能超过5M;
- 协议的比较
- 整体流程
RTMPS:基于TCP实时传输消息协议,更安全更可靠MPEG-DASH:是一种基于HTTP协议自适应比特率流媒体技术,应对复杂的环境
- 直播端使用 RTMPS 协议发送直播数据到边缘节点(POP)
- POP 使用RTMP发送数据到数据中心(DC)
- DC 将数据编码成不同的清晰度并进行持久化存储云端转码主要有两种分辨率400x400 和 720x720.
- 播放端通过 MPEG-DASH / RTMPS 协议接收直播数据如果用户网络不好MPEG-DASH会自动转换成低分辨率
- 直播流程
- 直播端使用 RTMPS 协议发送直播流数据到 POP 内的就近的代理服务器;
- 代理服务器转发直播流数据到数据中心的网关服务器(443转80);
- 网关服务器使用直播 id 的一致性哈希算法发送直播数据到指定的编码服务器;
- 编码服务器有几项职责:
-
4.1 验证直播数据的格式是否正确;
4.2 关联直播 id 以及编码服务器第一映射,保证客户端即使连接中断或者服务器扩容时,在重新连接的时候依然能够连接到相同的编码服务器;
4.3 使用直播数据编码成不同解析度的输出数据;
4.4 使用 DASH 协议输出数据并持久化存储;
- 播放流程
- 播放端使用 HTTP DASH 协议向 POP 拉取直播数据;
- POP 里面的代理服务器会检查数据是否已经在 POP 的缓存内。如果是的话,缓存会返回数据给播放端,否则,代理服务器会向 DC 拉取直播数据;
- DC 内的代理服务器会检查数据是否在 DC 的缓存内,如果是的话,缓存会返回数据给 POP,并更新 POP 的缓存,再返回给播放端。不是的话,代理服务器会使用一致性哈希算法向对应的编码服务器请求数据,并更新 DC 的缓存,返回到 POP,再返回到播放端;
- 收获
- 项目的成功不,代码只是内功,考虑适配不同的网络、利用可利用的资源;
- 惊群效应在热点服务器以及许多组件中都可能发生;
- 开发大型项目需要对吞吐量和时延、安全和性能做出妥协;
- 保证架构的灵活度和可扩展性,为内存、服务器、带宽耗尽做好规划;
▐ 直播高可用方案
网络可靠性:
- 根据网络连接速度来自动调整视频质量;
- 使用短时间的数据缓存来解决直播端不稳定,瞬间断线的问题;
- 根据网络质量自动降级为音频直播以及播放;
惊群效应:
- 当多个播放端向同一个 POP 请求直播数据的时候,如果数据不在缓存中;
- 这时候只有一个请求 A 会到 DC 中请求数据,其他请求会等待结果;
- 但是如果请求 A 超时没有返回数据的话,所有请求会一起向 DC 访问数据;
- 这时候就会加大 DC 的压力,触发惊群效应;
- 解决这个问题的方法就是通过实际的情况来调整请求超时的时间。这个时间如果太长的话会带来直播的延迟,太短的话会经常触发惊群效应(每个时间窗口只允许触发一次,设置允许最大回源数量);
▐ 性能优化方案
数据库优化: 数据库是最容易成为瓶颈的组件,考虑从 SQL 优化或者数据库本身去提高它的性能。如果瓶颈依然存在,则会考虑分库分表将数据打散,如果这样也没能解决问题,则可能会选择缓存组件进行优化。
集群最优:存储节点的问题解决后,计算节点也有可能发生问题。一个集群系统如果获得了水平扩容的能力,就会给下层的优化提供非常大的时间空间,由最初的 3 个节点,扩容到最后的 200 多个节点,但由于人力问题,服务又没有什么新的需求,下层的优化就一直被搁置着。
硬件升级:水平扩容不总是有效的,原因在于单节点的计算量比较集中,或者 JVM 对内存的使用超出了宿主机的承载范围。在动手进行代码优化之前,我们会对节点的硬件配置进行升级。
代码优化:代码优化是提高性能最有效的方式,但需要收集一些数据,这个过程可能是服务治理,也有可能是代码流程优化。比如JavaAgent 技术,会无侵入的收集一些 profile 信息,供我们进行决策。
并行优化:并行优化是针对速度慢的接口进行并行调用。所以我们通常使用 ContDownLatch 对需要获取的数据进行并行处理,效果非常不错,比如在 200ms 内返回对 50 个耗时 100ms 的下层接口的调用。
JVM 优化: JVM 发生问题时,优化会获得巨大的性能提升。但在 JVM 不发生问题时,它的优化效果有限。但在代码优化、并行优化、JVM 优化的过程中,JVM 的知识却起到了关键性的作用。
操作系统优化:操作系统优化是解决问题的杀手锏,比如像 HugePage、SWAP、“CPU 亲和性”这种比较底层的优化。但就计算节点来说,对操作系统进行优化并不是很常见。运维在背后会做一些诸如文件句柄的调整、网络参数的修改,这对于我们来说就已经够用了。
▐ 流量回放自动化测试
系统级的重构,测试回归的工作量至少都是以月为单位,对于人力的消耗巨大。一种应对方案是,先不改造,到系统实在扛不住了再想办法。另一种应对方案是,先暂停需求,全力进行改造。但在实际工作场景中,上述应对策略往往很难实现。
场景:
- 读服务均是查询,它是无状态的。
- 不管是架构升级还是日常需求,读服务对外接口的出入参格式是没有变化的。
- 日志收集,主要作用是收集被测系统的真实用户请求,基于一定规则处理后作为系统用例;Spring 里的 Interceptor 、Servlet 里的 Filter 过滤器,对所有请求的入参和出参进行记录,并通过 MQ 发送出去。(注意错峰、过滤写、去重等)。
- 数据回放是基于收集的用例,对被测系统进行数据回放,发起自动化测试回归;离线回放:只调用新服务,将返回的数据和日志里的出参进行比较,日志比较大实时回放:去实时调用线上系统和被测系统,并存储实时返回回放的结果信息,线上有负担。并行回放:新版本不即时上线,每次调用老版本接口时概率实时回放新版本接口,耗时间周期。
- 差异对比,通过差异对比自动发现与预期不一致的用例,进而确定 Bug。采用文本对比,可以直观地看到哪个字段数据有差异,从而更快定位到问题。正常情况下,只要存在差异的数据,均可认为是 Bug,是需要进行修复的。
方法论
Discovery考虑企业战略,分析客户需求,制定产品目标由外到内:竞争对手的方案,为什么做,以后怎么发展,如何去优化。自上而下:基于公司的战略,考虑自身能力和所处环境。自下而上:从资源、历史问题、优先级出发,形成一套可行性实施方法。
Define基于收集的信息,综合跨业务线的抽象能力和服务,先做什么后做什么,怎么做设计新的架构,重点设计解决痛点问题。拆分业务领域,重点划分工作临界上下文。
Design详细的业务设计,功能设计,交付计划,考核计划产品愿景,产品形态,相关竞品方案对比,价值、优势、收益梳理业务范围,要知道电商领域四大流(信息流、商流、资金流、物流)MVP最小可用比,让客户和老大看到结果,最后通编写story把故事编圆
Delivery交付阶段,根据反馈及时调整中台战略,减少损失和增大收益合理制定每个阶段的绩效考核目标:40%稳定+25%业务创新+20%服务接入+15%用户满意度