Java:92-Kafka详解

Kafka详解

Kafka是一款开源的消息引擎系统:

在这里插入图片描述

消息队列(MQ):
什么是消息队列,虽然前面学习过了RabbitMQ,但也继续介绍一下:
消息队列不知道大家看到这个词的时候,会不会觉得它是一个比较高端的技术
消息队列,一般我们会简称它为MQ(Message Queue).
消息队列是一种帮助开发人员解决系统间异步通信的中间件,常用于解决系统解耦和请求的削峰平谷的问题
队列(Queue):
Queue 是一种先进先出的数据结构,容器

在这里插入图片描述

消息(Message):
不同应用之间传送的数据
消息队列:
我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用
消息队列是分布式系统中重要 的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性
队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的
比如生产者发送消息1,2,3,…,对于消费者就会按照1,2,3,…的顺序来消费

在这里插入图片描述

mq的方式:

在这里插入图片描述

消息队列的应用场景:
消息队列在实际应用中包括如下四个场景:
1:异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间
2:应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败
3:限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况
4:消息驱动的系统:系统分为消息队列、消息生产者、消息消费者
生产者负责产生消息,消费者(可能有多个)负责对消息进行处理
下面详细介绍上述四个场景以及消息队列如何在上述四个场景中使用:
异步处理:
具体场景:
用户为了使用某个应用,进行注册,系统需要发送注册邮件并验证短信
对这两个子系统操作的处理方式有两种:串行及并行
涉及到三个子系统:注册系统、邮件系统、短信系统
串行方式:新注册信息生成后,先发送注册邮件,再发送验证短信

在这里插入图片描述

在这种方式下,需要最终发送验证短信后再返回给客户端
并行处理:新注册信息写入后,由发短信和发邮件并行处理

在这里插入图片描述

在这种方式下,发短信和发邮件 需处理完成后再返回给客户端
假设以上三个子系统处理的时间均为50ms,且不考虑网络延迟,则总的处理时间:
串行:50+50+50=150ms
并行:50+50 = 100ms
如果引入消息队列,在来看整体的执行效率:

在这里插入图片描述

在写入消息队列后立即返回成功给客户端,则总的响应时间依赖于写入消息队列的时间
而写入消息队列的时间本身是可以很快的,基本 可以忽略不计
因此总的处理时间为50ms,相比串行提高了2倍,相比并行提高了一倍
应用耦合:
具体场景:
用户使用QQ相册上传一张图片,人脸识别系统会对该图片进行人脸识别
一般的做法是,服务器接收到图片后,图片上传系统立即调用人脸识别系统,调用完成后再返回成功
如下图所示:
当然调用方式比如可以是如下:
webService、Http协议(HttpClient、RestTemplate)、Tcp协议(Dubbo)

在这里插入图片描述

该方法有如下缺点:
1:人脸识别系统被调失败,导致图片上传失败
2:延迟高,需要人脸识别系统处理完成后,再返回给客户端,即使用户并不需要立即知道结果
3:图片上传系统与人脸识别系统之间互相调用,需要做耦合
若使用消息队列:

在这里插入图片描述

客户端上传图片后,图片上传系统将图片信息批次写入消息队列,直接返回成功;
人脸识别系统则定时从消息队列中取数据,完成对新增图片的识别
图片上传系统并不需要关心人脸识别系统是否对这些图片信息的处理、以及何时对这些图片信息进行处理
事实上,由于用户并不需要立即知道人脸识别结果,人脸识别系统可以选择不同的调度策略
按照闲时、忙时、正常时间,对队列中的图片信息进行处理,当然,一般是直接的消费的,只是我们并不需要等待
限流削峰:
具体场景:
购物网站开展秒杀活动,一般由于瞬时访问量过大,服务器接收过大,会导致流量暴增,相关系统无法处理请求甚至崩溃
而加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲,即将调用变成消息的存放,即缓冲了

在这里插入图片描述

这里说明一下宕机问题:实际上服务器的宕机,是他默认设置的,即满足某些条件,服务器就会使得宕机,或者关机(挂掉)
因为如果不这样,那么一般有如下的情况
第一,长时间的情况下,会发热,造成硬件的损伤
第二,一般一直在运行,那么当你去排查时,基本操作不了,因为内存满了,或者磁盘满了,即一般会非常卡顿,且关机都很困难
第三,该服务一直的占用,导致,客户等待时间很长等等
当然一般还有其他的缺点,所以现在的机器,或者说服务器,在流量大到使得满足服务器设置的宕机条件的情况下,通常会宕机
即宕机是为了保护机器,现在的机器一般都有这样的设置
或者说服务器都会有这样的设置(一般程序可能也有设置,而不会操作机器的设置宕机条件)
该方法有如下优点:
请求先入消息队列,而不是由业务处理系统直接处理,做了一次缓冲,极大地减少了业务处理系统的压力
队列长度可以做限制,事实上,秒杀时,后入队列的用户无法秒杀到商品
这些请求可以直接被抛弃,返回活动已结束或商品已售完信 息
消息事件驱动的系统:
具体场景:
用户新上传了一批照片,人脸识别系统需要对这个用户的所有照片进行聚类,由对账系统重新生成用户的人脸索引(加快查询)
这三个子系统间由消息队列连接起来,前一个阶段的处理结果放入队列中,后一个阶段从队列中获取消息继续处理

在这里插入图片描述

该方法有如下优点:
避免了直接调用下一个系统导致当前系统失败
每个子系统对于消息的处理方式可以更为灵活,可以选择收到消息时就处理,可以选择定时处理
也可以划分时间段按不同处理速度处 理
至此,简单来说,消息队列就是在调用与被调用者之间,给出一个信息
该信息代表是否调用,一般调用者,返回正常信息,而被调用者,需要操作这个给出的信息,从而执行相关代码
来实现普通的调用与被调用者之间的调用模式
基本上唯一的缺点就是,消息队列并不能完全的保证是真实的成功调用,虽然可以使用确认机制来保证调用成功
但如果一直是没有调用成功的,那么自然是不能完全保证真实的调用的
消息队列的两种模式:
消息队列通常包括两种模式(统一的简称,具体可以到81章博客里查看):
点对点模式(point to point, queue)和发布/订阅模式(publish/subscribe,topic)
他们各自的模式通常来说本身也可以分为多种模式(如RabbitMQ里面的他们的模式的介绍)
但他们的多种模式之间,只是操作的不同,具体意思还是一样的
比如在RabbitMQ中:
点对点是消费者多少的操作(一个消息只能被一人消费)
而发布订阅只是路由的操作(比如是否操作路由键的添加),一个消息可以被多人消费
我们可以将发布/订阅简称为发布订阅,后面或者前面也可以这样说明
这里只是大致的说明,具体说明可以到81章博客里查看
点对点模式:
点对点模式下包括三个角色
消息队列
发送者 (生产者)
接收者(消费者)
每个消息都被发送到一个特定的队列,接收者从队列中获取消息,队列保留着消息
可以放在内存中,也可以持久化,直到他们被消费或超时

在这里插入图片描述

点对点模式特点:
每个消息只有一个消费者,一旦被消费,消息就不再在消息队列中
发送者和接收者间没有依赖性,发送者发送消息之后,不管有没有接收者在运行,都不会影响到发送者下次发送消息
接收者在成功接收消息之后需向队列应答成功,以便消息队列删除当前接收的消息
发布/订阅模式 :
发布/订阅模式下包括三个角色:
角色主题(Topic)
发布者(Publisher)
订阅者(Subscriber)
消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息
和点对点方式不同,发布到topic的消息会被多个 订阅者消费
即一个消息给多个订阅者(消费者),实际上还是多个队列
只是路由给而已,而不是指定一个队列
即不是一个消息只给一个(消费者),所以他们本质上还是队列的获取
只是发布订阅有个路由,来给多个队列,使得操作了多个消费者
这里的路由可以理解为:类似于RabbitMQ的路由,也可以称为RabbitMQ的交换机
但RabbitMQ的路由(交换机)只是一种逻辑上的概念而已,类似于我们生活中的交换机(操作路由的作用)的作用

在这里插入图片描述

发布/订阅模式特点:
每个消息可以有多个订阅者
发布者和订阅者之间有时间上的依赖性
为了消费消息,订阅者必须保持在线运行
消息队列实现机制:
JMS:
JMS(JAVA Message Service,Java消息服务)是一个Java平台中关于面向消息中间件的API
允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息
是一个消息服务的标准或者说是规范,是 Java 平台上有关面向消息中间件的技术规范
便于消息系统中的 Java 应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口,简化企业应用的开发
JMS 消息机制主要分为两种模型:PTP 模型(点对点模型)和 Pub/Sub 模型(即发布/订阅模型)
实现产品:Apache ActiveMQ
AMQP:
AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议
是应用层协议的一个开 放标准,为面向消息的中间件设计
基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品
不同的开发语言等条 件的限制,Erlang中的实现有RabbitMQ等

在这里插入图片描述

当然,除了上面的两种实现机制外,自己也可以规定实现机制,比如kafka就是自己规定的实现机制
只要满足,可以操作消息队列的情况
那么无论你是如何操作,比如操作程序,或者规定协议互相操作(一般也需要加上程序来操作协议内容,就如http协议一样)
可能也有其他的操作等等,只要可以操作消息队列,那么都可以
那么都可以称为自己规定的实现机制,比如kafka就是这样,他是自己定义的实现机制来操作消息队列
JMS VS AMQP :

在这里插入图片描述

那么你可能有一个问题,为什么java可以跨平台,而jms不可以,jms不是操作java的吗
答:他只是一个api,跨平台的含义是,在其他的机器上可以执行(有环境),且不依靠其他的文件或者代码
所以我们将java相关文件放过去,是可以执行的,且不需要其他的文件或者代码
但是你将他的api文件放过去明显是不能执行的,因为需要其他的java文件或者代码,而不是单独的给出api
因为需要调用,自然需要其他的java代码或者文件,那么既然需要其他的代码或者文件,自然不能够跨平台
即违背了跨平台的含义,但也正是因为是可以通过文件或者代码使得操作,我们也可以粗略的称为可以跨平台
虽然违背了含义(含义也只是含义,在某些情况下,自然可以被打破的,比如这里)
只要满足可以被存放消息和被消费消息的操作,那么就可以称为MQ消息队列
所以虽然对应的产品实现消息队列的方式不同(比如kafka和rabbitmq他们实现方式不同),但他们都可以称为消息队列
但是rabbitmq并不需要zookeeper来操作反馈,因为他本身就可以进行操作,比如返回机制(虽然前面并没有说明,或者大致说明)
而kafka通常需要zookeeper来满足反馈的信息操作,因为他好像并没有这样的操作,如果有,那么自然也不需要
但实际上或者说本质上他们是否操作zookeeper都是根据值不值得来操作的,而不是根据是否有对应的操作
由于rabbitmq处理的数据量少点,所以可以不用
而kafka处理的数据太多,一般需要使用
但是其他的原因,具体为什么使用,可以百度
而现在kafka一般并不依赖与zookeeper了,具体也可以百度
常见的消息队列产品:
RabbitMQ :
RabbitMQ 2007年发布,是一个在AMQP(高级消息队列协议)基础上完成的
可复用的企业消息系统,是当前最主流的消息中间件之一
ActiveMQ :
ActiveMQ是由Apache出品,ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现
它非常快速,支持多种语言的客户端 和协议,而且可以非常容易的嵌入到企业的应用环境中,并有许多高级功能
RocketMQ :
RocketMQ出自 阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进
消息可靠性上比 Kafka 更好,RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理等
Kafka :
Apache Kafka是一个分布式消息发布订阅系统
它最初由LinkedIn公司基于独特的设计实现为一个分布式的提交日志系统(a distributed commit log)
之后成为Apache项目的一部分,Kafka系统快速、可扩展并且可持久化,它的分区特性,可复制和可容错都是其不错的特 性

