一、引言

  1. 什么是消息?
    消息是系统间通信的载体,系统通讯(RPC)的介质,是分布式应用中不可或缺的一部分。
    目前系统间发送消息的方式有两种:
    ①同步消息(即时消息),生产消费同时存在,必须建立会话;
    ②异步消息(离线消息),生产不关心消费,不必建立会话,消费者自行消费。
  2. 不同消息使用场景
    即时消息:打电话,表单提交,webservice(soap),dubbo/springCloud
    离线消息:发短息,发邮件,写信 —— 利用消息队列(队列特点FIFO)
  3. 消息队列的使用场景 (三个常用)
    异步:异步通信,可以并行处理些任务,提高效率,改进用户体验。
    解耦:系统间解耦,生产或消费有一方宕机,不影响整体服务,即用户感受不到异样情况,后续恢复后,依然可以继续使用
    削峰填谷:避免大量请求访问数据库;或者负载均衡,起到合理的调配服务
    过滤:过滤敏感,不需要的消息

二、kafka

  • Kafka是一种高吞吐量的分布式发布订阅消息系统。

kafka广播机制 kafka如何实现广播_kafka广播机制

总结:
一个kafka的topic是以分区的形式存储在集群中,我们会给每个分区分配broker来当leader,同时也是其他分区的follower。一个broker可以是0~多个分区的leader,也可能是0~多个分区的follower

  • 什么是集群脑裂?
    集群的脑裂通常是发生在集群中部分节点之间不可达而引起的(或者因为节点请求压力较大,导致其他节点与该节点的心跳检测不可用)。当上述情况发生时,不同分裂的小集群会自主的选择出master节点,造成原本的集群会同时存在多个master节点。

三、名词解释

  1. Kafka的核心概念

名词

解释

Producer

消息的生成者

Consumer

消息的消费者

ConsumerGroup

消费者组,可以并行消费Topic中的partition的消息

Broker

缓存代理,Kafka集群中的一台或多台服务器统称broker.

Topic

Kafka处理资源的消息源(feeds of messages)的不同分类

Partition

Topic物理上的分组,一个topic可以分为多个partion,每个partion是一个有序的队列。partion中每条消息都会被分 配一个 有序的Id(offset)

Message

消息,是通信的基本单位,每个producer可以向一个topic(主题)发布一些消息

Producers

消息和数据生成者,向Kafka的一个topic发布消息的 过程叫做producers

Consumers

消息和数据的消费者,订阅topic并处理其发布的消费过程叫做consumers

  1. Producers的概念
    ①消息和数据生成者,向Kafka的一个topic发布消息的过程叫做producers
    ②Producer将消息发布到指定的Topic中,同时Producer也能决定将此消息归属于哪个partition;比如基于round-robin方式 或者通过其他的一些算法等;
    ③异步发送批量发送可以很有效的提高发送效率。kafka producer的异步发送模式允许进行批量发送,先将消息缓存到内存中,然后一次请求批量发送出去。
  2. broker的概念:
    ①Broker没有副本机制,一旦broker宕机,该broker的消息将都不可用。
    ②Broker不保存订阅者的状态,由订阅者自己保存。
    ③无状态导致消息的删除成为难题(可能删除的消息正在被订阅),Kafka采用基于时间的SLA(服务保证),消息保存一定时间(通常7天)后会删除。
    ④ 消费订阅者可以rewind back到任意位置重新进行消费,当订阅者故障时,可以选择最小的offset(id)进行重新读取消费消息
  3. Message组成
    ①Message消息:是通信的基本单位,每个producer可以向一个topic发布消息。
    ②Kafka中的Message是以topic为基本单位组织的,不同的topic之间是相互独立的,每个topic又可以分成不同的partition每个partition储存一部分
    ③partion中的每条Message包含以下三个属性:

offset

long

MessageSize

int32

data

messages的具体内容

  1. Consumers的概念
    消息和数据消费者,订阅topic并处理其发布的消息的过程叫做consumers.
    在kafka中,我们可以认为一个group是一个“订阅者”,一个topic中的每个partions只会被一个“订阅者”中的一个consumer消费,不过一个consumer可以消费多个partitions中的消息
    注:
    Kafka的设计原理决定,对于一个topic,同一个group不能多于partition个数的consumer同时消费,否则将意味着某些 consumer无法得到消息

