kafka

 

另外找一个zk,有客户端命令的
.\zkCli.cmd -server 127.0.0.1:2181
ls /brokers 查看注册信息

1,kafka不支持分布式事务消息 不支持消费失败重试
2,kafka的单机TPS能跑到每秒上百万,是因为Producer端将多个小消息合并,批量发向broker
3,RocketMQ写入性能上不如kafka, 主要因为kafka主要应用于日志场景,而RocketMQ应用于业务场景,为了保证消息必达牺牲了性能,且基于线上真实场景没有在RocketMQ层做消息合并,推荐在业务层自己做。
4,没有“中心主节点”的概念,集群中所有的服务器都是对等的,因此,可以在不做任何配置的更改的情况下实现服务器的的添加与删除
https://blog.csdn.net/pengweismile/article/details/117636252

https://kafka.apache.org/quickstart
.\bin\windows\zookeeper-server-start.bat .\config\zookeeper.properties
.\bin\windows\kafka-server-start.bat .\config\server.properties
.\bin\windows\kafka-topics.bat --create --topic topic-xmh --bootstrap-server localhost:9092
.\bin\windows\kafka-topics.bat --describe --topic topic-xmh --bootstrap-server localhost:9092

.\bin\windows\kafka-console-consumer.bat --topic topic-xmh --from-beginning --bootstrap-server localhost:9092 
.\bin\windows\kafka-console-producer.bat --topic topic-xmh --bootstrap-server localhost:9092


https://blog.csdn.net/syc0616/article/details/118156641
producer配置
bootstrap.servers: kafka的地址。
acks:消息的确认机制,默认值是0。
acks=0:如果设置为0,生产者不会等待kafka的响应。
acks=1:这个配置意味着kafka会把这条消息写到本地日志文件中,但是不会等待集群中其他机器的成功响应。
acks=all:这个配置意味着leader会等待所有的follower同步完成。这个确保消息不会丢失,除非kafka集群中所有机器挂掉。这是最强的可用性保证。
retries:配置为大于0的值的话,客户端会在消息发送失败时重新发送。
batch.size:当多条消息需要发送到同一个分区时,生产者会尝试合并网络请求。这会提高client和生产者的效率。
key.serializer: 键序列化,默认org.apache.kafka.common.serialization.StringDeserializer。
value.serializer:值序列化,默认org.apache.kafka.common.serialization.StringDeserializer。

public class ProMy {
    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");//,slave1:9092,slave2:9092
        props.put("acks", "all");
        props.put("retries", 0);
        props.put("batch.size", 1);//16384
        props.put("key.serializer", StringSerializer.class.getName());
        props.put("value.serializer", StringSerializer.class.getName());
        KafkaProducer producer = new KafkaProducer(props);
        producer.send(new ProducerRecord<String, String>("topic-xmh","xingkey2","xingvalues5"));
        producer.close();
        System.out.println("*************end-procuder");
    }
}

consumer配置
bootstrap.servers: kafka的地址。
group.id:组名 不同组名可以重复消费。例如你先使用了组名A消费了kafka的1000条数据,但是你还想再次进行消费这1000条数据,并且不想重新去产生,那么这里你只需要更改组名就可以重复消费了。
enable.auto.commit:是否自动提交,默认为true。
auto.commit.interval.ms: 从poll(拉)的回话处理时长。
session.timeout.ms:超时时间。
max.poll.records:一次最大拉取的条数。
auto.offset.reset:消费规则,默认earliest 。
earliest: 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费 。
latest: 当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据 。
none: topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常。
key.serializer: 键序列化,默认org.apache.kafka.common.serialization.StringDeserializer。
value.deserializer:值序列化,默认org.apache.kafka.common.serialization.StringDeserializer。

    public static void main(String[] args) {
        Properties props = new Properties();
        props.put("bootstrap.servers", "localhost:9092");//,slave1:9092,slave2:9092
        props.put("group.id", "group_x3");
        props.put("enable.auto.commit", "true");
        props.put("auto.commit.interval.ms", "1000");
        props.put("session.timeout.ms", "30000");
//        props.put("max.poll.records", 1);
        props.put("auto.offset.reset", "earliest");
//        props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
//        props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
        props.put("key.deserializer", StringDeserializer.class.getName());
        props.put("value.deserializer", StringDeserializer.class.getName());
        KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);

        consumer.subscribe(Arrays.asList("topic-xmh"));
        //for(;;) {
            ConsumerRecords<String, String> msgList = consumer.poll(1000);
