Flink-Kafka相关学习笔记

Kafka

1.什么是消息队列:
  ->简称为MQ(Message Queue),我们把要传输的数据放在队列中 (把数据放到消息队列叫做生产者,从消息队列里取数据叫做消费者)
  ->消息队列的两种模式:
    消息队列包括两种模式,点对点模式(point to point, queue)和发布/订阅模式(publish/subscribe, topic)
    点对点:消息被消费以后,queue中不再有存储,所以消息接收者不可能消费到已经被消费到消息
    发布/订阅模式:发布者将消息发送到Topic,系统将这些消息传递给多个订阅者
  ->常用消息队列:
    Kafka -> 在于分布式架构
    RabbitMQ -> 基于AMQP协议来实现,主从结构,适合事务性业务
    ActiveM ->  
    RocketMQ -> 参考kafka,但是主从结构,适合事务性业务
2.为什么要用消息队列:
  解耦合:多应用间通过消息队列对统一消息进行处理,避免调用接口失败导致整个过程失败;
  异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
  限流/削峰:避免流量过大导致应用系统挂掉的情况;
  ->应用场景:
    1.用户为了使用某个应用,进行注册,系统需要发送注册邮件并验证短信,两种处理方式:串行/并行(假设三个子步骤均为50ms)
      串行:50 + 50 + 50
      并行:50 + 50
      ->假设注册信息成功写入消息队列后即返回给客户端成功注册,这样相比于串行和并行就只剩下注册信息写入这个步骤的耗时了
    2.用户使用QQ相册上传一张图片,人脸识别系统会对该图片进行人脸识别,一般做法是服务器收到图片后,图片上传系统立即调用人脸识别系统,调用完成后再返回成功(
      缺点:
        人脸识别系统被调失败,导致图片上传失败;
        延迟高,需要人脸识别系统处理完成后,再返回给客户端,即使用户并不需要立即知道结果;
        图片上传系统与人脸识别系统之间相互调用,需要做耦合;)
      使用消息队列:
        客户端上传图片,图片上传系统将图片信息等批次写入消息队列,直接返回成功;
        人脸识别系统则定时从消息队列中取数据,完成对新增图片等识别
    3.购物网站开展秒杀活动,一般由于瞬时访问量过大,服务器接收过大,会导致流量暴增,相关系统无法处理请求甚至崩溃,加入消息队列后,系统可以从消息队列中取数据,相当于消息队列做了一次缓冲
3.怎么用
  常见问题:
    服务高可用;
    数据丢失问题;
    消费者怎么得到消息队列中的数据:
      生产者将数据放到消息队列中,消息队列有数据了,主动叫消费者去拿(俗称push)
      消费者不断去轮询消息队列,看看有没有新的数据,如果有就消费(俗称pull)
