找回密码
 会员注册
查看: 28|回复: 0

十年磨一剑,CKV单机QPS突破千万优化之路

[复制链接]

4

主题

0

回帖

13

积分

新手上路

积分
13
发表于 2024-9-20 22:44:28 | 显示全部楼层 |阅读模式
CKV(也称为CKV+)是腾讯新一代自研高性能NoSQLKV数据库,兼容Redis、Memcached、ASN协议,具备高性能、低延时、低成本的优势,能够应对海量数据访问、存储成本敏感、延时敏感等问题。CKV作为公司应用最广泛的NoSQL存储系统,已接入包括广点通、信鸽、QQ音乐、财付通、微视、看点等业务。随着业务规模的增长,成本和性能是用户最关注的问题,为了进一步降低用户的成本并提升性能,我们分别从性能和成本两个方面对CKV进行了优化。下面先回顾CKV的架构演进,然后分析当前架构的性能瓶颈,重点介绍性能方面的优化,并在性能上与开源Redis和友商竞品做对比。一、CKV的架构演进CKV作为公司有历史的分布式内存系统,下面罗列了一些重要的发展经历。2009年TMEM(tencentmemcached)作为高并发内存级存储解决方案上线,支撑了当时的QZone/朋友网等业务。2011年更名为CMEM(cloudmemcached),开始向云计算靠拢并为海量的第三方游戏用户提供了解决方案,比如偷菜,抢车位等。2012年随着业务规模的增加,内存的高成本成为了核心问题,通过引入冷数据存储介质降低了业务的使用成本。2014年CKV支持了微信红包业务。2017年基于seastar框架开发的CKV上线,兼容了REDIS协议,进一步提升了性能。1.1CMEM架构1.1.1架构总览CMem的模块众多,按照模块功能分为核心模块和外围模块。核心模块采用的是传统的三层架构,包括接入、存储和元数据(master)模块三部分。元数据(master)模块负责管理路由等元数据,元数据是本地文件存储,无备份节点。业务直接访问的是接入模块,接入模块会根据路由转发到后端的存储模块外围模块包括备份、搬迁、统计、探测、恢复等模块。CMem对业务数据通过Presharding的方式进行分片,将所有数据按照hash(key)%10000的方式逻辑上分成10000个Slot,每个Slot会对应存储节点的一对主备分片,存储端采用主备的方式来容灾。1.1.2架构优缺点优点底层数据存储采用共享内存,在进程异常的时候可以快速恢复。引擎设计上对于用户数据长度比较一致的业务上,可以充分利用内存。支持开源memcached协议,可以直接用memcached客户端访问。搬迁对用户影响很小,只影响搬迁中的删除操作。多租户方式,可以充分利用资源。缺点模块较多,出现异常定位问题链路较长。存储模型采用整机主备的设计,一个机器都是主,一个机器都是备,造成主机压力较大,而备机相对比较闲,而且只支持一主一备。主备同步用的rsync进程,是单进程单线程模型。在访问量较大的场景下,容易成为瓶颈。引擎设计对于随机用户数据长度上不太友好。存储模块采用多进程加锁的方式,不能充分利用资源。进程太少,能够利用的CPU就少,进程太多,锁的开销就增大。元数据管理模块采用的本地存储,并且是单点。元数据推送采用全量推送,效率较低。1.2CKV架构1.2.1架构总览上图中灰色部分是从CMem的架构中移除的模块,只保留了一些必要的模块(蓝色部分)。从功能上也分为核心和外围模块。核心模块采用的是二层架构,包括存储和元数据管理两个模块。元数据是保存在本地的ETCD服务中,采用ETCD服务于元数据服务1对1模式,至少是三分数据,提升了元数据的可靠性。存储模块兼顾接入的能力,业务直接访问存储模块,如果根据路由计算出是本机处理就直接处理,非本机请求就转发到目标节点处理。外围模块主要包括备份和OSS。备份负责数据的冷备与恢复,OSS负责管理整个集群,包括实例的创建/删除/备份/扩缩容等。1.2.2核心架构CKV从设计上为了适应新型的多CPU大内存机型,采用多租户方式,底层引擎采用共享内存,为了兼容REDIS协议,设计支持了string/list/hash/set/zset等数据结构。存储节点采用单进程多线程模型,实际存储分片按需分配,一个节点上尽量保证主备数量均衡,同时存储兼有转发的能力,也支持独立部署接入层的能力。元数据管理上基于ETCD保证元数据的可靠性,路由设计上采用增量推送的方式,提高了路由更新的效率。数据模型上跟CMem类似,采用Presharding,hash(key)%16384,Slot数从10000增加到了16384,跟Redis保持一致,并且支持一主多备。1.2.3线程模型CKV的Cache作为核心的存储模块,在设计的时候,性能是我们最看重的,同时为了充分利用当前多CPU大内存的机型资源,我们基于Seastar这个高性能网络通信框架来开发。http://seastar.io/Seastar是一个面向现代硬件多核架构的高性能异步网络框架,它是KVM作者AviKivity领衔开发的开源项目。Seastar采用Share-nothing架构、全异步编程、用户态任务调度器、支持内核协议栈和用户态协议栈、独立的内存管理等提供了极致的性能。Cache的设计采用对等模式,每个CPU可以理解为一个线程,所有线程的功能可以认为是一样的,不区分IO线程和worker线程。CKV的最小存储单元是分片,每个分片是独立的共享内存,采用按需分配的方式。CKV存储访问采用无锁设计,每个分片只会由一个CPU来管理,这样访问就不需要加锁。在上图中可以看到,一共有四个线程,其中第一个线程管理两个分片,第二个线程管理三个分片,后面两个线程分别只管理一个分片。每个线程接收到请求之后,会根据路由计算是不是当前线程处理,如果不是的话,会转发到其他线程来执行请求。同时每个线程都可以处理网络IO,一旦接受到数据包,在当前核完成编解码的工作,然后会根据路由将请求转发到指定的线程或者其他节点运行。这样就把网络IO和编解码的工作分摊到所有线程,可以充分利用CPU资源。Cache上大部分操作都是基于future/promise实现的异步,包括逻辑处理、网络IO和磁盘IO等,只有底层存储引擎是同步操作。这样的好处是所有的IO不需要等待,可以让出CPU,提高吞吐量。2、性能瓶颈及分析2.1性能数据基于Seastar框架开发的CKV整机测试性能数据。2.2CKV性能分析前面已经给出了CKV的性能数据,虽然相对CMem来说,性能提升了不少,为了进一步提高性能,我们对于整机场景了做了性能分析。 从火焰图上看没有明显的集中,CPU主要消耗是seastar::reactor::run_tasks和seastar::reactor:poll_once。其中run_tasks负责执行命令以及网络收包,而poll_once主要是负责网络包的发送。然后通过mpstat分析CPU消耗。上图中,第2列是CPU,第3列是%usr用户态占比,第5列是%sys内核态占比,第8列是%si软中断占比。可以看出软中断已经消耗了30%的CPU。这里的软中断主要是网卡收包导致的。网卡收到网络包后,会通过硬件中断通知内核有新的数据到了,内核会调用对应的中断处理程序来响应该事件,先将网卡的数据读到内存中,然后更新硬件寄存器的状态,然后内核会触发一个软中断,需要从内存中找到网络数据,再按照网络协议栈,对数据逐层解析和处理,最后将数据给到应用程序。2.3REDIS性能分析在同样的硬件条件下,测试机器包含72个物理核,在机器上启动了72个redis实例,然后对72个实例进行了整机压测。perf统计如下从上图可以看出,redis的CPU消耗主要是网络收发包。通过mpstat查看CPU消耗。从上图可以看出,CPU消耗主要集中也是在软中断。在整机测试的场景下可以看出网络成为了性能的瓶颈。在整机大压力访问场景中,无论是CKV还是REDIS,最终性能瓶颈都在网络上,因此传统的内核协议栈不能能充分利用最新硬件的能力,DPDK通过用户态协议栈则可以解决这个问题。3、DPDKDPDK全称IntelDataPlaneDevelopmentKit,是intel提供的数据平面开发工具集,是一个用来进行包数据处理加速的软件库。DPDK专注于网络应用中数据包的高性能处理,具体体现在DPDK应用程序是运行在用户态上利用自身提供的数据平面库来收发数据包,绕过了Linux内核协议栈对数据包处理过程。DPDK通过UIO、poll-mode网卡驱动、内存池、大页内存管理、无锁数据结构等数据手段来提升性能。3.1初始化SEASTAR1.seastar初始化的时候,先在cpu0调用smp::configure初始化配置。2.在cpu0调用rte_eal_init初始化DPDK的EAL。3.通过rte_eal_remote_launch在非0cpu上调用reactor::configure初始化引擎,在初始化引擎的时候会根据用户配置初始化协议栈4.等待其他核初始化完成后,在cpu0上初始化引擎和协议栈。完成框架的初始化。3.2初始化协议栈(DPDK)协议栈的初始化分成三大步。其中上图中设置成橙色的设置隔离模式和设置流规则是CKV增加的。1.网卡初始化。首先通过rte_eth_dev_info_get获取网卡信息,然后通过rte_flow_isolate设置网卡成隔离模式,最后调用rte_eth_dev_configure配置网卡2.队列初始化。通过调用rte_eth_rx_queue_setup和rte_eth_tx_queue_setup分别初始化入队列和出队列。然后会轮询rte_eth_rx_burst和rte_eth_tx_burst分别处理收包和发包。3.网卡启动。先调用rte_eth_dev_start启动网卡,然后调用rte_flow_create设置流规则,最后调用rte_eth_link_get_nowait检查网卡是否正确启动。3.3数据包处理在前面队列初始化完成后,我们会轮询RX和TX两个方向上的包。RX代表接受数据,TX代表传输数据。具体是通过注册一个poller不停的轮询处理。RX注册的是reactor::poller::simple([&]{returnpoll_rx_once();})TX注册的是reactor::poller::simple([this]{returnpoll_tx();}))对于Server端,首先会通过RSS(Receive-SideScaling,alsoknownasmulti-queuereceive)将包分发给指定的队列。将不同的流分发给不同的CPU,同一个流始终会在同一个CPU上,避免TCP的顺序和CPU的并行发生冲突。在协议栈实现中,在二三层都有RX方向的rx_stream,高层会调用低层stream的listen方法,注册包处理的回调函数,从dpdk_qp收到包,会将包从dpdk的rte_mbuf转化成packet格式,然后往上交给L2的stream,再往上交给L3的stream。包传递到二三层是通过stream/subscription的回调方式,送到四层直接调用l4→receive方法直接处理。目前Seastar的用户态协议栈已经支持了ARP、DHCP、ICMP、IP、TCP、UDP协议。四层主要是TCP/UDP层,我们重点分析TCP层。协议栈抽象了TCB对应内核协议栈的连接概念。每一个TCP连接对应一个TCB对象。当数据从三层传递给TCP层的时候,根据IP/PORT四元组可以找到对应的TCB,然后会将数据保存到TCB的接受队列里。上层的应用通过调用READ访问从TCB的接受队列读取数据,这里协议栈的实现为了zero-copy,单次读取是一个packet,我们优化成批量读取,单个连接的通信速度整体提升了一倍,从100MB/s提高到200MB/s.发送数据通过上层调用WRITE方法,首先将数据保存到对应TCB的unsent队列,在发送的时候会将数据先放到packetq的循环buffer,发送成功后然后再保存到发送的data队列里,等到对端ACK后才会将发送队列data的数据删除。3.4隔离模式和流规则DPDK的应用程序默认需要接管网卡,一旦程序运行,网卡的所有数据都通过DPDK处理,比如常见的SSH登录可能就会失败,还有一些其他公司的Agent探测和上报都会失效。对于这个问题我们通过DPDK提供的isolate模式来解决,也就是上文提到的设置隔离模式。isolate模式是指所有通过DPDK的数据默认还是走内核协议栈,然后通过配置一些流规则,可以将指定的流指向DPDK。4、性能数据单分片六核对比结果可见,CKV写性能比Tair高60%,读性能与Tair基本一致,value比较大时性能略低于Tair,是CKV未来优化方向。六分片六核在六分片六核场景下,基于DPDK的CKV相对内核协议栈CKV,整体提升明显,提升了100%左右。整机从测试结果看,CKV在引入DPDK后,通过DPDK代替了内核协议栈,优化了网络收发包的性能瓶颈,达到了单机千万的QPS。总结我们从性能方面对CKV做了一系列优化。引入DPDK,通过优化网络瓶颈,将整机性能提升到千万QPS。目前适配部分网卡,后续会适配更多类型的网卡。在压测的过程中,基于DPDK的原生协议栈在单个连接上收发包目前只能达到250MB/s的传输速度,对于CKV目前来说是可以应对绝大部分场景,但是还需要持续地优化提升单个连接上的传输性能。未来CKV会持续优化迭代,成本上会采用全类型数据下沉到SSD介质,进一步降低成本,性能上面会持续优化原生协议栈,持续挖掘性能。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 14:39 , Processed in 0.444432 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表