• 【中间件】Kafka消息中间件的简单介绍及应用


    一、介绍

      注:本文所指版本Kafka 1.1

      Kafka是由Apache开发的一款发布订阅消息系统,是分布式的,分区的重复的日志服务。

     1、为什么要用kafka?

      ①、解耦

         允许两方修改处理过程,只要遵循共同的接口约束。

      ②、灵活性和峰值处理能力

         面对突然增加的吞吐量有很好应对,发送信息量50M,消费信息量100M。

      ③、消息冗余

         消息队列把数据持久化直到已经完全被处理。与以往消息“插入 - 获取 - 删除”不同,在删除消息时,必须确定消息已被处理完毕。

      ④、扩展性

         扩展性较好,只要增加入队和消费处理过程即可。

      ⑤、顺序保证

         针对消息顺序的重要性,kafka保证一个partition内的消息有序性。

        

     2、应用场景

      ①、日志收集

        ELK日志采集框架中,利用kafka同Logstash来收集服务端日志。

      ②、消息系统

        解耦生产者与消费者,缓存消息,实现异步处理。

      ③、实现消息 “发布-订阅模式“

        对于不同消费者消费同一消息,利用Kafka实现:同一个topic中的消息只能被同一个Consumer Group中的一个消费者消费,但可以被多个Consumer Group消费这一消息。

      ④、用户活动跟踪

        Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到Hadoop、数据仓库中做离线分析和挖掘。

      ⑤、运营指标

        Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。

    二、Kafka分析

     大概介绍些知识点,作为一个后端开发,了解如何用及基本原理就可以了。

    kafka官网给出的交互流程

    消息处理流程

    producer.send(ProducerRecord<K,V> record);  生产者在发送消息时,没有找到topic,会自动创建???

    Broker Configs

      auto.create.topics.enable = true (默认为true,若没找到topic则自动创建)

    三、具体应用

    注意我这里有些值写的是伪代码,还需封装到一个公共类中调取。如kafka地址等。

    另外,下列代码全手打,有错误的地方请指正。

    1、pom依赖

    producer和consumer均依赖 kafka-clients.

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

    2、Producer

    2.1、producer configs

    Properties properties = new Properties();
    properties.put("bootstrap.servers", "ipAndPort"); //指定kafka服务地址,集群的情况用逗号分隔,如:host1:port1,host2:port2 ...
    properties.put("acks", "all");//表示完成Requests前需要承认的数量。 0:无需承认直接发送到socket  1:需要leader承认  all/-1:需要全部承认后发送
    properties.put("retries", 0);//发生错误时,重传次数。当开启重传时,需要将`max.in.flight.requests.per.connection`设置为1,否则可能导致失序
    properties.put("batch.size", 16384);
    properties.put("linger.ms", 1); //1毫秒,简单讲,就是延时1ms,把期间收集到的所有Requests聚合到一起发送,以此提高吞吐量
    properties.put("buffer.memory", 33554432);//默认值就是 33554443,缓存数据的内存大小;若生产速度大于Producer向Broker发送速度,会阻塞超时抛出异常
    properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //指定扔到kafka的键值对中键的类型,实例包下还有Long、Double、Short等等
    properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); //指定键值对的值类型,具体同上

    2.2、KafkaProducerUtil.java

    这里定义一个Producer的工具类,封装了producer的初始化及发送消息到指定topic。

    public class KafkaProducerUtil {
        private static Logger logger = Logger.getLogger(KafkaProducerUtil.class);
        private static Producer<String, String> producer;

      /** 做一个简单的单例模式,实例化producer对象 **/
    private static Producer<String, String> getProducer() { if (producer == null) { synchronized (KafkaProducerUtil.class) { if (producer == null) { Properties properties = new Properties(); //写入配置信息供初始化Producer producer = new KafkaProducer<String, String>(properties); } } } return producer; }
      /**
       * send record to topic
       */
    public static void sendToKafka(String topic, String message, Long timeOut) throws InterruptedException, ExecutionException, TimeoutException { producer = getProducer(); producer.send(new ProducerRecord<String, String>(topic, message)).get(timeOut, TimeUnit.SECONDS); logger.info("sendToKafka:" + message); } }

    2.3、KafkaProducerService.java

    提供工业务层调用的接口,这里做了http请求方式的兼容处理。

    public interface KafkaProducerService {
    /**
    * @param httpUrl 通过http发送请求的方式调用地址
    * @param code 生成topic
    * @param request 请求数据
    * @param version 加签
    * @param timeOut 超时时间
    */
    void send(String httpUrl, String code, Object request, String version, Long timeout);
    }
    @Service
    public class KafkaProducerServiceImpl implements KafkaProducerService {
      
      private static Logger logger = LoggerFactory.getLogger(KafkaProducerServiceImpl.class);
    
      public void send(String httpUrl, String code, Object request, String version, Long timeout) {
        String isOpenFlag = "可以作为系统参数,不同环境有不同的启用程度,开启走kafka,关闭走http方式";
        if ("open" == isOpenFlag) {
          sendByKafka(httpUrl, code, request, version, timeout);
        } else {
          sendByHttp(...);
        }
      }
    
      private void sendByKafka(String httpUrl, String code, Object request, String version, Long timeout) {
        // 按一定规则拼接topic
        String topic = "XXX可以依环境决定,也可以依系统决定" + "_" + code;
        // 取加签私钥
        String privateKey = "自行封装";
        logger.info("topic:" + topic + "请求报文:" + JSON.toJSONOString(request, SerializerFeature,WriteMapNullValue));
        // 报文加签
        Object producerObject = SecurityUtil.digest(request, privateKey, version);
        String producerRecord = JSON.toJSONString(producerObject, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteMapNullValue);
        try {
          KafkaProducerUtil.sendToKafka(topic, producerRecord, timeout);
        } catch (RuntimeException e) {
          //出现异常,改用http调用
          logger.error(e.getMessage());
          sendByHttp(httpUrl, request, version);
        }
      }
    
      private void sendByHttp(String httpUrl, Strign request, String version) {
        //TODO 自行封装,通常情况加验签,把请求报文打印日志,转成json格式发送至api接口,此处不过多赘述。
      }
    }

    以上基本满足Producer方使用。

    3、Consumer

    3.1、consumer configs

    Properties props = new Properties();
    props.put("bootstrap.servers", "IpAndPort");
    props.put("enable.auto.commit", "true");
    props.put("auto.commit.interval.ms", "1000");//自动提交时间间隔,前提是 enable.auto.commit设置为true
    props.put("session.timeout.ms", "30000");
    props.put("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");//指定接受到数据的键值对类型
    props.put("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
    props.put("group.id", "unique group id");//指定consumer的唯一group

     3.2、KafkaConsumerInit.java

    consumer方初始化类,封装参数配置、线程定义,业务接口调用等。

    public class KafkaConsumerInit extends Thread {
        private static final Logger logger = LoggerFactory.getLogger(KafkaConsumerInit.class);
    
        private final AtomicBoolean closed = new AtomicBoolean(false);
        KafkaConsumer consumer;
        //此处格式<topic对应code,对应业务bean名>
        private Map<String, String> topicsAndBeans;
    
        public KafkaConsumerInit(Map<String, String> topicsAndBeans) {
            super();
            // 做转换,根据部署环境,topic加环境的前缀
            Set<String> codes= topicsAndBeans.keySet();
            Map<String, String> realTopicsAndBeans = new HashMap<String, String>();
            for (String code : codes) {
                String topic = "XXX可以依环境决定,也可以依系统决定" + "_" + code;
                realTopicsAndBeans.put(topic, topicsAndBeans.get(code));
            }
            this.topicsAndBeans = realTopicsAndBeans;
        }
    
        @Override
        public void run() {
            String isOpenFlag = "XXX";
            if (!"open".equals(isOpenFlag)) {
                logger.info("===============配置文件设置 KafkaConsumer 不启动===============");
                return;
            }
            logger.info("===============启动KafkaConsumer===============");
            try {
                Properties props = new Properties();
                //TODO 添加consumer configs
                consumer = new KafkaConsumer<>(props);
                //给consumer注册topics 类型Collection<String>
                consumer.subscribe(topicsAndBeans.keySet());
                logger.info("初始化consumer参数");
                while (!closed.get()) {
                    ConsumerRecords<String, String> records = consumer.poll(100);
                    for (ConsumerRecord<String, String> record : records) {
                        try {
                            logger.info("offset = " + record.offset() + ", value = " + record.value());
                            logger.info(record.value());
                   //定义一个统一接口,不同业务实现同一接口。 CommunicationConsumerService communicationHandleService
    = (CommunicationConsumerService) ApplicationContext.getContext() .getBean(topicsAndBeans.get(record.topic())); communicationHandleService.doHandle(record.value()); } catch (Exception e) { logger.error("数据处理异常:" + record.value()); logger.error(e.getMessage(), e); } } sleep(1000); } } catch (WakeupException e) { logger.error(e.getMessage(), e); if (!closed.get()) { throw e; } } catch (InterruptedException e) { logger.error(e.getMessage(), e); } finally { consumer.close(); } } }

    3.3、KafkaConsumerListener.java

    服务启动后初始化kafka,这里是利用基于Spring的ApplicationListener接口实现的,若果这方面知识还不清楚,先请点这里

    @Component
    public class KafkaConsumerListener implements ApplicationListener<ApplicationEvent> {
        private static final Logger logger = LogManager.getLogger(KafkaConsumerListener.class);
    
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            logger.info("==============添加订阅的topic和对应的处理方法===============");
            Map<String, String> topicAndBeans = new HashMap<String, String>();
            topicAndBeans.put("codeForTopic1", "beanImpl1");
            topicAndBeans.put("codeForTopic2", "beanImpl2");
            topicAndBeans.put("codeForTopic3", "beanImpl3");
            //实例化consumer
            new KafkaConsumerInit(topicAndBeans).start();
        }
    }

    2.3.4、ComsumerService.java

    具体业务层实现及封装调用

    public interface ConsumerService {
        /**
         * @param message
         */
        void doHandle(String message);
    }
    @Serivce
    public class BeanImpl1 implements ConsumerService {
        public void doHandle(String message) {
            //TODO  集体业务实现          
        }
    }

    以上基本满足consumer方使用。

  • 相关阅读:
    python之数据规范化(Min-Max规范化)
    python对全班成绩进行数据清洗(pandas的使用)
    python统计全班的成绩(numpy的使用)
    python爬虫之动态渲染页面抓取-(Selenium)的使用
    python之小米应用商店搜索
    python之小米应用商店爬虫
    cmds系统数据库源端大表数据更新优化
    临时表空间扩容
    性能优化概要(2)数据库时间,监控和优化工具
    cmds挖掘redolog
  • 原文地址:https://www.cnblogs.com/liuxs13/p/9283129.html
Copyright © 2020-2023  润新知