四、kafka集群的搭建

  1. kafka不需要Hadoop的配置,需要有一些基本的环境搭建。
    安装jdk并配置环境变量、配置主机名和IP的映射关系、同步系统时钟、zookeeper集群并配置
  2. 启动三台机器的zookeeper服务,且正常启动
[root@CentOSX ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh start zoo.cfg
  1. 上传并解压
[root@CentOSX ~]# tar -zxf kafka_2.11-0.11.0.0.tgz -C /usr/
  1. 配置
[root@CentOSX ~]# vi /usr/kafka_2.11-0.11.0.0/config/server.properties

############################# Server Basics #########################
broker.id=0|1|2	#分别对应0、1、2
delete.topic.enable=true

############################# Socket Server Settings ################
listeners=PLAINTEXT://CentOSA|B|C:9092	#分别对应CentOSA、B、C

############################# Log Basics ############################
log.dirs=/usr/kafka-logs	#tmp改成usr

############################# Log Retention Policy ##################
log.retention.hours=168		#消息缓存时间,默认168小时 一周

############################# Zookeeper #############################
zookeeper.connect=CentOSA:2181,CentOSB:2181,CentOSC:2181
  1. 启动kafka集群
[root@CentOSX ~]# cd /usr/kafka_2.11-0.11.0.0/
[root@CentOSX kafka_2.11-0.11.0.0]# ./bin/kafka-server-start.sh -daemon config/server.properties
  1. 关闭kafka集群
    不支持,直接关闭,需要修改下。(将PIDS= 后面的内容修改如下)
[root@CentOSX kafka_2.11-0.11.0.0]# ./bin/kafka-server-start.sh -daemon config/server.properties
[root@CentOSA kafka_2.11-0.11.0.0]# vi bin/kafka-server-stop.sh
PIDS=$(jps | grep Kafka | awk '{print $1}')
if [ -z "$PIDS" ]; then
echo "No kafka server to stop"
exit 1
else
kill -s TERM $PIDS
fi
  • Kafka测试
//创建分区
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --create --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic topic01 --partitions 3 --replication-factor 3
//启动消费者
[root@CentOSB kafka_2.11-0.11.0.0]# ./bin/kafka-console-consumer.sh --bootstrap-server CentOSA:9092,CentOSB:9092,CentOSC:9092 --topic topic01 --from-beginning
//启动生产者
[root@CentOSC kafka_2.11-0.11.0.0]# ./bin/kafka-console-producer.sh --broker -list CentOSA:9092,CentOSB:9092,CentOSC:9092 --topic topic01
> hello kafka

五、Topic的基本操作

  • 创建topic
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --create --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic topic01 --partitions 3 --replication-factor 3

partitions:分区的个数,replication-factor副本因子

  • 查看topic详情
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --describe --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic topic01 
Topic:topic01	PartitionCount:3	ReplicationFactor:3	Configs:
	Topic: topic01	Partition: 0	Leader: 0	Replicas: 0,1,2	Isr: 0,1,2
	Topic: topic01	Partition: 1	Leader: 1	Replicas: 1,2,0	Isr: 1,2,0
	Topic: topic01	Partition: 2	Leader: 2	Replicas: 2,0,1	Isr: 2,0,1
#									副本集,leader和两个follower	备份集  正常情况:R=I
  • 查看所有Topic
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --list --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 
topic01
topic02
topic03
  • 删除topic
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --delete --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic topic03
Topic topic03 is marked for deletion.
Note: This will have no impact if delete.topic.enable is not set to true.
  • 修改Topic
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-topics.sh --alter --zookeeper CentOSA:2181,CentOSB:2181,CentOSC:2181 --topic topic02 --partitions 2
WARNING: If partitions are increased for a topic that has a key, the partition logic or ordering of the messages will be affected

六、JavaAPI操作Kafka

注意:因为是在集群环境下,所以要在C:\Windows\System32\drivers\etc下hosts文件追加

192.168.153.139 CentOSA
192.168.153.140 CentOSB
192.168.153.141 CentOSC

1、 导入依赖

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
    <version>0.11.0.0</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.2</version>
</dependency>
  1. JavaAPI操作Topic管理
/**
 * 管理kafka的Topic
 * AdminClient
 * Created by Turing on 2018/12/12 18:43
 */
public class KafkaAdminDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Properties props = new Properties();
   props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        //AdminClient
        AdminClient adminClient = KafkaAdminClient.create(props);
        //创建Topic
        Collection<NewTopic> topics=new ArrayList<NewTopic>();

        topics.add(new NewTopic("topic01",3, (short) 3));   //3个分区,3个副本因子
        topics.add(new NewTopic("topic02",3, (short) 3));
        topics.add(new NewTopic("topic03",3, (short) 3));

        ListTopicsResult listTopics = adminClient.listTopics();
        KafkaFuture<Set<String>> names = listTopics.names();
        for (String s : names.get()) {
            System.out.println(s);
        }
        adminClient.close();
    }

}
  1. 消费者
