Kafka消费顺序保证

面试被问到如何按照Producer的顺序去消费Consumer?故在此做个记录。

首先几个概念Topic,Producer

topic

-Topic:A topic is a category or feed name to which records are published. Topics in Kafka are always multi-subscriber; that is, a topic can have zero, one, or many consumers that subscribe to the data written to it.

- 主题:主题是被发布记录的类别或提要名称,Topics在kafka中通常是多订阅者模式;也就是说,一个主题可以有0,1,甚至是多个消费者去订阅消费topic所写的数据;

For each topic, the Kafka cluster maintains a partitioned log that looks like this:

- 对于每一个主题,kafka集群都维护如图所示的分区日志:(从左到右写入)

                                             

- Each partition is an ordered, immutable sequence of records that is continually appended to—a structured commit log. The records in the partitions are each assigned a sequential id number called the offset that uniquely identifies each record within the partition.

-每一个分区都按照有序的,不变的且在持续追加的日志记录 - 结构化提交日志。在分区中的每一个记录都会分配到一个有序的名为偏移量的id,并且该id能够唯一表示分区内的每一条记录;

- The Kafka cluster durably persists all published records—whether or not they have been consumed—using a configurable retention period. For example, if the retention policy is set to two days, then for the two days after a record is published, it is available for consumption, after which it will be discarded to free up space. Kafka's performance is effectively constant with respect to data size so storing data for a long time is not a problem.

 - kafka集群能持久的保持所有发布的记录 - 无论记录是否被消费 - 并且持久的保留期是可以配置的。举个例子,如果你设置保留期为2Days,那么记录发布两天之内是能够被消费的,两天之后该记录就会被丢弃以腾出存储空间。kafka关于数据大小的性能表现是极其稳定的所以长时间存储数据不是问题。

                                                   

- In fact, the only metadata retained on a per-consumer basis is the offset or position of that consumer in the log. This offset is controlled by the consumer: normally a consumer will advance its offset linearly as it reads records, but, in fact, since the position is controlled by the consumer it can consume records in any order it likes. For example a consumer can reset to an older offset to reprocess data from the past or skip ahead to the most recent record and start consuming from "now".

- 偏移量主要是由消费者管理,因此消费者可以自由控制消费信息的顺序,但是通常情况下都是线性推进的。

- This combination of features means that Kafka consumers are very cheap—they can come and go without much impact on the cluster or on other consumers. For example, you can use our command line tools to "tail" the contents of any topic without changing what is consumed by any existing consumers.

- 这个特性意味着kafka消费者是cheap的-因为他们在集群中的加入以及离开操作对集群其他消费者几乎没啥影响。

- The partitions in the log serve several purposes. First, they allow the log to scale beyond a size that will fit on a single server. Each individual partition must fit on the servers that host it, but a topic may have many partitions so it can handle an arbitrary amount of data. Second they act as the unit of parallelism—more on that in a bit.

- 日志分区有几个目的:首先,分区允许日志大小超过所在单服务器的容量大小。每一个分区必须适应所在服务器,但是一个topic可分成多个分区,所以一个topic就可以处理任意数量的数据。其次,分区还可以作为平行度的单位(类似于横向扩展?)

producer

- Producers publish data to the topics of their choice. The producer is responsible for choosing which record to assign to which partition within the topic. This can be done in a round-robin fashion simply to balance load or it can be done according to some semantic partition function (say based on some key in the record). 

- 生产者发布数据到主题。生产者决定哪条记录发布到topic中的那个分区 - 采用循环的方式(平衡负载),也会根据语义分配函数(比如基于记录中的key)

-- Translation未完待续。。。

综上所述,可以推出每一个topic中的每一partition中都是有序队列,但是partition之间就不一定,顺序是随机的;

- solutions:

1.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION

NAMEDESCRIPTIONTYPEDEFAULTVALID VALUESIMPORTANCE

max.in

.flight

.requests

.per.connection

The maximum number of unacknowledged requests the client will send on a single connection before blocking. Note that if this setting is set to be greater than 1 and there are failed sends, there is a risk of message re-ordering due to retries (i.e., if retries are enabled).int5[1,...]low

- 设置客户端在阻塞之前发送给单一连接点最大未确认请求的次数;缺省值为5,如果设置大于1且出现失败发送,就会有重复发送的操作,导致消息重复排序。

- 设置为1,顺序正常;

- 但是设置为1也会造成发送到同一个分区的消息重新排序;

retriesSetting a value greater than zero will cause the client to resend any record whose send fails with a potentially transient error. Note that this retry is no different than if the client resent the record upon receiving the error. Allowing retries without setting max.in.flight.requests.per.connection to 1 will potentially change the ordering of records because if two batches are sent to a single partition, and the first fails and is retried but the second succeeds, then the records in the second batch may appear first. Note additionally that produce requests will be failed before the number of retries has been exhausted if the timeout configured by delivery.timeout.ms expires first before successful acknowledgement. Users should generally prefer to leave this config unset and instead use delivery.timeout.ms to control retry behavior.int2147483647[0,...,2147483647]high

 

2.enable.idempotence = true

- 需要保证消息是有序且唯一的,可以将参数enable.idmpotence = true;

