1、创建maven项目并添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.easystudy</groupId>
  <artifactId>kafkaClient</artifactId>
  <version>0.0.1-SNAPSHOT</version>
 
  <!-- 项目属性:子模块不能引用父项目的properties变量 -->
  <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
      <java.version>1.8</java.version>
  </properties>
 
  <dependencies>
     <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
     </dependency>
     
     <!-- 日志包 -->
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <version>1.7.5</version>
    </dependency>
    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
       <version>1.7.5</version>
    </dependency>
     <!-- 会自动下载scala-library与zookeeper依赖库 -->
     <dependency>
         <groupId>org.apache.kafka</groupId>
         <artifactId>kafka_2.11</artifactId>
         <version>2.2.0</version>
     </dependency>
  </dependencies>
 
  <build>
       <sourceDirectory>src/main/java</sourceDirectory>
       <testSourceDirectory>src/main/test</testSourceDirectory>
       <plugins>
             <!-- maven-compiler-plugin,他不能将项目依赖的所有库一同打包进jar里面 -->
             <!-- 需要将所有的依赖jar包一同打包到jar中,能让jar独立运行,那么必须添加插件maven-assembly-plugin -->
            <!-- 将pom中所有的依赖全部打包进一个jar包中:采用export成runnable jar包的方式是行不通的 -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.dondown.WordCountApp</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <!-- 如下两行是控制生的jar包的名称,默认会增加jar-with-dependencies后缀  -->
                    <finalName>kafkaClient</finalName>
                    <appendAssemblyId>false</appendAssemblyId>               
                </configuration>
                <!-- 关联到package打包步骤 -->
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
       </plugins>
   </build>
 
</project>

这里需要注意两个地方:

  • 添加slf4j的日志包,否则调试运行报错
<!-- 日志包 -->
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <version>1.7.5</version>
    </dependency>
    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
       <version>1.7.5</version>
    </dependency>
  • build打包的时候需要用maven-assembly-plugin插件
    maven-assembly-plugin会把依赖的所有jar包打进去,maven-compiler-plugin插件不会打包依赖jar包,使用clean package install打包即可
    也可以自己导出jar包


    最后运行: jar -jar KafkaDemon.jar 即可!

2 创建生产这里类

package com.donwait.producer;
import java.util.Properties;
import org.apache.kafka.clients.producer.Callback;
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;
importorg.apache.kafka.common.serialization.StringSerializer;
public class ProducerTest {
    private KafkaProducer<String, String> producer = null;
   