/**
 * KafkaConsumer 消费者
 * Created by Turing on 2018/12/12 18:59
 */
public class KafkaConsumerDemo {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);  //key 反序列化
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);    //value 反序列化

        props.put(ConsumerConfig.GROUP_ID_CONFIG,"g1"); //指定组
        //Topic对消费者的消费形式,消费者以组的形式存在,组内消息某人消费了,其余人不会再消费它。

        KafkaConsumer<String,String> kafkaConsumer= new KafkaConsumer<String, String>(props);

        //订阅感兴趣的Topics
        kafkaConsumer.subscribe(Arrays.asList("topic01"));

        //获取Records
        while(true){    //循环去消费
            ConsumerRecords<String, String> records = kafkaConsumer.poll(1000); //每1秒获取数据一次

            if(records!=null && records.count()>0){// if(records!=null && !records.isEmpty())
                for (ConsumerRecord<String, String> record : records) {
                    String key=record.key();
                    String value=record.value();
                    long timestamp=record.timestamp();
                    int partition=record.partition();   //消息属于那个分区
                    long offset=record.offset();       //消息在分区的顺序
                    System.out.println(key+" : "+value+" timestamp:"+timestamp+" 分区:"+partition+" offset:"+offset);
                }
            }
        }
    }
}
  1. 生产者
/**
 * KafkaProducer 生产者
 * Created by Turing on 2018/12/12 19:05
 */
public class KafkaProducerDemo {
    public static void main(String[] args) {
        Properties props=new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);  //key 序列化
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);    //value 序列化
        //创建生产者
        KafkaProducer<String,String> kafkaProducer=new KafkaProducer<String, String>(props);
        //创建消息
        for(int i=1;i<10;i++){  //1~9
            ProducerRecord<String,String> record=new ProducerRecord<String,String>("topic01","0"+i+"00","用户编号是"+i+" ,性别:"+(i%2));
            //发布消息
            kafkaProducer.send(record);
        }
        //flush
        kafkaProducer.flush();
        //关闭生产者
        kafkaProducer.close();
    }
}
  1. 如何在消息系统中传递对象
<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
public class ObjectSerializer implements Serializer<Object> {...}
public class ObjectDeserializer implements Deserializer<Object> {...}
//①自己的类implements Serializable
public class User implements Serializable
//②两个自定义序列化
public class ObjectSerializer implements Serializer<Object> {
    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {

    }

    @Override
    public byte[] serialize(String topic, Object data) {
        return SerializationUtils.serialize((Serializable) data);
    }

    @Override
    public void close() {

    }
}

public class ObjectDeserializer implements Deserializer<Object> {
    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {

    }

    @Override
    public Object deserialize(String topic, byte[] data) {
        return SerializationUtils.deserialize(data);
    }

    @Override
    public void close() {

    }
}
//③在生产者和消费者中序列化和反序列化用自定义的类
//Consumer中
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ObjectDeserializer.class);
        
//Producer中
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ObjectSerializer.class);
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, DefaultPartitioner.class);
  1. 生产者发送消息的负载均衡策略
props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, XxxPartitioner.class);//自定义

public class XxxPartitioner implements Partitioner {
      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();
        //自定义你想要的分区规则
          return hash(key)%分区数;
      }
     public void close() {}
}
  1. 消费订阅两种形式
KafkaConsumer<String,User> consumer=new KafkaConsumer<String, User>(config);
  //订阅topic
  consumer.subscribe(Arrays.asList("topic02"));

必须设置消费组,可以实现组内消费者的rebalance

