KAFKA配置

基于日志的流处理平台

一、反序列化设置

二进制转换成特定的类型

实现接口Deserializer<T>,已经有的反序列化类有StringDeserializer

注意:自定义Serializer和Deserializer非常痛苦,而且上面还有很多异常情况没有处理,还有很多类型不支持,非常脆弱。复杂类型的支持更是一件痛苦的事情,不同版本之间的兼容性问题更是一个极大的挑战。由于Serializer和Deserializer影响到上下游系统,导致牵一发而动全身。自定义序列化&反序列化实现不是能力的体现,而是逗比的体现。所以强烈不建议自定义实现序列化&反序列化,推荐直接使用StringSerializer和StringDeserializer,然后使用json作为标准的数据传输格式。站在巨人的肩膀上,事半功倍。

public class NodeDeserializer implements Deserializer<Node> {

    private String encoding = "UTF8";

    @Override
    public void configure(Map<String, ?> configs, boolean isKey) {
        String propertyName = isKey ? "key.deserializer.encoding" : "value.deserializer.encoding";
        Object encodingValue = configs.get(propertyName);
        if (encodingValue == null) {
            encodingValue = configs.get("deserializer.encoding");
        }
        if (encodingValue != null && encodingValue instanceof String) {
            encoding = (String) encodingValue;
        }
    }

    @Override
    public Node deserialize(String topic, byte[] data) {
        String str = "";
        try {
            if (data == null) {
                return null;
            } else {
                str = new String(data, encoding);
            }
        } catch (UnsupportedEncodingException e) {
            throw new SerializationException("Error when deserializing byte[] to string due to unsupported encoding " + encoding);
        }
        try {
            return JSON.parseObject(str, Node.class);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public void close() {
// nothing to do
    }
}

二、分区 Partition

    Producer发送消息到broker时,会根据Paritition机制选择将其存储到哪一个Partition。如果Partition机制设置合理,所有消息可以均匀分布到不同的Partition里,这样就实现了负载均衡。如果一个Topic对应一个文件,那这个文件所在的机器I/O将会成为这个Topic的性能瓶颈,而有了Partition后,不同的消息可以并行写入不同broker的不同Partition里,极大的提高了吞吐率。可以在$KAFKA_HOME/config/server.properties中通过配置项num.partitions来指定新建Topic的默认Partition数量,也可在创建Topic时通过参数指定,同时也可以在Topic创建之后通过Kafka提供的工具修改。

    在发送一条消息时,可以指定这条消息的key,Producer根据这个key和Partition机制来判断应该将这条消息发送到哪个Parition。Paritition机制可以通过指定Producer的paritition. class这一参数来指定,该class必须实现 org.apache.kafka.clients.producer.Partitioner接口。

    可以自定义:如求余,2个分区,求余=0存0分区,求余=1存1分区。(每个Parition都会有个序号,序号从0开始)

public class CustomerPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        return Integer.valueOf((String) key).intValue() % 2;
    }
}

org.apache.kafka.clients.producer.internals.DefaultPartitioner

默认的分区策略是:

  • 如果在发消息的时候指定了分区,则消息投递到指定的分区
  • 如果没有指定分区,但是消息的key不为空,则基于key的哈希值来选择一个分区
  • 如果既没有指定分区,且消息的key也是空,则用轮询的方式选择一个分区

三、删除旧数据

Kafka集群会保留所有的消息,无论其被消费与否。当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此Kafka提供两种策略删除旧数据。一是基于时间,二是基于Partition文件大小。

可以通过配置$KAFKA_HOME/config/server.properties,让Kafka删除一周前的数据,也可在Partition文件超过1GB时删除旧数据。

############################# Log Retention Policy #############################

# The following configurations control the disposal of log segments. The policy can
# be set to delete segments after a period of time, or after a given size has accumulated.
# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens
# from the end of the log.

# The minimum age of a log file to be eligible for deletion due to age 
#数据保存1小时
#retention  -保持
#优先级 :log.retention.ms>log.retention.minutes>log.retention.hours
#log.retention.hours=1
#log.retention.ms

log.retention.minutes=10

#开启一些清理线程,执行定时清理任务
log.cleaner.enable=true

#清理策略   delete和compact
log.cleanup.policy=delete

# A size-based retention policy for logs. Segments are pruned from the log as long as the remaining
# segments don't drop below log.retention.bytes. Functions independently of log.retention.hours.
#超过特定的数据量或者时间,日志就会被删除
#log.retention.bytes=1073741824

# The maximum size of a log segment file. When this size is reached a new log segment will be created.
log.segment.bytes=1073741824

# The interval at which log segments are checked to see if they can be deleted according
# to the retention policies
log.retention.check.interval.ms=300000

四、TOPIC

