当前位置: 首页 > news >正文

大厂C++协作框架(基辅)技术原理

Kiev框架简介

基辅是大推平台目前使用的Linux-C++后台开发框架。由多位资深架构师和资深C++工程师打造,在拥有数千万用户的大型分布式系统push平台上经过近年的测试。现在基辅每天为大推送平台中的数百个服务完成数百亿次RPC调用。

基辅作为一个完整的开发框架,是专门为大规模分布式系统背景而构建的C++开发框架。

它由以下组件组成:

  • RPC框架(TCP/UDP)
  • FastCGI框架
  • redis客户端(基于hiredis封装)
  • mysql客户端(基于mysqlclient封装)
  • mongodb客户端
  • 配置中心客户端(Http协议, 基于curl实现)
  • 基于zookeeper的分布式组件(服务发现、负载均衡)
  • 日志模块
  • 状态监控模块
  • 核心模块是一个开源的CSP并发模型协程库(libgo)

并发模型

基辅采用了先进的CSP开发模型的变体(golang就是这样的模型),它继承了libgo。选择这种模式的主要原因是这种模式的开发效率远高于异步回调模式,而且不需要在性能上做任何妥协。本文将对几种常见的模型进行详细的比较。

CSP模型

CSP(Communicating Sequential Process)模型是目前非常流行的并发模型,golang语言采用的并发模型就是CSP模型。在CSP模型中,协作流程与协作流程之间没有直接通信,信息也不像Actor模型那样直接传递给目标协作流程,而是通过一个通道交换数据。

在这里插入图片描述
这种设计的好处是通过通道中间层降低了进程间交互的耦合性,同时保证了灵活性,非常适合开发并发程序。

【文章福利】小编推荐自己的Linux C++技术交流群:【384235878】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送大厂面试题。

在这里插入图片描述

资料领取直通车:大厂面试题锦集+视频教程
免费学习网站:C/C++Linux服务器开发/后台架构师

RPC框架

RPC(远程过程调用)是一种远程调用协议。简单地说,它使应用程序能够像本地方法一样调用远程进程或服务。它可以应用于分布式服务、分布式计算、远程服务调用等多种场景。大家对RPC都很熟悉。业界有很多优秀的开源RPC框架,比如Dubbo、Thrift、gRPC、Hprose等等。RPC框架的出现是为了简化后台服务之间的网络通信,让开发者可以专注于业务逻辑,而不必处理复杂的网络通信。在我们看来,RPC框架不仅仅是封装网络通信。要应对数百种不同服务、数千万用户、数百亿PV的业务挑战,RPC框架必须在高可用性、负载均衡、过载保护、通信协议向后兼容、优雅降级、超时处理、无序启动等维度足够完善。

服务发现

基辅使用zookeeper进行服务发现。当每个基辅服务开放时,将在zookeeper上注册一个节点,包括地址和协议信息。横向扩展时,同质化服务会注册在同一个路径下,产生多个节点。当依赖服务被调用时,从zookeeper查询哪些节点当前可以使用,根据负载均衡的策略选择一个连接进行调用。

负载均衡

内置了robin和conhash两种负载均衡策略,可以根据实际业务场景进行定制。

过载保护

基辅有一个内置的过载保护队列,有10个优先级。当每个请求到达时,它首先进入过载保护队列,然后工作协程取出请求进行处理。如果工作进程的处理速度低于请求到达速度,过载保护队列就会堆积起来,甚至填满。当过载保护队列已满时,新请求到达后,队列中优先级较低的请求将被删除,并为新请求腾出一个空位。同时,队列中的请求具有时效性,太久没有处理的请求会被丢弃,避免处理超时的请求。这种机制保证了当系统过载时,有限的资源能够尽可能多地提供给关键业务。

通信协议向后兼容

因为微服务架构往往需要部分发布,所以需要选择支持向后兼容的通信协议。基辅选择protobuf作为通信协议。

与第三方库协同工作

最早的基辅是基于异步回调模型,但是很多第三方库只提供同步模型的版本,很难匹配。目前的基辅是一个CSP并发模型。利用libgo提供的Hook机制,可以充分利用同步模型第三方库中阻塞的CPU时间来执行其他逻辑,并且可以自动转化为CSP并发模型。异步回调模型的第三方库也可以使用CSP模型中的通道等待回调触发;以便与第三方库完美配合。

kiev功能组件结构图

在这里插入图片描述

Kiev发展史与技术选型

基辅第一版采用多线程同步模型,业务逻辑按顺序编写,非常简单。但由于os对线程数量的支持有限,随着线程数量的增加,调度消耗的增加是非线性的,因此无法支持过多的请求并发。

随着用户数量的增长,我们需要支持更高的并发请求。因为协同过程没有现在这么流行,所以我们用异步回调模型来写基辅。早期的业务形式非常简单,异步回调模型勉强可以应付开发任务。

随后几年,采用异步回调模式的基辅开发了大量服务。在使用中,我们慢慢发现逻辑碎片化的问题越来越多。更可怕的是,有时漫长的回调链与有限状态机纠缠在一起,代码变得越来越难维护。类似下面的代码片段经常出现:
在这里插入图片描述

为了解决这个问题,引入了开源的协同进程库libco来执行协同进程中的同步代码逻辑。同时使用Hook技术,利用被阻塞的IO请求中的等待时间片,切换cpu执行其他协进程,当IO事件被触发时再切换回来继续执行逻辑。类似上面的碎片化代码变成了连续的业务逻辑,不再需要手动维护上下文数据。临时数据可以直接放在堆栈上,代码如下:
在这里插入图片描述