在这里插入图片描述

ms是毫秒,us是微秒,1毫秒=0.001秒,0.001毫秒=1微秒,所以RabbitMQ延迟很低,这是他的优点
综合上面的材料得出以下两点:
1:中小型软件公司:建议选RabbitMQ,一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便
正所谓,成也萧何,败也萧何,他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?
所幸,RabbitMQ的社区十分活跃,可以解决 开发过程中遇到的bug,这点对于中小型公司来说十分重要
不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司, 数据量没那么大
选消息中间件,应首选功能比较完备的,所以kafka排除
不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里 放弃维护rocketmq
中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐
2:大型软件公司:
根据具体使用在rocketMq和kafka之间二选一,一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量
针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的
至于kafka,根 据业务场景选择,如果有日志采集功能,肯定是首选kafka了
kafka的基本介绍:
什么是Kafka:
官网:http://kafka.apache.org/

在这里插入图片描述

Kafka是最初由Linkedin公司开发,是一个分布式、分区的、多副本的、多订阅者
基于zookeeper协调的分布式日志系统(也可以当做MQ系统)
常见可以用于web/nginx日志、访问日志,消息服务等等
Linkedin于2010年贡献给了Apache基金会并成为顶级开源项目
主要应用场景是:日志收集系统和消息系统
Kafka主要设计目标如下:
1:以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间的访问性能
算法复杂度:时间复杂度和空间复杂度
以时间复杂度为O(1)的方式:常数时间运行和数据量的增长无关
假如操作一个链表,那么无论链表的是大还是小,操作时间是一 样的(实际上是同级别复杂度)
2:高吞吐率,即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输
支持普通服务器每秒百万级写入请求,一般内部是使用了Memory mapped Files(现在了解即可)
3:支持Kafka Server间的消息分区,及分布式消费,同时保证每个partition内的消息顺序传输
4:同时支持离线数据处理和实时数据处理
5:Scale out:支持在线水平扩展
kafka的特点:
1:解耦,Kafka具备消息系统的优点,只要生产者和消费者数据两端遵循接口约束,就可以自行扩展或修改数据处理的业务过程
2:高吞吐量、低延迟。即使在非常廉价的机器上,Kafka也能做到每秒处理几十万条消息,而它的延迟最低只有几毫秒
3:持久性,Kafka可以将消息直接持久化在普通磁盘上,且磁盘读写性能自然有优异(因为一般磁盘总存储不同)
4:扩展性,Kafka集群支持热扩展,Kaka集群启动运行后,用户可以直接向集群添加
5:容错性,Kafka会将数据备份到多台服务器节点中
即使Kafka集群中的某一台加新的Kafka服务节点宕机,也不会影响整个系统的功能
6:支持多种客户端语言。Kafka支持Java、.NET、PHP、Python等多种语言
7:支持多生产者和多消费者
kafka的主要应用场景 :
1:消息处理(MQ):
KafKa可以代替传统的消息队列软件,使用KafKa来实现队列有如下优点:
KafKa的append来实现消息的追加,保证消息都是有序的有先来后到的顺序
对于稳定性强的队列在使用中最怕丢失数据,而KafKa能做到理论上的写成功不丢失
分布式容灾好
相对于内存队列来说容量大,所以KafKa一般放在硬盘上面,正是因为这样,所以他的容量基本受硬盘影响
数据量基本不会影响到KafKa的(执行)速度(基本是同级别的复杂度,所以执行速度的差异非常小)
2:分布式日志系统(Log):
在很多时候我们需要对一些庞大的数据进行存留,日志存储这块会遇到巨大的问题,日志不能丢,日志存文件不好找
定位一条消息 成本高(比如遍历当天日志文件),实时显示给用户难,这几类问题KafKa都能游刃有余:
KafKa的集群备份机制能做到n/2的可用,当n/2以下(包括n/2)的机器宕机时存储的日志不会丢失
否则可能会丢失(因为不算集群,就如zookeeper类似的半数机制)
KafKa可以对消息进行分组分片
KafKa非常容易做到实时日志查询
3:流式处理:
流式处理就是指实时地处理一个或多个事件流
流式的处理框架(spark, storm ,flink) 从主题中读取数据,对其进行处理
并将处理后的结果数据写入新的主题,供用户和应用程序使用
kafka的强耐久性在流处理的上下文中也非常的有用
kafka的架构 :
架构案例:

在这里插入图片描述

Kafka Cluster:由多个服务器组成,每个服务器有单独的名字broker(掮客)
kafka broker:kafka集群中包含的服务器
Kafka Producer:消息生产者、发布消息到 kafka 集群的终端或服务
Kafka consumer:消息消费者、负责消费数据
Kafka Topic:主题
可以理解为rabbitmq的队列中的路由,虽然并不是同样的意思,但也可以这样理解,因为看起来存放的操作是类似的
他是一类消息的名称,存储数据时将一类数据存放在某个topci下,消费数据也是消费一类数据
既然是一类消息,那么也就可以理解为相当于rabbitmq路由的一系列的绑定
而之所以并没有说是一样,而是类似,主要是因为数据的分发,在rabbitmq里面会给所有的队列
而这里并不是给所有的分区,一般根据分区策略来给的,这就是不同的地方
只是对象的指定还是所有(虽然会操作策略),即没有不会选择的分区
订单系统:创建一个topic,叫做order
用户系统:创建一个topic,叫做user
商品系统:创建一个topic,叫做product
注意:Kafka的元数据都是存放在zookeeper中,基本包括kafka的对应的信息,比如主题,分区,节点等等操作的信息
架构剖析:
kafka架构的内部细节剖析:

在这里插入图片描述

在这里插入图片描述

说明:kafka支持消息持久化(比如后面的flush.messages配置),消费状态和订阅关系有客户端负责维护
由于消费端为拉模型来拉取数据,所以消息消费完后
不会立即在内存删除或者不会删除(当然硬盘上的肯定不会删除,只操作内存)
且通常会保留历史消息,因此支持多订阅时,消息一般只会(或者说只要)存储一份就可以了
通常也只会存储一份(因为消息是追加的),当然你也可以手动的备份某个或者多个消息
Broker:kafka集群中包含一个或者多个服务实例,这种服务实例被称为Broker
Topic:每条发布到kafka集群的消息都有一个类别,这个类别就叫做Topic
Partition:分区,物理上的概念,每个topic包含一个或多个partition
一个partition对应一个文件夹,这个文件夹下存储partition的 数据和索引文件,每个partition内部是有序的
虽然实现方式与rabbitmq类似,但概念和具体实现操作方式是不同的
关系解释:
Topic & Partition:
Topic 就是数据主题,是数据记录发布的地方,可以用来区分业务系统
Kafka中的Topics总是多订阅者模式,一个topic可以拥有一个或者多个消费者来订阅它的数据
一个topic为一类消息,每条消息必须指定一个topic
对于每一个topic, Kafka集群都会维持一个分区日志(分区日志,并不是代表一个日志文件,而是多个文件的总体),如下图
针对分区日志说明:
可能你看起来认为,是一个分区对应一个日志,实际上他们是将总日志分开的,所以看起来只有分散的日志文件
而我们说的每一个topic, Kafka集群都会维持一个分区日志,代表的就是一个总日志,虽然是分开,所以这两种说明方式都可以
至此针对分区日志说明已经完毕
每个分区都是有序且顺序不可变的记录集,并且不断地追加到结构化的commit log文件
分区中的每一个记录都会分配一个id号来表示顺序,称之为offset,offset用来唯一的标识分区中每一条记录

在这里插入图片描述

在每一个消费者中唯一保存的元数据(为描述数据的数据,比如数据中的某些属性,比如id)是offset(偏移量)
即消费在log中的位置
偏移量由消费者所控制,通常在读取记录后,消费者会以线 性的方式增加偏移量
但是实际上,由于这个位置由消费者控制,所以消费者可以采用任何顺序来消费记录
例如,一个消费者可以重置到一个旧的偏移量,从 而重新处理过去的数 据
也可以跳过最近的记录,从"现在"开始消费,当然了如果是未来,那么可能会操作到没有的数据,这时一般会有报错或者提示信息
所以消费者 可以读取自己操作的日志位置信息了,实际上上面说明的日志
一般来说,就是我们操作的消息,只是以日志来说明而已,所以kafka是以日志文件的方式来操作消息
而正是因为上面的有序且可以随意读取的偏移量,所以他读取日志非常快,也就是操作消息非常快
这就是kafka能够处理非常多的数据的原因,且执行速度在ms级以内,虽然执行速度比不上rabbitmq,因为他是事先规定好的
但处理的数据比他要强
这些细节说明Kafka 消费者是非常廉价的,消费者的增加和减少,对集群或者其他消费者没有多大的影响
kafka集群环境搭建 :
ZooKeeper 作为给分布式系统提供协调服务的工具被 kafka 所依赖
在分布式系统中,消费者需要知道有哪些生产者是可用的
而如果 每次消费者都需要和生产者建立连接并测试是否成功连接,那效率也太低了,显然是不可取的
而通过使用 ZooKeeper 协调服务,Kafka 就能将 Producer,Consumer,Broker 等结合在一起
同时借助 ZooKeeper,Kafka 就能够将所有组件在无状态的条件下建立起生产者和 消费者的订阅关系,实现负载均衡

在这里插入图片描述

图中的ip地址,只是举例,具体看自己的ip地址
准备工作:
环境准备:
准备三台服务器,安装jdk1.8,其中每一台虚拟机的hosts文件中都需要配置如下的内容
192.168.164.11 node1
192.168.164.12 node2
192.168.164.13 node3
#具体ip根据自己设置的机器ip一致
在这之前,首先我们先只克隆一个机器,当我们操作完这个克隆机器后,将我们操作的克隆的机器再次的克隆
这样我们就不需要再次的重复操作其他两个克隆机器了(到那时只需要局部的修改细节即可,比如后面zookeeper给值的细节)
实现方式:
若学习了前面90章博客的内容,那么可以克隆对应的虚拟机,记得看看mac地址是否一致,重新生成即可
修改IP地址,具体实现方式可以看55章博客,这里说明一下
对应重启网络服务时,出现的选项,并不需要填写,虽然前面可能也说了添加yes或者y(可能也并没有说明)
因为他一般并不是需要我们填写选择来执行的操作,可能应该习惯于填写y吧
安装目录创建:
安装包存放的目录:/export/software
安装程序存放的目录:/export/servers
数据目录:/export/data
日志目录:/export/logs #虽然可能没有使用
创建各级目录命令:
mkdir -p /export/servers/
mkdir -p /export/software/
mkdir -p /export/data/
mkdir -p /export/logs/ 
将上面的目录都创建好
至此那么就可以修改hosts了,虽然81章博客也操作过,但实在太基础,所以你若不知道,最好百度
一般我们都重启机器来直接的生效,或者可以执行命令,具体可以百度查看,但是一般都需要重启才可生效
Zookeeper集群搭建:
Linux安装JDK,zookeeper需要jdk,可能之前没有说明如下:
一般zookeeper需要jdk独有的且不属于jre的功能(可能也不需要,所以可能jre也可以),所以需要jdk
上传JDK到Linux之前,首先说明一下上传文件两种方式:
使用SSH方式(使用比如scp命令或者Xshell操作文件的sftp,sftp是ssh协议的一部分)
使用CRT方式,这里给出CRT方式
使用CRT需要先在Linux虚拟机上安装lrzsz上传工具,安装方式: yum install -y lrzsz
安装lrzsz之后,是需要在在Linux命令行中输入:rz ,就可以弹出一个文件上传窗口
比如我操作过程中的图片:

