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
在这之前,首先我们先只克隆一个机器,当我们操作完这个克隆机器后,将我们操作的克隆的机器再次的克隆
这样我们就不需要再次的重复操作其他两个克隆机器了(到那时只需要局部的修改细节即可,比如后面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 -ivh jdk-8u261-linux-x64.rpm
vi /etc/profile
export JAVA_HOME = /usr/java/jdk1.8.0_261-amd64
export PATH = $PATH : $JAVA_HOME /bin
source /etc/profile
java -version
Linux 安装Zookeeper
对应的文件地址:
链接:https://pan.baidu.com/s/1F-X7SmiD7C6m1EcK-7X7fw
提取码:alsk
解压并配置zookeeper(配置data目录,集群节点):
tar -zxf zookeeper-3.4.14.tar.gz -C /opt
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
mkdir -p /var/lagou/zookeeper/data
echo 1 > /var/lagou/zookeeper/data/myid
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
source /etc/profile
很明显,这里有个地方在克隆时需要注意的细节,就是对应给的值,分别是1,2,3(记得修改)
现在我们进行再次的克隆两次,克隆两次后,将对应的值修改成2和3即可,其他的自然都是一样的
不需要改动了(但也要记得修改ip,去55章博客查看即可)
至此我们的初步工作已经完成
启动zookeeper:
zkServer.sh start
zkServer.sh status
接下来我们搭建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为例):
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
broker.id 需要保证每一台kafka都有一个独立的broker
log.dirs 数据存放的目录
zookeeper.connect zookeeper的连接地址信息
delete.topic.enable 是否可以直接删除topic
host.name 主机的名称
listeners = PLAINTEXT://node1:9092
broker.id = 0
num.network.threads = 3
num.io.threads = 8
socket.send.buffer.bytes = 102400
socket.receive.buffer.bytes = 102400
socket.request.max.bytes = 104857600
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
log.retention.check.interval.ms = 300000
zookeeper.connect = node1:2181,node2:2181,node3:2181
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
scp -r kafka/ node3:$PWD
Linux scp 命令用于 Linux 之间复制文件和目录
scp 是 secure copy 的缩写,scp 是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令
拷贝后,需要修改每一台的broker.id 和 host.name和listeners:
broker.id = 0
host.name = node1
listeners = PLAINTEXT://node1:9092
broker.id = 1
host.name = node2
listeners = PLAINTEXT://node2:9092
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
./kafka-server-start.sh /export/servers/kafka/config/server.properties
nohup ./kafka-server-start.sh /export/servers/kafka/config/server.properties 2 >&1 &
./kafka-server-stop.sh
可以通过 jps命令查看 kafka进程是否已经启动了,如果出现了Kafka,则代表启动了,一般
注意当启动后,他会注册到zookeeper里面,我们可以到对应的zookeeper的bin下执行:
zkCli.sh
ls /brokers/ids
至此我们的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 -L https://github.com/docker/compose/releases/download/1.8.0/run.sh > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
docker-compose --version
拉取镜像:
docker pull zookeeper:3.4
docker pull wurstmeister/kafka
docker pull sheepkiller/kafka-manager:latest
创建集群网络:
基于Linux宿主机而工作的,也是在Linux宿主机创建,创建之后Docker容器中的各个应用程序可以使用该网络
docker network create --driver bridge --subnet 192.168 .0.0/24 --gateway 192.168 .0.1 kafka
docker network ls
网络设置:
WARNING: IPv4 forwarding is disabled. Networking will not work.
echo "net.ipv4.ip_forward=1" >> /usr/lib/sysctl.d/00-system.conf
systemctl restart network && systemctl restart docker
搭建过程:
每个镜像一个Yml文件,即Zookeeper、Kafka、Kafka-Manager各一个
编写yml文件:
在/home里面加上如下文件:
docker-compose-zookeeper.yml:
Zookeeper各个节点的信息,端口映射,集群信息,网络配置:
version : '2'
services :
zoo1 :
image : zookeeper: 3.4
restart : always
hostname : zoo1
container_name : zoo1
ports :
- 2184: 2181
environment :
ZOO_MY_ID : 1
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
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 :
- 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 :
ZK_HOSTS : zoo1: 2181 , zoo2: 2181 , zoo3: 2181
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的主题, 有一个分区,有三个副本,一个主题下可以有多个分区,每个分区可以用对应的副本
docker exec -it 9218e985e160 /bin/bash
cd opt/kafka/bin/
kafka-topics.sh --create --zookeeper zoo1:2181 --replication-factor 3 --partitions 1 --topic test
然后到如下:
点击左边的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
This is a message
This is another message
dd
消费者消费数据 (消息并不是真的消费,消息还是在的,因为是拉取,前面说过了):
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
注意:如果发送消息和消费消息的命令对应的主题不存在,那么会自动创建(发送消息需要发送后才会创建,否则不会)
当然,使得创建的消息也算是发送的,即可以获取得到
创建后,自然也会发送消息或者消费消息
默认是1个分区以及一个副本,主题名称就是你指定的名称,如果创建时出现错误信息,并不需要理会,因为没有影响
对于kafka来说,一个主题多个分区,分区内消息有序,后面的随机(策略的说明)实际上基本都是操作策略的
而之所以现在说成是随机,是因为现在并没有说明是什么策略(当然也添加了策略的说明的),即保守的说法
后面说到策略时,就知道了,注意即可
而主题的创建基本也是随机的(一般是根据策略选择节点,通常选择主节点)给集群的某个节点
主要是这一个,因为主题只有一个,而没有多个,或者说操作同步主题
虽然是随机的,但是我们操作时,基本都会指定主题,即注意即可
但如果要确保消费者消费的消息是顺序的
通常需要把消息存放在同一个topic的同一个分区
分区有副本,虽然其他可能没有主题,但也只是存放数据,等待变成主
然后自动移动对应主题,到主的节点,即任然是同一个主题(主要是这一个,因为主题只有一个,而没有多个)
或者说是同步了主题,那么只需要等待变成主
通过主题,可以确定是那些节点的分区,简单来说就是多分区,而不是只会在当前主题的当前节点里面操作多个分区
因为分区是分配给节点的,只是主题只有一个而已,且通常在主节点,主要用来确定分区的地方
无论生产还是消费,在这些确定的分区里面,基本操作了分区策略
但一般指定的是所有的分区(类似于rabbitmq路由一样的操作所有对应队列,不同的是,他的队列在当前节点
而不是其他节点,rabbitmq是操作完全同步的
但这里却是操作策略给的,前面说明过了,但对象指定还是所有,没有不会选择的分区),而不是独有
消费也一般是消费所有分区(而不是类似于rabbitmq一样,一个消费者只操作一个队列)
消费顺序除了分区里面的消息是有序外
分区选择基本是随机的(一般是根据策略选择分区,可能是随机且有序,注意是可能,无论是这里还是其他的框架或者中间件
并没有真正的随机,因为随机中,必然是有有序的,因为在程序里面是伪随机的
虽然这里可能并没有操作随机的,主要看策略,具体可以百度有什么策略
所以一般多次的消费多分区的消息,可能返回顺序不同,但可以看到相同的地方
特别在后面的java消费者和java生产者之间的操作)
当然消息发送给分区也是根据分区策略来给的
当然,这些都可以进行设置目标的,所以如果是一个分区,那么顺序基本相同,但是如果是多个分区,可能不会相同
因为一个分区,必然是指定该分区,所以得到一个消息后,无论怎么选择,还是该分区,且由于分区消息(可以说成信息)有序
所以得到的消息也有序,但是,如果是多个分区,在得到一个消息后
可能会选择其他分区,这个选择,可能是随机的
且必须选择有数据的(比如没有数据的不考虑或者跳过,然后继续随机其他的分区)
而正是这样,可能使得顺序不是添加的顺序了,自己测试就知道了,记得多次的执行消息信息
运行describe的命令:
运行describe查看topic的相关详细信息:
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
删除配置:
动态删除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
创建maven的工程,导入kafka相关的依赖
< dependencies>
< dependency>
< groupId> org.apache.kafka</ groupId>
< artifactId> kafka-clients</ artifactId>
< version> 1.0.0</ version>
</ dependency>
</ dependencies>
< build>
< plugins>
< plugin>
< groupId> org.apache.maven.plugins</ groupId>
< artifactId> maven-compiler-plugin</ artifactId>
< version> 3.2</ version>
< configuration>
< source> 1.8</ source>
< target> 1.8</ target>
< encoding> UTF-8</ encoding>
</ 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 {
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" ) ;
properties. put ( ProducerConfig . KEY_SERIALIZER_CLASS_CONFIG , StringSerializer . class ) ;
properties. put ( ProducerConfig . VALUE_SERIALIZER_CLASS_CONFIG , StringSerializer . class ) ;
KafkaProducer < String , String > producer = new KafkaProducer < String , String > ( properties) ;
String topic = "lagou" ;
for ( int i = 1 ; i<= 100 ; i++ ) {
String msg = "hello," + i;
ProducerRecord < String , String > producerRecord = new ProducerRecord < > ( topic, msg) ;
producer. send ( producerRecord) ;
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" ) ;
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" ) ;
KafkaConsumer < String , String > consumer = new KafkaConsumer < > ( properties) ;
String topic = "lagou" ;
consumer. subscribe ( Collections . singletonList ( topic) ) ;
while ( true ) {
System . out. println ( 1 ) ;
ConsumerRecords < String , String > poll = consumer. poll ( 500 ) ;
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 ( "========" ) ;
}
}
}
上面通过我们设置的配置,那么在连接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
properties. put ( ProducerConfig . ACKS_CONFIG , "0" ) ;
properties. put ( ProducerConfig . ACKS_CONFIG , "1" ) ;
properties. put ( ProducerConfig . ACKS_CONFIG , "-1" ) ;
消息消费者:
kafka消费消息的模型:
即消费消息,设置好offset,类比一下:
Kafka动作 看书动作 消费消息 看书 offset位移 书签
什么时候消费者丢失数据呢?
由于Kafka consumer默认是自动提交位移的(先更新位移,再消费消息)
如果消费程序出现故障,没消费完毕,则丢失了消息,此时,broker并不知道,但位移已经更新了
所以相当于丢失了消息,因为消费不到那个消息了,只能消费下一个消息
你也可以理解为我们取出数组的数据,当我们要取某一个下标的值时,突然出现故障(假设的)
使得没有取出数据,但下标加1了(就是提交偏移量,提交的是下一个位置)
当我们再次来取出数据时,由于下标加1,那么原来的下标的数据就取不出来了,即数据丢失了
大多数的情况下,我们理解的偏移量是这样的意思
解决方案:
enable.auto.commit=false,关闭自动提交位移
在消息被完整处理之后再手动提交位移:
properties.put( ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false" ) ;
消息存储及查询机制 :
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 {
public int partition ( String topic, Object key, byte [ ] keyBytes,
Object value, byte [ ] valueBytes, Cluster cluster) ;
public void close ( ) ;
}
如果你自己要定义一个自己的类来操作策略,那么你实现这个接口即可,然后通过properties来指定类的地址
具体可以百度,比如说:
properties. put ( ProducerConfig . PARTITIONER_CLASS_CONFIG , "com.kafka.utils.PartitionerUtil" ) ;
默认实现类:org.apache.kafka.clients.producer.internals.DefaultPartitioner
如果是用户指定了partition,生产就不会调用DefaultPartitioner.partition()方法
数据分发策略的时候,可以指定数据发往哪个partition
当ProducerRecord 的构造参数中有partition的时候,就可以发送到对应partition上
public ProducerRecord ( String topic, Integer partition, K key, V value) {
this ( topic, partition, null , key, value, null ) ;
}
DefaultPartitioner源码:
如果指定key,是取决于key的hash值,如果不指定key,轮询分发(前面的生产者就是这个策略)
public int partition ( String topic, Object key, byte [ ] keyBytes,
Object value, byte [ ] valueBytes, Cluster cluster) {
List < PartitionInfo > partitions = cluster. partitionsForTopic ( topic) ;
int numPartitions = partitions. size ( ) ;
if ( keyBytes == null ) {
int nextValue = this . nextValue ( topic) ;
List < PartitionInfo > availablePartitions = cluster. availablePartitionsForTopic ( topic) ;
if ( availablePartitions. size ( ) > 0 ) {
int part = Utils . toPositive ( nextValue) % availablePartitions. size ( ) ;
return ( ( PartitionInfo ) availablePartitions. get ( part) ) . partition ( ) ;
} else {
return Utils . toPositive ( nextValue) % numPartitions;
}
} else {
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个消息(好大的数)
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的服务
我们继续操作之前的三个服务器即可
zkServer.sh start
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
cd /export/software/
unzip kafka-eagle.zip
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
cluster1.zk.list = node1:2181,node2:2181,node3:2181
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
如果出现如下:
代表操作成功,注意:如果你登录时,进不去,可以试着将所有相关的都重启一遍,如果还不行,可以百度查看原因
当然,若上面的配置都正确,那么大多数的情况下,是网络延迟的原因,或者说你电脑实在是太卡了