linux虚拟机搭建kafka集群

1 VMwarePro16下载

2 VMwarePro16安装

3 Centos7.6下载

4 创建Centos7.6虚拟机

选择自定义安装模式
选择带有GUI的server

5 配置NAT网络

修改ifcfg-ens33配置文件
service network restart

6 配置yum

一般自带
yum --version // 查看有无安装

7 安装java环境

rpm  -qa | grep java
(openjdk不全,需要删除)
su yum romove jdk*


oracle下载jdk8

mkdir /usr/local/java
mv jdk_path /usr/local/java
tar -zxvf jdk8.tar.gz
source /etc/ profile


8 克隆3台虚拟机

安装jdk8后,先拍摄快照
基于快照拍摄3台虚拟机
kafkaServer1 kafkaServer2 kafkaServer3

9 3台虚拟机互通

修改ifcfg-ens33配置文件

systemctl status firewalld (有的说zookeeper集群要关闭防火墙)
当我执行这个命令时, 此时显示activate

10 下载zookeeper

下载的kafka版本是2.4.1, 里面zookeeper是3.5.7

11 搭建zookeeper集群

tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz
mv apache-zookeeper-3.5.7-bin zookeeper3.5.7

echo "1" > ./zkData/myid
echo "2" > ./zkData/myid
echo "3" > ./zkData/myid


在搭建之前, 一个思考题:需要关闭防火墙吗?


1.创建logs, data文件夹
2.myid(添加myid文件,myid文件要在dataDir下)
3.path (添加到系统路径, 执行脚本时可以不在bin目录下)
4.zoo.cfg(修改zoo.cfg配置文件 重要)
# scp zoo.cfg root@192.168.129.12:/usr/local/zookeeper/zookeeper3.5.7/conf(涉及到公钥密钥) 可以不需要


zkServer.sh start


zkServer.sh status
Client port found: 2181. Client address: localhost.
Error contacting service. It is probably not running.

报了这个错误,解决方式是关闭防火墙或者给防火墙添加入站规则
改过之后,这个错误解决,出现leader, follower

[root@localhost zookeeper3.5.7]# zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper/zookeeper3.5.7/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader


systemctl status firewalld

systemctl stop firewalld
systemctl start firewalld
systemctl disable firewalld
systemctl enable firewalld

zkServer.sh stop
zkServer.sh status


tickTime=2000
initLimit=10
syncLimit=5

dataDir=/usr/local/zookeeper/zookeeper3.5.7/zkData
dataLogDir=/usr/local/zookeeper/zookeeper3.5.7/zkLogs

clientPort=2181

server.1=192.168.129.11:2888:3888
server.2=192.168.129.12:2888:3888
server.3=192.168.129.13:2888:3888


12 编写脚本启动zookeeper集群

这个地方需要3台服务器中任意两台能够ssh随意登录

ssh-keygen -t rsa
# 此时在/root/.ssh下生成私钥和公钥
# 这个命令执行会遇到:输入, 直接enter,不需要密码锁
ssh-copy-id -i ~/.ssh/id_rsa.pub -p 22 root@192.168.129.11
# 将公钥拷贝到目标服务器上的authorized_keys文件下



#!/bin/bash
node1=192.168.129.11
node2=192.168.129.12
node3=192.168.129.13
case $1 in
"start"){
        for i in $node1 $node2 $node3
        do
                 echo -------------------------------- $i zookeeper 启动 ---------------------------
                ssh $i "/usr/local/zookeeper/zookeeper3.5.7/bin/zkServer.sh start"
        done
}
;;
"stop"){
        for i in $node1 $node2 $node3
        do
                echo -------------------------------- $i zookeeper 停止 ---------------------------
                ssh $i "/usr/local/zookeeper/zookeeper3.5.7/bin/zkServer.sh stop"
        done
}
;;
"status"){
        for i in $node1 $node2 $node3
        do
                echo -------------------------------- $i zookeeper 状态 ---------------------------
                ssh $i "/usr/local/zookeeper/zookeeper3.5.7/bin/zkServer.sh status"
        done
}
;;
esac
# 无需担心退出登录问题, ssh接命令,是一种伪终端登录,命令执行完后就会退出

执行脚本命令时发现
bash myZk.sh status
-------------------------------- 192.168.129.11 zookeeper 状态 ---------------------------
Error: JAVA_HOME is not set and java could not be found in PATH.
解决措施:
在zookeeper目录下的bin目录下,有一个zkEnv.sh文件,在文件代码部分的 前面 加上自己的JAVA_HOME路径即可。