enable.idempotenceWhen set to 'true', the producer will ensure that exactly one copy of each message is written in the stream. If 'false', producer retries due to broker failures, etc., may write duplicates of the retried message in the stream. Note that enabling idempotence requires max.in.flight.requests.per.connection to be less than or equal to 5, retriesto be greater than 0 and acks must be 'all'. If these values are not explicitly set by the user, suitable values will be chosen. If incompatible values are set, a ConfigExceptionwill be thrown.booleanfalse low

- 幂等性:在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

- 设置为true时,生产者保证每一次消息的copy都是唯一的,也就是说底层在出现retry的时候也会通过增加一个用户不可见的pid值,而broker只会接受同一个pid值且sequence值+1的消息,这样就算是失败了重试也能保证数据不重复。

- 参数前提:保证max.in.flight.requests.per.conection 小于等于5大于0且acks必须是all;

如有错误,欢迎指正~

REF:http://kafka.apache.org/documentation/

<div class="post-text" itemprop="text"> <p>I've never used kafka before. I have two test Go programs accessing a local kafka instance: a reader and a writer. I'm trying to tweak my producer, consumer, and kafka server settings to get a particular behavior.</p> <p>My writer:</p> <pre><code>package main import ( "fmt" "math/rand" "strconv" "time" "github.com/confluentinc/confluent-kafka-go/kafka" ) func main() { rand.Seed(time.Now().UnixNano()) topics := []string{ "policymanager-100", "policymanager-200", "policymanager-300", } progress := make(map[string]int) for _, t := range topics { progress[t] = 0 } producer, err := kafka.NewProducer(&kafka.ConfigMap{ "bootstrap.servers": "localhost", "group.id": "0", }) if err != nil { panic(err) } defer producer.Close() fmt.Println("producing messages...") for i := 0; i < 30; i++ { index := rand.Intn(len(topics)) topic := topics[index] num := progress[topic] num++ fmt.Printf("%s => %d ", topic, num) msg := &kafka.Message{ Value: []byte(strconv.Itoa(num)), TopicPartition: kafka.TopicPartition{ Topic: &topic, }, } err = producer.Produce(msg, nil) if err != nil { panic(err) } progress[topic] = num time.Sleep(time.Millisecond * 100) } fmt.Println("DONE") } </code></pre> <p>There are three topics that exist on my local kafka: policymanager-100, policymanager-200, policymanager-300. They each only have 1 partition to ensure all messages are sorted by the time kafka receives them. My writer will randomly pick one of those topics and issue a message consisting of a number that increments solely for that topic. When it's done running, I expect the queues to look something like this (topic names shortened for legibility):</p> <pre><code>100: 1 2 3 4 5 6 7 8 9 10 11 200: 1 2 3 4 5 6 7 300: 1 2 3 4 5 6 7 8 9 10 11 12 </code></pre> <p>So far so good. I'm trying to configure things so that any number of consumers can be spun up and consume these messages in order. By "in-order" I mean that no consumer should get message 2 for topic 100 until message 1 is COMPLETED (not just started). If message 1 for topic 100 is being worked on, consumers are free to consume from other topics that currently don't have a message being processed. If a message of a topic has been sent to a consumer, that entire topic should become "locked" until either a timeout assumes that the consumer failed or the consumer commits the message, then the topic is "unlocked" to have it's next message made available to be consumed.</p> <p>My reader:</p> <pre><code>package main import ( "fmt" "time" "github.com/confluentinc/confluent-kafka-go/kafka" ) func main() { count := 2 for i := 0; i < count; i++ { go consumer(i + 1) } fmt.Println("cosuming...") // hold this thread open indefinitely select {} } func consumer(id int) { c, err := kafka.NewConsumer(&kafka.ConfigMap{ "bootstrap.servers": "localhost", "group.id": "0", // strconv.Itoa(id), "enable.auto.commit": "false", }) if err != nil { panic(err) } c.SubscribeTopics([]string{`^policymanager-.+$`}, nil) for { msg, err := c.ReadMessage(-1) if err != nil { panic(err) } fmt.Printf("%d) Message on %s: %s ", id, msg.TopicPartition, string(msg.Value)) time.Sleep(time.Second) _, err = c.CommitMessage(msg) if err != nil { fmt.Printf("ERROR commiting: %+v ", err) } } } </code></pre> <p>From my current understanding, the way I'm likely to achieve this is by setting up my consumer properly. I've tried many different variations of this program. I've tried having all my goroutines share the same consumer. I've tried using a different <code>group.id</code> for each goroutine. None of these was the right configuration to get the behavior I'm after.</p> <p>What the posted code does is empty out one topic at a time. Despite having multiple goroutines, the process will read all of 100 then move to 200 then 300 and only one goroutine will actually do all the reading. When I let each goroutine have a different <code>group.id</code> then messages get read by multiple goroutines which I would like to prevent.</p> <p>My example consumer is simply breaking things up with goroutines but when I begin working this project into my use case at work, I'll need this to work across multiple kubernetes instances that won't be talking to each other so using anything that interacts between goroutines won't work as soon as there are 2 instances on 2 kubes. That's why I'm hoping to make kafka do the gatekeeping I want.</p> </div>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页