在这里插入图片描述

然后选择其他的目录,比如E盘下,具体看自己操作了
这里可以点击此电脑,然后点击E盘,一般都是双击进去,而一个点击则是选择,这是基本的电脑操作知识:

在这里插入图片描述

选择后,就相当于我们直接的拖拽到里面,只是他可以在里面进行选择目录
但无论是直接的拖拽还是使用rz,基本只能是操作压缩的文件,或者单独的文件(单独的文件好像也不行)
而不能直接的移动目录文件(即文件夹)
安装并配置JDK:
对应的JDK文件地址:
链接:https://pan.baidu.com/s/1AwhGvD0buHFztwlPIIQOsw
提取码:alsk
#到如下目录
cd /home
# 使用rpm安装JDK
rpm -ivh jdk-8u261-linux-x64.rpm
# 默认的安装路径是/usr/java/jdk1.8.0_261-amd64
# 配置JAVA_HOME
vi /etc/profile
# 文件最后添加两行
export JAVA_HOME=/usr/java/jdk1.8.0_261-amd64
export PATH=$PATH:$JAVA_HOME/bin
# 退出vi,使配置生效
source /etc/profile
#然后输入命令进行测试:
java -version
Linux 安装Zookeeper
对应的文件地址:
链接:https://pan.baidu.com/s/1F-X7SmiD7C6m1EcK-7X7fw
提取码:alsk
解压并配置zookeeper(配置data目录,集群节点):
# 解压到/opt目录
tar -zxf zookeeper-3.4.14.tar.gz -C /opt # -C 解压到指定目录里面,好像没有-c(小c的命令)
# 配置
cd /opt/zookeeper-3.4.14/conf
# 配置文件重命名后生效
cp zoo_sample.cfg zoo.cfg
#编辑
vi zoo.cfg
# 设置数据目录
dataDir=/var/lagou/zookeeper/data
# 添加
server.1=node1:2881:3881
server.2=node2:2881:3881
server.3=node3:2881:3881
# 退出vim(vi)
mkdir -p /var/lagou/zookeeper/data
echo 1 > /var/lagou/zookeeper/data/myid #其他两台机器,分别是node2给2,node3给3
#他是覆盖写入的,也就是说,相当于里面的值全部都被单独的1覆盖了,所以文件里面的值就是1,自己可以进行测试
#可以这样理解:首先清空文件,然后输入1

# 配置环境变量
vi /etc/profile
# 添加
export ZOOKEEPER_PREFIX=/opt/zookeeper-3.4.14
export PATH=$PATH:$ZOOKEEPER_PREFIX/bin
export ZOO_LOG_DIR=/var/lagou/zookeeper/log
# 退出vi,让配置生效
source /etc/profile
很明显,这里有个地方在克隆时需要注意的细节,就是对应给的值,分别是1,2,3(记得修改)
现在我们进行再次的克隆两次,克隆两次后,将对应的值修改成2和3即可,其他的自然都是一样的
不需要改动了(但也要记得修改ip,去55章博客查看即可)
至此我们的初步工作已经完成
启动zookeeper:
#到对应解压后的zookeeper-3.4.14文件里面的bin里面执行如下
#实际上因为设置了环境变量的原因(也是设置操作了对应bin里面的),所以可以直接在任意地方执行如下:
# 在三台Linux上启动Zookeeper
zkServer.sh start
# 在三台Linux上查看Zookeeper的状态
zkServer.sh status
#因为环境变量的原因,可以不用加上"./"了
#这里就与windows有一点不同,需要明确的指定才可当作命令,而windows却已命令为主
接下来我们搭建kafka集群:
下载安装包:
中文网站(可能进入不了):http://kafka.apachecn.org/
英文网站: http://kafka.apache.org/
假如进入英文网站,点击右边的下载按钮,即"download kafka"(DOWNLOAD KAFKA),下面的内容界面发生改变
然后手动的滑动下面的内容,可以找到如下:

在这里插入图片描述

虽然他是老的版本,但这里是可以操作的,即不考虑新的,防止出现问题
由于kafka是scala语言编写的,基于scala的多个版本,kafka发布了多个版本,其中2.11是推荐版本
如果不想去上面的下载地址,可以到如下地址获取:
链接:https://pan.baidu.com/s/146u2IAVDD8te4baGbaKO-A
提取码:alsk
上传安装包并解压(以node1为例):
#使用 rz 命令将安装包上传至 /export/software
#切换目录上传安装包
cd /export/software
rz   
# 选择对应安装包上传即可
#解压安装包到指定目录下
tar -zxvf kafka_2.11-1.0.0.tgz -C /export/servers/
cd /export/servers/
#重命名(因为名称太长了)
mv kafka_2.11-1.0.0 kafka
修改kafka的核心配置文件(以node1为例):
cd /export/servers/kafka/config/
vi server.properties
#主要修改一下6个地方:
broker.id           需要保证每一台kafka都有一个独立的broker
log.dirs             数据存放的目录  
zookeeper.connect   zookeeper的连接地址信息  
delete.topic.enable 是否可以直接删除topic
host.name           主机的名称
#修改: 
listeners=PLAINTEXT://node1:9092 #这里记得修改成这样

#上面就是6个主要的地方

#broker.id 标识了kafka集群中一个唯一broker
broker.id=0 #这里记得要修改,因为如果相同,那么后启动的会报错,即会导致启动失败 
#好像可以设置负数 ,但只能是-1,否则也会报错,因为默认的就是-1
#但是操作时,默认却是从上一个id开始(删除数据文件,然后重新创建即可),没有默认为-1
#但是有些时候,却未必是-1,而是从1001开始,多次的启动,会加1,整个集群都是如此
#无论是否删除数据目录,因为是zookeeper操作的(大概是吧,可以百度查看)
#注意即可
num.network.threads=3   
num.io.threads=8   
socket.send.buffer.bytes=102400   
socket.receive.buffer.bytes=102400   
socket.request.max.bytes=104857600  
# 存放生产者生产的数据的目录,数据一般以topic的方式(该方式后面会说明,一般是"主题_分区id"作为名称)存放    
log.dirs=/export/data/kafka #这里记得要修改      
num.partitions=1   
num.recovery.threads.per.data.dir=1   
offsets.topic.replication.factor=1   
transaction.state.log.replication.factor=1   
transaction.state.log.min.isr=1   
log.retention.hours=168   
log.segment.bytes=1073741824   #1024*1024*1024=1073741824,即1GB,1073741824B=1048576KB=1024MB=1GB
log.retention.check.interval.ms=300000     
# zk的信息,代表操作zookeeper,否则不会操作,但通常规定需要指定,否则可能启动不了
zookeeper.connect=node1:2181,node2:2181,node3:2181 
#这里记得要修改,这里的逗号位置并不需要死磕
#虽然以前死磕过,比如89章博客Spring Cloud和91章博客ES里面就说明过
#因为没有意义,通常没有说明的,是不能加上空格的,可能也可以,但我们统一认为不加空格,无论是否对错
#因为别人修改一下源代码,那么这里的解释可能就会不正确了,即逗号位置没有意义(统一认为不加空格)
#大多数情况下也是如此,注意即可

zookeeper.connection.timeout.ms=6000   
group.initial.rebalance.delay.ms=0  

#下面两个需要添加
delete.topic.enable=true
host.name=node1

#注意:如果不想修改,可以清空文件,复制这里的数据也可以
#虽然可能会有问题,但一般都没有,除非上面漏写或者写错了
将配置好的kafka分发到其他二台主机 :
cd /export/servers
scp -r kafka/ node2:$PWD #这里的-r代表可以操作目录,如果这里没有-r,则代表只能操作文件
#你自己去进行测试即可
scp -r kafka/ node3:$PWD
Linux scp 命令用于 Linux 之间复制文件和目录
scp 是 secure copy 的缩写,scp 是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令
拷贝后,需要修改每一台的broker.id 和 host.name和listeners:
#ip为11的服务器: 
broker.id=0
host.name=node1
listeners=PLAINTEXT://node1:9092
#ip为12的服务器:
broker.id=1
host.name=node2
listeners=PLAINTEXT://node2:9092
#ip为13的服务器:
broker.id=2
host.name=node3
listeners=PLAINTEXT://node3:9092
在每一台的服务器执行创建数据文件的命令:
mkdir -p /export/data/kafka
启动集群:
注意事项:在kafka启动前,一定要让zookeeper启动起来(无论是集群还是单独,单独即可以说是单机),且必须有该配置存在
否则可能启动失败以及提示没有对应的zookeeper配置的错误
虽然后台执行在一瞬间可以使用jps看到,但启动失败必然是需要等待一会才会出现
到那时,使用jps就没有对应的进程了,且使用的jsp对应的kafka位置一般会出现提示
cd /export/servers/kafka/bin 
#前台启动(到对应的控制台地方了,可以使用ctrl+c退出)
./kafka-server-start.sh /export/servers/kafka/config/server.properties
#后台启动
nohup ./kafka-server-start.sh /export/servers/kafka/config/server.properties 2>&1 &
#启动后,实际上虽然没有出现对应的命令行,但相当于在命令行,可以直接执行jps或者随意的输入什么来显示

#注意:可以启动一台broker,单机版,也可以同时启动三台broker,组成一个kafka集群版
#kafka停止
./kafka-server-stop.sh
可以通过 jps命令查看 kafka进程是否已经启动了,如果出现了Kafka,则代表启动了,一般
注意当启动后,他会注册到zookeeper里面,我们可以到对应的zookeeper的bin下执行:
#启动zookeeper客户端,在程序里一般称为得到客户端,因为这里使用自己的来使用,而程序是使用他得到的客户端来使用
zkCli.sh
#输入如下:
ls /brokers/ids
#返回了[0, 1, 2]
#可以发现的确注册了

#具体的ls目录,一般来说如果没有其他的操作的话
#只有zookeeper这一个节点(这个节点不是服务器,而是目录的结构说明)
#如果出现其他的,那么基本是操作了zookeeper,或者zookeeper版本的问题
至此我们的kafka搭建完毕,但我们可以发现,他们之间并没有联系,那么为什么要称为集群呢
实际上是根据zookeeper间接的变成集群的,所以也通常需要zookeeper先启动才可
那么zookeeper是如何确定他们是一个集群的,答:只要指定了同一个zookeeper集群的kafka机器那么就认为是一个集群
所以我们在选择kafka时,实际上只需要指定一个kafka即可(可能都不需要指定),然后自动的指向集群,因为是通过zookeeper
Docker环境下的Kafka集群搭建:
准备工作 :
克隆VM,修改IP地址为192.168.164.20
安装docker - compose(针对于下载来说的安装,最终结果也是拉取):
Compose 是用于定义和运行多容器 Docker 应用程序的工具
如果我们还是使用原来的方式操作docker
那么就需要下载三个镜像:Zookeeper、Kafka、Kafka-Manager(管理kafka的客户端界面)
并需要对Zookeeper安装三次 并配置集群、也需要对Kafka安装三次,修改配置文件,Kafka-Manager安装一次
且需要配置端口映射机器Zookeeper和Kafka容器(包括管理kafka的客户端界面)的信息
但是引入Compose之后,可以使用yaml格式的配置文件配置好这些信息
每个image(镜像)只需要编写一个yaml文件,可以在文件中定义集群信息、端口映射等信息
运行该文件(使用一个该命令)即可创建完成集群
所以通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务
然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有 服务
Compose 使用的两个步骤:
使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行
执行 docker-compose up 命令来启动并运行整个应用程序
#curl 是一种命令行工具,作用是发出网络请求,然后获取数据
curl -L https://github.com/docker/compose/releases/download/1.8.0/run.sh > /usr/local/bin/docker-compose 
#-L 跟随链接重定向,他一般返回的是获取对应文件的数据
#当然,如果是ip加端口,一般是默认其操作的数据,再81章博客内容的最后返回的那个数据就是