//            System.out.println("consumer*******:" + msgList);
            for (ConsumerRecord<String, String> record:msgList){
                System.out.println("**************consumer*******record1:"+record+","+record.key()+","+record.value());
            }
        //}

//        consumer.close();
    }

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka_2.12</artifactId>
    <version>1.0.0</version>
    <scope>provided</scope>
</dependency>

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

<dependency>
    <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-streams</artifactId>
    <version>1.0.0</version>
</dependency>

 

 

 

spring-kafka

@Autowired
private KafkaTemplate<Object, Object> kafkaTemplate;
@RequestMapping("/send")
public void sendMultiple() {
String message = "xmh发送到Kafka的消息";
kafkaTemplate.send("xmh-simple","xmhkey", message );
System.out.println(message );
}

    /**
     * 监听kafka消息
     */
    @KafkaListener(groupId = "${spring.kafka.consumer.group-id}", topics = {"xmh-simple"})
    public void listenTopicsAuto(ConsumerRecord<Object, Object> consumerRecord, Acknowledgment ack) {
        try {

            System.out.println("listenTopics接受消息:" + consumerRecord.value());
            //手动确认
            ack.acknowledge();
        } catch (Exception e) {
            System.out.println("消费失败:" + e);
        }
    }

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.8</version>
        </dependency>

        <!-- kafka -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.8.8</version>
        </dependency>


spring:
  kafka:
    bootstrap-servers: ${KAFKA_LIST:172.x.x.xx:9092}
    producer:
      # 发生错误后,消息重发的次数。
      retries: 1
      #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。 1000000
      batch-size: 16389
      # 设置生产者内存缓冲区的大小。 33554432
      buffer-memory: 33554432
      # 键的序列化方式
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      # 值的序列化方式
      value-serializer: org.apache.kafka.common.serialization.StringSerializer
      # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
      # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
      # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
      acks: all
      compression-type: lz4
    consumer:
      # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D
      auto-commit-interval: 1S
      group-id: xmh2
      # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
      # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
      # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
      auto-offset-reset: earliest
      # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
      enable-auto-commit: false
      # 键的反序列化方式
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 值的反序列化方式
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      # 主动拉取消息模式下,每次拉取消息个数,该值默认是500
      max-poll-records: 10000
      properties:
        # 每次最多获取100M的数据
        max.partition.fetch.bytes: 104857600
    listener:
      # 在侦听器容器中运行的线程数。
      concurrency: 5
      #listner负责ack,每调用一次,就立即commit
      ack-mode: manual_immediate
      missing-topics-fatal: false

@Configuration
public class KafkaProviderConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
    @Value("${spring.kafka.producer.acks}")
    private String acks;
    @Value("${spring.kafka.producer.retries}")
    private String retries;
    @Value("${spring.kafka.producer.batch-size}")
    private String batchSize;
    @Value("${spring.kafka.producer.buffer-memory}")
    private String bufferMemory;
    @Bean
    public Map<String, Object> producerConfigs() {
        Map<String, Object> props = new HashMap<>(16);
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        //响应模式,我们使用acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
        props.put(ProducerConfig.ACKS_CONFIG, acks);
        //发生错误后,消息重发的次数,开启事务必须大于0
        props.put(ProducerConfig.RETRIES_CONFIG, retries);
        //当多个消息发送到相同分区时,生产者会将消息打包到一起,以减少请求交互. 而不是一条条发送
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
        //有的时刻消息比较少,过了很久,比如5min也没有凑够16KB,这样延时就很大,所以需要一个参数. 再设置一个时间,到了这个时间,
        props.put(ProducerConfig.LINGER_MS_CONFIG, "5000");
        //生产者内存缓冲区的大小
        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);

		//用户名密码配置,没有用户名密码可以去掉以下配置