Topic在逻辑上可以被认为是一个queue,每条消费都必须指定它的Topic,可以简单理解为必须指明把这条消息放进哪个queue里。为了使得Kafka的吞吐率可以线性提高,物理上把Topic分成一个或多个Partition,每个Partition在物理上对应一个文件夹,该文件夹下存储这个Partition的所有消息和索引文件。若创建topic1和topic2两个topic,且分别有13个和19个分区,则整个集群上会相应会生成共32个文件夹。

 五、consumer group

    这是Kafka用来实现一个Topic消息的广播(发给所有的Consumer)和单播(发给某一个Consumer)的手段。一个Topic可以对应多个Consumer Group。如果需要实现广播,只要每个Consumer有一个独立的Group就可以了。要实现单播只要所有的Consumer在同一个Group里。用Consumer Group还可以将Consumer进行自由的分组而不需要多次发送消息到不同的Topic。

    一个topic 可以配置几个partition,produce发送的消息分发到不同的partition中,consumer接受数据的时候是按照group来接受,kafka确保每个partition只能同一个group中的同一个consumer消费,如果想要重复消费,那么需要其他的组来消费。Zookeerper中保存这每个topic下的每个partition在每个group中消费的offset 
新版kafka把这个offsert保存到了一个__consumer_offsert的topic下 
这个__consumer_offsert 有50个分区,通过将group的id哈希值%50的值来确定要保存到那一个分区.  这样也是为了考虑到zookeeper不擅长大量读写的原因。
所以,如果要一个group用几个consumer来同时读取的话,需要多线程来读取,一个线程相当于一个consumer实例。当consumer的数量大于分区的数量的时候,有的consumer线程会读取不到数据。

六、kafka消费者如何分配分区

参考博客:

Kafka分区与消费者的关系 - 废物大师兄 - 博客园

10. kafka消费者如何分配分区_阿飞Javaer的专栏-CSDN博客_kafka 分区

七、配置参数

apache kafka中server.properties配置文件参数说明_水墨青花的专栏-CSDN博客_num.partitions

八、代码实例

public class ProducerThread {

    public static void main(String[] args) {
        Node node = new Node("1", "15", "ha");
        connectConsumer("5", node);
    }

    private static Properties getProperties() {
        Properties props = new Properties();
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
        props.put(ProducerConfig.RETRIES_CONFIG, 0);
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "com.ssh.hikvision.kafka.NodeSerializer");
        props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, CustomerPartitioner.class);
        return props;
    }

    private static void connectConsumer(String key, Node node) {
        KafkaProducer<String, Node> kafkaProducer = new KafkaProducer<>(getProperties());
        ProducerRecord<String, Node> record = new ProducerRecord<>("age", key, node);
        Future<RecordMetadata> send = kafkaProducer.send(record);
        try {
            RecordMetadata recordMetadata = send.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
public class ConsumerThread {

    public static void main(String[] args) {
        connectConsumer();
    }

    private static Properties getProperties() {
        Properties props = new Properties();
        // Set this property, if auto commit should happen.
        //该属性指定了消费者是否自动提交偏移量,默认值是true
        props.put("enable.auto.commit", false);
        // fetch.max.wait.ms
        props.put("fetch.max.wait.ms", 150);
        // Make Auto commit interval to a big number so that auto commit
        // does not happen,
        // we are going to control the offset commit via
        // consumer.commitSync(); after processing record.
//        if (config.isAutoCommitEnable()) {
//            // 自动提交的时间间隔
//            props.put("auto.commit.interval.ms", config.getAutoCommitIntervalMs());
//        }
        //默认值是1MB,该属性指定了服务器从每个分区里返回给消费者的最大字节数
        props.put("max.partition.fetch.bytes", 524288);
        props.put("heartbeat.interval.ms", 6000);
        //默认3s,该属性指定了消费者在被认为死亡之前可以与服务器断开连接的时间
        props.put("session.timeout.ms", 60000);
        // # 当zookeeper中没有初始的offset时,或者超出offset上限时的处理方式 。新建Group时使用latest
        // # 0.9.0:String must be one of: latest, earliest, none
        /**
         * earliest
         * 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
         * latest
         * 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
         * none
         * topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
         */
        props.put("auto.offset.reset", "earliest");
        props.put("max.poll.interval.ms", 300000);
        props.put("bootstrap.servers", "127.0.0.1:9092");
        props.put("group.id", "test8");
        props.put("client.id", "groupId + clientId");
        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        props.put("value.deserializer", "com.ssh.hikvision.kafka.NodeDeserializer");
        return props;
    }


    private static void connectConsumer() {
        KafkaConsumer<String, Node> kafkaConsumer = new KafkaConsumer<>(getProperties());
        kafkaConsumer.subscribe(Arrays.asList("age"));
        while (true) {
            try {
                // 最多等待100毫秒去拉数据//拉数据 可能连续拉好几条
                ConsumerRecords<String, Node> records = kafkaConsumer.poll(100);
                if (records != null && !records.isEmpty()) {
                    List<Node> datas = new ArrayList<>();
                    for (ConsumerRecord<String, Node> record : records) {
                        datas.add(record.value());
                    }
                    if (!CollectionUtils.isEmpty(datas)) {
                        // 如果可以提交,则提交offset并且记录提交时间
                        kafkaConsumer.commitAsync();
                    }
                }

                Thread.sleep(100);
            } catch (Exception e) {
                kafkaConsumer.close();
            }
        }

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值