#chmod(change mode)命令是控制用户对文件的权限的命令
chmod +x /usr/local/bin/docker-compose #放在这里是为了可以随时的使用的,而不用到对应的目录里面使用"./"执行
#查看版本(顺便会进行拉取操作)
docker-compose --version #因为在/usr/local/bin/里面,所以可以这样执行
#当然不只是查询版本的操作,比如后面的操作创建容器的操作,具体看后面就知道了
拉取镜像:
#拉取Zookeeper镜像
docker pull zookeeper:3.4
#拉取kafka镜像
docker pull wurstmeister/kafka
#拉取kafka-manager镜像
docker pull sheepkiller/kafka-manager:latest

#既然是镜像,那么一定是弄好了环境,我们只需要启动即可,比如zookeeper镜像里面就有jdk环境 
创建集群网络:
基于Linux宿主机而工作的,也是在Linux宿主机创建,创建之后Docker容器中的各个应用程序可以使用该网络
#创建
docker network create --driver bridge --subnet 192.168.0.0/24 --gateway 192.168.0.1 kafka
#查看
docker network ls
网络设置:
#新建网段之后,比如后面创建容器的命令docker-compose,可能会出现:
WARNING: IPv4 forwarding is disabled. Networking will not work.
#解决方式: 
#第一步:在宿主机上执行: 
echo "net.ipv4.ip_forward=1" >>/usr/lib/sysctl.d/00-system.conf
#第二步:重启network和docker服务:
systemctl restart network && systemctl restart docker
#之后再次的操作即可,如果还没有解决,那么就去百度吧
搭建过程:
每个镜像一个Yml文件,即Zookeeper、Kafka、Kafka-Manager各一个
编写yml文件:
在/home里面加上如下文件:
docker-compose-zookeeper.yml:
Zookeeper各个节点的信息,端口映射,集群信息,网络配置:
version: '2' #指定 compose 文件的版本


services: #通过镜像安装容器的配置
  zoo1: #一般要与容器名称一样,当然也可以不同,即这里基本可以随便写
  #只要没有与这个位置名称相同即可,即唯一即可,这样就可以认为是一个整体的操作配置
  #如果有相同的名称,那么谁的配置文件在后面,那么就以谁为主,即覆盖了前面的了,自己测试就知道了
  #比如若这里的名称是zoo2,那么就没有zoo1这个容器,因为后面的zoo2覆盖了,即只会出现创建zoo3和zoo2这两个容器
    image: zookeeper:3.4 #使用的镜像
    restart: always #当Docker重启时,该容器重启
    hostname: zoo1 #类似于之前在基于Linux虚拟机Kafka集群中hosts文件的对应值,可以说是主机别名
    #当然并不是一定要与容器名称一致,基本可以随便写
    container_name: zoo1 #容器的名称
    ports: 
    - 2184:2181 #端口映射
    environment: #集群环境
      ZOO_MY_ID: 1 #当前Zookeeper实例的id
      #集群节点
      ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
    networks: #使用的网络配置
      kafka:
        ipv4_address: 192.168.0.11


  zoo2:
    image: zookeeper:3.4
    restart: always
    hostname: zoo2
    container_name: zoo2
    ports:
    - 2185:2181
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=0.0.0.0:2888:3888 server.3=zoo3:2888:3888
    networks:
      kafka:
        ipv4_address: 192.168.0.12


  zoo3:
    image: zookeeper:3.4
    restart: always
    hostname: zoo3
    container_name: zoo3
    ports:
    - 2186:2181
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=0.0.0.0:2888:3888
    networks:
      kafka:
        ipv4_address: 192.168.0.13


networks:
  kafka:
    external:
      name: kafka
      
      
      
      
      
docker-compose-kafka.yml:
version: '2'


services:
  kafka1:
    image: wurstmeister/kafka #image
    restart: always
    hostname: kafka1
    container_name: kafka1
    privileged: true
    ports:
    - 9092:9092
    environment: #集群环境配置
      KAFKA_ADVERTISED_HOST_NAME: kafka1
      KAFKA_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka1:9092
      KAFKA_ADVERTISED_PORT: 9092
      KAFKA_ZOOKEEPER_CONNECT: zoo1:2181,zoo2:2181,zoo3:2181
    external_links: # 配置Zookeeper集群的地址,上面的zoo1,zoo2,zoo3就是下面的信息,下面去找对应容器名称
    #所以这里是上面集群的操作zookeeper的前提
    - zoo1
    - zoo2
    - zoo3
    networks:
      kafka:
        ipv4_address: 192.168.0.14


  kafka2:
    image: wurstmeister/kafka
    restart: always
    hostname: kafka2
    container_name: kafka2
    privileged: true
    ports:
    - 9093:9093
    environment:
      KAFKA_ADVERTISED_HOST_NAME: kafka2
      KAFKA_LISTENERS: PLAINTEXT://kafka2:9093
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka2:9093
      KAFKA_ADVERTISED_PORT: 9093
      KAFKA_ZOOKEEPER_CONNECT: zoo1:2181,zoo2:2181,zoo3:2181
    external_links:
    - zoo1
    - zoo2
    - zoo3
    networks:
      kafka:
        ipv4_address: 192.168.0.15


  kafka3:
    image: wurstmeister/kafka
    restart: always
    hostname: kafka3
    container_name: kafka3
    privileged: true
    ports:
    - 9094:9094
    environment:
      KAFKA_ADVERTISED_HOST_NAME: kafka3
      KAFKA_LISTENERS: PLAINTEXT://kafka3:9094
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka3:9094
      KAFKA_ADVERTISED_PORT: 9094
      KAFKA_ZOOKEEPER_CONNECT: zoo1:2181,zoo2:2181,zoo3:2181
    external_links:
    - zoo1
    - zoo2
    - zoo3
    networks:
      kafka:
        ipv4_address: 192.168.0.16


networks:
  kafka:
    external:
      name: kafka

docker-compose-manager.yml:
version: '2'


services:
  kafka-manager:
    image: sheepkiller/kafka-manager:latest
    restart: always
    container_name: kafka-manager
    hostname: kafka-manager
    ports:
     - 9000:9000
    environment: #可以管理zookeeper集群和kafka集群
     #单独写一个也行,因为是集群,如果删除,那么访问时(可以访问),对应的界面可能会出现错误提示
     #当然,如果有不存在的,即无论是否有正确的,只要有不存在的,那么也是有错误提示,他们基本是同一个错误提示
     ZK_HOSTS: zoo1:2181,zoo2:2181,zoo3:2181 
     #这里如果都是9092或者单独,其实也可以,因为只要有一个正确的kafka即可(可能都不需要指定,即这里可以删除)
     #因zookeeper里面包含了kafka集群的信息,自然也包含了地址等等,即可以不用写kafka的配置,即可以删除
     KAFKA_BROKERS: kafka1:9092,kafka2:9093,kafka3:9094
     APPLICATION_SECRET: letmein
     KM_ARGS: -Djava.net.preferIPv4Stack=true
    networks:
     kafka:
      ipv4_address: 192.168.0.17




networks:
  kafka:
    external:
      name: kafka
当然,你可以现在自己电脑上创建好,然后将yaml文件上传到Docker宿主机中:
安装:yum install -y lrzsz
然后输入rz,上传文件到当前目录即可
如果上面的文件,在操作时,出现问题,可以使用这里地址的文件
因为上面复制粘贴时可能有语法格式错误或者有隐藏字符的影响:
链接:https://pan.baidu.com/s/1wUG2Vf4Z3rUZqQpEwTtcFg
提取码:alsk
开始部署:
使用命令:docker-compose -f xxx up -d
参数说明:
up表示启动,如果没有容器,则会创建后,在启动,有容器的话,就只有启动
一般启动顺序是随机的,看谁先抢到了资源(资源可以认为是cpu资源),类似于多线程,所以是随机,因为你基本不会一直抢到
-d表示后台运行,-f表示加载指定位置的yaml文件
docker-compose -f /home/docker-compose-zookeeper.yml up -d
docker-compose -f /home/docker-compose-kafka.yml up -d
docker-compose -f /home/docker-compose-manager.yml up -d
如果没有对应镜像,也会拉取,就如上面的docker-compose --version命令一样
实际上只要使用了docker-compose命令,那么就会使用镜像,没有就会拉取
测试:
浏览器访问宿主机:http://192.168.164.20:9000/
如果访问后,什么都没有,那么一般需要我们手动的选择部署
即上面他对应的文件可能只是操作了定义,可能并没有自动部署
如图:

在这里插入图片描述

当然,如果不出现这个页面,即访问不了对应的地址,可以重启容器,或者删除容器,再次的创建容器
甚至你可以全部删除然后重新来过
我们点击Cluster,然后点击Add Cluster进入如下:

在这里插入图片描述

输入上面的值,后面的不用修改,然后点击后面的保存即可,如果出现问题,输入他提示的默认值即可(一般都是默认2这个值)
然后出现如下:

在这里插入图片描述

点击Go to cluster view.即可

在这里插入图片描述

这时自己进行点击查看吧,这样我们就已经操作成功了
即创建了一个部署名称节点来操作我们的部署后的信息,即zookeeper_cluster这个名称
这个名称在对应客户端的服务器里面,如果他删除,然后来个新的,自然他也会没有,因为是他的名称节点
当然手动删除也可以
需要使得无效,才会出现删除按钮,到时也点击不了,使得有效即可回来,那么也就可以点击了,需要根据界面来理会,即如下:

在这里插入图片描述

点击:即点击zookeeper_cluster这个,Disable代表使得无效,自己进行测试就知道了,这里就不做说明
现在我们点击Topic,然后点击Create,进入如下,来创建主题:

在这里插入图片描述

点击创建,如果没有错误,那么就说明的确操作完成,即集群操作完毕
你也可以点击Topic,然后点击List,然后找到并点击test查看即可
现在我们使用docker搭建的集群来操作
kafka的基本操作:
创建topic:
创建一个名字为test的主题, 有一个分区,有三个副本,一个主题下可以有多个分区,每个分区可以用对应的副本
#登录到Kafka容器
#进入一个kafka集群,这里的9218e985e160指定的是容器id,当然指定容器名称也可以
#自己选择一个kafka集群的其中一个
docker exec -it 9218e985e160 /bin/bash 
#切换到bin目录
cd opt/kafka/bin/
#执行创建test主题
kafka-topics.sh --create --zookeeper zoo1:2181 --replication-factor 3 --partitions 1 --topic test
#如果test存在,一般执行时会提示错误信息,可以创建testt,将上面的test修改成testt即可
#--create:新建命令
#--zookeeper:Zookeeper节点,一个或多个,因为是集群,创建主题一般会进行操作zookeeper集群,如注册(添加)信息
#其中的zoo1,代表是操作zoo1名称的容器
#--replication-factor:指定副本,每个分区有三个副本
#--partitions:表示几个分区
然后到如下:

在这里插入图片描述

点击左边的3,出现如下:

在这里插入图片描述