//        props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
//        props.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
//        props.put("java.security.auth.login.config", "10000");
//        // 可以在nacos配置文件中配置
//        props.put(SaslConfigs.SASL_JAAS_CONFIG, String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin1234\";"));
        return props;
    }
	// 生产者工厂
    @Bean("kafkaProduceFactory")
    public ProducerFactory<Object, Object> producerFactory() {
        DefaultKafkaProducerFactory<Object, Object> factory = new DefaultKafkaProducerFactory<>(producerConfigs());
        factory.setTransactionIdPrefix("kafkaXmh-");
        return factory;
    }
	// 事务处理
	// 这里的事务处理会和项目中的其他事务起冲突,所以我一般会把@Bean去掉,不用spring代理
    @Bean("kafkaTransactionManager")
    @Primary
    public KafkaTransactionManager<Object, Object> kafkaTransactionManager(ProducerFactory<Object, Object> producerFactory) {
        return new KafkaTransactionManager<Object, Object>(producerFactory);
    }
    @Bean
    public KafkaTemplate<Object, Object> kafkaTemplate() {
        KafkaTemplate template = new KafkaTemplate<>(producerFactory());
        template.setProducerListener(new KafkaSendResultHandler());
        return template ;
    }


@Configuration
public class KafkaConsumerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String bootstrapServers;
    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;
    @Value("${spring.kafka.consumer.enable-auto-commit}")
    private boolean enableAutoCommit;
    @Value("${spring.kafka.properties.session.timeout.ms:10000}")
    private String sessionTimeout;
    @Value("${spring.kafka.properties.max.poll.interval.ms:600000}")
    private String maxPollIntervalTime;
    @Value("${spring.kafka.consumer.max-poll-records:3}")
    private String maxPollRecords;
    @Value("${spring.kafka.consumer.auto-offset-reset:latest}")
    private String autoOffsetReset;
    @Value("${spring.kafka.listener.concurrency:4}")
    private Integer concurrency;
    @Value("${spring.kafka.listener.missing-topics-fatal:false}")
    private boolean missingTopicsFatal;
    @Value("${spring.kafka.listener.poll-timeout:600000}")
    private long pollTimeout;
    @Bean
    public Map<String, Object> consumerConfigs() {
        Map<String, Object> propsMap = new HashMap<>(16);
        // 服务器地址,不多说配置直接用
        propsMap.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        // groupId不多说,直接用
        propsMap.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        //是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量
        propsMap.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        //自动提交的时间间隔,自动提交开启时生效
        propsMap.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "2000");
        //该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
        //我们使用latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
        propsMap.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        //两次poll之间的最大间隔,默认值为5分钟。如果超过这个间隔会触发reBalance
        propsMap.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalTime);
        //这个参数定义了poll方法最多可以拉取多少条消息,默认值为500。
        propsMap.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
        //当broker多久没有收到consumer的心跳请求后就触发reBalance,默认值是10s
        propsMap.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, sessionTimeout);
        //序列化(我们这边使用StringDeserializer,与生产者保持一致)
        propsMap.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        propsMap.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        // 下面四个参数是用户名密码的参数,没有用户名密码可以去掉以下配置
