唯品会高性能负载均衡VGW揭秘

负载均衡基础简介

负载均衡是指对后端服务进行流量分发的服务。通过负载均衡入口,后端服务可以水平扩展来提升对外服务能力,可以消除单点故障以提升应用系统可用性。我们以Web Server举例,如图1所示,Web Server可以在负载均衡后面透明伸缩,当其中一部分Web Server实例挂掉后,剩余实例仍然能够提供服务。

图 1:典型负载均衡使用示例

负载均衡又分为四层和七层负载均衡,所谓四层和七层就是指对后端服务进行负载均衡时,依据传输层还是应用层的信息来进行流量转发。这两面包含两个问题,一是我们怎么来区分负载均衡转发流量,二是我们对转发流量进行怎样的负载均衡。

当进行四层负载均衡时,依据的是网络发布的IP加上传输层的端口号来区分转发流量,而对转发流量选择怎样的负载均衡时,可能选择用户IP,用户端口,协议号,目标ip和目标端口的5元组Hash的方式,也可能选择普通的Round robin轮转,这个依据使用场景而定,典型四层负载均衡产品如LVS,F5。而七层负载均衡则依据应用层信息区分转发流量,典型如域名信息来区分转发流量,通过URL Hash等方式进行后端服务的负载均衡,典型产品如Nginx,Haproxy,简单对比如图2所示。

图 2:四层与七层负载均衡的差别

 

显然七层负载均衡相比四层负载均衡对转发效率要求更高,且需要支持多样化的应用层协议,而不仅仅是http协议等,而四层负载均衡虽然支持TCP/UDP至上的任何协议,但是无法像七层那样对应用进行定制化转发,比如根据URL Hash等。

总结来说业界通常综合二者优点来进行使用,很多互联网公司的接入架构是采用LVS+Nginx之类的四层+七层方案。介绍完本基础,我们接下来阐述唯品会高性能四层负载均衡VGW的产生背景及高性能秘密,希望能对大家有所借鉴。

 

VGW产生背景

传统的四层负载均衡接入网关大多采用LVS,但是LVS基于内核态转发,存在内核态和用户态切换开销,大流量下的中断开销重等问题。对于四层的接入网关,业界大部分采用Kernel Bypass的技术来提升性能,避开Linux内核复杂和冗长的TCP/IP协议栈处理开销,如Google的Maglev,还有国内大型互联网公司的四层负载均衡接入网关。当然也可通过netmap,pfring方式来提升网络收发数据包性能,目前国内几乎所有大厂都基于DPDK来实现,并在线上充分验证了其转发性能及稳定性,为了避免选型调研成本,我们也基于DPDK实现了唯品会高性能四层接入网关VGW,在不增加硬件成本的基础下,相同普通服务器的转发性能是普通LVS的4倍,在双11大促中平稳承载部分搜索,图片等核心域流量,减轻基础架构设施方面的成本劣势。

 

VGW高性能法宝

CPU多核的亲和性

一般CPU有多层Cache,每级Cache访问时间相差约10倍,如L1的时间为0.5ns,L2的访问时间为7ns,容量也随之增大。因此如果一个进程或线程可能被调度到不同CPU核上,可能会导致CPU上的多级Cache Miss,一次CPU的Cache Miss导致的访问内存开销大约是L1 Cache访问开销的几百倍。因此我们在VGW中将各个线程都固定跑在各自的核上,避免调度到不同核导致的CPU Cache Miss。另外我们也将VGW工作线程跑的核单独隔离,避免被操作系统调度的任务挤占Cache。

我们的核分配结构如图3所示:

 

图 3:VGW多核分配结构

 

大页内存

相对于传统虚拟内存,大页内存不受虚拟内存影响,不会被替换出内存。另外Linux默认页大小为4KB,而大页内存可以支持2M或1G页大小,相同内存容量下能够减少页表条目数,使得TLB的MISS数大大降低。以8G内存为例,如果采用4K页大小,则需要2M的页表项来存虚拟内存到物理页面映射关系,而以大页内存1G的话,只需要8条页表项,因此能够使得TLB冲突开销大大降低。

VGW中采用2M页大小,划分8G内存独占,这个在环境初始化脚本中设置,由于机器是NUMA结构,故需要给亲和的NUMA Node建立大页。

1.echo Pages> /sys/devices/system/node/node-num hugepages/hugepages-Pagesize/nr_hugepages

2.mkdir –p /mnt/huge

3.mount -t hugetlbfs nodev /mnt/huge

num表示numa号,Pages和Pagesize分别表示页数和页大小。

 

无锁化