一般上面的第一个是默认存在的主题,即__consumer_offsets 这个topic是由kafka自动创建的
默认50个分区,一个副本,一般存储消费位移信息(offset)
比这里还老的版本的架构中一般是存储在Zookeeper(可以加上集群两字)中,而不是kafka(可以加上集群两字)里面
我们发现,在启动中可以操作信息,就相当于我们启动mysql服务(或者rabbitmq服务)进入控制台(客户端),执行命令一样
虽然这里看起来并没有出现在独有的控制台来操作,但相当于在控制台执行
我们说的控制台,只是一个可以执行命令的地方,并不是说,一定要在某个地方才算控制台,可以简称为
只要能够操作命令,就可以称为控制台(或者说客户端)
换言之,就是能直接操作启动服务里的数据的,就可以称为控制台(客户端)
当然,他并不是像一些文件或者其他框架一样需要重新部署
比如tomcat,由于操作的方式不同,所以有区别
即他tomcat基本只操作内存(通常需要部署),而这里是操作硬盘(可以这样说,那么就不需要部署)
如果我们创建如下:
kafka-topics.sh --create --zookeeper zoo1:2181 --replication-factor 3 --partitions 3 --topic testtt
那么点击如下:

在这里插入图片描述

点击testtt,到如下:

在这里插入图片描述

可以发现,有三个分区,且对应的副本也有三个(与es副本不同的是,首先在自己机器来个副本,而不是一开始就是下一个)
查看主题命令:
查看kafka当中存在的主题:
kafka-topics.sh --list --zookeeper zoo1:2181,zoo2:2181,zoo3:2181 #当然,因为集群,选择一个也可以
生产者生产数据:
模拟生产者来生产数据:
Kafka自带一个命令行客户端,它从文件或标准输入中获取输入,并将其作为message(消息)发送到Kafka集群
默认情况下,每行将作为单独的message发送
运行 producer,然后在控制台输入一些消息以发送到服务器
kafka-console-producer.sh --broker-list kafka1:9092,kafka2:9093,kafka3:9094 --topic test
#上面可以进入控制台,但也只针对于test主题来操作的,其中也可以只写一个节点(kafka1:9092),而不用都写上
#因为他们是集群,只需要指定一个即可,一般是集群的,通常只需要指定一个,有些会自动导致操作集群的可以不写
#如前面docker-compose-manager.yml文件里面的kafka集群配置那里,就可以不写,即可以删除

#然后输入如下:
This is a message
This is another message

dd
#ctrl+c退出命令行
#控制台,客户端,命令行,他们可以是一个意思
#无论是否有对应专属的命令窗口,只要可以直接操作具体数据,那么都是一个意思
#或者也可以认为没有专属的会默认到专属窗口里面去,我们一般以这个为主
#那么无论你是使用java得到客户端还是直接使用客户端,都只是该专属窗口的多种打开方式,即客户端可以认为是专属窗口
#就如mysql一样,打开多个窗口,但都可以操作数据
消费者消费数据 (消息并不是真的消费,消息还是在的,因为是拉取,前面说过了):
kafka-console-consumer.sh --bootstrap-server kafka1:9092,kafka2:9093,kafka3:9094 --topic test --from-beginning

#显示:
This is a message
This is another message

dd
#使用ctrl+c退出显示,一般使用后,会出现显示了多少行的信息出来

#--broker-list与--bootstrap-server是一样的操作,但针对于命令来说不一样,其中--broker-list是旧版本命令
#即虽然是实现一个功能
#但是对于kafka-console-producer.sh可以是--broker-list和--bootstrap-server
#而kafka-console-consumer.sh只能是--bootstrap-server,否则会提示命令错误
注意:如果发送消息和消费消息的命令对应的主题不存在,那么会自动创建(发送消息需要发送后才会创建,否则不会)
当然,使得创建的消息也算是发送的,即可以获取得到
创建后,自然也会发送消息或者消费消息
默认是1个分区以及一个副本,主题名称就是你指定的名称,如果创建时出现错误信息,并不需要理会,因为没有影响
对于kafka来说,一个主题多个分区,分区内消息有序,后面的随机(策略的说明)实际上基本都是操作策略的
而之所以现在说成是随机,是因为现在并没有说明是什么策略(当然也添加了策略的说明的),即保守的说法
后面说到策略时,就知道了,注意即可
而主题的创建基本也是随机的(一般是根据策略选择节点,通常选择主节点)给集群的某个节点
主要是这一个,因为主题只有一个,而没有多个,或者说操作同步主题
虽然是随机的,但是我们操作时,基本都会指定主题,即注意即可
但如果要确保消费者消费的消息是顺序的
通常需要把消息存放在同一个topic的同一个分区
分区有副本,虽然其他可能没有主题,但也只是存放数据,等待变成主
然后自动移动对应主题,到主的节点,即任然是同一个主题(主要是这一个,因为主题只有一个,而没有多个)
或者说是同步了主题,那么只需要等待变成主
通过主题,可以确定是那些节点的分区,简单来说就是多分区,而不是只会在当前主题的当前节点里面操作多个分区
因为分区是分配给节点的,只是主题只有一个而已,且通常在主节点,主要用来确定分区的地方
无论生产还是消费,在这些确定的分区里面,基本操作了分区策略
但一般指定的是所有的分区(类似于rabbitmq路由一样的操作所有对应队列,不同的是,他的队列在当前节点
而不是其他节点,rabbitmq是操作完全同步的
但这里却是操作策略给的,前面说明过了,但对象指定还是所有,没有不会选择的分区),而不是独有
消费也一般是消费所有分区(而不是类似于rabbitmq一样,一个消费者只操作一个队列)
消费顺序除了分区里面的消息是有序外
分区选择基本是随机的(一般是根据策略选择分区,可能是随机且有序,注意是可能,无论是这里还是其他的框架或者中间件
并没有真正的随机,因为随机中,必然是有有序的,因为在程序里面是伪随机的
虽然这里可能并没有操作随机的,主要看策略,具体可以百度有什么策略
所以一般多次的消费多分区的消息,可能返回顺序不同,但可以看到相同的地方
特别在后面的java消费者和java生产者之间的操作)
当然消息发送给分区也是根据分区策略来给的
当然,这些都可以进行设置目标的,所以如果是一个分区,那么顺序基本相同,但是如果是多个分区,可能不会相同
因为一个分区,必然是指定该分区,所以得到一个消息后,无论怎么选择,还是该分区,且由于分区消息(可以说成信息)有序
所以得到的消息也有序,但是,如果是多个分区,在得到一个消息后
可能会选择其他分区,这个选择,可能是随机的
且必须选择有数据的(比如没有数据的不考虑或者跳过,然后继续随机其他的分区)
而正是这样,可能使得顺序不是添加的顺序了,自己测试就知道了,记得多次的执行消息信息
运行describe的命令:
运行describe查看topic的相关详细信息:
#查看topic主题详情,Zookeeper节点写一个和全部写,效果一致
kafka-topics.sh --describe --zookeeper zoo1:2181,zoo2:2181,zoo3:2181 --topic test
结果:

在这里插入图片描述

这是输出的解释,第一行给出了所有分区的摘要,每个附加行提供有关一个分区的信息,有几个分区,下面就显示几行
leader:是负责给定分区的所有读取和写入的节点,每个节点将成为随机选择的分区部分的领导者
replicas:显示给定partiton所有副本所存储节点的节点列表,不管该节点是否是leader或者是否存活
isr:副本都已同步的的节点集合,这个集合中的所有节点都是存活状态,并且跟leader同步
所以这里与其他的框架或者中间件不同的是,真正的副本存在,要重点关注这个
增加topic分区数(最好理解为添加到对应的分区数):
任意kafka服务器执行以下命令可以增加topic分区数
8代表总数,而不是添加8个分区,添加时需要大于本身的分区数,所以下一次添加就需要大于8了,否则会有错误信息:
kafka-topics.sh --zookeeper zoo1:2181 --alter --topic test --partitions 8
当然,也可以在这里添加:

在这里插入图片描述

右边有一个Add Partitions,点击后到如下:

在这里插入图片描述

上面的test是当然主题的名称,如果你是在test主题里点击的,那么这个不能改变,因为只能操作自己,而不是操作其他主题
上面的10代表总共多少分区,如果没有超过10,那么不会给你创建
即这里要大于10,而不能等于10,代表总共多少,而不是添加多少,因为总不能少于或者不变吧
否则当你点击添加时,会到一个错误页面,点击错误信息的"Try again"即可,就会再次的回到上面图中的页面
增加配置
一般添加的配置都可以写在配置文件里,变成永久的,而不是只针对当前命令,命令结束,那么他又需要加上该命令:
flush.messages:
此项配置指定时间间隔:
强制进行fsync日志(简单来说就是我们发送消息,并不是在硬盘上而是在内存里,他用来操作内存到硬盘的时间间隔)
他的默认值为None,代表不放在硬盘
当然,默认值可能也是5,即5秒(注意这里不是"5条",而是"5秒",看清楚了)消息写入一次
例如,如果这个选项设置为1,那么每条消息之后都需要进行fsync,如果设置为5,则每5条消息就需要进行一次fsync
一般来说,建议你不要设置这个值,此参数的设置,需要在"数据可靠性"与"性能"之间做必要的权衡
因为:
如果此值过大(性能好点),将会导致每次"fsync"的时间较长(IO阻塞)
如果此值过小(可靠性好点),将会导致"fsync"的次数较多
但这也意味着可能整体的client请求有一定的延迟,或者物理server故障,可能也将会导致没有fsync的消息丢失
默认的5秒,是保证性能,虽然可能并不可靠
动态修改kakfa的配置:
kafka-topics.sh --zookeeper zoo1:2181 --alter --topic test --config flush.messages=1
#实际上命令的"--xxx"可以不分先后(在54章有类似的说明,比如"-"),自己测试即可
#当然固定后面要有数据的,不能分开
删除配置:
动态删除kafka集群配置:
kafka-topics.sh --zookeeper zoo1:2181 --alter --topic test --delete-config flush.messages
#删除后的配置,一般就是根据默认值了,实际上只是没有属性操作赋值而已,所以是操作默认值
删除topic:
目前删除topic在默认情况只是打上一个删除的标记
在重新启动kafka后才会操作删除(当然有些版本可能不会重启删除),删除是删除硬盘上的
而我们显示的内容也是硬盘上的,而不会显示内存上的,这里与rabbitmq是不同的,即信息保存在硬盘,而不是内存里面
虽然前面也说过,而内存信息的用处是通过上面的flush.messages属性变成硬盘的信息的
但是这个删除我们也是不能看到的,因为删除标记的存在
如果需要立即删除,则需要在server.properties中配置属性如下:
delete.topic.enable=true,默认值为false
最好在所有的集群节点都进行设置,虽然只设置当前的节点也可以,但也只能操作当前的节点
因为,如果操作kafka集群的话,可能操作的不是这一个设置了delete.topic.enable=true的节点
那么可能就操作false了,虽然并没有太大的影响,但可能会有其他影响,虽然并不知道(你可以百度查看)
所以所有的节点最好都设置delete.topic.enable=true
这里删除了主题,那么也会删除在不同的kafka节点(包括自己)中分配的分组信息和副本信息,这些信息也会删除
然后执行以下命令进行删除topic
kafka-topics.sh --zookeeper zoo1:2181 --delete --topic test
具体添加方式,可以使用echo delete.topic.enable=true >> /opt/kafka/config/server.properties追加到里面去
这里是两个">“,而不是一个”>“,即”>>“则代表追加,而不是覆盖,而”>"代表覆盖,而不是追加
追加后,重启容器,即可生效
Java API操作kafka(虽然是以192.168.200为开头的例子,而不是192.168.164为开头的例子):
因为主机名称在前面设置与容器名称一样,所以这里可以说成以主机名称通信,实际上是以容器名称通信的

在这里插入图片描述