4.Kafka:(官方文档 -> https://kafka.apache.org/documentation/)
  流处理平台有三个关键能力:
    以类似于消息队列的形式进行发布和订阅消息;
    低错误率的存储流数据;
    当记录到达时开始处理
  -> Kafka可以达到前两种能力
    在系统和应用之间构建可靠的实时流数据管道;
    构建实时流应用程序以转换或响应数据流
  什么是Kafka:
    Kafka以集群的形式运行在跨多个数据中心的单台或多台服务器上;
    Kafka集群通过主题topic存储流数据;
    每条记录包含一个key, 一个value和一个时间戳
    Kafka有五个核心API:
      1.Producer API (官方文档地址 -> https://kafka.apache.org/documentation.html#producerapi) 发布流数据到一个或多个Kafka topic的应用;
      2.Consumer API (官方文档地址 -> https://kafka.apache.org/documentation.html#consumerapi) 订阅一个或多个topic并对其进行处理的应用;
      3.Stream API (官方文档 -> https://kafka.apache.org/documentation/streams/)  以流处理应用的身份从一个或多个源topic进行消费并产生输出流到一个或多个目标topic中;
      4.Connector API (官方文档 -> https://kafka.apache.org/documentation.html#connect) 构建一个生产者或消费者到Kafka topic的持续连接;
      5.AdminClient API (官方文档 -> https://kafka.apache.org/documentation/#adminapi) 管理和检查topics、brokers和其他的Kafka对象
    kafka的通信是通过TCP协议来实现的;
    topic是已经发布的数据的一个主题或概要名称;(Kafka中的topic总是多者订阅的);
    对于每个topic,Kafka集群维护一个partition log:
      每个partition都是有序的切固定不变的记录序列,并且可以持续添加的结构化提交日志;
      partition中的记录都是通过offset进行唯一标记的;
      消费者控制自己的offset:通常,一个消费者会线性的按照offset读取数据,但事实上,消费者可以以任何顺序进行消费,例如消费者可以消费老的数据来重新处理过去的数据,或者跳过最近的一些数据,然后从"现在"开始消费
    生产者:...
    消费者:
      如果所有的消费者都在同一个消费组内,则数据会被均衡的发给消费者;
      如果所有的消费者都不在同一个消费族内,则记录会被广播给所有的消费者进程;
  为什么是Kafka:
    Kafka Architecture:
      Producer、Consumer、Stream、Connector
      brokers -> zookeeper
      topic -> brokers    partitions -> topic
    Kafka设计思想:
      动机:Kafka被设计为一个用来处理企业级实时数据的一体化平台
        高吞吐
        妥善处理数据积压
        低延迟
      持久化:(文件系统带来的问题)
        Kafka对消息的存储和缓存严重依赖于文件系统(磁盘速度是瓶颈):
          6个7200rpm、SATA接口、RAID-5的磁盘阵列在JBOD配置下的顺序写入性能约为600MB/秒;
          随机写入的性能仅约100k/秒;
          ->参考https://queue.acm.org/detail.cfm?id=1563874
      ...

    ...
  Kafka的使用:
    ->kafka快速入门实际操作(基于独立的zookeeper集群环境下,多机器 现在最新版本的kafka 执行命令中用--bootstrap-server代替了--zookeeper是什么意思):
    ->启动zookeeper集群和kafka集群:(在kafka根目录下,在后台运行时,需要在命令后加1>/dev/null 2>&1 &)
      ./bin/kafka-server-start.sh ./config/server.properties
    ->创建topic:(3个副本数,一个分区,topic名称是youshuo_test_topic)
      ./bin/kafka-topics.sh --create --zookeeper hadoop:2181 --replication-factor 3 --partitions 1 --topic test01
    ->查看topic列表:
      ./bin/kafka-topics.sh --list --zookeeper hadoop:2181
      hadoopTopic2
      test01
      youshuo_test_topic
    ->创建生产者:
      ./bin/kafka-console-producer.sh --broker-list hadoop:9092 --topic test01
    ->创建消费者:
      ./bin/kafka-console-consumer.sh --zookeeper hadoop:9092 --topic test01 --from-beginning
    ->查看某个话题状态信息 :
      ./bin/kafka-topics.sh --describe --zookeeper hadoop:2181 --topic test01
    ->配置多服务节点集群:(https://kafka.apache.org/documentation/#quickstart_multibroker)

    Producer API:发送流数据到Kafka集群的topic的应用
      需要引入的依赖:
	    <dependency>
		  <groupId>org.apache.kafka</groupId>
		  <artifactId>kafka-clients</artifactId>
		  <version>2.3.0</version>
	    </dependency>
      官方实例-Examples:(官方文档javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/clients/producer/KafkaProducer.html)
    Consumer API:从Kafka集群的topic中读取数据流的应用
      需要引入的依赖:
        同上
      官方实例-Examples: (官方文档javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/clients/consumer/KafkaConsumer.html)
    Stream API: 以流处理应用的身份从一个或多个源topic进行消费并产生输出流到一个或多个目标topic中
      需要引入的依赖:
        <dependency>
		  <groupId>org.apache.kafka</groupId>
		  <artifactId>kafka-streams</artifactId>
		  <version>2.3.0</version>
		</dependency>
	  官方实例-Examples: (官方文档javadocs -> https://kafka.apache.org/23/javadoc/index.html?org/apache/kafka/streams/KafkaStreams.html)
	  新增Streams接口文档地址:https://kafka.apache.org/23/documentation/streams/
	Connect API:构建一个生产者或消费者到Kafka topic的持续连接
	  ...


->kafka基本概念:
  Fast: 非常快,单个Kafka broker每秒能够读写数百兆字节从数千台客户端,
  Scalable: 可扩展,容量不够之后,可以通过增加机器来解决
  Durable: 消息可以持久化到硬盘上
  Distributed by Design

  kafka是一个分布式的消息缓存系统
  kafka集群中的服务器都叫做broker
  kafka有两类客户端,一类叫producer(消息生产者),一类叫做consumer(消费者),客户端和broker服务器之间采用tcp协议连接
  kafka中不同业务系统的消息可以通过topic进行区分,而且每一个消息topic都会被分区,以分担消息读写的负载
  每一个分区都可以有多个副本,以防止数据的丢失
  某一个分区中的数据如果需要更新,都必须通过该分区所有副本中的leader进行更新
  消费者可以分组,比如有两个消费者组A和B,共同消费一个topic: order_info, A和B所消费的消息不会重复,比如:order_info中有100个消息,每个消息有一个id, 编号从0-99,那么,如果A组消费0-49号,B组就消费50-99号
  消费者在具体消费某个topic中的消息时,可以指定起始偏移量(宕机后,可以从指定地方读起)

  ->kafka快速入门实际操作(基于独立的zookeeper集群环境下,多机器 现在最新版本的kafka 执行命令中用--bootstrap-server代替了--zookeeper是什么意思):
    ->启动zookeeper集群和kafka集群:(在kafka根目录下,在后台运行时,需要在命令后加1>/dev/null 2>&1 &)
      ./bin/kafka-server-start.sh ./config/server.properties
    ->创建topic:(3个副本数,一个分区,topic名称是youshuo_test_topic)
      ./bin/kafka-topics.sh --create --zookeeper hadoop:2181 --replication-factor 3 --partitions 1 --topic test01
    ->查看topic列表:
      ./bin/kafka-topics.sh --list --zookeeper hadoop:2181
      hadoopTopic2
      test01
      youshuo_test_topic
    ->创建生产者:
      ./bin/kafka-console-producer.sh --broker-list hadoop:9092 --topic test01
    ->创建消费者:
      ./bin/kafka-console-consumer.sh --zookeeper hadoop:9092 --topic test01 --from-beginning
    ->查看某个话题状态信息 :
      ./bin/kafka-topics.sh --describe --zookeeper hadoop:2181 --topic test01

->kafka客户端编程:
//生产者
public class ProducerDemo {
  public static void main(String[] args) {
    Properties props = new Properties();
    props.put("zk.connect", "hadoop:2181,hadoop02:2181,hadoop03:2181");
    props.put("metadata.broker.list", "hadoop:9092,hadoop02:9092,hadoop03:9092");
    props.put("serializer.class", "kafka.serializer.StringEncoder");
    ProducerConfig config = new ProducerConfig(props);
    Producer<String, String> producer = new Producer<String, String>(config);

    //发送业务消息
    //读取文件 读取内存数据库 读socket端口
    for (int i = 1; i <= 1000; i++) {
      producer.send(new KeyedMessage<String, String>("Hey ", "i said i love you for " + i + "times"));
      Thread.sleep(500);
    }
  }
}
//消费者
public class ConsumerDemo {
  private static final String topic = "order";
  private static final Integer threads = 1;

  public static void main(String[] args) {
    Properties props = new Properties();
    props.put("zk.connect", "hadoop:2181,hadoop02:2181,hadoop03:2181");
    props.put("group.id", "1111");
    props.put("auto.offset.reset", "smallest");

    ConsumerConfig = config = new ConsumerConfig(props);
    ConsumerConnector consumer = Consumer.createJavaConsumerConnector(config);
    Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
    topicCountMap.put("test01", 1); //param1: topicName; param2: threads
    topicCountMap.put("test02", 10);
    Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
    List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);

    for (final KafkaStream<byte[], byte[]> KafkaStream : streams) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          for(MessageAndMetadata<byte[], byte[]> mm : kafkaStream) {
            String msg = new String(mm.message);
            sout(msg);
          }
        }
      }).start();
    }
  }
}

flink中国github社区: PPT + 视频

->flink介绍
    bound (bound、unbound)
    state (stateful、)
    time (Event Time、Processing Time)
    API
  ->flink基本概念:有状态流式处理引擎的基石
    有状态流式处理
    有状态流式处理的挑战
      状态容错(State Fault Tolerance)
      状态维护(State Managerment)
      Event-time处理(Event-time Processing)
      状态保存与迁移(Savepoints and Job Migration)
  ->flink安装部署以及环境部署等快速入门:
    ->选择flink的版本进行安装(视频中采用源码文件编译安装的方法,实际操作打算采用二进制压缩文件进行安装)
    ->IDEA中添加Java的Checkstyle:
      Flink在编译时会强制代码风格的检查,如果代码风格不符合规范,可能会编译失败。
      Intellij内置对Checkstyle的支持,可以检查一下Checkstyle-IDEA plugin是否安装(IntelliJ
	  IDEA -> Preferences -> Plugins,搜索“Checkstyle-IDEA”)
	    相关配置详见1.3 Flink 安装部署、环境配置及运行应用程序

->flink DataStream API编程:
    分布式流处理基础
    Flink Datastream API概览
    其他问题及源码简析
    ->分布式流处理的基本模型:
      逻辑模型(DAG):图略,Source数据源->Operation操作->Sink数据汇
    ->流处理API的衍变:
      Storm: ("面向操作",底层)
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("spout", new RandomSentenceSpout(), 5);
        builder.setBolt("split", new SplitSentence(), 8).shuffleGrouping("spout");
        builder.setBolt("count", new WordCount(), 12).fieldsGrouping("split", new Fields("word"));
      Flink: ("面向数据",高层)
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStream<String> text = env.readTextFile("input");
        DataStream<Tuple2<String, Integer>> counts = text.flatMap(new Tokenizer()).keyBy(0).sum(1);
        counts.writeAsText("output");

->DataStream 基本转换:(Flink中的所有转换都是围绕着DataStream的抽象进行转换)
      1.DataStream以map的形式进行转换(即输入是DataStream,输出是DataStream)
      2.将一条数据(SplitStream接口对输入的DataStream进行切分,后可以调用select方法选择带有某个标签的切分流)
      3.将两条DataStream流通过connect(DataStream)进行合并,合并后的流是ConnectedStream对象
      4.对ConnectedStream进行的map操作,叫做coMap(本身还是map)
      5.对无限的流进行切分(可以按照时间或按照元素的个数,即把无限的流给分成无数个mini-batch),每个mini-batch的生成都是由AllWindowedStream中的逻辑来生成的。(DataStream通过windowAll生成AllWindowedStream)
      6.AllWindowedStream对已经拆分好的mini-batch(DataStream)通过apply进行逻辑处理,最终得到的还是DataStream.
      7.对DataStream进行keyBy操作(即一种分组操作),得到的是一个KeyedStream
      8.(对于普通的DataStream我们是没有办法对其进行reduce操作的,只有把DataStream按照某些key进行分组之后,才可以对于每个分组进行reduce操作,主要是出于计算量的一个考虑)
      9.KeyedStream也可以进行window操作,得到的是WindowedStream
      10.WindowedStream通过apply方法同样也可以回到DataStream
      ->理解KeyedStream:(将一条数据流利用窗口这个操作切分成若干个窗口,每个窗口叫做AllWindowedStream,因为Flink是分布式计算引擎,当我们把切分好的窗口中的数据不做任何处理直接进行计算后得出唯一结果,则丝毫体现不出分布式计算的优势。所以,可以调用Keyby()将数据根据不同的类别进行横向的切分,从而将数据流中不同类别的数据分别发送到不同的处理节点/任务上进行处理,即不同任务之间是并行的)
    ->物理分组:
      dataStream.global() 			全部发往第一个task
      dataStream.broadcast()		广播(复制n份分别发送给下游节点)
      dataStream.forward()			上下游并发度一样时一对一发送
      dataStream.shuffle()			随机均匀分配
      dataStream.rebalance()		Round-Robin(轮流分配)
      dataStream.recale()			Local Round-Robin(本地轮流分配)
      dataStream.partitionCustom()	自定义单播
    ->类型系统:(TypeInformation:如 String、Tuple2<String, Integer>)
      基本类型		Java的基本类型(包装类)以及void、String、Date、BigDecimal、BigInteger
      符合类型		Tuple和Scala case class(不支持null)、Row、POJO
      辅助、集合类型	Option、Either、List、Map等
      上述类型的数组	
      其他类型		自定义TypeInformation或Kryo处理,不推荐使用

DataStream转换图解


->源码解析(实例:实时统计成交额):
	  数据源: Tuple2<String, Integer> 商品类别 -> 成交额
	  任务  : 
	    1.实时统计每个类别的成交额;		
	    2.实时统计全部类别的成交额。
RichParallelSourceFunction<Tuple2<String, Integer>>: 支持以并发的形式产生数据
private static class DataSource extends RichParallelSourceFunction<Tuple2<String, Integer>> {
  private volatile boolean running = true;

  @Override
  public void run(SourceContext<Tuple2<String, Integer>> ctx) throws Exception {

    Random random = new Random(System.currentTimeMills());
    while(running) {
      Thread.sleep((getRuntimeContext().getIndexOfThisSubtask() + 1) * 1000 + 500);
      String key = "类别" + (char)('A' + random.nextInt(3));
      int value = random.nextInt(10) + 1;
      System.out.println(String.format("Emit:\t(%s, %d)", key, value));
      ctx.collect(new Tuple2<>(key, value));
    }
  }

  @Override
  public void cancel() {
    running = false;
  }
}

public static void main(String[] args) {
  StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
  env.setParallelism(2);

  DataStream<Tuple2<String, Integer>> ds = env.addSource(new DataSource());
  KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = ds.keyBy(0);

  keyedStream.sum(1).keyBy(new KeySelector<Tuple2<String, Integer>, Object>() {
    //为了统计全局信息我们通过getKey()方法里返回常数“欺骗”Flink将所有数据分成一组
    @Override
    public Object getKey(Tuple2<String, Integer> value) throws Exception {
      return "";
    }
  }).fold(
          new HashMap<String, Integer>(), new FoldFunction<Tuple2<String, Integer>, Map<String, Integer>>() {
            @Override
            public Map<String, Integer> fold(Map<String, Integer> accumulator, Tuple2<String, Integer> value) throws Exception {
              accumulator.put(value.f0, value.f1);
              return accumulator;
            }
          }
  ).addSink(new SinkFunction<Map<String, Integer>>() {
    @Override
    public void invoke(Map<String, Integer> value, Context context) throws Exception {
      System.out.println(value.values().stream().mapToInt(v -> v).sum());
    }
  });
  ds.addSink(new SinkFunction<Tuple2<String, Integer>>() {
    public void invoke(Tuple2<String, Integer> value, Context context) throws Exception {
      System.out.println(String.format("Get:\t(%s, %d)", value.f0, value.f1));
    }
  });

  env.execute();
}
   ->API原理:(DataStream 是如何转换成另一个 DataStream的)

Flink整合kafka:

->Flink的kafka消费者被称为FlinkKafkaConsumer。它提供对一个或多个kafka主题的访问,构造函数接受一下参数:
    1.主题名称/主题名称列表
    2.DeserializationSchema / KeyedDeserializationSchema用于反序列化来自kafka的数据
    3.Kafka消费者的属性。需要以下属性:
      -> "bootstrap.servers"
      -> "zookeeper.connect"
      -> "group.id"

正在更新…

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值