kafka基础篇(三)——kafka生产者客户端

前言

在第一节中我们讲到,在kafka服务端我们可以通过命令创建生产者并发送消息。但是在实际开发中,我们都是以java形式在项目中进行生产者的创建和消息的发送。本节我们基于JAVA API的基础讲解kafka生产者。

一、JAVA API调用kafka生产者入门

先上代码,看java如何创建生产者并发送消息。
首先,在maven工程的pom中引入kafka客户端jar包,如下图:
在这里插入图片描述
我们这里讲解的是2.30版本,所以jar包也选2.3版本。
然后创建生产者类,给kafka服务发送消息,代码如下:

public class HelloKafkaProducer {
    public static void main(String[] args) {
        //创建Properties集合,设定并存放kafka生产者的属性
        Properties properties = new Properties();
        //kafka服务地址,集群环境可以设置多个,用逗号隔开
        properties.put("bootstrap.servers","127.0.0.1:9092");
        //kafka接收消息只认识字节数组,定义消息的key的序列化器和value的序列化器,将其转化成字节数组
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
       //创建kafka生产者对象,并将属性传给生产者
        try (KafkaProducer<String,String> producer = new KafkaProducer<String, String>(properties);){
            //生产者的消息存放在ProducerRecord对象中
            ProducerRecord<String,String> record;
            try {
                //TODO 发送4条消息
                for(int i=0;i<4;i++){
                    //设定消息发送的主题,消息的key值和消息的内容
                    record = new ProducerRecord<String,String>("Hello World", null,"lison");
                    //发送消息
                    producer.send(record);
                    System.out.println(i+",message is sent");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } 
    }



}

运行代码,消息就发送到了kafka服务端,主题在kafka服务端原本没有,是新建的主题。下面我们就上面这段代码,进一步进行深入的理解和分析。

二、生产者的属性

上面的入门代码中我们可以看到,需要对生产者设置一些属性,比如kafka服务地址,序列化器等。下面我们总结一下生产者的属性都有哪些。
在kafka的JAVA API中,提供了ProducerConfig类,这个类里,定义了生产者的所有属性。我们看其中几个重点属性:

  • acks:0,1,all,-1
    消息确认机制。默认值为1。在ProducerConfig类里,有对acks机制的解释:
    在这里插入图片描述
    ACKS_DOC就是对acks属性的解释,翻译后的解释如下:
    在请求完成之前,生产者要求首领收到的确认数。这将控制发送的记录的持久性。前面的知识我们讲到,生产者是与分区首领进行交互的,所以,需要分区首领去确认数。
    我们继续往下翻译:
    允许以下设置:
  • **acks=0**如果设置为零,则生产者根本不会等待来自服务器的任何确认。记录将立即添加到套接字缓冲区并被视为已发送。在这种情况下,无法保证服务器已接收到记录,重试配置将不会生效(因为客户端通常不知道任何失败)。为每条记录返回的偏移量将始终设置为-1
  • **acks=1**这意味着首领会将记录写入本地日志,但不会等待所有追随者副本的完全确认。在这种情况下,如果领导者在确认记录之后但在追随者复制它之前立即失败,那么记录将丢失
  • acks=all这意味着领导者将等待全套同步副本确认记录。这保证了只要至少有一个同步副本保持活动状态,记录就不会丢失。这是最有力的保证。这相当于acks=-1设置。”

由上面的翻译我们知道,acks为0时,不管kafka服务的分区首领是否接收成功,发了就行。且acks为0时,kafka的重试机制也就不用了。acks为1时,确认首领成功接收了消息。至于该条消息是否被其他的副本成功复制,生产者不关心。根据我们前面集群讲到的知识可知,如果该条消息在首领中给其他副本复制时失败了,那么该条消息就不会被消费者消费了。acks为all或-1时,是分区以及其他副本都收到了消息,生产者才会认为是消息发送成功了,这是最安全的机制,但是该机制也比较影响性能。

  • batch.size
    当多个消息被发送同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。当批次内存 被填满后,批次里的所有消息会被发送出去。但是生产者不一定都会等到批次被填满才发送,半满甚至只包含一个消息的批次也有可能被发送。缺省 16384(16k) ,如果一条消息超过了批次的大小,会写不进去。

  • linger.ms
    指定了生产者在发送批次前等待更多消息加入批次的时间, 缺省0 50ms 0就是不按批次走,有就发。

  • max.request.size
    控制生产者发送请求最大大小,默认1M (这个参数和Kafka主机的message.max.bytes 参数有关系,设置成一致即可)

  • buffer.memory
    生产者内存缓冲区大小

  • retries
    消息发送失败后的重试次数,缺省 Integer.MAX_VALUE。默认情况下,生产者在每次重试之间等待 100ms,可以通过参数 retry.backoff.ms 参数来改变这个时间间隔。

  • request.timeout.ms
    客户端将等待请求的响应的最大时间 默认30秒

  • max.block.ms
    最大阻塞时间,超过则抛出异常 缺省60000ms

  • compression.type
    于压缩数据的压缩类型。默认是无压缩 ,none、gzip、snappy
    压缩的是使用时间换空间的思想,具体来说就是使用CPU的时间去换取空间或网络I/0传输量。

三、序列化器

kafka服务接收字节数组,所以在生产者中,需要制定消息的key和value的序列化器。主要实现org.apache.kafka.common.serialization.Serializer接口的类,都可以作为kafka的序列化器。
我们看Serializer接口的源码:

public interface Serializer<T> extends Closeable {
    default void configure(Map<String, ?> configs, boolean isKey) {
    }

    byte[] serialize(String var1, T var2);

    default byte[] serialize(String topic, Headers headers, T data) {
        return this.serialize(topic, data);
    }

    default void close() {
    }
}

由源码可知,最终序列化的返回值是byte[]。
我们看kafka的JAVA API给我们提供了哪些序列化实现类。
在这里插入图片描述
kafka提供了很多序列化器,且序列化器都对应着一个反序列化器。我们看几个实用的序列化器。

  • StringSerializer
    顾名思义,这是序列化字符串的序列化器。我们看StringSerializer源码中的serialize方法:
 public byte[] serialize(String topic, String data) {
        try {
            return data == null ? null : data.getBytes(this.encoding);
        } catch (UnsupportedEncodingException var4) {
            throw new SerializationException("Error when serializing string to byte[] due to unsupported encoding " + this.encoding);
        }
    }

我们可以看到,其就是调用了String的getBytes方法,返回成byte[]类型。对于简单的字符串而言,我们无需多说,对于对象而言,我们可以将其转化成json字符串,然后用StringSerializer序列化器发送到kafka服务端去。

  • UUIDSerializer
    我们看UUID序列化器的serialize方法:
public byte[] serialize(String topic, UUID data) {
        try {
            return data == null ? null : data.toString().getBytes(this.encoding);
        } catch (UnsupportedEncodingException var4) {
            throw new SerializationException("Error when serializing UUID to byte[] due to unsupported encoding " + this.encoding);
        }
    }

可以看到,我们直接传入UUID对象即可。该序列化器用于发送业务数据的唯一id时使用。

  • JsonSerializer
    json序列化器,序列化jackson形式的json对象。我们看其源码:
public byte[] serialize(String topic, JsonNode data) {
        if (data == null) {
            return null;
        } else {
            try {
                return this.objectMapper.writeValueAsBytes(data);
            } catch (Exception var4) {
                throw new SerializationException("Error serializing JSON message", var4);
            }
        }
    }

可以看到,需要传入一个JsonNode对象,其是jackson工具的一个类。jackson是一个json处理工具,这里我们不进行讲解。
此外,kafka还提供了byte,short,double,long等基本类型的序列化器,供我们使用。
自定义序列化器:
我们可以自己定义序列化器,制定序列化规则,只要实现了Serializer接口即可。同时我们还需要制定反序列化器,按照我们事先约定好的规则,进行反序列化。一般情况下,示例如下:
我们先定义一个自定义类,将其进行序列化:

public class DemoUser {
    private int id;
    private String name;

    public DemoUser(int id) {
        this.id = id;
    }

    public DemoUser(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "DemoUser{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

然后我们制定该类的序列化器:

public class SelfSerializer implements Serializer<DemoUser> {
    public void configure(Map<String, ?> configs, boolean isKey) {
        //do nothing
    }

    /**
     * 序列化器最终就是返回byte数组,里面的规则,我们可以自己制定,然后反序列化的时候,按照设定的规则去反就行
     * @param topic
     * @param data
     * @return
     */
    public byte[] serialize(String topic, DemoUser data) {
        try {
            byte[] name;
            int nameSize;
            if(data==null){
                return null;
            }
            if(data.getName()!=null){
                name = data.getName().getBytes("UTF-8");
                //字符串的长度
                nameSize = data.getName().length();
            }else{
                name = new byte[0];
                nameSize = 0;
            }
            /*id的长度4个字节,字符串的长度描述4个字节,
            字符串本身的长度nameSize个字节*/
            ByteBuffer buffer = ByteBuffer.allocate(4+4+nameSize);
            buffer.putInt(data.getId());//4
            buffer.putInt(nameSize);//4
            buffer.put(name);//nameSize
            return buffer.array();
        } catch (Exception e) {
            throw new SerializationException("Error serialize DemoUser:"+e);
        }
    }

    public void close() {
        //do nothing
    }
}

然后我们需要定义反序列化器:

public class SelfDeserializer implements Deserializer<DemoUser> {


    public void configure(Map<String, ?> configs, boolean isKey) {
        //do nothing
    }

    public DemoUser deserialize(String topic, byte[] data) {
        try {
            if(data==null){
                return null;
            }
            if(data.length<8){
                throw new SerializationException("Error data size.");
            }
            ByteBuffer buffer = ByteBuffer.wrap(data);
            int id;
            String name;
            int nameSize;
            id = buffer.getInt();
            nameSize = buffer.getInt();
            byte[] nameByte = new byte[nameSize];
            buffer.get(nameByte);
            name = new String(nameByte,"UTF-8");
            return new DemoUser(id,name);
        } catch (Exception e) {
            throw new SerializationException("Error Deserializer DemoUser."+e);
        }

    }

    public void close() {
        //do nothing
    }
}

这样,就实现了我们对自定义类的自定义序列化需求。但是kafka本身为我们提供的序列化器绝大多数情况下都能满足我们的需求,我们尽量避免自定义序列化器。因为kafka提供的序列化器肯定要比我们自定义的安全。

四、消息的key与分区的映射关系

在上面的代码中我们可以看到,在创建ProducerRecord对象时,有3个参数:

 record = new ProducerRecord<String,String>("Hello World", null,"lison");

其中第一个参数是主题,第二个参数是消息的key值,第三个参数的消息的value值。在前面我们介绍过,kafka的主题是由一个或多个分区构成的。默认情况下,kafka均衡的将消息分散在各个分区中。其应用的也是最少使用原则。哪个分区的消息少,就往哪个分区中发送消息。当我们给消息设置key值时,也就是第二个参数,那么同一个key值得消息,会发送给同一个分区,前提条件是分区固定。一旦分区数量发生了改变,就无法保证同一个key的消息都在同一个分区了。所以,在创建主题时,要规划好分区数量,我们在消费者会讲到分区再均衡,它也跟规划分区有关,总之,分区事先规划好很重要。
自定义分区器:
kafka客户端提供了Partitioner接口,该接口是分区器的规范。上面我们提到kafka默认情况是均衡的将消息发送到分区上,这种机制其实就是Partitioner的实现类去实现的。我们看一下kafka提供的默认分区器的源码:

public class DefaultPartitioner implements Partitioner {
    private final ConcurrentMap<String, AtomicInteger> topicCounterMap = new ConcurrentHashMap();

    public DefaultPartitioner() {
    }

    public void configure(Map<String, ?> configs) {
    }
//这里定义分区规则
    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();
        //如果key值为空,则进行均匀散列
        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 {//如果key值不为空,则按固定算法计算分区。所以,key值相同的消息进入了同要给分区
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

    private int nextValue(String topic) {
        AtomicInteger counter = (AtomicInteger)this.topicCounterMap.get(topic);
        if (null == counter) {
            counter = new AtomicInteger(ThreadLocalRandom.current().nextInt());
            AtomicInteger currentCounter = (AtomicInteger)this.topicCounterMap.putIfAbsent(topic, counter);
            if (currentCounter != null) {
                counter = currentCounter;
            }
        }

        return counter.getAndIncrement();
    }

    public void close() {
    }
}

我们也可以自定义分区,按照我们的规则将消息发送到不同的分区,比如:指定的消息发送到指定的分区中。大家可以自行手写一个自定义分区哦。
需要注意的是,要使用自定义分区,生产者的属性里,需要设置ProducerConfig.PARTITIONER_CLASS_CONFIG属性,即"partitioner.class"属性,值为我们自定义分区器的包名路径。

五、生产者的三种发送方式

  • 发送并忘记
    在上面的入门代码中,我们可以看到生产者直接调用了send方法,没有接收任何的返回值,这就是发送并忘记模式。该模式把消息发送出去就不管了,不管kafka服务是否接收消息成功。对于不重要的数据而言,我们可以采用该种方式。因为kafka有重试机制,对于一次没发送成功的数据,可以重试发送,所以,消息丢失的概率会减少,对于可接受丢失数据的业务而言,可以使用该模式。
  • 同步发送
    send方法是有返回值的,当我们忽略send方法返回值时,就是采用发送并忘记模式。send方法的返回值是Future对象,同步发送就是获取返回值Future对象,并在后面的代码中调用Future的get方法,将线程阻塞。待消息发送成功后,线程才往下执行。代码如下:
public class KafkaFutureProducer {

    private static KafkaProducer<String,String> producer = null;

    public static void main(String[] args) {
        public static void main (String[]args){
            Properties properties = new Properties();
            properties.put("bootstrap.servers", "39.100.116.73:9092");
            properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
            producer = new KafkaProducer<String, String>(properties);
            try {
                ProducerRecord<String, String> record;
                try {
                    record = new ProducerRecord<String, String>(
                            "Hello World", null, "同步发送");
                    //获取send方法的返回值,RecordMetadata对象是kafka服务收到消息后返回给生产者的消息
                    Future<RecordMetadata> future = producer.send(record);
                    System.out.println("这里可以处理其他业务");
                    //阻塞线程,直到kafka服务返回RecordMetadata数据
                    RecordMetadata recordMetadata = future.get();//阻塞在这个位置
                    if (null != recordMetadata) {
                        //RecordMetadata对象中包含了消息在kafka服务中的偏移量,分区等信息
                        System.out.println("offset:" + recordMetadata.offset() + "-" + "partition:" + recordMetadata.partition());
                    }

                } catch (Exception e) {
                 //如果发送失败,则抛出异常,进入catch语句,我们根据实际业务处理该异常
                    e.printStackTrace();
                }

            } finally {
                producer.close();
            }
        }


    }
}

  • 异步发送
    异步发送,在send方法里定义回调函数即可。第二个参数传入实现接口 org.apache.kafka.clients.producer.Callback类的实现类作为回调。
    代码如下:
public class KafkaAsynProducer {

    //private static KafkaProducer<String,String> producer = null;

    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("bootstrap.servers","127.0.0.1:9092");
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        KafkaProducer<String,String> producer = new KafkaProducer<String, String>(properties);
        /*待发送的消息实例*/
        ProducerRecord<String,String> record;
        try {
            record = new ProducerRecord<String,String>(
                    "ceshi",null,"异步发送");
            //第二个参数是Callback类,实现其onCompletion方法,进入回调函数
            producer.send(record, new Callback() {
                public void onCompletion(RecordMetadata metadata,
                                         Exception exception) {
                    if(null!=exception){
                        System.out.println("回掉函数抛出异常");
                        exception.printStackTrace();
                    }
                    if(null!=metadata){
                        System.out.println("进入了回调函数");
                        System.out.println("offset:"+metadata.offset()+"-"
                                +"partition:"+metadata.partition());
                    }
                }
            });
            System.out.println("回调之后");
        }catch (Exception e){
            System.out.println("catch语句抛出异常");
            e.printStackTrace();
        }finally {
            producer.close();
        }
        System.out.println("方法最后");
    }

这里,我们测试一下kafka的异步效果,是什么样的,首先,我们让消息发送成功,我们来看输出语句的顺序:
在这里插入图片描述
我们可以看到,调用send方法后,先执行了下面的"回调之后"输出语句,再执行了finally语句,才执行回调函数里的方法。这说明他是异步发生消息的,发送消息并不影响下面代码的执行流程。为什么"方法最后"最后输出呢,因为它是try之外的代码块,执行完finally,才会执行后面的代码。
下面,我们修改kafka的ip或端口,修改成一个错误的,或者把kafka服务关掉,总之就是模拟消息发送不成功的情况,我们再看其输出顺序:
在这里插入图片描述

运行代码后可以看到,"回调方法之后"的输出语句并没有第一时间输出,而是一直卡着等待kafka服务的连接,最后没连上,进入了回调函数,而后才走的输出语句。这就说明,kafka的异步发送,是在消息正常发送的情况下异步发送的。当消息没有正常发送时,线程还是会卡顿。所以,kafka的异步方式,并不是完全异步。其原理,我们在后面高级篇中进行讲解,可以看这篇文章进行参考:
https://blog.csdn.net/liuxiao723846/article/details/106106257

六、多线程环境下的生产者

kafka的生产者是线程安全的,即多个线程公用一个生产者,也不会发生数据安全问题。这里我们不做过多讲解,多线程下公用生产者的实际场景还是很少出现的。

七、生产者错误处理方式

生产者客户端发送消息给kafka服务端时,如果发生错误,分两种情况,一种情况是可重试错误。发生这种错误后,生产者会自动进行重试,不会报出错误,当重试次数到达设定次数后,如果还是失败,才会抛出错误。另一种情况是直接抛出错误的,如消息发送过大,超过了kafka服务端设定的阈值,就会直接报错。

八、生产者监听器

问题:ProducerListener监听器和发送消息的回调,有何区别?
@KafkaListener注解的使用

九、spring整合生产者客户端

我们在实际开发中,都会将kafka客户端整合在spring或springboot中,很少使用kafka提供的独立API进行开发。下面我们将spring整合kafka生产者。
首先,在pom中引入jar包。

 <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.3.0.RELEASE</version>
 </dependency>

然后,我们配置kafka的连接信息kafka.properties,类似于连接数据库的jdbc.properties文件。
在这里插入图片描述
然后,我们在spring的配置文件中,配置kafka生产者的整合信息:

 <!-- 配置kafka.properties文件路径 -->
 <context:property-placeholder location="classpath*:config/kafka.properties" />
  <!-- 定义producer的参数,就是我们讲的生产者的属性 -->
    <bean id="producerProperties" class="java.util.HashMap">
        <constructor-arg>
            <map>
                <entry key="bootstrap.servers" value="${bootstrap.servers}" />
                <entry key="key.serializer"
                       value="org.apache.kafka.common.serialization.StringSerializer" />
                <entry key="value.serializer"
                       value="org.apache.kafka.common.serialization.StringSerializer" />
            </map>
        </constructor-arg>
    </bean>

 <!-- 创建kafkatemplate需要使用的producerfactory bean。使用工厂类创建kafkatemplate -->
    <bean id="producerFactory"
          class="org.springframework.kafka.core.DefaultKafkaProducerFactory">
        <constructor-arg>
            <ref bean="producerProperties"/>
        </constructor-arg>

    </bean>
<!-- 生产者发送监听器bean -->
    <bean id="sendListener" class="xxx.xxx.xxx" />
     <!-- 创建kafkatemplate bean,使用的时候,只需要注入这个bean,
    即可使用template的send消息方法 -->
    <bean id="kafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate">
        <constructor-arg ref="producerFactory" />
        <constructor-arg name="autoFlush" value="true" />
        <!-- 配置发送监听器bean -->
        <property name="producerListener" ref="sendListener"></property>

    </bean>

我们在项目中,调用KafkaTemplate提供的方法,即可实现生产者功能。(KafkaTemplate类其实是spring通过调用Kafka提供的JAVA API封装的另一套方法)

其中,需要我们自定义一个监听器,实现ProducerListener接口,来定义消息发送成功或者发送失败后的回调函数,代码如下:

public class SendListener implements ProducerListener {

    public void onSuccess(String topic, Integer partition,
                          Object key, Object value, RecordMetadata recordMetadata) {
        System.out.println("offset:"+recordMetadata.offset()+"-"
                +"partition:"+recordMetadata.partition());
    }

    public void onError(String topic, Integer partition,
                        Object key, Object value, Exception exception) {

    }

    public boolean isInterestedInSuccess() {
        return true;
    }
}

十、springboot整合生产者客户端

首先,在pom文件中引入jar包:

 <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.3.0.RELEASE</version>
        </dependency>

然后,配置信息可以在配置类中书写:

@Configuration
@EnableKafka
public class KafkaProducerConfig {
    @Value("${kafka.producer.servers}")
    private String servers;
    @Value("${kafka.producer.retries}")
    private int retries;
    @Value("${kafka.producer.batch.size}")
    private int batchSize;
    @Value("${kafka.producer.linger}")
    private int linger;
    @Value("${kafka.producer.buffer.memory}")
    private int bufferMemory;


    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = new HashMap<>();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        props.put(ProducerConfig.RETRIES_CONFIG, retries);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
        props.put(ProducerConfig.LINGER_MS_CONFIG, linger);
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        return props;
    }

    public ProducerFactory<String, String> producerFactory() {
        return new DefaultKafkaProducerFactory<>(producerConfigs());
    }

    @Bean
    public KafkaTemplate<String, String> kafkaTemplate() {
        KafkaTemplate kafkaTemplate
                = new KafkaTemplate<String, String>(producerFactory()) ;
        //kafkaTemplate.setProducerListener();
        return kafkaTemplate;
    }
}

这样,就可以通过KafkaTemplate对象实现生产者的功能了。

1/kafka是一个分布式的消息缓存系统 2/kafka集群中的服务器都叫做broker 3/kafka有两类客户端,一类叫producer(消息生产者),一类叫做consumer(消息消费者),客户端和broker服务器之间采用tcp协议连接 4/kafka中不同业务系统的消息可以通过topic进行区分,而且每一个消息topic都会被分区,以分担消息读写的负载 5/每一个分区都可以有多个副本,以防止数据的丢失 6/某一个分区中的数据如果需要更新,都必须通过该分区所有副本中的leader来更新 7/消费者可以分组,比如有两个消费者组A和B,共同消费一个topic:order_info,A和B所消费的消息不会重复 比如 order_info 中有100个消息,每个消息有一个id,编号从0-99,那么,如果A组消费0-49号,B组就消费50-99号 8/消费者在具体消费某个topic中的消息时,可以指定起始偏移量 每个partition只能同一个group中的同一个consumer消费,但多个Consumer Group可同时消费同一个partition。 n个topic可以被n个Consumer Group消费,每个Consumer Group有多个Consumer消费同一个topic Topic在逻辑上可以被认为是一个queue,每条消费都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。若创建topic1和topic2两个topic,且分别有13个和19个分区 Kafka的设计理念之一就是同时提供离线处理和实时处理。根据这一特性,可以使用Storm这种实时流处理系统对消息进行实时在线处理,同时使用Hadoop这种批处理系统进行离线处理,还可以同时将数据实时备份到另一个数据中心,只需要保证这个操作所使用的Consumer属于不同的Consumer Group即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码的小小酥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值