修改Windows的hosts文件:
目录:C:\Windows\System32\drivers\etc (一般win10是这个目录)
内容:
192.168.164.20 kafka1
192.168.164.20 kafka2
192.168.164.20 kafka3
#后面操作时,根据名称自己加上对应端口即可,可以看上图就知道了:kafka1是9092,kafka2是9093,kafka3是9094
#这样主要是为了规范以及好的分别(分别处理,即区分),当然,你直接的写地址也可以
创建maven的工程,导入kafka相关的依赖
<dependencies>
 <!-- https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients -->
    <dependency>
        <!--有KafkaProducer类(消息生产者类)可以操作-->
        <groupId>org.apache.kafka</groupId>
        <artifactId>kafka-clients</artifactId>
        <version>1.0.0</version>
    </dependency>    
</dependencies>
<build>
    <plugins>
        <!-- java编译插件 -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.2</version>
            <configuration>
                <source>1.8</source><!--开发中的jdk版本-->
                <target>1.8</target><!--开发后的,即class文件的jdk版本-->
                <encoding>UTF-8</encoding><!--整个代码使用UTF-8编码-->
            </configuration>
             </plugin>
    </plugins>
</build>
具体项目目录:

在这里插入图片描述

生产者代码:
创建com.lagou.Producer类:
package com.lagou;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

/**
 * 消息的生成者
 */
public class Producer {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //要构造消息生产者的对象,需要有关于kafka集群等的配置,可以从Properties文件里加载
        //或者说成也可以从Properties对象中加载
        //kafka生产者按照固定的key取出对应的value
        Properties properties = new Properties();
        //指定集群节点,在前面的命令中,我们知道
        //对于kafka-console-producer.sh可以是--broker-list和--bootstrap-server
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.164.20:9092,192.168.164.20:9093,192.168.164.20:9094"); //只要有一个即可,因为是集群,若有正确的,那么无论是否有不正确的都可以,与zookeeper不同
        //zookeeper操作选择,当然并不绝对

        //发送消息,网络传输,需要对key和value指定对应的序列化类
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        //创建消息生产者类的对象,一般通过网络直接操作信息
        //通常会操作网络序列化传递(比如dubbo,rabbitmq)
        //当然上面的传递,一般都理解为序列化(序列化:数据的传递规则,比如AMQP,http,tcp的等等)
        //并不是java说的类的序列化,当然他也可以说是,因为也是一种规则
        //当然AMQP协议相当于我们定义的(虽然用在rabbitmq上),基本上http或者tcp是固定
        //所以可以理解成相当于我们自己操作一个规则来操作数据,然后通过http或者tcp传输一样的意思
        //通常我们将网络数据的传输操作,称为序列化,自己之间的就叫传递,因为网络之间的传输一般是有一定规则的
        //而自己之间通常没有规则
        KafkaProducer<String, String> producer = new KafkaProducer<String,String>(properties);

        //定义主题
        String topic = "lagou";

        //发出100条消息
        for(int i = 1; i<=100; i++){
            //设置消息的内容
            String msg = "hello," + i;
            //构建一个消息对象:指定主题和消息
            ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topic,msg);

            //发送,即通过上面的操作,我们可以直接的给主题发送消息
            //你可以理解为我们首先进入客户端,然后发送消息,但是这是死板的理解
            //不同的连接操作可能不同,实际上只要数据对应,自然也是发送
            //所以我们也可以这样理解,窗口只是给定初始数据或者单纯的平台
            //具体的执行才是发送,只是这里一步到位(直接发送)而已
            
            //这里是创建主题的地方(方法)
            //如果主题存在,那么直接给该主题发送消息
            //否则若不存在,会帮你创建该主题(名称是你传入的名称),这里就是lagou
            //然后给该主题发送消息(创建的主题,操作一个分区,一个副本)
            producer.send(producerRecord);
            /*
            也可以这样,来确认是否发送成功
            RecordMetadata recordMetadata = producer.send(producerRecord).get();
            System.out.println(recordMetadata); 
            如果返回了消息,则代表发送成功,否则如果发送失败的话,就会抛出异常,或者返回null,一般是抛出异常
            
            或者这样
            Future<RecordMetadata> send = producer.send(producerRecord);
            RecordMetadata recordMetadata = send.get();
            System.out.println(recordMetadata);
            */
            System.out.println("消息发送成功,msg:"+msg);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //关闭消息生产者对象,因为他是连接的,要不然怎么能执行语句呢
        //或者说网络的连接的数据通道可以认为是文件的连接,虽然并不是,只是一种理解而已
        producer.close();
    }
}

消费者代码:
在lagou包下创建consumer类:
package com.lagou;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Collections;
import java.util.Properties;

/**
 *
 */
public class consumer {
    public static void main(String[] args) {

        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.164.20:9092,192.168.164.20:9093,192.168.164.20:9094"); //只要有一个即可,因为是集群,若有正确的,那么无论是否有不正确的都可以,与zookeeper不同
        //zookeeper操作选择,当然并不绝对

        //接收消息,网络传输,需要对key和value指定对应的反序列化类
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        //指定分组的名称
        properties.put(ConsumerConfig.GROUP_ID_CONFIG,"lagou_group1");
        //这里你可能会有疑问,为什么之前没有这样的操作呢,在这之前,首先问一个问题
        //如果两个消费者同时执行(假设没有分组)
        //那么取得的数据是否可能是一样的或者可能少了消费数据(偏移量的问题)
        //答:可能都是同样的数据或者可能少了消费数据,他们都是取得生产者的数据,但实际上必须指定分组
        //而使用分组,那么如果消费者都是在一个分组里面,那么你消费时,是分组给你消费的消息
        //也就是说,无论这些消费者是启动多少,都是分组去拿数据然后给你的
        //这也就使得了,数据不会取得一样的,因为分组只有一个,且当成消费者
        //那么自然一个消费者的数据自然是完整的获取生产者的数据的,而不会出现获取相同的数据
     //相当于即多个消费者抢占分组获取的消息,分组是保留到集群的,所以固定一个,否则也就相当于只有多个消费者了
        //虽然这里说明成是分组给我们消息,实际上分成两种情况
        //第一种,如果是一个消费者,那么相当于他去其他分区拿消息,以分组来说,该消费者可以就看出分组
        //如果是第二种(上面说明的就是),那么相当于他们各自占用分区拿数据
        //后面的消费者负载均衡机制里,会说明这种情况
        //即第二种以分组来说,可以理解成,分组将分区的数据划分,而不是一个一个的划分
        
        //上面说明两个消费者可以取得一个数据,那么也就认为不同的组
        //可以取得相同的数据,这是必然的,因为按照实际情况来说,用户自然是很多的
        //所以分组简单来说就是:约束消息的分发,而让消息只能发给一个消费者

        //所以为了规定不会获取同一个消息,一般都操作了分组,否则不指定分组的话,一般会报错

        //那么在命令的操作中,是否操作了分组,答:操作了分组
        //一般命令那里的分组有默认的(这里java操作没有,所以需要指定)
        //可能是""(空串),或者是test-consumer-group
        //也有可能是随机的值(不会操作已经存在的),所以可以不用指定(我们操作命令时也并没有指定)

        //那么由于分组信息在集群中,所以你消费的数据,就不能再次的消费了
        //即这里执行后,再次的执行,没有数据,但是命令操作有,因为不是同一个分组
        //一般是分组偏移量的原因,该偏移量是对主题的总体偏移量
        //虽然一个分区的是有序的,但是对于他们来说,虽然偏移量对自己都有相同的,但是整体却没有
        /*
        即可以这样的理解:
        =========================
        =     ==       ==       =
        上面有三个分区,但是他们自己也自带一个偏移量,但是正是因为这样,所以在多个分区操作时
        可能获取数据对应的偏移量可能上一个是100,下一个可能是30(到另外一个分区的偏移量了)
        或者出现上一个是30,下一个也是30,另外一个正好也是30的偏移量
        所以对于整体来说是没有的相同的(虽然偏移量相同)
        
        但是我们也可以看到,当生产者启动,然后消费者启动消费时
        对应的偏移量是首先先全部操作完毕,也就是说,三个分区,打印信息时
        出现三个相同偏移量,然后再下一个,根据这个理解
        我们可以继续解释前面的发送消息到分区域以及获取分区消息的策略,大概是偏移量平均
        即首先先弄好所有的偏移量,然后才能下一个偏移量
        但先给分区那个基本就是随机了(可能也有策略,如前面说的随机且有序)
        获取消息也是如此,先获取同一个偏移量(具体是那个分区,可能也有策略,如前面说的随机且有序)
        然后才能下一个偏移量,当然上面的偏移量可能也是操作策略,这里简称"偏移量策略"
        这里是程序的打印,接下来我们可以通过命令的打印,可以发现,与程序的不同,通过研究发现
        之所以不同是因为他先操作已经消费的数据,也就是说,如果是已经存在的数据,那么就有如下
        他是选择一个分区,将所有的消息打印出现,而不是一个偏移量
        只是这个选择分区也是根据策略的,如前面说的随机且有序
        但他并不是根据相同偏移量来的(与偏移量策略不同),简称"直接策略"
        
        继续通过测试,可以发现,无论是程序还是命令
        如果是实时的获取,那么就是按照偏移量策略,否则就是直接策略
        即很明显他们之间肯定是有不同的设置,具体可以百度策略
        
        因为我们主要操作程序,所以获取消息的策略(不是选择分区的策略)
        以偏移量策略为主,直接策略为辅
         */

        //消息消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //定义主题
        String topic = "lagou";

        //订阅消息
        consumer.subscribe(Collections.singletonList(topic));
        while(true){
            System.out.println(1); //打印多次次,因为获取值,只会获取一个值
            //获取消息的方法是一个阻断式方法,只要没有消息就会一直等待,如果有消息立即读取消息
            //他获取主题数据时,如果没有主题,那么他会帮忙创建(默认也是一个分区,一个副本)
            //名称是你传入的名称,这里就是lagou
            ConsumerRecords<String, String> poll = consumer.poll(500);//500就是超时时间
            //如果在超时时间内,没有消息,那么返回数据
            //即没有值的数据,自然下面的集合不会得到,因为是空的集合

       //自然读取的是一个消息(包含了除了对应对应偏移量从0开始的消息外,还有其他信息,比如主题,偏移量等等)

            //集合的长度,如果没有数据(即空集合),则是0,否则一般有数据,也只是一个数据,则是1
            System.out.println(poll.count()); 
            for(ConsumerRecord<String,String> consumer1 : poll){

                System.out.println("主题:"+consumer1.topic());
                System.out.println("偏移量:"+consumer1.offset());
                System.out.println("消息:"+consumer1.value());
            }
            System.out.println("========");



        }
        //因为上面是无限循环,所以这里不用写如下:
        //consumer.close();
        //写了会报错提示,除非上面的代码不无限,这是idea的大致检查的作用
    }
}

上面通过我们设置的配置,那么在连接kafka时,会首先初始化连接信息,相当于通过上面的理想控制台操作
实际上大多数可以设置的,是服务器给客户端的例外的权限,基本只会操作客户端操作的信息
至此我们使用java操作kafka的消费者和生产者操作完毕
最后注意:在程序里我们是操作无限循环的,实际上我们操作命令时,也是无限循环的,即实际上命令也是一个发送的程序
只是这里是我们写而已,但对象都是kafka集群的数据
Apache kafka原理 (大致了解即可):
分区副本机制:
kafka有三层结构:kafka有多个主题,每个主题有多个分区,每个分区又有多条消息
至于为什么这样说明,看如下:
分区机制:主要解决了单台服务器存储容量有限和单台服务器并发数限制的问题,一个分片的不同副本不能放到同一个broker上
即副本数量不能大于分区数量,这里与前面的es是不同的,这是kafka的限制,因为es的消息主要是查询
而我们主要是消费,即必须保证一个机器只能是一个对应的消息
当然,当前主分区的副本是当成备份的,而不会参与变成主的操作(其他的副本会,这里是主要的区别)
但他们没有变成主之前,都是不可以被消费的消息
当然副本也可以手动操作(主要操作主分区的副本,因为是备份,虽然其他分区也可以)
但是当主题数据量非常大的时候,一个服务器存放不了,就将数据分成两个或者多个部分,存放在多台服务器上
每个服务器上的数据,叫做一个分片