但是libco只提供了两个功能,协议和HOOK,协议切换需要自己完成。为了简单起见,RPC框架已经发展成了连接池模式。每次启动RPC调用时,都会从连接池中取出一个连接来发送请求,等待响应,然后释放回连接池。每个连接同一时间只能运行一个请求,rpc协议退化为半双工模式。此时,为了保证性能,每两个相关服务之间必须建立数百个TCP连接。这样,依赖级别的服务扩展到很多进程时,会与这些进程建立数百个连接,TCP连接高达数千个甚至上万个,给服务器带来很大压力。下图显示了这些请求,其中每条连接线代表数百个TCP连接。

在这里插入图片描述

相应的,基辅的redis、mysql、fastcgi模块也进行了更新,全部改为协同学模式。

在最初的几个月中,这种方法大大提高了开发效率,同时,它具有相当好的性能(Rpc请求几乎有20K QPS)。随着时间的推移,我们的用户越来越多,请求越来越多,然后一个非关键业务就失败了。

失败的服务是接受来自移动电话的订阅请求的服务。订阅请求超时(约30秒)后,手机将再次尝试发起请求。当时由于系统过载,处理速度比请求速度慢,队列中积压了大量请求。随着时间的推移,服务处理请求的响应速度越来越慢,最终导致很多请求在被处理之前就被认为超时了,第二个请求被重新发起,产生了雪崩效应。当时紧急加了一些服务器,故障恢复了。总结后发现,事件的主要原因是没有过载保护机制。于是基辅内置了过载保护功能,增加了一个10优先级的过载保护队列。当每个请求到达时,它首先进入过载保护队列,然后工作协程取出请求进行处理。当过载保护队列已满时,删除队列中优先级最低的请求以腾出空位。同时,队列中的请求具有时效性,太久没有处理的请求会被丢弃,避免处理超时的请求。如下图所示:

在这里插入图片描述
随着机器数量的不断增加,以及随后出现的一些超长链接请求的业务形式(这里解释一下长链接请求的问题,一个长链接请求意味着一个请求要被很多服务处理。在处理过程中,前一个服务必须等到后续所有服务都处理完毕或者超时后,才能释放其占用的TCP连接,这将极大地影响整个系统的并发请求数),TCP连接的压力越来越大,最后我们不得不考虑在单连接上改为全双工模式。但是当时使用的libco函数过于简单,基于它很难开发出全双工的RPC框架。就在这时,一个同事在github上做了一个开源项目叫libgo,是一个类似golang语言的CSP并发模型的协作库。所以我们做了一段时间的技术预研,看看能否替代现有的libco。下表是两个项目在我们更关心的一些方面的比较:

在这里插入图片描述

用libgo代替libco。

基于CSP模型实现全双工通信RPC非常容易。客户端只需要在每个请求发出后保存id和通道,并以阻塞的方式等待相应的通道。当收到响应时,它可以根据id找到相应的通道并写入数据。这样,只需要一个TCP连接,就可以并发发送无数个请求。分布式水平扩展带来的TCP连接管理压力已经不是问题。同时,由于每个RPC需要的资源更少,性能得到了很大的提升,Rpc请求的QPS轻松提升到100K以上。这个性能指数现在已经超过了大多数开源RPC框架。

与流行开源框架相比

在这里插入图片描述

相关文章:

  • 【云原生】基于Kubernetes开发的阿里云ACK之存储管理
  • 【Java多线程】必须要了解的基本概念
  • 走进音视频的世界——Opus编解码协议
  • SaaS vs 低代码,谁在成为中国产业服务的楔子?
  • Redis实战精讲(3)
  • Flink(二)
  • 全息干涉图补零尺寸与三种重构方法重建像间的关系研究
  • Vue2(十三):路由的使用及注意点、嵌套路由、路由的query和params参数、props配置
  • 如何增强 ABAP 系统,允许开发人员给 ABAP 对象设置标签,方便快速检索
  • 【Python】面向对象 -没有对象?new一个吧
  • ELK集添加安全认证
  • 智能家居离线语音识别控制系统设计(SU-03T)
  • 商家如何玩好“种草神器”?小红书KOL达人种草这样做
  • 基于共词分析的中国近代史实体关系图构建(毕业设计:图数据渲染)
  • 猿创征文|阿里云MaxCompute存取性能测试报告
  • ES6指北【2】—— 箭头函数
  • css选择器
  • ES10 特性的完整指南
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • Lucene解析 - 基本概念
  • Median of Two Sorted Arrays
  • node和express搭建代理服务器(源码)
  • PHP 的 SAPI 是个什么东西
  • Spark学习笔记之相关记录
  • win10下安装mysql5.7
  • 欢迎参加第二届中国游戏开发者大会
  • 跨域
  • 聊聊sentinel的DegradeSlot
  • 深入浏览器事件循环的本质
  • 使用权重正则化较少模型过拟合
  • 吴恩达Deep Learning课程练习题参考答案——R语言版
  • 详解NodeJs流之一
  • 小程序测试方案初探
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ​Linux·i2c驱动架构​
  • ​香农与信息论三大定律
  • #微信小程序:微信小程序常见的配置传值
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (二)Linux——Linux常用指令
  • (接口封装)
  • (四)图像的%2线性拉伸
  • (五)网络优化与超参数选择--九五小庞
  • (正则)提取页面里的img标签
  • (终章)[图像识别]13.OpenCV案例 自定义训练集分类器物体检测
  • (转)Linux下编译安装log4cxx
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • ..thread“main“ com.fasterxml.jackson.databind.JsonMappingException: Jackson version is too old 2.3.1
  • .Net 8.0 新的变化
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Core 中插件式开发实现
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .Net程序帮助文档制作
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • [ vulhub漏洞复现篇 ] Jetty WEB-INF 文件读取复现CVE-2021-34429
  • [<事务专题>]