    // ag:192.168.12.150:9092,192.168.12.151:9092
    public ProducerTest(String brokerList){
     Properties props = new Properties();
     // kafka服务器地址: IP:PORT形式,多个以逗号隔开
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, brokerList);
        // kafka消息key序列化类 若传入key的值,则根据该key的值进行hash散列计算出在哪个partition上 
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // kafka消息序列化类 即将传入对象序列化为字符【可以为字节数组 】
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
        // 批处理大小0则完全禁用批处理
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 0);          
        // 对应partition的leader写到本地后即返回成功。极端情况下,可能导致失败
        // 0从不等待消息确认,1只需leader副本接收到消息就发送确认,-1所有副本接收完成后发送确认消息
        props.put(ProducerConfig.ACKS_CONFIG, "1");
        // 如果为异步发送,则无需设置确认机制,即使设置也无用,1:async, 2:sync
        // props.put("producer.type", "async"); 或设置1和2
        // 异步发送情况下无需设置acks(设置也无效)
        props.put("producer.type", "1");
       
        //request.required.acks
        // 0, which means that the producer never waits for an acknowledgement from the broker (the same behavior as 0.7). This option provides the lowest latency but the weakest durability guarantees (some data will be lost when a server fails).
        // 1, which means that the producer gets an acknowledgement after the leader replica has received the data. This option provides better durability as the client waits until the server acknowledges the request as successful (only messages that were written to the now-dead leader but not yet replicated will be lost).
        //-1, which means that the producer gets an acknowledgement after all in-sync replicas have received the data. This option provides the best durability, we guarantee that no messages will be lost as long as at least one in sync replica remains.
        //props.put("request.required.acks","-1");
       
        //    props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        // 生产者发送失败后的重试次数,默认0
          //  props.put(ProducerConfig.RETRIES_CONFIG, 0);
        // 生产者将等待达到给定延迟以允许发送其他记录,以便发送可以一起批量发送,TCP中的Nagle算法类似
          //  props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        // 生产者可用于缓冲等待发送到服务器的记录的总内存字节数
          //  props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        // 是否使用自定义的分区计算方法
        props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.donwait.producer.SimplePartitioner");
        producer = new KafkaProducer<String, String>(props);
    }
   
    /*
     * 当发送消息时我们不指定key时,producer将消息分发到各partition的机制是:
          Scala版本的producer:
          在你的producer启动的时候,随机获得一个partition,然后后面的消息都会发送到这个partition,
          也就是说,只要程序启动了,这个producer都会往同一个partition里发送消息,
          所以,当使用Scala版本的producer时,尽量传入key,保证消息在partition的平均性
          
          java版本的producer:
          会轮询每个partition,所以发送的会比较平均
     */
    public void send(String topic, String key, String data, boolean sync){
        if (sync) {
            try {
              // 实际上调用send方法并不能保证消息被成功写入到kafka
              // 为了实现同步的发送消息,并监控每条消息是否发送成功,需要对每次调用send方法后返回的Future对象调用get方法
              // get方法的调用会导致当前线程block,直到发送结果返回,不管是成功还是失败,key指定后可以使用自己的分区算法
              // KafkaProducer在调用send方法发送消息至broker的过程中,首先是经过拦截器Inteceptors处理,然后是经过序列化
              // Serializer处理,之后就到了Partitions阶段,即分区分配计算阶段。在某些应用场景下,业务逻辑需要控制每条消息落
              // 到合适的分区中,有些情形下则只要根据默认的分配规则即可。在KafkaProducer计算分配时,首先根据的是ProducerRecord
              // 中的partition字段指定的序号计算分区,KafkaProducer中还支持自定义分区分配方式,实现org.apache.kafka.clients.producer.Partitioner接口
                producer.send(new ProducerRecord<String, String>(topic, key, data)).get();
               
                // 同步发送消息确认回调-在发送消息后,收到回执确认-生产者配置为sync时候等待ack
                //long starttime = System.currentTimeMillis();
                //producer.send(new ProducerRecord<String, String>(topic, key, data), new Callback() {
                   //   @Override
                   //   public void onCompletion(RecordMetadata metadata, Exception exception) {
                   //         System.out.println("收到 ack回执: "+ (System.currentTimeMillis()-starttime) + "ms");
                   //   }
                   //});
            } catch (Exception e) {
              // 当get方法抛出一个错误时,说明数据没有被正确写入,此时需要处理这个错误。
                e.printStackTrace();
            }
        } else {
            producer.send(new ProducerRecord<String, String>(topic, key, data));
        }
        // 必须写下面这句,相当于发送
        producer.flush();
    }
}

这里需要注意两点:
(1)发送的时候是否给对应的key

当发送消息时我们不指定key时,producer将消息分发到各partition的机制是:
---- Scala版本的producer:
在你的producer启动的时候,随机获得一个partition,然后后面的消息都会发送到这个partition,
也就是说,只要程序启动了,这个producer都会往同一个partition里发送消息,
所以,当使用Scala版本的producer时,尽量传入key,保证消息在partition的平均性
-----java版本的producer:
会轮询每个partition,所以发送的会比较平均

(2)同步异步发送

实际上调用send方法并不能保证消息被成功写入到kafka,为了实现同步的发送消息,并监控每条消息是否发送成功,需要对每次调用send方法后返回的Future对象调用get方法,
get方法的调用会导致当前线程block,直到发送结果返回,不管是成功还是失败!

3、创建main函数入口

package com.donwait;
import com.donwait.producer.ProducerTest;
public class Demon {
     
     public static void main(String[] args) {
          ProducerTest producer = new ProducerTest("192.168.12.150:9092,192.168.12.151:9092,192.168.12.152:9092");
          producer.send("test-topic", "key1", "来自我的java测试程序~", true);
          
          System.out.println("发送完成!");
     }
}

4、参数调优

message.send.max.retries : 发送失败重试次数
retry.backoff.ms : 未接到确认,认为发送失败的时间
producer.type : 同步或者异步发送
batch.num.messages: 异步发送时,累计最大消息数
queue.buffering.max.ms: 异步发送时,累计最大时间

具体参数看kafka官方网站,可能不同版本名称或值有变动