在这里插入图片描述

分区对于 Kafka 集群的好处是:实现负载均衡,高存储能力、高伸缩性,分区对于消费者来说,可以提高并发度,提高效率
副本:副本备份机制解决了数据存储的高可用问题
当数据只保存一份的时候,有丢失的风险,为了更好的容错和容灾,将数据拷贝几份,保存到不同的机器上

在这里插入图片描述

即出现了一个主题,有多个分区,但也很明显,使用副本,比如需要大的空间,所以副本数量一般要好好考虑
多个follower副本通常存放在和leader副本不同的broker中
通过这样的机制实现了高可用,当某台机器挂掉后,其他follower副本也能迅 速"转正",开始对外提供服务
kafka的副本都有哪些作用?
在kafka中,实现副本的目的就是冗余备份,且仅仅是冗余备份,所有的读写请求都是由leader副本进行处理的
follower副本仅有一 个功能,那就是从leader副本拉取消息,尽量让自己跟leader副本的内容一致
说说follower副本为什么不对外提供服务?
这个问题本质上是对性能和一致性的取舍,试想一下,如果follower副本也对外提供服务那会怎么样呢?
首先,性能是肯定会有所提升 的,但同时,会出现一系列问题,类似数据库事务中的幻读,脏读
比如你现在写入一条数据到kafka主题a,消费者b从主题a消费数据,却发现消费不到,因为消费者b去读取的那个分区副本中
最新消息还没写入,而这个时候,另一个消费者c却可以消费到最新那条数据,因为它消费了leader副本
为了提高那么些性能而导致出现数据不一致问题,那显然是不值得的
kafka保证数据不丢失机制:
从Kafka的大体角度上可以分为:数据(消息)生产者,Kafka集群,还有就是消费者,这三个角度
而要保证数据的不丢失也要从这三个角度去考虑
消息生产者:
消息生产者保证数据不丢失:消息确认机制(ACK机制),参考值有三个:0,1,-1,默认值是1
//ack=0
//producer无需等待来自broker的确认而继续发送下一批消息
//这种情况下数据传输效率最高,但是数据可靠性确是最低的       
properties.put(ProducerConfig.ACKS_CONFIG,"0");

//ack=1
//producer只要收到一个分区副本成功写入的通知就认为推送消息成功了
//这里有一个地方需要注意,这个副本必须是leader副本
//只有leader副本成功写入了,producer才会认为消息发送成功
//也就是说,我们在前面发送消息都是操作leader副本(或者说是主题存在的服务器,因为主题一般在主服务器)
//然后他给我们操作到其他分区,那么可能给分区消息时,可能会先给主,其他的可能是随机且有序的
//当然,一般都是整体的
//而读取并没有这样的解释,读取是操作整体
properties.put(ProducerConfig.ACKS_CONFIG,"1"); //默认这个

//ack=-1
//简单来说就是,producer只有收到分区内所有副本的成功写入的通知才认为推送消息成功了
properties.put(ProducerConfig.ACKS_CONFIG,"-1");

//这里的副本,都是机器,分为:follower副本和leader副本,即kafka集群的节点


//上面写在生产者代码里面,使用对应的配置前面,这是基本的操作,使得生效
消息消费者:
kafka消费消息的模型:

在这里插入图片描述

即消费消息,设置好offset,类比一下:
Kafka动作看书动作
消费消息看书
offset位移书签
什么时候消费者丢失数据呢?
由于Kafka consumer默认是自动提交位移的(先更新位移,再消费消息)
如果消费程序出现故障,没消费完毕,则丢失了消息,此时,broker并不知道,但位移已经更新了
所以相当于丢失了消息,因为消费不到那个消息了,只能消费下一个消息
你也可以理解为我们取出数组的数据,当我们要取某一个下标的值时,突然出现故障(假设的)
使得没有取出数据,但下标加1了(就是提交偏移量,提交的是下一个位置)
当我们再次来取出数据时,由于下标加1,那么原来的下标的数据就取不出来了,即数据丢失了
大多数的情况下,我们理解的偏移量是这样的意思
解决方案:
enable.auto.commit=false,关闭自动提交位移
在消息被完整处理之后再手动提交位移:
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
#然后再消费代码后面加上提交位移的代码,具体可以百度,比如consumer.commitAsync();,异步提交
#commitSync()是同步提交
#同步自然是根据代码顺序来执行的,我没有执行完,后面的代码不能操作
#而异步则不会,后面的代码照常操作

#上面写在消费者代码里面,使用对应的配置前面,这是基本的操作,使得生效

#那么有个问题,消费者除了消息丢失外,还有其他的问题吗,答:有
#虽然是自动提交,但是再kafka中,自动这个词,可能是有延迟的,那么假设为5秒提交一次(虽然一般都默认是5秒)
#那么在这个中间,是没有提交了,当同样的组的消费者消费时
#可能在这个间隙里面,会消费掉,也就使得消费了同一个消息了

#当然,如果你并没有设置手动的提交
#那么由于真实偏移量(我们操作消费的消息是操作下标,而不是起始,这个叫做下标偏移量)不变
#即虽然你消费时,是往后消费的
#但是其他的同组消费者或者本身再次的消费
#是以偏移量消费,而不是你的下标偏移量消费,具体的解释就是,只要你不提交偏移量
#那么其他的消费者就不会以对应的你没有提交的偏移量开始

#所以偏移量虽然说成是下标,实际上真正的意思却是可以认为是起始下标


#上面你可以通过测试来完成,其中自动的是有延迟的,可以等待多执行一下,一般上面的解释没有错误,因为我就是测试过的
#除非我的测试有误差
消息存储及查询机制 :
kafka 使用日志文件的方式来保存生产者消息,每条消息都有一个 offset 值来表示它在分区中的偏移量
Kafka 中存储的一般都是海量的消息数据,为了避免日志文件过大
一个分片(也可以说成分区) 并不是直接对应在一个磁盘上的日志文件,而是对应磁盘上 的一个目录
这个目录的命名规则是<topic_name>_<partition_id>
目录一般是/kafka/里面的,具体看kafka配置我呢见的log.dirs属性的值,当然,不同的节点
ls命令查询的目录自然是不同的,因为分区是分开的,所以我们也将分区说成分片
消息存储机制 :
我们可以进入其中的一个分区数据里面查看(他被分了多个分区)

在这里插入图片描述

log分段:
每个分片目录中,kafka 通过分段的方式将 数据分为多个 LogSegment,一开始都是一个LogSegment文件
后面会增加,如后缀为log文件的大小到达了1g
一个 LogSegment 对应磁盘上的一个日志文件(00000000000000000000.log)和一个索引文件
索引文件如上:00000000000000000000.index,所有分区的LogSegment文件一开始一般都是以00000000000000000000开头
其中日志文件是用来记录消息的,索引文件是用来保存消息的索引
每个LogSegment 的大小可以在server.properties 中log.segment.bytes=107370 选项进行设置
这样可以设置分段大小,不写的话默认是1gb,当然如果写了,基本必须写一个INT类型的数,否则可能不会启动kafka
当后缀为log文件等于1G时,新的会写入到下一个segment(可以认为是LogSegment的简写)中
以创建的这个为主了,即基本只会生产到创建的新文件,而不会生产到旧的文件了
无论是否存在或者不存在,都是先执行删除,然后创建
命名规则是:若后缀为log日志文件等于1g,且当前偏移量是50000(通过后缀为index索引文件可以知道,虽然可能查看是乱码)
那么新的segment就是以00000000000000050001开头的,以50001偏移量开始,因为50000那个偏移量提交后,就是50001了
后缀自然也是log以及index,即命令根据偏移量来的
当然这是添加的文件,而不是覆盖
我们查看进入分区文件后,查询信息时,可以看到后缀为timeindex文件,他是kafka的具体时间日志
而每个目录下一般都有一个leader-epoch-checkpoint文件,他是操作选举(发送消息)的文件,操作具体主的发送消息的截断
当然有些老版本只会存在后缀为log以及index的文件,这里只是大致的说明,了解即可
可能并不准确,具体可以查看百度来知道具体原理
通过 offset 查找 message:
存储的结构:一个主题 --> 多个分区 ----> 多个日志段(多个文件)
第一步:查询segment file:
segment file命名规则跟offset有关,根据segment file可以知道它的起始偏移量
因为Segment file的命名规则是上一个segment文件 最后一条消息的offset值
所以只要根据offset 二分查找文件列表,就可以快速定位到具体文件
比如:
第一个segment file是00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0
第二个是00000000000000091933.index:代表消息量起始偏移量为91933 = 91932 + 1
可能百度查询的有些博客说明的文件是00000000000000091932.index,如果是这样的话
那么是认为第一个偏移量不算起始偏移量吧(除了0),因为他并不能操作位移的,但这里就按我的来说明
那么offset=5000时应该定位00000000000000000000.index
第二步通过segment file查找message:
通过第一步定位到segment file,当offset=5000时
依次定位到00000000000000000000.index的元数据物理位置和00000000000000000000.log的物理偏移地址
然后再通过00000000000000000000.log顺序查找直到offset=5000为止
生产者消息分发策略:
kafka在数据生产的时候,有一个数据分发策略,默认的情况使用DefaultPartitioner.class类
这个类中就定义数据分发的策略,且实现了Partitioner接口
public interface Partitioner extends Configurable, Closeable {
    /**
     * Compute the partition for the given record.
     *
     * @param topic The topic name
     * @param key The key to partition on (or null if no key)
     * @param keyBytes The serialized key to partition on( or null if no key)
     * @param value The value to partition on or null
     * @param valueBytes The serialized value to partition on or null
     * @param cluster The current cluster metadata
     */
    public int partition(String topic, Object key, byte[] keyBytes, 
                         Object value, byte[] valueBytes, Cluster cluster);
    /**
     * This is called when partitioner is closed.
     */
    public void close();
}
如果你自己要定义一个自己的类来操作策略,那么你实现这个接口即可,然后通过properties来指定类的地址
具体可以百度,比如说:
//设置这个就使用该类地址的方法,如果该类没有实现Partitioner接口,那么启动时会报错,自己测试便可
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.kafka.utils.PartitionerUtil");
//com.kafka.utils.PartitionerUtil这个就是自己定义的实现了Partitioner接口的类的地址
//那么就会使用他的方法,而不会使用默认的实现类DefaultPartitioner.partition()的方法了
//若自定义的方法里面并不操作分区策略,那么还是会操作默认的实现类DefaultPartitioner.partition()的方法了
//即自定义方法的操作分区策略会导致会不会操作默认的实现类DefaultPartitioner的分区策略,如果有操作分区策略
//即也就不会调用DefaultPartitioner.partition()方法,如果没有操作分区策略
//那么任然会调用DefaultPartitioner.partition()方法来操作分区策略

//当然,你可以验证是否执行了自定义的partition()方法,即可以在里面输入System.out.println(2);
//看看发送信息的打印信息
//你可以看到,在发送的代码中send方法里面操作了这个自定义的partition()方法,看打印先后就知道了
默认实现类:org.apache.kafka.clients.producer.internals.DefaultPartitioner
如果是用户指定了partition,生产就不会调用DefaultPartitioner.partition()方法
数据分发策略的时候,可以指定数据发往哪个partition
当ProducerRecord 的构造参数中有partition的时候,就可以发送到对应partition上
/**
 * Creates a record to be sent to a specified topic and partition
 *
 * @param topic The topic the record will be appended to
 * @param partition The partition to which the record should be sent
 * @param key The key that will be included in the record
 * @param value The record contents
 */