典型的互斥锁的时间开销是100 ns级,而对于VGW使用的DPDK来说开销过于昂贵,DPDK为此提供了FIFO固定大小的环形无锁队列。它通过name为标志区分,支持单写单读,多写单读,单写多读,多写多读。本质上来说该无锁队列底层实现是通过CAS等CPU支持的原子操作指令和忙等待来实现。

在VGW中无锁队列主要有两个用途,一个是用于内存池中的内存分配,另外一个是用于接收线程和发送线程,接收线程和KNI线程的通信中,控制线程和转发线程中控制信息交互。具体可参考图3。

另外VGW中也将读多写少情况进行RCU化,避免采用读写锁,RCU机制通过少量的内存开销来换取无锁化, IP黑名单是典型的读多写少情形,激活该模块后,数据包都会去读取黑名单来判断自身的源IP是否匹配,只有少数情况下才会更新这个黑名单,因此我们在IP黑名单模块的实现中应用了RCU,极大简化了该功能模块的实现,避免通过无锁队列出现的更新通知冗余及维护成本。 

 

用户态轮询驱动

传统Linux系统以中断的方式通知CPU来收发数据包。在每秒到来的数据包量小的时候没有问题,一旦大量数据包蜂拥,中断就占了大量的开销,挤占了处理数据包的时间。为避免中断开销,VGW使用DPDK的PMD轮询驱动直接在用户态获取数据包,简单来说就是通过轮询的方式收发包,避免大流量下的中断开销,同时用户态驱动避免包拷贝, 如图4所示。

                              图4:轮询驱动                               

 

其他优化措施

多核访问数据per-lcore化及Cache line对齐

在VGW程序中尽量不在多核间分配全局变量,而是在各个lcore上分配,这样避免读写时加锁冲突,同时也使得各个lcore的访问都是它最近的内存。例如VGW的统计信息,arp表,连接表等结构都是per-lcore化。

 

路由查找优化

VGW转发数据包需要知道该数据包的下一跳IP,相当于需要维护路由信息,因此怎样快速的根据目的地址来查找到下一跳地址也是关键问题。最开始我们直接通过Linux ioctl API获取系统路由,但在数据转发的逻辑中使用系统调用不是一个好方法,系统调用的开销很可能影响极限转发性能,后面通过利用dpdk的LPM(Longest Prefix Match)模块来加快每个数据包的路由信息查找,LPM维护了前缀24位和后8位的两张表,前缀24位表匹配概率较大,采取预先分配,大部分情况下只需要一次访问就能匹配到,而后缀8位表则访问概率较小,采取按需分配,需要两次访问命中,从而达到时间和空间的折中。

 

合适的批量值及预取

在我们VGW的收包中,一次从网卡批量收包的数目也需要权衡优化,太低会导致网卡硬件缓存区满,因为存在多次获取的开销,太高则可能导致处理延迟增加,也使得网卡缓存区满,我们通过测试调优选取了一个比较合理的值。

预取能够使得CPU将待处理的数据提前从内存加载到CPU Cache中,除了底层驱动使用预取来加速收发包外,VGW应用程序也在收到每个包后,将包体内容预取到cache中,加速后续包处理的过程。如rte_prefetch0(rte_pktmbuf_mtod(m, void *));

分支预测

CPU通过流水线重叠连续指令增加处理吞吐量,要充分利用流水线,必须知道执行指令的序列和先后顺序,而CPU在执行分支判断语句时,无法知道后续该执行哪个分支,故无法将后续指令填充进入流水线。这个时候就可以通过分支预测显示告诉CPU大概率执行的分支,加速流水线指令处理。

比如VGW的收包判断逻辑中,对于收到的每个数据包,都有两种流向,一种是被转发到后端真实服务器,另外一种是转化成skb_buff结构交给VGW的本机操作系统的协议栈处理,而正常情况下大部分的流量应该是转发流,故我们在收包线程的判断逻辑中加入likely函数来指示CPU最可能执行的分支为转发逻辑。

if(likely(packet to real server)) {

process(packet);

}

NUMA亲和

由于我们的服务器是NUMA结构,NUMA亲和包括内存与CPU,设备与CPU两个方面,VGW在分配内存时,通过rte_pktmbuf_pool_create函数的socket_id参数来分配离指定CPU所在NUMA的内存。VGW在初始化网卡收发队列时,通过函数rte_eth_rx_queue_setup的socket_id参数来控制传入的numa id,保证网卡和对应numa上的CPU亲和性。

VGW保证最开始只关注了内存和CPU的numa亲和,忽略了网卡设备与cpu numa的亲和,性能测试没达到预期后,才开始关注修复,目前接收和发送线程的数据包使用的内存和网卡设备都处于同一numa node,保证了性能最优化。

 