TopicPartition p1 = new TopicPartition("topic02", 0);
  List<TopicPartition> topicPartitions = Arrays.asList(p1);
  consumer.assign(topicPartitions);
  consumer.seek(p1,2);

手动设置offset偏移量

七、了解Kafka Stream(kafka流式计算)

示例:实时统计字符中单词数

  1. 依赖
<dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-streams</artifactId>
            <version>0.11.0.0</version>
        </dependency>
  1. admin
public class KafkaAdminDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Properties props = new Properties();

        props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        //AdminClient
        AdminClient adminClient = KafkaAdminClient.create(props);
        //创建Topic
        Collection<NewTopic> topics=new ArrayList<NewTopic>();

        topics.add(new NewTopic("TextLinesTopic",3, (short) 3));    //输入
        topics.add(new NewTopic("WordsWithCountsTopic",3, (short) 3));  //输出

        adminClient.createTopics(topics);

        ListTopicsResult listTopics = adminClient.listTopics();
        KafkaFuture<Set<String>> names = listTopics.names();
        for (String s : names.get()) {
            System.out.println(s);
        }
        adminClient.close();
    }
}
  1. 数据处理逻辑——java7
public class KafkaStreamingDemoJava7 {
    public static void main(String[] args) {
        Properties config = new Properties();
        config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application");
        config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOSA:9092,CentOSB:9092,CentOSC:9092");
        config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        KStreamBuilder builder = new KStreamBuilder();

        KStream<String, String> textLines = builder.stream("TextLinesTopic");
        // 001 hello world
        // 001 hello
        // 001 world
        KTable<String, Long> wordCounts = textLines
                .flatMapValues(new ValueMapper<String, Iterable<String>>() {
                    @Override
                    public Iterable<String> apply(String line) {
                        return Arrays.asList(line.split(" "));
                    }
                })
                .groupBy(new KeyValueMapper<String, String, String>() {
                    @Override
                    public String apply(String key, String value) {
                        return value;
                    }
                })
                .count("Counts");
        wordCounts.to(Serdes.String(), Serdes.Long(), "WordsWithCountsTopic");

        KafkaStreams streams = new KafkaStreams(builder, config);
        streams.start();
    }
}
  1. 数据处理逻辑——java8
    一个接口只有一个方法,我们称为函数接口,适用于lambda 表达式
public class KafkaStreamingJavaJava8 {
    public static void main(String[] args) {
        Properties config = new Properties();
        config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application");
        config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOSA:9092,CentOSB:9092,CentOSC:9092");
        config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        KStreamBuilder builder = new KStreamBuilder();

        KStream<String, String> textLines = builder.stream("TextLinesTopic");

        KTable<String, Long> wordCounts = textLines
                .flatMapValues(textLine -> Arrays.asList(textLine.toLowerCase().split(" ")))
                .groupBy((key, word) -> word)
                .count("Counts");
        wordCounts.to(Serdes.String(), Serdes.Long(), "WordsWithCountsTopic");

        KafkaStreams streams = new KafkaStreams(builder, config);
        streams.start();
    }
}
  1. 消费者Consumer
public class KafkaConsumerDemo {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"CentOSA:9092,CentOSB:9092,CentOSC:9092");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, LongDeserializer.class);
        props.put(ConsumerConfig.GROUP_ID_CONFIG,"g2");

        KafkaConsumer<String,Long> kafkaConsumer= new KafkaConsumer<String, Long>(props);

        //订阅感兴趣的Topics
        kafkaConsumer.subscribe(Arrays.asList("WordsWithCountsTopic"));

        //获取Records
        while(true){
            ConsumerRecords<String, Long> records = kafkaConsumer.poll(1000);
            if(records!=null && records.count()>0){
                for (ConsumerRecord<String, Long> record : records) {
                    String key=record.key();
                    Long value=record.value();
                    System.out.println(key+" : "+value);
                }
            }
        }
    }
}
  1. 测试
[root@CentOSA kafka_2.11-0.11.0.0]# ./bin/kafka-console-producer.sh --broker-list CentOSA:9092,CentOSB:9092,CentOSC:9092 --topic TextLinesTopic

八、总结

  • 消息队列的使用场景(3个)
    异步、解耦、削峰填谷
  • kafka架构图
  • 基于API操作
  • 消息队列,组内均分,组间广播