1 前言
信任各位小伙伴之前或多或少接触过音讯行列,比较知名的包含Rocket MQ和Kafka,在京东内部运用的是自研的音讯中间件JMQ,从JMQ2晋级到JMQ4的也是带来了功用上的明显进步,而且JMQ4的底层也是参阅Kafka去做的规划。在这儿我会给咱们展示Kafka它的高功用是怎么规划的,咱们也能够学习相关办法论将其运用在实践项目中,也许下一个尖端项目就在各位的代码中产生了。
2 怎么了解高功用规划
2.1 高功用规划的”秘籍”
先抛开kafka,咱们先来议论一下高功用规划的实质,在这儿借用一下网上的一张总结高功用的思维导图:
从中能够看到,高功用规划的手法仍是十分多,从”微观规划”上的无锁化、序列化,到”宏观规划”上的缓存、存储等,能够说是五花八门,令人目不暇接。可是在我看来实质就两点:核算和IO。下面将从这两点来浅析一下我认为的高功用的”道”。
2.2 高功用规划的”道法”
2.2.1 核算上的”道”
核算上的优化手法无外乎两种办法:1.削减核算量 2.加速单位时刻的核算量
- 削减核算量:比方用索引来替代大局扫描、用同步替代异步、经过限流来削减恳求处理量、采用更高效的数据结构和算法等。(举例:mysql的BTree,redis的跳表等)
- 加速单位时刻的核算量:能够运用CPU多核的特性,比方用多线程替代单线程、用集群替代单机等。(举例:多线程编程、分治核算等)
2.2.2 IO上的”道”
IO上的优化手法也能够从两个方面来体现:1.削减IO次数或许IO数据量 2.加速IO速度
- 削减IO次数或许IO数据量:比方借助体系缓存或许外部缓存、经过零仿制技能削减 IO 仿制次数、批量读写、数据紧缩等。
- 加速IO速度:比方用磁盘次序写替代随机写、用 NIO 替代 BIO、用功用更好的 SSD 替代机械硬盘等。
3 kafka高功用规划
了解了高功用规划的手法和实质之后,咱们再来看看kafka里边运用到的功用优化办法。各类音讯中间件的实质都是一个生产者-顾客模型,生产者发送音讯给服务端进行暂存,顾客从服务端获取音讯进行消费。也便是说kafka分为三个部分:生产者-服务端-顾客,咱们能够依照这三个来别离概括一下其关于功用优化的手法,这些手法也会包含在咱们之前梳理的脑图里边。
3.1 生产者的高功用规划
3.1.1 批量发送音讯
之前在上面说过,高功用的”道”在于核算和IO上,咱们先来看看在IO上kafka是怎么做规划的。
IO上的优化
kafka是一个音讯中间件,数据的载体便是音讯,怎么将音讯高效的进行传递和耐久化是kafka高功用规划的一个重点。依据此分析kafka肯定是IO密集型运用,producer需求经过网络IO将音讯传递给broker,broker需求经过磁盘IO将音讯耐久化,consumer需求经过网络IO将音讯从broker上拉取消费。
- 网络IO上的优化:producer->broker发送音讯不是一条一条发送的,kafka形式会有个音讯发送推迟机制,会将一批音讯进行聚合,一口气打包发送给broker,这样就成功削减了IO的次数。除了传输音讯自身以外,还要传输十分多的网络协议自身的一些内容(称为Overhead),所以将多条音讯合并到一同传输,可有用削减网络传输的Overhead,然后进步了传输功率。
- 磁盘IO上的优化:咱们知道磁盘和内存的存储速度是不同的,在磁盘上操作的速度是远低于内存,可是在本钱上内存是高于磁盘。kafka是面向大数据量的音讯中间件,也便是说需求将大批量的数据耐久化,这些数据放在内存上也是不现实。那kafka是怎么在磁盘IO上进行优化的呢?在这儿我先直接给出办法,详细细节在后文中解说(它是借助于一种磁盘次序写的机制来进步写入速度)。
3.1.2 负载均衡
1.kafka负载均衡规划
Kafka有主题(Topic)概念,他是承载真实数据的逻辑容器,主题之下还分为若干个分区,Kafka音讯组织办法实践上是三级结构:主题-分区-音讯。主题下的每条音讯只会在某一个分区中,而不会在多个分区中被保存多份。
Kafka这样规划,运用分区的作用便是供给负载均衡的才能,对数据进行分区的首要目的便是为了完结体系的高伸缩性(Scalability)。不同的分区能够放在不同的节点的机器上,而数据的读写操作也都是针对分区这个粒度进行的,每个节点的机器都能独立地履行各自分区读写恳求。咱们还能够经过添加节点来进步全体体系的吞吐量。Kafka的分区规划,还能够完结业务级别的音讯次序的问题。
2.详细分区战略
- 所谓的分区战略是指决定生产者将音讯发送到那个分区的算法。Kafka供给了默许的分区战略是轮询,一同kafka也支撑用户自己制定。
- 轮询战略:也称为Round-robin战略,即次序分配。轮询的长处是有着优异的负载均衡的体现。
- 随机战略:尽管也是追求负载均衡,但总体体现差于轮询。
- 音讯键划分战略:还要一种是为每条音讯装备一个key,按音讯的key来存。Kafka答应为每条音讯指定一个key。一旦指定了key ,那么会对key进行hash核算,将相同的key存入相同的分区中,而且每个分区下的音讯都是有序的。key的作用很大,能够是一个有着明晰业务意义的字符串,也能够是用来表征音讯的元数据。
- 其他的分区战略:依据地理方位的分区。能够从一切分区中找出那些 Leader 副本在某个地理方位一切分区,然后随机选择一个进行音讯发送。
3.1.3 异步发送
1.线程模型
之前现已说了kafka是选择批量发送音讯来进步全体的IO功用,详细流程是kafka生产者运用批处理试图在内存中堆集数据,主线程将多条音讯经过一个ProduceRequest恳求批量发送出去,发送的音讯暂存在一个行列(RecordAccumulator)中,再由sender线程去获取一批数据或许不超过某个推迟时刻内的数据发送给broker进行耐久化。
长处:
- 能够进步kafka全体的吞吐量,削减网络IO的次数;
- 进步数据紧缩功率(一般紧缩算法都是数据量越大越能挨近预期的紧缩作用);
缺陷:
- 数据发送有必定推迟,可是这个推迟能够由业务要素来自行设置。
3.1.4 高效序列化
1.序列化的优势
Kafka 音讯中的 Key 和 Value,都支撑自界说类型,只需求供给相应的序列化和反序列化器即可。因而,用户能够依据实践状况选用快速且紧凑的序列化办法(比方 ProtoBuf、Avro)来削减实践的网络传输量以及磁盘存储量,进一步进步吞吐量。
2.内置的序列化器
- org.apache.kafka.common.serialization.StringSerializer;
- org.apache.kafka.common.serialization.LongSerializer;
- org.apache.kafka.common.serialization.IntegerSerializer;
- org.apache.kafka.common.serialization.ShortSerializer;
- org.apache.kafka.common.serialization.FloatSerializer;
- org.apache.kafka.common.serialization.DoubleSerializer;
- org.apache.kafka.common.serialization.BytesSerializer;
- org.apache.kafka.common.serialization.ByteBufferSerializer;
- org.apache.kafka.common.serialization.ByteArraySerializer;
3.1.5 音讯紧缩
1.紧缩的目的
紧缩秉承了用时刻换空间的经典trade-off思维,即用CPU的时刻去换取磁盘空间或网络I/O传输量,Kafka的紧缩算法也是出于这种目的。而且一般是:数据量越大,紧缩作用才会越好。
因为有了批量发送这个前期,然后使得 Kafka 的音讯紧缩机制能真实发挥出它的威力(紧缩的实质取决于多音讯的重复性)。比照紧缩单条音讯,一同对多条音讯进行紧缩,能大幅削减数据量,然后更大程度进步网络传输率。
2.紧缩的办法
想了解kafka音讯紧缩的规划,就需求先了解kafka音讯的格局:
- Kafka的音讯层次分为:音讯调集(message set)和音讯(message);一个音讯调集中包含若干条日志项(record item),而日志项才是真实封装音讯的当地。
- Kafka底层的音讯日志由一系列音讯调集-日志项组成。Kafka一般不会直接操作详细的一条条音讯,他总是在音讯调集这个层面上进行写入操作。
每条音讯都含有自己的元数据信息,kafka会将一批音讯相同的元数据信息给进步到外层的音讯调集里边,然后再对整个音讯调集来进行紧缩。批量音讯在耐久化到 Broker 中的磁盘时,依然保持的是紧缩状况,终究是在 Consumer 端做了解紧缩操作。
紧缩算法功率比照
Kafka 共支撑四种首要的紧缩类型:Gzip、Snappy、Lz4 和 Zstd,详细功率比照方下:
3.2 服务端的高功用规划
3.2.1 Reactor网络通信模型
kafka比较其他音讯中间件最出彩的当地在于他的高吞吐量,那么关于服务端来说每秒的恳求压力将会巨大,需求有一个优异的网络通信机制来处理海量的恳求。假如 IO 有所研讨的同学,应该清楚:Reactor 形式正是采用了很经典的 IO 多路复用技能,它能够复用一个线程去处理很多的 Socket 衔接,然后确保高功用。Netty 和 Redis 为什么能做到十万乃至百万并发?它们其实都采用了 Reactor 网络通信模型。
1.kafka网络通信层架构
从图中能够看出,SocketServer和KafkaRequestHandlerPool是其间最重要的两个组件:
- SocketServer:首要完结了 Reactor 形式,用于处理外部多个 Clients(这儿的 Clients 指的是广义的 Clients,或许包含 Producer、Consumer 或其他 Broker)的并发恳求,并担任将处理结果封装进 Response 中,返还给 Clients
- KafkaRequestHandlerPool:Reactor形式中的Worker线程池,里边界说了多个工作线程,用于处理实践的I/O恳求逻辑。
2.恳求流程
- Clients 或其他 Broker 经过 Selector 机制建议创建衔接恳求。(NIO的机制,运用epoll)
- Processor 线程接纳恳求,并将其转换成可处理的 Request 目标。
- Processor 线程将 Request 目标放入共享的RequestChannel的 Request 行列。
- KafkaRequestHandler 线程从 Request 行列中取出待处理恳求,并进行处理。
- KafkaRequestHandler 线程将 Response 放回到对应 Processor 线程的 Response 行列。
- Processor 线程发送 Response 给 Request 发送方。
3.2.2 Kafka的底层日志结构
根本结构的展示
Kafka是一个Pub-Sub的音讯体系,无论是发布仍是订阅,都须指定Topic。Topic只是一个逻辑的概念。每个Topic都包含一个或多个Partition,不同Partition可位于不同节点。一同Partition在物理上对应一个本地文件夹(也便是个日志目标Log),每个Partition包含一个或多个Segment,每个Segment包含一个数据文件和多个与之对应的索引文件。在逻辑上,能够把一个Partition当作一个十分长的数组,可经过这个“数组”的索引(offset)去拜访其数据。
2.Partition的并行处理才能
- 一方面,topic是由多个partion组成,Producer发送音讯到topic是有个负载均衡机制,根本上会将音讯平均分配到每个partion里边,一同consumer里边会有个consumer group的概念,也便是说它会以组为单位来消费一个topic内的音讯,一个consumer group内包含多个consumer,每个consumer消费topic内不同的partion,这样经过多partion进步了音讯的接纳和处理才能
- 另一方面,因为不同Partition可位于不同机器,因而能够充分运用集群优势,完结机器间的并行处理。而且Partition在物理上对应一个文件夹,即便多个Partition位于同一个节点,也可经过装备让同一节点上的不同Partition置于不同的disk drive上,然后完结磁盘间的并行处理,充分发挥多磁盘的优势。
3.过期音讯的铲除
- Kafka的整个规划中,Partition相当于一个十分长的数组,而Broker接纳到的一切音讯次序写入这个大数组中。一同Consumer经过Offset次序消费这些数据,而且不删去现已消费的数据,然后防止了随机写磁盘的进程。
- 因为磁盘有限,不或许保存一切数据,实践上作为音讯体系Kafka也没必要保存一切数据,需求删去旧的数据。而这个删去进程,并非经过运用“读-写”形式去修正文件,而是将Partition分为多个Segment,每个Segment对应一个物理文件,经过删去整个文件的办法去删去Partition内的数据。这种办法铲除旧数据的办法,也防止了对文件的随机写操作。
3.2.3 朴素高效的索引
1.稀少索引
能够从上面看到,一个segment包含一个.log后缀的文件和多个index后缀的文件。那么这些文件详细作用是干啥的呢?而且这些文件除了后缀不同文件名都是相同,为什么这么规划?
- .log文件:详细存储音讯的日志文件
- .index文件:位移索引文件,可依据音讯的位移值快速地从查询到音讯的物理文件方位
- .timeindex文件:时刻戳索引文件,可依据时刻戳查找到对应的位移信息
- .txnindex文件:已间断事物索引文件
除了.log是实践存储音讯的文件以外,其他的几个文件都是索引文件。索引自身规划的原来是一种空间换时刻的概念,在这儿kafka是为了加速查询所运用。kafka索引不会为每一条音讯树立索引联系,这个也很好了解,究竟对一条音讯树立索引的本钱仍是比较大的,所以它是一种稀少索引的概念,就好比咱们常见的跳表,都是一种稀少索引。
kafka日志的文件名一般都是该segment写入的第一条音讯的开始位移值baseOffset,比方000000000123.log,这儿边的123便是baseOffset,详细索引文件里边纪录的数据是相关于开始位移的相对位移值relativeOffset,baseOffset与relativeOffse的加和即为实践音讯的索引值。假定一个索引文件为:00000000000000000100.index,那么开始位移值即 100,当存储位移为 150 的音讯索引时,在索引文件中的相对位移则为 150 – 100 = 50,这么做的优点是运用 4 字节保存位移即可,能够节省十分多的磁盘空间。(ps:kafka真的是极致的紧缩了数据存储的空间)
2.优化的二分查找算法
kafka没有运用咱们熟知的跳表或许B+Tree结构来规划索引,而是运用了一种更为简略且高效的查找算法:二分查找。可是相关于传统的二分查找,kafka将其进行了部分优化,个人觉得规划的十分巧妙,在这儿我会进行胪陈。
在这之前,我先弥补一下kafka索引文件的构成:每个索引文件包含若干条索引项。不同索引文件的索引项的巨细不同,比方offsetIndex索引项巨细是8B,timeIndex索引项的巨细是12B。
这儿以offsetIndex为例子来胪陈kafka的二分查找算法:
1)一般二分查找
offsetIndex每个索引项巨细是8B,但操作体系拜访内存时的最小单元是页,一般是4KB,即4096B,会包含了512个索引项。而找出在索引中的指定偏移量,关于操作体系拜访内存时则变成了找出指定偏移量所在的页。假定索引的巨细有13个页,如下图所示:
因为Kafka读取音讯,一般都是读取最新的偏移量,所以要查询的页就集中在尾部,即第12号页上。依据二分查找,将顺次拜访6、9、11、12号页。
当跟着Kafka接纳音讯的添加,索引文件也会添加至第13号页,这时依据二分查找,将顺次拜访7、10、12、13号页。
能够看出拜访的页和上一次的页完全不同。之前在只要12号页的时分,Kafak读取索引时会频频拜访6、9、11、12号页,而因为Kafka运用了mmap来进步速度,即读写操作都将经过操作体系的page cache,所以6、9、11、12号页会被缓存到page cache中,防止磁盘加载。可是当增至13号页时,则需求拜访7、10、12、13号页,而因为7、10号页长时刻没有被拜访(现代操作体系都是运用LRU或其变体来管理page cache),很或许现已不在page cache中了,那么就会形成缺页中断(线程被堵塞等待从磁盘加载没有被缓存到page cache的数据)。在Kafka的官方测试中,这种状况会形成几毫秒至1秒的推迟。
2)kafka优化的二分查找
Kafka对二分查找进行了改善。已然一般读取数据集中在索引的尾部。那么将索引中最后的8192B(8KB)划分为“热区”(刚好缓存两页数据),其余部分划分为“冷区”,别离进行二分查找。这样做的优点是,在频频查询尾部的状况下,尾部的页根本都能在page cahce中,然后防止缺页中断。
下面咱们仍是用之前的例子来看下。因为每个页最多包含512个索引项,而最后的1024个索引项所在页会被认为是热区。那么当12号页未满时,则10、11、12会被判定是热区;而当12号页刚好满了的时分,则11、12被判定为热区;当增至13号页且未满时,11、12、13被判定为热区。假定咱们读取的是最新的音讯,则在热区中进行二分查找的状况如下:
当12号页未满时,顺次拜访11、12号页,当12号页满时,拜访页的状况相同。当13号页出现的时分,顺次拜访12、13号页,不会出现拜访长时刻未拜访的页,则能有用防止缺页中断。
3.mmap的运用
运用稀少索引,现已根本解决了高效查询的问题,可是这个进程中依然有进一步的优化空间,那便是经过 mmap(memory mapped files) 读写上面说到的稀少索引文件,进一步进步查询音讯的速度。
究竟怎么了解 mmap?前面说到,惯例的文件操作为了进步读写功用,运用了 Page Cache 机制,可是因为页缓存处在内核空间中,不能被用户进程直接寻址,所以读文件时还需求经过体系调用,将页缓存中的数据再次仿制到用户空间中。
1)惯例文件读写
- app拿着inode查找读取文件
- address_space中存储了inode和该文件对应页面缓存的映射联系
- 页面缓存缺失,引发缺页反常
- 经过inode找到磁盘地址,将文件信息读取并填充到页面缓存
- 页面缓存处于内核态,无法直接被app读取到,因而要先仿制到用户空间缓冲区,此处发生内核态和用户态的切换
tips:这一进程实践上发生了四次数据仿制。首要经过体系调用将文件数据读入到内核态Buffer(DMA仿制),然后运用程序将内存态Buffer数据读入到用户态Buffer(CPU仿制),接着用户程序经过Socket发送数据时将用户态Buffer数据仿制到内核态Buffer(CPU仿制),最后经过DMA仿制将数据仿制到NIC Buffer。一同,还伴跟着四次上下文切换。
2)mmap读写形式
- 调用内核函数mmap(),在页表(类比虚拟内存PTE)中树立了文件地址和虚拟地址空间中用户空间的映射联系
- 读操作引发缺页反常,经过inode找到磁盘地址,将文件内容仿制到用户空间,此处不触及内核态和用户态的切换
tips:采用 mmap 后,它将磁盘文件与进程虚拟地址做了映射,并不会招致体系调用,以及额定的内存 copy 开支,然后进步了文件读取功率。详细到 Kafka 的源码层面,便是依据 JDK nio 包下的 MappedByteBuffer 的 map 函数,将磁盘文件映射到内存中。只要索引文件的读写才用到了 mmap。
3.2.4 音讯存储-磁盘次序写
关于咱们常用的机械硬盘,其读取数据分3步:
- 寻道;
- 寻觅扇区;
- 读取数据;
前两个,即寻觅数据方位的进程为机械运动。咱们常说硬盘比内存慢,首要原因是这两个进程在拖后腿。不过,硬盘比内存慢是绝对的吗?其实不然,假如咱们能经过次序读写削减寻觅数据方位时读写磁头的移动间隔,硬盘的速度仍是相当可观的。一般来讲,IO速度层面,内存次序IO > 磁盘次序IO > 内存随机IO > 磁盘随机IO。这儿用一张网上的图来比照一下相关IO功用:
Kafka在次序IO上的规划分两方面看:
- LogSegment创建时,一口气恳求LogSegment最大size的磁盘空间,这样一个文件内部尽或许分布在一个连续的磁盘空间内;
- .log文件也好,.index和.timeindex也罢,在规划上都是只追加写入,不做更新操作,这样防止了随机IO的场景;
3.2.5 Page Cache的运用
为了优化读写功用,Kafka运用了操作体系自身的Page Cache,便是运用操作体系自身的内存而不是JVM空间内存。这样做的优点有:
- 防止Object耗费:假如是运用 Java 堆,Java目标的内存耗费比较大,一般是所存储数据的两倍乃至更多。
- 防止GC问题:跟着JVM中数据不断增多,垃圾收回将会变得复杂与缓慢,运用体系缓存就不会存在GC问题
比较于运用JVM或in-memory cache等数据结构,运用操作体系的Page Cache更加简略牢靠。
- 首要,操作体系层面的缓存运用率会更高,因为存储的都是紧凑的字节结构而不是独立的目标。
- 其次,操作体系自身也关于Page Cache做了很多优化,供给了 write-behind、read-ahead以及flush等多种机制。
- 再者,即便服务进程重启,JVM内的Cache会失效,Page Cache依然可用,防止了in-process cache重建缓存的进程。
经过操作体系的Page Cache,Kafka的读写操作根本上是依据内存的,读写速度得到了极大的进步。
3.3 消费端的高功用规划
3.3.1 批量消费
生产者是批量发送音讯,音讯者也是批量拉取音讯的,每次拉取一个音讯batch,然后大大削减了网络传输的 overhead。在这儿kafka是经过fetch.min.bytes参数来操控每次拉取的数据巨细。默许是 1 字节,表明只要 Kafka Broker 端积攒了 1 字节的数据,就能够回来给 Consumer 端,这实在是太小了。咱们仍是让 Broker 端一次性多回来点数据吧。
而且,在生产者高功用规划目录里边也说过,生产者其实在 Client 端对批量音讯进行了紧缩,这批音讯耐久化到 Broker 时,依然保持的是紧缩状况,终究在 Consumer 端再做解紧缩操作。
3.3.2 零仿制-磁盘音讯文件的读取
1.zero-copy界说
零仿制并不是不需求仿制,而是削减不必要的仿制次数。一般是说在IO读写进程中。
零仿制字面上的意思包含两个,“零”和“仿制”:
- “仿制”:便是指数据从一个存储区域转移到另一个存储区域。
- “零” :表明次数为0,它表明仿制数据的次数为0。
实践上,零仿制是有广义和狭义之分,目前咱们一般听到的零仿制,包含上面这个界说削减不必要的仿制次数都是广义上的零仿制。其实了解到这点就足够了。
咱们知道,削减不必要的仿制次数,便是为了进步功率。那零仿制之前,是怎样的呢?
2.传统IO的流程
做服务端开发的小伙伴,文件下载功用应该完结过不少了吧。假如你完结的是一个web程序 ,前端恳求过来,服务端的使命便是:将服务端主机磁盘中的文件从已衔接的socket发出去。关键完结代码如下:
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
write(sockfd, buf , n);
传统的IO流程,包含read和write的进程。
- read:把数据从磁盘读取到内核缓冲区,再仿制到用户缓冲区
- write:先把数据写入到socket缓冲区,最后写入网卡设备
流程图如下:
- 用户运用进程调用read函数,向操作体系建议IO调用,上下文从用户态转为内核态(切换1)
- DMA操控器把数据从磁盘中,读取到内核缓冲区。
- CPU把内核缓冲区数据,仿制到用户运用缓冲区,上下文从内核态转为用户态(切换2) ,read函数回来
- 用户运用进程经过write函数,建议IO调用,上下文从用户态转为内核态(切换3)
- CPU将用户缓冲区中的数据,仿制到socket缓冲区
- DMA操控器把数据从socket缓冲区,仿制到网卡设备,上下文从内核态切换回用户态(切换4) ,write函数回来
从流程图能够看出,传统IO的读写流程 ,包含了4次上下文切换(4次用户态和内核态的切换),4次数据仿制(两次CPU仿制以及两次的DMA仿制 ),什么是DMA仿制呢?咱们一同来回顾下,零仿制触及的操作体系知识点。
3.零仿制相关知识点
1)内核空间和用户空间
操作体系为每个进程都分配了内存空间,一部分是用户空间,一部分是内核空间。内核空间是操作体系内核拜访的区域,是受维护的内存空间,而用户空间是用户运用程序拜访的内存区域。 以32位操作体系为例,它会为每一个进程都分配了4G (2的32次方)的内存空间。
- 内核空间:首要供给进程调度、内存分配、衔接硬件资源等功用
- 用户空间:供给给各个程序进程的空间,它不具有拜访内核空间资源的权限,假如运用程序需求运用到内核空间的资源,则需求经过体系调用来完结。进程从用户空间切换到内核空间,完结相关操作后,再从内核空间切换回用户空间。
2)用户态&内核态
- 假如进程运转于内核空间,被称为进程的内核态
- 假如进程运转于用户空间,被称为进程的用户态。
3)上下文切换
cpu上下文
CPU 寄存器,是CPU内置的容量小、但速度极快的内存。而程序计数器,则是用来存储 CPU 正在履行的指令方位、或许行将履行的下一条指令方位。它们都是 CPU 在运转任何使命前,必须的依赖环境,因而叫做CPU上下文。
cpu上下文切换
它是指,先把前一个使命的CPU上下文(也便是CPU寄存器和程序计数器)保存起来,然后加载新使命的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新方位,运转新使命。
一般咱们说的上下文切换 ,便是指内核(操作体系的中心)在CPU上对进程或许线程进行切换。进程从用户态到内核态的转变,需求经过体系调用 来完结。体系调用的进程,会发生CPU上下文的切换 。
4)DMA技能
DMA,英文全称是Direct Memory Access ,即直接内存拜访。DMA 实质上是一块主板上独立的芯片,答应外设设备和内存存储器之间直接进行IO数据传输,其进程不需求CPU的参加 。
咱们一同来看下IO流程,DMA帮助做了什么工作。
能够发现,DMA做的工作很明晰啦,它首要便是帮助CPU转发一下IO恳求,以及仿制数据 。
之所以需求DMA,首要便是功率,它帮助CPU做工作,这时分,CPU就能够闲下来去做别的工作,进步了CPU的运用功率。
4.kafka消费的zero-copy
1)完结原理
零仿制并不是没有仿制数据,而是削减用户态/内核态的切换次数以及CPU仿制的次数。零仿制完结有多种办法,别离是
- mmap+write
- sendfile
在服务端那里,咱们现已知道了kafka索引文件运用的mmap来进行零仿制优化的,现在告诉你kafka顾客在读取音讯的时分运用的是sendfile来进行零仿制优化。
linux 2.4版本之后,对sendfile做了优化晋级,引入SG-DMA技能,其实便是对DMA仿制加入了scatter/gather操作,它能够直接从内核空间缓冲区中将数据读取到网卡。运用这个特点搞零仿制,即还能够多省去一次CPU仿制 。
sendfile+DMA scatter/gather完结的零仿制流程如下:
- 用户进程建议sendfile体系调用,上下文(切换1)从用户态转向内核态。
- DMA操控器,把数据从硬盘中仿制到内核缓冲区。
- CPU把内核缓冲区中的文件描述符信息 (包含内核缓冲区的内存地址和偏移量)发送到socket缓冲区
- DMA操控器依据文件描述符信息,直接把数据从内核缓冲区仿制到网卡
- 上下文(切换2)从内核态切换回用户态 ,sendfile调用回来。
能够发现,sendfile+DMA scatter/gather完结的零仿制,I/O发生了2 次用户空间与内核空间的上下文切换,以及2次数据仿制。其间2次数据仿制都是包DMA仿制 。这便是真实的 零仿制(Zero-copy) 技能,全程都没有经过CPU来搬运数据,一切的数据都是经过DMA来进行传输的。
2)底层完结
Kafka数据传输经过 TransportLayer 来完结,其子类 PlaintextTransportLayer 经过Java NIO 的 FileChannel 的 transferTo 和 transferFrom 办法完结零仿制。底层便是sendfile。顾客从broker读取数据,便是由此完结。
@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
return fileChannel.transferTo(position, count, socketChannel);
}
tips: transferTo 和 transferFrom 并不确保必定能运用零仿制。实践上是否能运用零仿制与操作体系相关,假如操作体系供给 sendfile 这样的零仿制体系调用,则这两个办法会经过这样的体系调用充分运用零仿制的优势,不然并不能经过这两个办法自身完结零仿制。
4 总结
文章第一部分为咱们解说了高功用常见的优化手法,从”秘籍”和”道法”两个方面来诠释高功用规划之路该怎么走,并引申出核算和IO两个优化方向。
文章第二部分是kafka内部高功用的详细规划——别离从生产者、服务端、顾客来进行全方位解说,包含其规划、运用及相关原理。
期望经过这篇文章,能够使咱们不仅学习到相关办法论,也能明白其办法论详细的落当地案,一同学习,一同成长。
作者:京东物流 李鹏
来历:京东云开发者社区