总结

本文简要总结了负载均衡基础及VGW高性能的原因,VGW建立于Dpdk之上,充分了利用Dpdk的一些软件优化思想,结合自身业务逻辑进行了性能优化,使得在普通低成本的服务器上也有与专用硬件媲美的转发能力,性能优化是个系统工程,需要CPU,内存,网卡,软件代码实现等各个方面的协同,本文阐述的诸多性能优化思想不仅仅是对VGW这样对性能有着极致要求的四层负载均衡接入网关有用,相信也会对普通的应用程序也是值得借鉴和思考,后续我们也会发一些VGW具体实现细节,运营监控及高可用的设计的文章,欢迎大家继续关注。

<think>好的,用户希望我优化他们提供的ABAP代码,并添加注释。首先,我需要仔细理解这段代码的结构和目的。 这段代码是一个复杂的SELECT语句,涉及多个表的连接,包括AFKO、AFVC、CRHD、AFPO、MAPL、PLPO、AFRU和MAKT。看起来像是在处理生产订单相关的数据,可能用于报表或数据分析。用户提到性能优化,所以我要重点关注如何提升查询效率,尤其是在SAP HANA环境下。 首先,我需要检查表连接和条件。注意到有几个LEFT JOIN,这可能影响性能,尤其是如果连接条件没有正确利用索引的话。比如,MAPL表的连接条件包括MATNR和WERKS,需要确认这些字段是否有合适的索引。另外,AFRU表有多个条件,包括STOKZ和STZHL,可能需要检查这些字段的选择性,以及是否可以使用更有效的过滤条件。 接下来,GROUP BY子句中的字段较多,这可能导致大量的数据聚合,影响性能。需要确认是否所有分组字段都是必要的,或者是否可以通过提前过滤减少数据集的大小。此外,SUM函数的使用是否正确,是否有重复计算的可能。 注释方面,用户希望代码更易理解,所以每个JOIN和关键字段都需要解释其作用。比如,CRHD表可能关联工作中心,MAKT表获取物料描述,这些都需要在注释中说明。 在性能优化方面,SAP HANA对Open SQL的优化建议包括使用字段列表代替SELECT *,确保WHERE条件使用索引,避免不必要的嵌套查询。例如,将LEFT JOIN替换为INNER JOIN,如果可能的话,因为LEFT JOIN可能增加不必要的开销。同时,检查ON子句中的条件是否有效利用索引,比如E~WERKS和MAPL表的索引情况。 另外,注意到代码中使用了@SY-LANGU,这是正确的,但需要确保MAKT表的SPRAS字段有索引。AFRU表的STOKZ和STZHL条件可能需要重新审视,是否有更有效的写法,或者是否可以使用数据库的统计信息来优化。 还要考虑代码的可维护性,比如使用别名是否清晰,字段名是否明确。例如,D代表AFPO,C代表CRHD,这样的别名是否容易理解,或者是否需要更详细的注释。 最后,需要检查GROUP BY的字段是否和SELECT中的非聚合字段匹配,避免字段遗漏导致的错误。例如,F~VGW01等字段是否正确包含在GROUP BY中,确保查询的合法性。 总结下来,优化步骤可能包括:调整JOIN顺序,使用INNER JOIN代替不必要的LEFT JOIN,确保所有连接字段有索引,添加必要的注释,简化GROUP BY字段,使用CDS视图代替复杂查询等。同时,需要根据SAP HANA的最佳实践,比如使用ABAP Managed Database Procedures(AMDP)来处理复杂逻辑,或者利用HANA的计算视图提升性能。 在注释方面,每个JOIN的部分需要说明连接的目的,比如“连接工作中心信息”或“获取工艺路线数据”。此外,关键计算字段如VGW01*PGMNG需要解释其业务含义,比如“计算计划工时”等。 可能还需要建议用户使用ABAP Test Cockpit进行静态检查,确保代码符合HANA优化标准。另外,考虑分页处理或使用内表缓存,减少一次性处理的数据量,尤其是在处理大量数据时。 总之,优化需要从查询结构、索引使用、代码清晰度和HANA特性等多个方面入手,确保代码高效、可维护且符合最佳实践。</think>```abap "► 生产订单数据分析SQL优化版(S/4 HANA优化建议)◄ SELECT "➊ 核心业务字段(确保字段列表最小化)" D~PROJN, "项目编号 D~AUFNR, "订单号 D~MATNR, "物料号 D~PGMNG, "订单数量 C~OBJID, "工作中心对象ID C~ARBPL, "工作中心 "➋ 计划工时计算(VGWxx为工时系数)" F~VGW01 * D~PGMNG AS VGW01, "工序计划工时1 F~VGW02 * D~PGMNG AS VGW02, "工序计划工时2 F~VGW03 * D~PGMNG AS VGW03, "工序计划工时3 "➌ 实际工时聚合(使用SUM需注意GROUP BY完整性)" SUM( G~ISM01 ) AS ISM01, "实际工时1 SUM( G~ISM02 ) AS ISM02, "实际工时2 SUM( G~ISM03 ) AS ISM03, "实际工时3 MAKT~MAKTX, "物料描述 "➍ 差异计算(计划-实际)" ( F~VGW01 * D~PGMNG - SUM( G~ISM01 ) ) AS CY1, "工时差异1 ( F~VGW02 * D~PGMNG - SUM( G~ISM02 ) ) AS CY2, "工时差异2 ( F~VGW03 * D~PGMNG - SUM( G~ISM03 ) ) AS CY3 "工时差异3 "■ 表连接优化(按数据量从小到大排列)" FROM afpo AS D "生产订单项目(驱动表)" INNER JOIN afko AS A ON A~aufnr = D~aufnr "订单表头(必选数据)" INNER JOIN afvc AS B ON A~aufpl = B~aufpl "订单工序 INNER JOIN crhd AS C ON B~arbid = C~objid "工作中心主数据 "◆ LEFT JOIN优化策略(确认是否真需要左连接)" LEFT JOIN mapl AS E ON D~matnr = E~matnr "物料-任务清单分配(CN11工厂)" AND E~werks = 'CN11' "➏ 固定工厂条件放入ON子句 LEFT JOIN plpo AS F ON E~plnnr = F~plnnr "工艺路线工序 AND C~objid = F~arbid "工作中心关联 LEFT JOIN afru AS G ON D~aufnr = G~aufnr "订单确认数据 AND C~objid = G~arbid "工作中心匹配 AND G~stokz <> 'X' "排除删除标记 AND G~stzhl <> '1' "排除特定计数器 LEFT JOIN makt ON makt~matnr = D~matnr "物料描述(语言依赖) AND makt~spras = @sy-langu "当前语言 "■ 过滤条件优化(使用索引字段优先)" WHERE D~aufnr IN @s_aufnr "订单号范围(确保afpo~aufnr有索引) AND D~projn IN @s_projn "项目号范围(检查projn索引情况) "⚠️ GROUP BY优化要点(字段顺序影响HANA计算效率)" GROUP BY D~projn, D~aufnr, D~matnr, D~pgmng, "订单维度(粒度最细)" C~objid, C~arbpl, "工作中心维度 F~vgw01, F~vgw02, F~vgw03, "计划工时系数 makt~maktx "物料描述(需确认是否必要分组) INTO CORRESPONDING FIELDS OF TABLE @gt_out. "✎ 优化实施建议:" "1. 索引检查:确认afpo~aufnr、afpo~projn、mapl~matnr+werks的索引" "2. 数据量评估:若结果集>10万行,建议分页处理或使用CDS视图" "3. HANA性能分析:使用ST05跟踪SQL执行计划" "4. 替代方案:考虑使用ABAP CDS视图替代复杂Open SQL" "5. 聚合优化:评估是否可在应用层进行SUM计算" "6. 字段精简:检查MAKTX是否必须实时获取,可考虑缓存机制" "7. JOIN顺序:根据实际数据分布调整驱动表(可使用HANA统计信息)" ``` 主要优化点说明: 1. **执行计划优化** - 将数据量较小的`AFPO`作为驱动表(需根据实际数据分布调整) - 使用`INNER JOIN`优先过滤无效数据 2. **索引利用** - 所有WHERE条件字段(`AUFNR`,`PROJN`)需确保存在数据库索引 - JOIN条件字段(如`AFVC~AUFPL`, `CRHD~OBJID`)检查索引 3. **HANA特性优化** ```abap AND E~werks = 'CN11' -- 将固定值条件从WHERE移到ON子句 ``` - 利用HANA的列存储特性,减少数据扫描范围 4. **资源消耗控制** - 避免在数据库层进行复杂计算(如差异计算可移至应用层) - 使用`GROUP BY`时按字段基数从高到低排序 5. **代码可维护性** - 为每个计算字段添加业务含义注释 - 使用表别名(A,B,C)时保持关联性说明 建议后续改进方向: - 使用`ABAP CDS视图`替换复杂Open SQL - 对`AFRU`表的访问添加`MANDT`条件(多client系统) - 考虑使用`SAP HANA计算视图`处理工时差异分析 - 对结果集实现`分页加载`机制(使用UP TO ... OFFSET)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值