export JAVA_HOME=/usr/local/java/jdk1.8.0_391
ZOOBINDIR="${ZOOBINDIR:-/usr/bin}"
ZOOKEEPER_PREFIX="${ZOOBINDIR}/.."

13 下载kafka

下载kafka2.4.1

14 搭建kafka集群

1 tar -xzvf kafka-2.4.1.tar.gz

2 mkdir ./kafka-2.4.1/kafka-logs

cd ./config
3 修改server.properties, 其他节点也要修改

修改内容如下
broker.id=0
listeners=PLAINTEXT://192.168.129.11:9092
log.dirs=/usr/local/kafka/kafka_2.12-2.4.1/kafka-logs
zookeeper.connect=192.168.129.11:2181,192.168.129.12:2181,192.168.129.13:2181

4 编写kafka启动和停止脚本

5 bash myKafka.sh start

报错
kafka集群启动 nohup: failed to run command ‘java’: No such file or directory
解决方法 启动脚本里加入 source /etc/profile

6 测试
# 查看主题列表
bash ./bin/kafka-topics.sh --list --bootstrap-server 192.168.129.12:9092
bash  ./bin/kafka-topics.sh --list --zookeeper 192.168.129.11:2181

# 创建主题
bash ./bin/kafka-topics.sh --create --bootstrap-server 192.168.129.12:9092 --replication-factor 3 --partitions 1 --topic test
bash ./bin/kafka-topics.sh --create --bootstrap-server 192.168.129.13:9092 --replication-factor 3 --partitions 3 --topic shopping

bin/kafka-topics.sh --create --zookeeper 192.168.129.11:2181,192.168.129.12:2181,192.168.129.13:2181 --replication-factor 3 --partitions 3 --topic test  

执行这个命令,客户端只会和其中一个zookeeper服务器建立连接。写多个目的是保证高可用和负载均衡
带有--zookeeper的写法是老版,带有--bootstap-server是新版。 推荐新版
这里zookeeper服务器一定是要leader? 答:不需要。读命令直接读,写命令转交给leader

# 再次 查看主题列表
bash ./bin/kafka-topics.sh --list --bootstrap-server 192.168.129.12:9092


# 向主题中生产消息,程序不会自动退出
bash ./bin/kafka-console-producer.sh --broker-list 192.168.129.12:9092 --topic test
bash ./bin/kafka-console-producer.sh --broker-list 192.168.129.12:9092,192.168.129.11:9092,192.168.129.13:9092 --topic shopping

只会和其中一个broker建立连接,多个broker为了保证高可用和负载均衡
两个命令执行的效果是一样的

# 从主题中消费消息, 程序不会自动退出
bash ./bin/kafka-console-consumer.sh --bootstrap-server 192.168.129.12:9092 --topic test --from-beginning

# 从主题中消费消息,如果超过5s都没有消息,程序就会自动退出

bash ./bin/kafka-console-consumer.sh --bootstrap-server 192.168.129.12:9092 --topic test --from-beginning --timeout-ms 5000

bash ./bin/kafka-console-consumer.sh --bootstrap-server 192.168.129.12:9092,192.168.129.11:9092,192.168.129.13:9092  --topic test --from-beginning --timeout-ms 5000

bash ./bin/kafka-console-consumer.sh --bootstrap-server 192.168.129.12:9092,192.168.129.11:9092,192.168.129.13:9092  --topic shopping --from-beginning --timeout-ms 5000

从主题消费消息时,--bootstrap-server也可以接受多个broker地址

# q1
bash ./bin/kafka-topics.sh --list --bootstrap-server 192.168.129.13:9092

bash  ./bin/kafka-topics.sh --list --zookeeper 192.168.129.11:2181

这两个命令的区别?第一个是新版,第二个是老版
一个是直接与kafka broker建立连接获取主题列表信息
一个是与zookeeper建立连接获取主题列表信息。
并且,这两个命令不需要接所有的服务器,只需要其中一个就可以了。
因为这个与客户端建立连接的broker会和其他broker进行通信,获取到主题列表信息
笔者试过修改zkSever和broker的服务器的地址,发现拿到的结果都是一样的。


如果写上所有的broker地址,那么客户端是和所有的broker建立连接吗?
你在 --broker-list 参数中指定多个 Kafka broker 地址时,客户端实际上只会与其中的一个 broker 建立连接。指定多个 broker 地址的作用是提供高可用性和负载均衡。




15 编写kafka集群脚本

