零拷贝技术浅浅析kafka实现百万级吞吐量基础

Posted 负债程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了零拷贝技术浅浅析kafka实现百万级吞吐量基础相关的知识,希望对你有一定的参考价值。

家人们,这期的知识点可能会让你掉几根头发,为了尽可能让你理解,进入正题前我们先来看个栗子

给女朋友送礼物

如果,我是说如果啊,你有一个女朋友(没有的不要灰心,认真看完文章可以找博主领取)

情人节来了,你想在网上给女朋友买个礼物,但是吧,网上现成的礼物直接送过去显得没诚意,为了避免被发好人卡,你想先买回来DIY一下再送

受疫情影响,快递员不能进小区,你也不能出小区,快递员先把礼物放到小区快递点,你去快递点拿,拿回家DIY完后再放回快递点,快递员再从快递点拿到礼物送到女朋友家

简单画了个流程图

看看这个流程,有太多不合理的地方,人的时间和精力是宝贵的,应该用来打游戏,而不是送 / 拿快递

时间过了大概好几年,物流公司研发了送快递机器人,从此以后快递小哥不用再上门送件,于是流程变成了

有了快递机器人,快递员解放了,但是你还需要去快递点拿礼物,DIY完了还得送回快递点,显然也不合理,有这时间我玩把中单亚索不香吗

于是你给礼物店老板提了个需求:能不能帮我DIY,然后直接寄到我女朋友家

老板觉得有道理,推出了新服务:需要DIY的用户,只需要在订单备注即可

于是流程变成了

你下单的时候,备注让店家帮你DIY,完事儿直接寄到女朋友家,女朋友收到快递感动到流了两斤眼泪

至此,你的两次出门也省了

回顾一下

从送礼物整个过程来看,最开始快递员需要跑路两次,你也需要跑路两次,到现在只需要机器人跑路两次

简单来说四次跑路变成了两次跑路,而这两次还都是机器人来完成,所以整个过程的人为跑路次数由4次变成了0次,这个过程我姑且称之 为零跑路


ok该来的始终还是来了,上面这个例子只是想引起你的兴趣,想要理解零拷贝,我们现在把术语代入

  • 你家 ==> 应用程序缓存
  • 快递点 ==> 操作系统内核缓存
  • durex旗舰店 ==> 磁盘
  • 女朋友家 ==> 目标磁盘或者socket

为什么要实现零拷贝

背景:文件操作属于危险操作,操作系统不可能直接把权限交给应用程序或者用户,只有内核才有权限
应用程序操作文件如复制、传输时,需要经历一下几个步骤:
从磁盘把文件复制到系统内核
从系统内核复制到应用程序
从应用程序复制到系统内核
从系统内核复制到磁盘、socket等消费端

简单画个流程图

说明:本文重点在于帮助大家理解零拷贝,关于操作系统内核方面的知识过于偏题,所以流程图画的很简单,如 “操作系统内核缓存” 其实还可细分为 “PageCache” 和 “Socket 缓冲区”,类似这些细节就不画了,而且画复杂了看着头疼
但请放心,虽然简单,但重点都没省略,丝毫不影响理解零拷贝


上面1、2、3、4几个步骤都是由cpu来完成,我们知道,cpu运算效率和磁盘不是一个量级

对服务器而言,cpu资源是很稀缺的,这四个复制操作一直占用cpu,显然会拉低整个系统性能

于是计算机界大佬们想了个办法,在主板上加了一块元件,叫 DMA(全称Direct Memory Access Controller,直接内存访问控制器),它是一块物理硬件,专门用于磁盘到操作系统内核缓存之间的数据复制

于是流程变成

红字解释:目前的网卡几乎都支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(可以用ethtool -k eth0 | grep scatter-gather命令查看是否支持),关于这点了解即可,不需要深入研究,这已经是嵌入式开发领域的知识点了,咱没必要花太多时间在上面

如果到现在有点懵,没关系,你只需要记住,有了DMA,cpu不再亲自参与整个数据复制或传输过程,只需要给DMA发送指令,告知它应该把哪个文件复制或传输到哪里即可,等DMA完成数据操作后通知cpu


再来说说DMA吧,DMA的实现依赖于操作系统和硬件:

操作系统方面,os提供了sendfile()函数

它接收四个参数:target端文件描述符、source端文件描述符、偏移量、数据长度

我们没有必要研究如何调用这个函数,因为Java已经帮我们做好了封装

你现在不需要管这个方法在哪个类中,我后面会写代码演示

kafka为什么能做到百万级吞吐量,靠的就是零拷贝模型,如果你仔细看过kafka源码,你会发现它最终调用了transferTo()方法

硬件方面,主板上增加了DMAC元件,其实不止主板,计算机发展到今天,几乎所有牵涉到io操作的地方都集成了DMAC

至此,我们可以做个小总结:所谓的零拷贝技术,其实可以理解成软件、硬件、语言的结合,目的在于减少cpu等待时间,提升数据传输效率


Talk is cheap,Show me the code

我们以复制文件为例,测试一下零拷贝模型带来的性能提升

新建一个测试文件

600MB

先来尝试下传统io复制(代码放在最后)

600MB花了800ms,这已经算是io中很快的了,如果用不带buffer的普通io,时间还得加3倍左右

再来看看零拷贝的nio

300ms左右,相对io提升了一倍多,看着不咋地,你想想高并发场景下,动不动上万次类似操作,这节约下来的时间足以让甲方激动到拍打轮椅

代码:

public class ZeroCopyTest 
    public static void main(String[] args) throws IOException, InterruptedException 
        long start = System.currentTimeMillis();
        File source = new File("D:/fileTest/copy.txt");
        File target = new File("D:/fileTest/copy2.txt");

        ioCopy(source, target);
//        ioCopyWithBuffer(source, target);
//        nioCopy(source, target);
        System.out.println(System.currentTimeMillis() - start);
        Thread.sleep(200);
        target.delete();
    

    public static void ioCopy(File source, File target) throws IOException 
        try (InputStream is = new FileInputStream(source);
             OutputStream os = new FileOutputStream(target)) 
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) > 0) 
                os.write(buffer, 0, length);
            
        
    

    public static void ioCopyWithBuffer(File source, File target) throws IOException 
        try (InputStream is = new BufferedInputStream(new FileInputStream(source));
             OutputStream os = new BufferedOutputStream(new FileOutputStream(target))) 
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) > 0) 
                os.write(buffer, 0, length);
            
        
    

    public static void nioCopy(File source, File target) throws IOException 
        try (FileChannel sourceChannel = new FileInputStream(source).getChannel();
             FileChannel targetChannel = new FileOutputStream(target).getChannel()) 
            for (long count = sourceChannel.size(); count > 0; ) 
                long transferred = sourceChannel.transferTo(sourceChannel.position(), count, targetChannel);
                sourceChannel.position(sourceChannel.position() + transferred);
                count -= transferred;
            
        
    

两根头发又无了,嘤嘤嘤~


前面说的领女朋友,其实,是骗你们的


ok我话说完

以上是关于零拷贝技术浅浅析kafka实现百万级吞吐量基础的主要内容,如果未能解决你的问题,请参考以下文章

百万级数据量Kafka发送

支持百万级TPS,Kafka是怎么做到的?答案藏在这10张图里

零基础快速掌握JavaScript数组深拷贝与浅拷贝

如何实现百万级的语音聊天室

浅析深拷贝与浅拷贝--godDie--

车联网平台百万级消息吞吐架构设计