//        propsMap.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, SecurityProtocol.SASL_PLAINTEXT.name);
//        propsMap.put(SaslConfigs.SASL_MECHANISM, "PLAIN");
//        propsMap.put("java.security.auth.login.config", "10000");
//        // 这里username设置用户名, password设置密码我写死到代码里了,可以更改为nacos配置
//        propsMap.put(SaslConfigs.SASL_JAAS_CONFIG, String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"admin\" password=\"admin1234\";"));
        return propsMap;
    }
	// 消费者工厂,将配置信息加载进去
    @Bean("consumerFactory")
    public DefaultKafkaConsumerFactory consumerFactory(){
        return new DefaultKafkaConsumerFactory(consumerConfigs());
    }
    @Bean("listenerContainerFactory")
    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Object, Object>> kafkaListenerContainerFactory() {
        ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
        factory.setConsumerFactory(consumerFactory());
        //在侦听器容器中运行的线程数,一般设置为 机器数*分区数
        factory.setConcurrency(concurrency);
        //消费监听接口监听的主题不存在时,默认会报错,所以设置为false忽略错误
        factory.getContainerProperties().setMissingTopicsFatal(missingTopicsFatal);
        //自动提交关闭,需要设置手动消息确认
        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
        factory.getContainerProperties().setPollTimeout(pollTimeout);
        return factory;
    }


@Component
public class KafkaSendResultHandler implements ProducerListener<Object, Object> {
    @Override
    public void onSuccess(ProducerRecord producerRecord, RecordMetadata recordMetadata) {
        System.out.println("消息发送成功***:" + producerRecord.toString());
    }
    @Override
    public void onError(ProducerRecord producerRecord, @Nullable RecordMetadata recordMetadata, Exception exception) {
        System.out.println("消息发送失败***:" + producerRecord.toString() + exception.getMessage());
    }

@Component
public class KafkaConsumerListenerError implements KafkaListenerErrorHandler {
    @Override
    @NonNull
    public Object handleError(Message<?> message, ListenerExecutionFailedException e) {
        return new Object();
    }
    @Override
    public Object handleError(Message<?> message, ListenerExecutionFailedException exception, Consumer<?, ?> consumer) {
        System.out.println("消息详情:" + message);
        System.out.println("异常信息::" + exception);
        System.out.println("消费者详情::" + consumer.groupMetadata());
        System.out.println("监听主题::" + consumer.listTopics());
        return KafkaListenerErrorHandler.super.handleError(message, exception, consumer);
    }


   /**
     * 监听kafka消息
     * 使用autoStartup = "false"必须指定id
     */
    @KafkaListener(id = "${spring.kafka.consumer.group-id}", topics = {"KAFKA_TEST_TOPICS"}, autoStartup = "false")
    public void listenTopics(ConsumerRecord<Object, Object> consumerRecord, Acknowledgment ack) {
        try {

            System.out.println("listenTopics接受消息:" + consumerRecord.value());
            //手动确认
            ack.acknowledge();
        } catch (Exception e) {
            System.out.println("消费失败:" + e);
        }
    }
    /**
     * 下面的方法可以手动操控kafka的队列监听情况
     * 先发送一条消息,因为autoStartup = "false",所以并不会看到有消息进入监听器。
     * 接着启动监听器,/start/testGroup。可以看到有一条消息进来了。
     * start是开启监听,stop是关闭监听
     * pause是暂停监听,resume是继续监听
     * @param listenerId consumer的group-id
     */
    @RequestMapping("/pause/{listenerId}")
    public void pause(@PathVariable String listenerId) {
        try {
            Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).pause();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @RequestMapping("/resume/{listenerId}")
    public void resume(@PathVariable String listenerId) {
        try {
            Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).resume();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @RequestMapping("/start/{listenerId}")
    public void start(@PathVariable String listenerId) {
        try {
            Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @RequestMapping("/stop/{listenerId}")
    public void stop(@PathVariable String listenerId) {
        try {
            Objects.requireNonNull(kafkaListenerEndpointRegistry.getListenerContainer(listenerId)).stop();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 

posted @ 2022-08-16 14:51  XUMT111  阅读(57)  评论(0编辑  收藏  举报