#!/bin/bash
node1=192.168.129.11
node2=192.168.129.12
node3=192.168.129.13
case $1 in
"start"){
        for i in $node1 $node2 $node3
        do
                 echo -------------------------------- $i kafka 启动 ---------------------------
                 ssh $i "source /etc/profile;/usr/local/kafka/kafka_2.12-2.4.1/bin/kafka-server-start.sh -daemon /usr/local/kafka/kafka_2.12-2.4.1/config/server.properties"
        done
}
;;
"stop"){
        for i in $node1 $node2 $node3
        do
                echo -------------------------------- $i kafka 停止 ---------------------------
                ssh $i "/usr/local/kafka/kafka_2.12-2.4.1/bin/kafka-server-stop.sh"
        done
}
;;
esac

16 Java操作kafka集群

16.1 生产者客户端-同步发送消息

package p7.producer;

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

import java.util.Properties;

public class MyProducer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //	1.配置生产者启动的关键属性参数
        //	1.1	BOOTSTRAP_SERVERS_CONFIG:连接kafka集群的服务列表,如果有多个,使用"逗号"进行分隔
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.129.11:9092,192.168.129.12:9092,192.168.129.13:9092");

        properties.put(ProducerConfig.CLIENT_ID_CONFIG, "shopping-producer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        KafkaProducer<String, String> myProducer = new KafkaProducer<>(properties);
        try{
            for (int i = 0; i < 10; i++) {
                String msg = "user"+i+": i buy an huawei phone";
                ProducerRecord<String, String> kafkaMsg = new ProducerRecord<>("shopping", msg);
                RecordMetadata metadata = myProducer.send(kafkaMsg).get();
                System.out.println("同步方式发送消息结果:" + "topic-" + metadata.topic() + "|partition-"
                        + metadata.partition() + "|offset-" + metadata.offset());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myProducer.close();
        }

    }
}

16.2 生产者客户端-异步发送消息

package p7.producer;

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

import java.util.Properties;

public class MyAsyncProducer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //	1.配置生产者启动的关键属性参数
        //	1.1	BOOTSTRAP_SERVERS_CONFIG:连接kafka集群的服务列表,如果有多个,使用"逗号"进行分隔
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.129.11:9092,192.168.129.12:9092,192.168.129.13:9092");

        properties.put(ProducerConfig.CLIENT_ID_CONFIG, "shopping-producer");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        KafkaProducer<String, String> myProducer = new KafkaProducer<>(properties);
        try{
            for (int i = 0; i < 10; i++) {
                String msg = "user"+i+": i buy an huawei phone";
                ProducerRecord<String, String> kafkaMsg = new ProducerRecord<>("shopping", msg);
                myProducer.send(kafkaMsg, new Callback() {
                    @Override
                    public void onCompletion(RecordMetadata metadata, Exception e) {
                        if(e != null){
                            System.out.println("发送消息失败:" + e.getStackTrace());
                        }
                        if(metadata != null){
                            System.out.println("异步方式发送消息结果:" + "topic-"+
                                    metadata.topic() + "|partition-"+
                                    metadata.partition() + "|offset-" + metadata.offset());
                        }

                    }
                });
                Thread.sleep(2000);  // 这个线程做了别的事情
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myProducer.close();
        }
    }
}

16.3 消费者客户端

package p7.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.*;


public class MyConsumer {
    private static final long MAX_IDLE_CNT=20;
    public static void main(String[] args) {


        //	1. 配置属性参数
        Properties properties = new Properties();

        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.129.11:9092,192.168.129.12:9092,192.168.129.13:9092");

        //	org.apache.kafka.common.serialization.StringDeserializer
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        //	非常重要的属性配置:与我们消费者订阅组有关系
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "shopping-group");
        //	常规属性:会话连接超时时间
        properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 10000);
        //	消费者提交offset: 自动提交 & 手工提交,默认是自动提交
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 5000);

        //	2. 创建消费者对象
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);

        //	3. 订阅你感兴趣的主题:quick_start
        consumer.subscribe(Collections.singletonList("shopping"));

        System.err.println("shopping consumer started...");

        try {
            int cur_idle_cnt=0;
            //	4.采用拉取消息的方式消费数据
            while (true) {
                //	等待多久拉取一次消息
                //	拉取TOPIC_QUICKSTART主题里面所有的消息
                //	topic 和 partition是 一对多的关系,一个topic可以有多个partition
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(1000));
                if(records.isEmpty()){
                    ++cur_idle_cnt;
                }else{
                    cur_idle_cnt=0;
                }
                if(cur_idle_cnt>=MAX_IDLE_CNT){
                    System.out.println("已经有20次拉取的消息为空");
                    break;
                }

                //	因为消息是在partition中存储的,所以需要遍历partition集合
                for (TopicPartition topicPartition : records.partitions()) {
                    //	通过TopicPartition获取指定的消息集合,获取到的就是当前topicPartition下面所有的消息
                    List<ConsumerRecord<String, String>> partitionRecords = records.records(topicPartition);
                    //	获取TopicPartition对应的主题名称
                    String topic = topicPartition.topic();
                    //	获取当前topicPartition下的消息条数
                    int size = partitionRecords.size();

                    System.err.println(String.format("--- 获取topic: %s, 分区位置:%s, 消息总数: %s",
                            topic,
                            topicPartition.partition(),
                            size));

                    for (int i = 0; i < size; i++) {
                        ConsumerRecord<String, String> consumerRecord = partitionRecords.get(i);
                        //	实际的数据内容
                        String value = consumerRecord.value();
                        //	当前获取的消息偏移量
                        long offset = consumerRecord.offset();
                        //	ISR : High Watermark, 如果要提交的话,比如提交当前消息的offset+1
                        //	表示下一次从什么位置(offset)拉取消息
                        long commitOffser = offset + 1;
                        System.err.println(String.format("获取实际消息 value:%s, 消息offset: %s, 提交offset: %s",
                                value, offset, commitOffser));
                    }
                }
            }
        } finally {
            consumer.close();
        }
    }
}

