一、异步通信原理
- 观察者模式
作用:解耦
我们不用频繁的、轮询的访问服务器,因为我们客户端不知道服务端什么时候更新数据,当服务器更新了通知我们即可。
比如京东的到货通知、比如美团外卖来新订单了(老板不用时时刻刻上美团看有没有新订单)、再比如我的手机的操作系统过段时间就会通知更新。
一个被观察者对象(发布者),一个观察者,当被观察对象(发布者)发生变化时,观察者将被观察对象(发布者)发生变化的消息发送给关注他的人(订阅者)。 - 生产者消费者模式
生产者消费者模式并不属于23种设计模式中的一种,23种设计模式强调的是对象与对象之间的关系,而生产者消费者模式只是为了实现两个对象之间的解耦,更多的描述的是面向过程的东西。
之前是流水线的方式,流水线1没干完活,流水线2就没法继续干活,同理,流水线3、4、5…都没法干活。耦合性太高。
生产者消费者模式是生产者生产完消息,将其放到缓冲区中,消费者拿消息进行消费。这样的话上下游就得到了很好的解耦。异步通信。
美团外卖遵循生产者消费者模式,但是又不能让消费者一直去取数据,于是在缓冲区上加一个观察者模式,有了观察者模式,老板就不用一直刷新来取数据了,等着美团给他推送即可。
这两种模式结合在一起,非常适合传递消息的场景,非常适合生产层和消费层的解耦。
二、消息通信原理
缓冲区三大好处:解耦、支持并发、支持忙闲不均。即解耦、异步、削峰。
点对点消息传递:push可以保证实时处理数据,但是如果消费者消费能力不够,就会出现问题。
发布订阅消息传递:poll其实就是观察者模式,消息到达缓冲区后通知消费者来消费,消费者来poll消息。
现在的消息模型基本上以发布订阅消息模型为主。
三、kafka的系统架构
broker,可以看成节点(node)
topic,可以看成表(table)
es和kafka的对比:
如下图,虽然有4个broker(节点),但是我只有一个broker1中有我要读或者写的topic,剩下3个broker都没用到,这样就遇到了单点不能扩充的问题。
于是,在topic的基础上,有了partition这个概念。
一般情况下搞三个备份,同时坏两台机器的可能性不大。
生产者往topic里面写数据,topic通过它的路由规则,找到对应的leader(主),把leader的数据同步到follower(从)。
假如生产者要写6条数据,把数据存放在topic1–partition1–leader中的数据是:1、3、5;把数据存放在topic1–partition2–leader中的数据是:2、4、6。
注意:不同的partition内部是不保证有序的,但是同一个partition内部是保证有序的。
所以说partition没法保证数据的完全有序性,但是它可以保证数据的内部有序性。
如果你对数据的有序性要求比较强,那你只能搞一个partition。
kafka中消费者不能从follower中读取数据,只能从leader中读取数据。备用节点只是为了安全考虑,不参与服务(不参与读写 )。
再考虑这种情况,t1-p1-L中的数据被多个消费者所消费,比如消费者a消费了1,消费者b消费了3,那消费者1和消费者2读数据的时候,怎么记录读到哪了呢?
这时就用到了zookeeper,zk中会记录每个消费者的偏移量(offset)。zk会记录消费者a的偏移量是0,消费者b的偏移量是1。
那zk中什么时候记录数据就变得很关键了,消费者a和b读到数据1和3就开始记录呢(可能会导致消息被重复消费的现象)还是消费者a和b读到数据1和3后并处理完数据(可能会导致消息丢失)才开始记录呢?
kafka会交给我们来处理,让我们来设置。
同理,生产者生把消息传给topic就算传输完成(可能会导致消息丢失)还是topic记录下消息后才算传输完成(生产者发送给topic,topic节点崩了,就需要生产者重复的发送消息)?
kafka不帮你做决定,kafka给你策略,让你自己去选择。
生产者往kafka中写数据的时候,是写内存还是写硬盘?
写内存。
那如何保证数据不丢呢?就要保证flush的频率。如果想要效率高点,flush频率就低,如果想要安全性高,flush频率就高。
broker:
一个服务器节点就是一个broker,所以上图就有4个broker。
这幅图除了数据文件是存在你本机中,其他都存在zk中m,所以说,你的集群有多少个broker,“多少”这个数据就存在zk中。
topic:
为什么说一个topic存在于多个broker中?因为一个topic有多个partition,partition放在多个broker中。
用户只需要指定消息的topic即可生产或消费数据,而不需关心数据存于何处。因为kafka内部帮我们实现了消息的路由,让生产者或消费者所生产或消费的数据传输到对应的partition中。
partition:
0、1、2、3、4…对应的是消息的编号,0可能存的数据是张三,1可能存的数据是李四…
partition其实就是一个队列,队尾写消息,对头读消息。
例如下图就是在0开始读消息,12写消息。读到哪个位置会通过offset来记录。
leader和follower:
假设每个leader所在的broker为a,那么follower所在的broker就是a+1.
kafka要求所有的数据必须从主节点读取,从节点只负责备份,这是为了保证数据的一致性。
类比zk,我们从zk中读取的数据不一定是最新的数据,因为zk追求的是最终一致性。有可能半数的从节点的数据都同步到最新了,但可能我读到的是另一半未更新的节点。
类比redis,redis读取到的数据也不一定是最新的数据。
四、kafka环境搭建
一个consumer Group中的2个consumer不会消费到相同的数据,这样岂不是出现重复消费的问题。
图中1个leader有2个follower。
kafka的具体映射关系都会存储在zk上。
一个consumer可以读多个partition,但是一个partition只能给一个consumer读。
环境搭建
1、安装zookeeper
下载地址:https://zookeeper.apache.org/releases.html
解压:tar -zxvf apache-zookeeper-3.7.0-bin.tar.gz
liushihao06@liushihao06deMacBook-Pro development_tools % cd apache-zookeeper-3.7.0-bin
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % ls
LICENSE.txt README_packaging.md docs
NOTICE.txt bin lib
README.md conf
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % cd conf
liushihao06@liushihao06deMacBook-Pro conf % ls
configuration.xsl log4j.properties zoo_sample.cfg
liushihao06@liushihao06deMacBook-Pro conf % mv zoo_sample.cfg zoo.cfg # 修改zoo_sample.cfg文件名为zoo.cig
liushihao06@liushihao06deMacBook-Pro conf % ls
configuration.xsl log4j.properties zoo.cfg
liushihao06@liushihao06deMacBook-Pro conf % vim zoo.cfg
# 修改配置文件中dataDir=/tmp/zookeeper为dataDir=/opt/module/zookeeper/zkData. 我暂时没有修改
启动:
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % cd bin
liushihao06@liushihao06deMacBook-Pro bin % ls # 最常用的是zkCli.sh、zkServer.sh
README.txt zkEnv.sh zkSnapShotToolkit.sh
zkCleanup.sh zkServer-initialize.sh zkSnapshotComparer.cmd
zkCli.cmd zkServer.cmd zkSnapshotComparer.sh
zkCli.sh zkServer.sh zkTxnLogToolkit.cmd
zkEnv.cmd zkSnapShotToolkit.cmd zkTxnLogToolkit.sh
liushihao06@liushihao06deMacBook-Pro bin % cd ..
# 服务端
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkServer.sh start # 启动服务端
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /Users/liushihao06/development_tools/apache-zookeeper-3.7.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % jps
72353 Launcher
75872
85831 Jps
85817 QuorumPeerMain # 服务端进程
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkServer.sh status # 查看服务端状态
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /Users/liushihao06/development_tools/apache-zookeeper-3.7.0-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost. Client SSL: false.
Mode: standalone # 单机模式
# 客户端
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkCli.sh # 启动客户端
[zk: localhost:2181(CONNECTED) 1] ls / # 查看根节点
[zookeeper]
[zk: localhost:2181(CONNECTED) 2] quit # 退出客户端进程
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkServer.sh stop # 退出服务端进程
https://www.bilibili.com/video/BV1PW411r7iP?p=7
2、安装kafka
下载地址:https://kafka.apache.org/downloads
解压:
tar -zxvf kafka_2.12-2.8.0.tgz
liushihao06@liushihao06deMacBook-Pro development_tools % cd kafka_2.12-2.8.0
liushihao06@liushihao06deMacBook-Pro kafka_2.12-2.8.0 % ls
LICENSE bin libs site-docs
NOTICE config licenses
liushihao06@liushihao06deMacBook-Pro kafka_2.12-2.8.0 % cd config
liushihao06@liushihao06deMacBook-Pro config % ls
connect-console-sink.properties consumer.properties
connect-console-source.properties kraft
connect-distributed.properties log4j.properties
connect-file-sink.properties producer.properties
connect-file-source.properties server.properties
connect-log4j.properties tools-log4j.properties
connect-mirror-maker.properties trogdor.conf
connect-standalone.properties zookeeper.properties
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % ls
LICENSE.txt README_packaging.md docs
NOTICE.txt bin lib
README.md conf logs
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % cd bin
liushihao06@liushihao06deMacBook-Pro bin % ls
README.txt zkEnv.sh zkSnapShotToolkit.sh
zkCleanup.sh zkServer-initialize.sh zkSnapshotComparer.cmd
zkCli.cmd zkServer.cmd zkSnapshotComparer.sh
zkCli.sh zkServer.sh zkTxnLogToolkit.cmd
zkEnv.cmd zkSnapShotToolkit.cmd zkTxnLogToolkit.sh
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkServer.sh start # 启动服务端
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /Users/liushihao06/development_tools/apache-zookeeper-3.7.0-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
liushihao06@liushihao06deMacBook-Pro apache-zookeeper-3.7.0-bin % bin/zkCli.sh # 启动客户端
kafka的元数据信息都记录在zookeeper上。
六、Kafka数据检索机制
log.segment.ms:如果花了7天的时间还没写满1GB,仍会创建一个segment。
也就是说,创建一个segment有两个条件;
- 数据量大小达到1GB
- 时间达到7天
.log为真正的数据文件
.index为数据的索引信息
00000900.log的数据从00000901开始
1,1235表示偏移量是1(900往后偏移一位),数据是1235
901,1235表示901这条数据对应的值是1235
七、数据的安全性 (七到九是数据从producer到leader过程)
根据request.required.acks来选择哪种数据安全性。
这个参数可以确定数据会不会丢失。丢失体现在生产者往MQ写数据的时候,应该多久写一次。
At least once:消息绝对不会丢,先发消息再写日志。有可能消息已经发送但是日志中没记录,日志中没记录,那就得重写发送一次。可能会造成重复消费问题。
At most once:先写日志再发消息,日志写好后,从偏移量后面去读。可能会造成消息的丢失。
acks = 0 :producer发送消息给leader,leader收到并确认后producer才会发送下一条消息。如果leader中途宕机,没确认,此时producer就会重新发送消息给leader。这样就造成重复消费问题。
acks = 1:producer不会等leader确认。如果leader宕机,没收到消息,就会造成消息的丢失。
acks = all:producer发送消息给leader,leader要同步消息到所有的follower,leader和所有的follower都确认后producer才能接着发送消息。这样的话效率最低,但是安全性最高。
如何选择acks要看业务,如果你的数据都是日志数据,丢失无所谓,选at most once,如果是银行流失,一定是不能丢的,选at least once。
重复消费可以从代码层面上解决这个问题,比如说收到重复的流水号就不处理了,或者标记出重复的流水号,最后再做二次处理。
八、kafka的ISR机制
follower从leader中同步数据,但是每个follower同步的数据量或者和leader的差的数据量事不一致的。
这样就会有一个问题,当leader挂掉的时候就要从follower中选一个为主,肯定选同步数据最多的。
那怎样知道哪个follower同步的数据最多呢?kafka想了个办法,维护一个链表,这个链表就叫ISR。
如果leader认为某个follower和他相差无几,就将其维护到ISR链表中去,将来leader挂掉选候选人的时候就会从ISR中去选。
OSR说白了就是和leader相差太多的follower。
ISR的作用是快速从候选节点中选主节点。
判断标准:
1、follower超过10s没同步数据,就把该follower从ISR中剔除。
2、follower和leader相差4000条数据,就把该follower从ISR中剔除。
脏选举:
当你的leader刚刚收到producer发送的数据,还未来得及同步给follower时候,leader就挂了,这时所有的follower中都没有这条最新的数据。可还得选,这就叫做脏选举(虽然你不完美,但我也只能选你)。
九、Broker的数据存储机制
十、数据的安全性 (从consumer端如何保证?)
-
autocommit(exactly once):读到数据和处理数据是有一个时间点的,如果读到后未处理就出现问题,但我已经把业务提交了。这样就会出现丢失数据的问题。
-
读完消息先commit再处理消息(at most once):读完消息1001后commit,此时consumer group宕机,下次重启后,因为已经commit消息1001,就会认为消息1001已经消费,此时就会消费消息1002。这样会造成消息丢失问题。
-
读完消息先处理消息再commit(at least once):读完消息1001并处理完后,此时consumer group宕机,下次重启后,因为消息1001还未commit,就会认为消息1001还未消费,此时就会重新读取消息1001并处理完再commit。这样会造成消息的重复消费问题。
at most once 和 at least once 的性能消耗都是比较小的,而exactly once的性能消耗较大。
要做到exactly once,即精确的消费一条数据不丢失也不重复,怎么做呢?
两阶段提交。(待学习)