中间件

作者: Impossible安徒生 | 来源:发表于2020-04-09 17:23 被阅读0次

    中间件

    1. Kafka

      Kafka 是一个分布式的,支持多分区、多副本,基于 Zookeeper 的分布式消息流平台,它同时也是一款开源的基于发布订阅模式的消息引擎系统。

      <img src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/yHymVyagL06usUpU1T0CccGKxr5HiaRzRnmUuDAyvcnuRiaHFicWRklHcVtFyZsVP92AIFuthTOWhJGOYRPhB1JrQ/640?wx_fmt=png" alt="img" style="zoom:50%;" /><img src="https://user-gold-cdn.xitu.io/2019/11/28/16eb068cd8d08784?imageslim" alt="img" style="zoom:25%;" />

      特点

      1. 消息持久化: 为了从大数据中获取有价值的信息,任何信息的丢失都是负担不起的。Kafka使用了O(1)的磁盘结构设计,这样做即便是在要存储大体积的数据时也是可以提供稳定的性能。使用Kafka时,message会被存储并且会被复制以防止数据丢失。
      2. 高吞吐量: 设计是工作在普通的硬件设施上多个客户端能够每秒处理几百兆的数据量。
      3. 分布式: Kafka Broker的中心化集群支持消息分区,而consumer采用分布式进行消费。
      4. 多种Client支持: Kafka很容易与其它平台进行支持,例如:Java、.NET、PHP、Ruby、Python。
      5. 实时: 消息由producer产生后立即对consumer可见。这个特性对于基于事件的系统是很关键的。

      Kafka各组件说明

      Topic:消息类别名,一个topic中通常放置一类消息。每个topic都有一个或者多个订阅者,也就是消息的消费者consumer。Producer将消息推送到topic,由订阅该topic的consumer从topic中拉取消息。

      Broker:每个kafka server称为一个Broker,多个borker组成kafka cluster。一个机器上可以部署一个或者多个Broker,这多个Broker连接到相同的ZooKeeper就组成了Kafka集群。

      <img src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/yHymVyagL06usUpU1T0CccGKxr5HiaRzRSrZ91ZJlMP1AJNUWQNgnGWZznwEkgaUpK0hHHibmXJD7ELKkXpNrwhA/640?wx_fmt=png" alt="640?wx_fmt=png" style="zoom:40%;" /><img src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/yHymVyagL06usUpU1T0CccGKxr5HiaRzRpjCibIcFfSNG4Thv1sgZ3Fo9NINn02HZGI8nQiajlTngMAtA8h4qukgw/640?wx_fmt=png" alt="img" style="zoom:50%;" />

      一个Broker上可以创建一个或者多个Topic。同一个topic可以在同一集群下的多个Broker中分布。

      <img src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/yHymVyagL06usUpU1T0CccGKxr5HiaRzRAyMpY3YuCicH5UtbZxMJzfpWJvichKZibHITYCkL8jV1pzYH1d76MfVYg/640?wx_fmt=png" alt="img" style="zoom:45%;" /><img src="https://ss.csdn.net/p?https://mmbiz.qpic.cn/mmbiz_png/yHymVyagL06usUpU1T0CccGKxr5HiaRzRKkfKmVgG8ArZPlPrG76x2XsTNUOFdXPqY0kuLPeZfQicue1t1MXR7xA/640?wx_fmt=png" alt="img" style="zoom:35%;" />

      partition

      Kafka会为每个topic维护了多个分区(partition),每个分区会映射到一个逻辑的日志(log)文件:

      每当一个message被发布到一个topic上的一个partition,broker应会将该message追加到这个逻辑log文件的最后一个segment上。这些segments 会被flush到磁盘上。Flush时可以按照时间来进行,也可以按照message 数来执行。

      每个partition都是一个有序的、不可变的结构化的提交日志记录的序列。在每个partition中每一条日志记录都会被分配一个序号——通常称为offset,offset在partition内是唯一的。论点逻辑文件会被化分为多个文件segment(每个segment的大小一样的)。

      Broker集群将会保留所有已发布的message records,不管这些消息是否已被消费。保留时间依赖于一个可配的保留周期。例如:如果设置了保留策略是2day,那么每一条消息发布两天内是被保留的,在这个2day的保留时间内,消息是可以被消费的。过期后不再保留。

      日志分区是分布式的存在于一个kafka集群的多个broker上。每个partition会被复制多份存在于不同的broker上。这样做是为了容灾。具体会复制几份,会复制到哪些broker上,都是可以配置的。经过相关的复制策略后,每个topic在每个broker上会驻留一到多个partition。

      Producer:Producer作为消息的生产者,在生产完消息后需要将消息投送到指定的目的地(某个topic的某个partition)。Producer可以根据指定选择partition的算法或者是随机方式来选择发布消息到哪个partition。

      Consumer:在Kafka中,同样有consumer group的概念,它是逻辑上将一些consumer分组。因为每个kafka consumer是一个进程。所以一个consumer group中的consumers将可能是由分布在不同机器上的不同的进程组成的。Topic中的每一条消息可以被多个consumer group消费,然而每个consumer group内只能有一个consumer来消费该消息。所以,如果想要一条消息被多个consumer消费,那么这些consumer就必须是在不同的consumer group中。所以也可以理解为consumer group才是topic在逻辑上的订阅者。

      每个consumer可以订阅多个topic。

      每个consumer会保留它读取到某个partition的offset。而consumer 是通过zookeeper来保留offset的。

    2. kafka 的消息是有序的吗(单个partition 有序,多个无序)

    3. producer发消息到队列时,通过加锁保证有序
      broker leader在给producer发送ack时,因网络原因超时,那么Producer 将重试,造成消息重复。
      先后两条消息发送。t1时刻msg1发送失败,msg2发送成功,t2时刻msg1重试后发送成功。造成乱序。

    2.解决重试机制引起的消息乱序

    为实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。对于每个PID,该Producer发送消息的每个<Topic, Partition>都对应一个单调递增的Sequence Number。同样,Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每Commit一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比Broker维护的序号)大一,则Broker会接受它,否则将其丢弃:

    • 如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息,Producer抛出InvalidSequenceNumber
    • 如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息,Producer抛出DuplicateSequenceNumber
    • Sender发送失败后会重试,这样可以保证每个消息都被发送到broker

    <img src="https://user-gold-cdn.xitu.io/2019/11/28/16eb068baff14810?imageslim" alt="img" style="zoom:30%;" />

    如何保证多个partition 有序

    • 创建Topic只指定1个partition,这样的坏处就是磨灭了kafka最优秀的特性。
    • Kafka 中发送1条消息的时候,可以指定(topic, partition, key) 3个参数。partiton 和 key 是可选的。如果你指定了 partition,那就是所有消息发往同1个 partition,就是有序的。并且在消费端,Kafka 保证,1个 partition 只能被1个 consumer 消费。或者你指定 key(比如 order id),具有同1个 key 的所有消息,会发往同1个 partition。也是有序的。
    1. Kafka 的消费者如何做消息去重?

      Producer端:因为producer端是批量发送方式,因此需要进行幂等校验,就是发送端每次发消息带一个sequenceId,接收端进行校验,重复的丢弃掉。

      Consumer端:需要自己管理offset,也就是对消息的消费和offset的保存绑定为一个事务。

    2. isr

      对于同一个partition,它所在任何一个broker,都有能扮演两种角色:leader、follower。

      每个partition的Leader的用于处理到该partition的读写请求的。每个partition的followers是用于异步的从它的leader中复制数据的。

      Kafka会动态维护一个与Leader保持一致的同步副本(in-sync replicas (ISR))集合,并且会将最新的同步副本(ISR )集合持久化到zookeeper。如果leader出现问题了,就会从该partition的followers中选举一个作为新的leader。

      所以呢,在一个kafka集群中,每个broker通常会扮演两个角色:在一个partition中扮演leader,在其它的partition中扮演followers。Leader是最繁忙的,要处理读写请求。这样将leader均分到不同的broker上,目的自然是要确保负载均衡。

    3. kafka为什么这么快

      1、顺序读写
      磁盘顺序读或写的速度400M/s,能够发挥磁盘最大的速度。
      随机读写,磁盘速度慢的时候十几到几百K/s。这就看出了差距。
      kafka将来自Producer的数据,顺序追加在partition,partition就是一个文件,以此实现顺序写入。
      Consumer从broker读取数据时,因为自带了偏移量,接着上次读取的位置继续读,以此实现顺序读。
      顺序读写,是kafka利用磁盘特性的一个重要体现。

      img

      2、零拷贝 sendfile(in,out)
      数据直接在内核完成输入和输出,不需要拷贝到用户空间再写出去。
      kafka数据写入磁盘前,数据先写到进程的内存空间。

      3、mmap文件映射
      虚拟映射只支持文件;
      在进程 的非堆内存开辟一块内存空间,和OS内核空间的一块内存进行映射,
      kafka数据写入、是写入这块内存空间,但实际这块内存和OS内核内存有映射,也就是相当于写在内核内存空间了,且这块内核空间、内核直接能够访问到,直接落入磁盘。
      这里,我们需要清楚的是:内核缓冲区的数据,flush就能完成落盘。

      mmap 和 sendfile总结

      1、都是Linux内核提供、实现零拷贝的API;
      2、sendfile 是将读到内核空间的数据,转到socket buffer,进行网络发送;
      3、mmap将磁盘文件映射到内存,支持读和写,对内存的操作会反映在磁盘文件上。
      RocketMQ 在消费消息时,使用了 mmap。kafka 使用了 sendFile。

    4. 零拷贝

      对于kafka来说,Producer生产的数据存到broker,这个过程读取到socket buffer的网络数据,其实可以直接在OS内核缓冲区,完成落盘。并没有必要将socket buffer的网络数据,读取到应用进程缓冲区;在这里应用进程缓冲区其实就是broker,broker收到生产者的数据,就是为了持久化。

    5. mmap

      简称mmap,简单描述其作用就是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。
      它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候)。

      通过mmap,进程像读写硬盘一样读写内存(当然是虚拟机内存),也不必关心内存的大小有虚拟内存为我们兜底。
      使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。

      mmap也有一个很明显的缺陷——不可靠,写到mmap中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用flush的时候才把数据真正的写到硬盘。Kafka提供了一个参数——producer.type来控制是不是主动flush;如果Kafka写入到mmap之后就立即flush然后再返回Producer叫同步(sync);写入mmap之后立即返回Producer不调用flush叫异步(async)。

    6. zookeeper?

      分布式协调服务,可以在分布式系统中共享配置,协调锁资源,提供明明服务。

      • Zookeeper的数据模型

      树是由节点所组成,Zookeeper的数据存储也同样是基于节点,这种节点叫做Znode。但是,不同于树的节点,Znode的引用方式是路径引用,类似于文件路径: / 动物 / 仓鼠 / 植物 / 荷花这样的层级结构,让每一个Znode节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。

      • znode

        data:Znode存储的数据信息。

        ACL:记录Znode的访问权限,即哪些人或哪些IP可以访问本节点。

        stat:包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等。

        child:当前节点的子节点引用,类似于二叉树的左孩子右孩子。

    7. zab:保证主从一致性

      三种节点状态

      Looking :选举状态。

      Following :Follower节点(从节点)所处的状态。

      Leading :Leader节点(主节点)所处状态。

      最大ZXID:节点本地的最新事务编号,包含epoch和计数两部分。epoch是纪元的意思,相当于Raft算法选主时候的term。

      ZAB的崩溃恢复分成三个阶段:

      1.Leader election选举阶段

      此时集群中的节点处于Looking状态。它们会各自向其他节点发起投票,投票当中包含自己的服务器ID和最新事务ID(ZXID)。接下来,节点会用自身的ZXID和从其他节点接收到的ZXID做比较,如果发现别人家的ZXID比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的ZXID所属节点。

      每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准Leader,状态变为Leading。其他节点的状态变为Following。

      2.Discovery发现阶段

      用于在从节点中发现最新的ZXID和事务日志。或许有人会问:既然Leader被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个Leader的情况。

      所以这一阶段,Leader集思广益,接收所有Follower发来各自的最新epoch值。Leader从中选出最大的epoch,基于此值加1,生成新的epoch分发给各个Follower。各个Follower收到全新的epoch后,返回ACK给Leader,带上各自最大的ZXID和历史事务日志。Leader选出最大的ZXID,并更新自身历史日志。

      3.Synchronization同步阶段,把Leader刚才收集得到的最新历史事务日志,同步给集群中所有的Follower。只有当半数Follower同步成功,这个准Leader才能成为正式的Leader。

      应用

      1.分布式锁:利用Zookeeper的临时顺序节点,可以轻松实现分布式锁。

      2.服务注册和发现:利用Znode和Watcher,可以实现分布式服务的注册和发现。最著名的应用就是阿里的分布式RPC框架Dubbo。

      3.共享配置和状态信息:Redis的分布式解决方案Codis,就利用了Zookeeper来存放数据路由表和 codis-proxy 节点的元信息。同时 codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy。此外,Kafka、HBase、Hadoop,也都依靠Zookeeper同步节点信息,实现高可用。

    8. docker的隔离怎么实现的

    Docker 技术 完全是依赖 Linux 内核特性 Namespace 和Cgroup 技术来实现的,本质来说:你运行在容器的应用在宿主机来说还是一个普通的进程,还是直接由宿主机来调度的,相对来说,性能的损耗就很少,这也是 Docker 技术的重要优势。

    Linux NameSpace的调用类别,分别表示创建新的进程命名空间和 挂载命名空间。

    • CLONE_NEWPID会让执行的程序内部重新编号PID,也就是从1号进程开始
    • CLONE_NEWNS 会克隆新的挂载环境出来,通过在子进程内部重新挂载proc文件夹,可以屏蔽父进程的进程信息。

    Cgroups技术,它是为进程设置资源限制的重要手段,在Linux 中,一切皆文件,所以Cgroups技术也会体现在文件中。

    <img src="https://user-gold-cdn.xitu.io/2020/1/20/16fc2694caef90bc?imageslim" alt="img" style="zoom:50%;" />

    相关文章

      网友评论

          本文标题:中间件

          本文链接:https://www.haomeiwen.com/subject/skthmhtx.html