16.4 一些思考的问题

1. 生产者向shopping主题下发了100条消息。之后消费者第一次或者再次启动,消费者能消费到100条消息吗?
这里需要实践。我实践了一次。当时第一次启动的情况不会消费到。但是再次启动的情况就可以消费到。


17 知识点扩充

服务器A上ssh登录远程服务器B, 为什么公钥放在B上而不是A上?

答: 这个要了解ssh客户端登录远程服务器的过程,才能解决这个问题。

这个使用公钥和私钥登录的详细过程说一下,拿服务器A和B举例?

答:当使用公钥和私钥进行身份验证登录时,以下是服务器A和B之间的详细过程:

  1. 生成密钥对:在本地计算机上的服务器A上,打开终端或命令提示符,并运行以下命令生成密钥对:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

这将生成一个 RSA 类型的密钥对,长度为 4096 位,并将其关联到你提供的电子邮件地址。

  1. 将公钥复制到服务器B:在服务器A上,运行以下命令将公钥复制到服务器B:
ssh-copy-id user@serverB_address

将 user 替换为你要登录的用户名,serverB_address 替换为服务器B的 IP 地址或域名。系统会要求你输入服务器B的密码。

这将自动将你的公钥添加到服务器B上的 ~/.ssh/authorized_keys 文件中,以授权你使用私钥进行身份验证。

  1. 登录到服务器B:现在,在服务器A上,你可以通过运行以下命令来登录到服务器B:
ssh user@serverB_address
 系统会提示你输入私钥的密码(如果你在第一步中设置了密码)。如果私钥没有密码,系统会直接连接到服务器B。
此时,SSH 客户端会自动使用本地的私钥与服务器B进行身份验证。如果私钥与公钥匹配,你将成功登录到服务器B。
通过这些步骤,你现在应该能够使用生成的公钥和私钥进行 SSH 身份验证,并通过服务器A登录到服务器B。
请确保在操作之前已经安装了 OpenSSH,并具备足够的权限来执行这些命令。如果你遇到问题或需要更详细的指导,
请参考相关操作系统和 SSH 客户端的文档,或向系统管理员寻求帮助。

并要求你提供与公钥对应的私钥进行身份验证。 这个私钥进行身份验证是在A还是B?

答:在使用 SSH 公钥/私钥进行身份验证时,私钥用于在本地计算机(服务器 A)上进行身份验证。在连接到远程服务器(服务器 B)时,SSH 客户端会将本地计算机上的私钥发送到服务器 B 进行身份验证。

	 具体来说,当你尝试连接到服务器 B 时,SSH 客户端将会向服务器 B 发送你的公钥。服务器 B 将检查该公钥是否与用户帐户
下 ~/.ssh/authorized_keys 文件中存储的公钥匹配。如果匹配,则服务器 B 将会向 SSH 客户端发送一个挑战文本。SSH 客户
端使用本地计算机上的私钥生成响应文本,并将其发送回服务器 B。如果服务器 B 能够使用公钥验证响应文本,则 SSH 客户
端被授权访问服务器 B。
	 因此,在这个过程中,私钥和公钥都不会离开各自的计算机。私钥用于本地计算机上的签名和身份验证,而公钥用于远程服务
器上的身份验证和加密。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值