//之前发送消息的方法,new ProducerRecord<>(topic,msg);,即ProducerRecord的其他构造方法
public ProducerRecord(String topic, Integer partition, K key, V value) {
    this(topic, partition, null, key, value, null);
}

//其中partition代表分区编号,在kafka的界面客户端可以看到,从0开始,如果是三个分区,那么一般就是0,1,2

DefaultPartitioner源码:
如果指定key,是取决于key的hash值,如果不指定key,轮询分发(前面的生产者就是这个策略)
public int partition(String topic, Object key, byte[] keyBytes, 
                     Object value, byte[] valueBytes, Cluster cluster) {
    	//获取该topic的分区列表
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
    	//获得分区的个数
        int numPartitions = partitions.size();
        //如果key值为null
        if (keyBytes == null) { //如果没有指定key,那么就是轮询
            //维护一个key为topic的ConcurrentHashMap,并通过CAS操作的方式对value值执行递增+1操作
            int nextValue = this.nextValue(topic);
            //获取该topic的可用分区列表
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) { //如果可用分区大于0
                //执行求余操作,保证消息落在可用分区上
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return ((PartitionInfo)availablePartitions.get(part)).partition();
            } else {
                // 没有可用分区的话,就给出一个不可用分区
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else { //不过指定了key,key肯定就不为null
            // 通过计算key的hash,确定消息分区
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }
所以实际上也并不是随机,而是操作了策略(虽然前面说成随机,但也有说明是操作策略)
消费者负载均衡机制 :
同一个分区中的数据,只能被一个消费者组中的一个消费者所消费
例如 P0分区中的数据不能被Consumer Group A中C1与C2同时消费
消费组:一个消费组中可以包含多个消费者,前面说明过了,比如:
//指定分组的名称
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"lagou_group1");
如果该消费组有四个 消费者,主题有四个分区,那么每人一个,多个消费组可以重复消费消息

在这里插入图片描述

如果有3个Partition,p0/p1/p2,同一个消费组有3个消费者,c0/c1/c2,则为一 一对应关系
如果有3个Partition,p0/p1/p2,同一个消费组有2个消费者,c0/c1
则其中一个消费者消费2个分区的数据,另一个消费者消费一个分区的数据,自然不会操作同一个分区
防止消费同样的数据,或者少了消费的数据(偏移量的问题)
如果有2个Partition, p0/p1,同一个消费组有3个消费者,c0/c1/c3
则其中有一个消费者空闲,另外2个消费者消费分别各自消费一个 分区的数据
那么为什么不能在一个分区里,有多个消费者呢,主要是偏移量的存在,假设可以有多个消费者
那么如果他们都操作偏移,那么自然消费者比如有些地方,由于另外一个消费者偏移过去了
即可能没有消费到,或者他们正好消费了同一个,所以也规定只能是一个消费者
因为这样就跟没有分组一样(虽然必须有分组,这也是需要分组的原因)
最后注意:起始偏移量只是针对对应的分组,其他的分组都有自带的起始偏移量,即偏移量不是公用的
kakfa配置文件说明 :
在/opt/kafka/config下(我的是这个地址),或者你的kafka的config下,有如下文件
server.properties:
1:broker.id=0:
kafka集群是由多个节点组成的,每个节点称为一个broker,中文翻译是代理
每个broker都有一个不同的brokerId,由broker.id指定, 是一个不小于0的整数
各brokerId必须不同(因为如果相同,那么后启动的会报错,即会导致启动失败 ),但不必连续
如果我们想扩展kafka集群,只需引入新节点,分配一个不同的broker.id即可
启动kafka集群时,每一个broker都会实例化并启动一个kafkaController,并将该broker的brokerId注册到zooKeeper的相应节点中
集群各broker会根据选举机制选出其中一个broker作为leader,即leader kafkaController
leader kafkaController负责主题的创建与删除、 分区和副本的管理等
当leader kafkaController宕机后,其他broker会再次选举出新的leader kafkaController
2:log.dir = /export/data/kafka/ broker:
持久化消息到哪个数据目录
3:log.retention.hours = 168:
log文件最小存活时间,默认是168h,即7天
相同作用的还有log.retention.minutes、log.retention.ms,retention是保存的意思
数据存储的最大时间超过这个时间会根据log.cleanup.policy设置的策略处理数据,也就是消费端能够多久去消费数据
log.retention.bytes和log.retention.hours任意一个达到要求,都会执行删除,会被topic创建时的指定参数覆盖
4:log.retention.check.interval.ms:
多长时间检查一次是否有log文件要删除,默认是300000ms,即5分钟
5:log.retention.bytes:
限制单个分区的log文件的最大值,超过这个值,将删除旧的log,以满足log文件不超过这个值,默认是-1,即不限制
6:log.roll.hours:
多少时间会生成一个新的log segment,默认是168h,即7天,相同作用的还有log.roll.ms、segment.ms
7:log.segment.bytes:
log segment多大之后会生成一个新的log segment,默认是1073741824,即1G
8:log.flush.interval.messages:
指定broker每收到几个消息就把消息从内存刷到硬盘(刷盘),默认是9223372036854775807个消息(好大的数)
    /*
    单位B<KB<MB<GB<TB<PB<EB<ZB<YB<BB
    
    9223372036854775807B = 
    9223372036854775808B-1B = 
    9007199254740992KB-1B = 
    8796093022208MB-1B = 
    8589934592GB-1B = 
    8388608TB-1B = 
    8192PB-1B = 
    8EB-1B
    */
kafka官方不建议使用这个配置,建议使用副本机制和操作系统的后台刷新功能
因为这更高效,这个配置可以根据不同的topic设置不同的 值,即在创建topic的时候设置值
一般默认是这样,如果设置了,那么就会操作设置的刷新,否则还是操作系统的刷新,副本机制一直不变的
补充说明:
在Linux操作系统中,当我们把数据写入到文件系统之后,数据其实在操作系统的page cache里面,并没有刷到磁盘上去
如果此时操作系统 挂了,其实数据就丢了
kafka是多副本的,当你配置了同步复制之后,多个副本的数据都在page cache里面
出现多个副本同时挂掉的概率比1个副本挂掉,概率 就小很多了
操作系统有后台线程,可以操作定期刷盘,如果应用程序每写入1次数据,都调用一次fsync,那性能损耗就很大
所以一般都会在性能和可靠性之间 进行权衡,因为对应一个应用来说,虽然应用挂了,只要操作系统不挂,数据就不会丢
9:log.flush.interval.ms:
指定broker每隔多少毫秒就把消息从内存刷到硬盘,默认值同log.flush.interval.messages一样, 即9223372036854775807
同log.flush.interval.messages一样,kafka官方也不建议使用这个配置
10:delete.topic.enable=true:
是否允许直接从物理上删除topic,而不用重启才会删除(当然有些版本可能不会重启删除),默认值是false
kafka监控与运维:
kafka-eagle概述:
在生产环境下,在Kafka集群中,消息数据变化是我们关注的问题
当业务前提不复杂时,我们可以使用Kafka 命令提供带有Zookeeper
客户端工具的工具,可以轻松完成我们的工作
随着业务的复杂性,增加Group和 Topic,那么我们使用Kafka提供命令工具,已经感到无能 为力
那么Kafka监控系统目前尤为重要,我们需要观察 消费者应用的细节
为了简化开发者和服务工程师维护Kafka集群的工作有一个监控管理工具,叫做 Kafka-eagle
这个管理工具可以很容易地发现分布在集 群中的哪些topic分布不均匀,或者是分区在整个集群分布不均匀的的情况
它支持管理多个集群、选择副本、副本重新分配以及创建Topic
同时,这个管理工具也是一个非常好的可以快速浏览这个集群的工具
搭建安装 kafka-eagle:
如果有镜像,那么可以操作镜像,当然,并不是所有的框架或者中间件或者工具,都有镜像的
环境要求:需要安装jdk,启动zk以及kafka的服务
我们继续操作之前的三个服务器即可
# 启动Zookeeper
zkServer.sh start

#启动Kafka
nohup ./kafka-server-start.sh /export/servers/kafka/config/server.properties 2>&1 &
搭建步骤:
下载kafka-eagle的源码包:
kafka-eagle官网:http://download.kafka-eagle.org/
我们可以从官网上面直接下载最新的安装包即可,这里我们下载kafka-eagle-bin-1.3.2.tar.gz这个版本即可
代码托管地址:
https://github.com/smartloli/kafka-eagle/releases
可以使用我的地址:
链接:https://pan.baidu.com/s/1EmJFsFfbcNPuDYoZ5VFhGA
提取码:alsk
上传安装包并解压:
这里我们选择将kafak-eagle安装在第一台(11结尾的,即node1)
如果要解压的是zip格式,需要先安装命令支持
yum install unzip
安装好后,然后这样操作:unzip xxxx.zip
#将安装包上传至 node01服务器的/export/software路径下,然后解压
cd /export/software/
unzip kafka-eagle.zip #如果没有unzip命令,下载yum install unzip即可
cd kafka-eagle/kafka-eagle-web/target/
tar -zxf kafka-eagle-web-2.0.1-bin.tar.gz -C /export/servers
cd /export/servers
准备数据库:
kafka-eagle需要使用一个数据库来保存一些元数据信息,我们这里直接使用msyql数据库来保存即可
如果你有数据库,那么使用你自己的,如果没有或者不知道数据库如何操作,可以到55章博客里查看,有创建mysql数据库的步骤
在node1服务器执行以下命令创建一个mysql数据库即可,创建eagle名称的数据库
CREATE DATABASE eagle CHARACTER SET utf8;
修改kafka-eagle配置文件:
cd /export/servers/kafka-eagle-web-2.0.1/conf
vi system-config.properties
#内容如下:(记得修改如下) 
kafka.eagle.zk.cluster.alias=cluster1 #指定集群的别名,指定了那么就会操作下面别名的配置
#没有的则不会操作,或者对应没有使用别名的配置直接跳过,所以你写上其他的基本不会有什么错误
#比如就在某一行写上dd也没有问题,但最好不要,可能不同的版本会有操作
cluster1.zk.list=node1:2181,node2:2181,node3:2181 
#因为kafka在zookeeper里面,那么可以通过zookeeper来操作对应的kafka集群信息,自然只需要指定zookeeper集群即可

#数据库的地址连接
kafka.eagle.driver=com.mysql.jdbc.Driver
kafka.eagle.url=jdbc:mysql://192.168.164:128:3306/eagle
kafka.eagle.username=root
kafka.eagle.password=QiDian@666 #写上自己的密码
默认情况下MySQL只允许本机连接到MYSQL实例中,所以如果要远程访问,必须开放权限:
在55章博客里已经说明过了(可以去查看,主要代码如下):
select host from user where user='root'; 
update user set host='%' where user='root';
flush privileges;
配置环境变量:
kafka-eagle必须配置环境变量,node1服务器执行以下命令来进行配置环境变
vi /etc/profile
#内容如下:
export KE_HOME=/export/servers/kafka-eagle-web-2.0.1
export PATH=:$KE_HOME/bin:$PATH
#让修改立即生效,执行  
source /etc/profile
启动kakfa-eagle:
cd /export/servers/kafka-eagle-web-2.0.1/bin
chmod u+x ke.sh
./ke.sh start
访问主界面:
http://192.168.164.11:8048/ke/account/signin?/ke/
用户名:admin
密码:123456
如果出现如下:

在这里插入图片描述

代表操作成功,注意:如果你登录时,进不去,可以试着将所有相关的都重启一遍,如果还不行,可以百度查看原因
当然,若上面的配置都正确,那么大多数的情况下,是网络延迟的原因,或者说你电脑实在是太卡了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值