腾讯专家10年沉淀:后海量时代的架构设计
01
02
架构的边界
边界思维、边界意识,探索边界、扩张边界
职责分离、防火隔离 契约精神 高内聚、低耦合、层次分明
协议通信的 layout 怎么定义?一般分包头和包体。
终端和后台有几次交互,每次交互的请求和返回字段是什么? 采用什么样的协议交互,JSON、JCE、ProtocolBuffers ? 错误码怎么定义?是否有二级错误码?头部一个错误码,代表整体的错误和异常情况,比如登录过期等。而包体有错误码定义,标识当前请求的返回情况。
struct ReqHead {
Int cmdId;
...
}
struct Request {
ReqHead head;
vector<char> body;
}
struct PkgReq {
PkgReqHead head;
Request request;
}
三个字节的 magic bytes,用于做标识终端和后台协议。 | |||
0x00000002 (版本,4bytes 网络字节序) | 4 个字节网络字节序的版本号,用来区分不同协议定义版本,也偏于扩充每一次终端和后台的交互。 | ||
用 ReqHead.cmdId 来区分,body 对应具体的 Jce 结构。例如 Cmd1,定义了对应的 Request 和 Response 结构,结尾必须是 Request 和 Response,并且 Response 的第 0 个 tag 必须是 Int 类型,明确了 ret >= 0,为命令字成功,ret < 0 为失败,便于在接入层统一监控和统计。 | |||
ReqHead | |||
Struct Cmd1Request { }
Struct Cmd1Response {
Int ret;
}
能支持厂商通道,在终端设备不在线时也能收到消息推送(除非用户手动关闭消息接收提醒)。
支持定时发送。
支持对所有在线设备群发消息。
需要对消息的接收能做确认,至少包含在线设备的网络层发送成功(通过已有的 TCP 长连接通道)、终端设备确认接收成功(终端收到推送消息时通过长连接通道发送 ACK 确认消息到后台)。
发送失败的消息,能按照一定的策略重发。
业务方要能查询发送消息的状态,并支持条件订阅。
Android 和 iOS 希望能有一套统一的消息推送方式,减少上层业务使用 PUSH系统的成本,并且屏蔽不同终端设备的差异。
interface PushAPI {
//单设备推送
int pushSingleDevice(PushSingleDeviceReq req, out PushSingleDeviceRsp rsp);
//多设备推送
int pushMultiDevice(PushMultiDeviceReq req, out PushMultiDeviceRsp rsp);
//全量在线设备推送
int pushAllOnlineDevice(PushAllOnlineDeviceReq req, out PushAllOnlineDeviceRsp rsp);
//全量设备推送
int pushAllDevice(PushAllDeviceReq req, out PushAllDeviceRsp rsp);
};
03
架构的组织属性
在要调用 L5 的机器上部署 L5 Agent。
在代码中通过 L5 的 API 获取要调用的 IP 和 port,所调用的服务接口由指定的 mod、cmd 两个参数来标识。
组包,包括 PDU 结构的头部和包体。
通过 tcp 连接发送。
每台主调机器需要部署 L5 Agent,这是 SNG 运维在维护,MIG 运维无法很好的支持。
调用方代码重复很多。
无法监控和统计成功率。
边界很模糊,或者说边界很厚。
查问题很麻烦,因为 L5 后端的接入层没有很完善的结果。
提供统一 taf 接口给所有需要调用 L5 接口的 taf 服务。
只需要在 PDUBridgeServer 的机器上部署 L5 Agent。
PDUBridgeServer 中支持对所有 mod、cmd 所标识接口的监控和告警,会加上一个字符串描述来更直观的看监控统计数据。
监控统计效果如下:
当时遇到一个问题,开发者监控到的某个 mod、cmd 接口的异常率高达 10+%,开发者看到的是 PDUBrige 到 L5 的异常率,而对方看的是 L5 到 B 之间的异常率,因为 L5 本身是有负载均衡策略,根据后端的负载情况会拒绝主调调用,导致 PDUBridge 到 L5 的异常率高,后面开发者通过修改 L5 的参数,异常率降低了很多。我们推测是 L5 的负载阈值比较高,具体是什么阈值不得而知。因为这些问题,非常影响效率和团队关系。
虽然加了一层调用,但是处理非常简单,只有 ms 级的耗时增加,将 PDUBridgeServer 做为两个组织的边界,通过监控来直观反应接口调用质量和流量统计,减少了部署成本,也方便 taf 主调服务去调用(直接通过 taf 接口而非 L5 原生接口去调用)。
由以上案例可以看出:架构边界和组织架构边界重合时,或者说在考虑系统架构时,要考虑组织的边界。例如要有简单明确的调用接口方式,做好监控和统计、告警,以及系统的整体反馈,大家的认识要一致,确定好统一的目标和衡量指标。例如异常率的定义、成功率等,不能不对等或者理解不一致。这样才能更好的交流和界定职责和边界,否则会带来很多团队协作问题。例如推诿、争端等,效率也会极其低下。
04
众所周知,监控和告警是为了发现问题、定位问题以及更好地解决问题。但是整个系统只是监控、告警和统计,远远不足以反应整个架构的系统性运行状态。因此,将这些能反映架构运行状态的所有手段,统称为架构的反馈。既然是架构运行的反馈,必然对系统本身的优化和完善,也会有作用。
讲个亲身经历,我们在一次重构应用宝 App 搜索和内容搜索时,转辗了几个团队。由于用了开源的 ElasticSearch 解决方案,不能很好的和 TAF 的机制很好的结合,例如自动伸缩、负载均衡和容灾容错等,开发者成员做了不少工作来整合。同时为了更好的监控搜索系统的运行状况,和搜索业务的整体情况,例如 Query 分析、刷量等问题,也做了不少监控告警统计。其实这些工作,都是为了更好的反馈整个搜索系统。
App 搜索因为各种利益(刷关键词、刷自己、刷对手等),经常会有刷关键词的情况存在。对后台来说,如何识别刷量请求、识别后如何处理、应对刷量带来的突发流量压力等,都成为要考虑的问题。一般的做法,识别到刷量请求后(比如明显的请求特征 GUID 聚集等)会拒绝请求。「堵不如疏」,在识别到刷量请求后,系统直接从 Cache 中正常返回搜索结果,不走后续复杂的 Query 分析、ES 搜索、召回、排序等耗时环节。这种做法会迷惑刷量用户,并且在后续的搜索结果曝光、点击、下载等一系列后续数据上报环节,都会带上后台识别出来的刷量标识(会保证刷量标识不会在终端被篡改),在后续数据处理环节也能识别并具备剔除刷量请求以保证上报数据不受影响。
基于 TAF 的 PP 监控(全称是Property Plus监控。允许用户通过自定义维度与自定义指标,上报特性, 它由维度名、指标值、以及对应的指标统计方法构成。),开发者做了一个 Query 监控,其能反应 Query 的请求量情况趋势和耗时对比。某个违禁词本身在后台已经被识别为刷量,并且从下图可以看出,请求量波动比较大、耗时非常低,正常都在 100 多ms,而这个 Query 的耗时是 2ms。
实际的情况,某个违禁词是刷量,波动非常大,在时间分布上也比较集中,因为识别为刷量,会命中 Cache,因此搜索耗时非常小。
大数据 A/B testing 算法对照监控图
常规意义上的监控和告警、统计,已经无法更好地反映系统整体的运行情况,需要更全面、系统化的方式来反映。同时,也能通过这些体系化的监控统计手段,来回馈架构,对架构的进一步演化提供依据。或者从另一个层面来讲,监控和统计被赋予了更多的意义。
参考阅读:
本文由高可用架构转载。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