You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1537 lines
43 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

### 4、工程结构
![screenshot](screenshot/1.png)
### 5、上报服务系统
![screenshot](screenshot/2.png)
#### 5.1、Spring Boot
上报服务系统是一个 Java Web 工程,为了快速开发 web 项目,采用 JavaWeb 最流行的 Spring Boot。
Spring Boot 是一个基于 Spring 之上的快速应用快速构建框架。Spring Boot 主要解决两方面问题:
- 依赖太多问题
- 轻量级 JavaEE 开发,需要导入大量的依赖
- 依赖直接还存在版本冲突问题
- 配置太多问题
- 大量的 XML 配置
**开发 Spring Boot 程序的基本步骤:**
- 导入 Spring Boot 依赖(起步依赖)
- 编写 `application.properties` 配置文件
- 编写 `Application` 入口程序
#### 5.2、配置 Maven 本地仓库
#### 5.3、导入 Maven 依赖
[pom文件](/report/pom.xml)
[配置文件](/resources/application.properties)
#### 5.4、创建项目包结构
包名 | 说明
---|---
`com.henry.report.controller` | 存放 Spring MVC 的controller
`com.henry.report.bean` | 存放相关的 Java Bean 实体类
`com.henry.report.util` | 存放相关的工具类
#### 5.5、验证 Spring Boot 工程是否创建成功
**步骤:**
1. 创建 SpringBoot 入口程序 Application
2. 创建`application.properties` 配置文件
3. 编写一个简单的`Spring MVC` Controller/Handler接收浏览器请求参数并打印回显
4. 打开浏览器测试
**实现:**
1. 创建 SpringBoot 入口程序`ReportApplication`,用来启动 SpringBoot 程序
- 在类上添加注解
```
@SpringBootApplication
```
- 在 main 方法中添加代码,用来运行 Spring Boot 程序
```
SpringApplication.run(ReportApplication.class);
```
2. 创建一个`TestController`
在该类上要添加注解
```java
@RestController
public class TestController{
}
```
3. 编写一个`test Handler`
从浏览器上接收一个名为 json 的参数,并打印显示
```java
@RequestMapping("/test")
public String test(String json){
System.out.println(json);
return json;
}
```
4. 编写配置文件
- 配置端口号
```properties
server.port=8888
```
5. 启动 Spring Boot 程序
6. 打开浏览器测试 Handler 是否能够接收到数据
[访问连接: http://localhost:8888/test?json=666](http://localhost:8888/test?json=666)
访问结果:
![接收显示](screenshot/3.png)
---
#### 5.6、安装 Kafka-Manager
Kafka-Manager 是 Yahool 开源的一款 Kafka 监控管理工具。
**安装步骤:**
1. 下载安装包 [Kafka-Manager下载地址](https://github.com/yahoo/kafka-manager/releases)
2. 解压到 `/usr/local/src/`
只需要在一台机器装就可以
```bash
tar -zxvf kafka-manager-1.3.3.7.tar.gz
```
需要编译
```bash
cd kafka-manager-1.3.3.7
./sbt clean dist
```
3. 修改 `conf/application.conf`
```bash
kafka-manager.zkhosts="master:2181,slave1:2181,slave2:2181"
```
4. 启动 zookeeper
```bash
zkServer.sh start
```
5. 启动 kafka
```bash
./kafka-server-start.sh ../config/server.properties > /dev/null 2>&1 &
```
![screenshot](screenshot/4.png)
6. 启动 kafka-manager
```bash
cd /usr/local/src/kafka-manager-1.3.3.17/bin
nohup ./kafka-manager 2>&1 & # 默认启动9000 端口
nohup ./kafka-manager 2>&1 -Dhttp.port=9900 & # 指定端口
```
![](screenshot/dc0e0c05.png)
页面显示:
![](screenshot/a66b3e6f.png)
---
#### 5.7、编写 Kafka 生产者配置工具类
由于项目需要操作 Kafka所以需要先构建出 KafkaTemplate这是一个 Kafka 的模板对象,通过它可以很方便的发送消息到 Kafka。
**开发步骤**
1. 编写 Kafka 生产者配置
2. 编写 Kafka 生产者 SpringBoot 配置工具类 `KafkaProducerConfig`,构建 `KafkaTemplate`
**实现**
1. 导入 Kafka 生产者配置文件
将下面的代码拷贝到`application.properties`中
```bash
#
# Kafka
#
#============编写kafka的配置文件生产者===============
# kafka的服务器地址
kafka.bootstrap_servers_config=master:9092,slave1:9092,slave2:9092
# 如果出现发送失败的情况,允许重试的次数
kafka.retries_config=0
# 每个批次发送多大的数据
kafka.batch_size=4096
# 定时发送,达到 1ms 发送
kafka.linger_ms_config=1
# 缓存的大小
kafka.buffer_memory_config=40960
# TOPOC 名字
kafka.topic=pyg
```
2. 编写 `kafkaTemplate`
```java
@Bean // 2、表示该对象是受 Spring 管理的一个 Bean
public KafkaTemplate kafkaTemplate() {
// 构建工程需要的配置
Map<String, Object> configs = new HashMap<>();
// 3、设置相应的配置
// 将成员变量的值设置到Map中在创建kafka_producer中用到
configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap_servers_config);
configs.put(ProducerConfig.RETRIES_CONFIG, retries_config);
configs.put(ProducerConfig.BATCH_SIZE_CONFIG, batch_size_config);
configs.put(ProducerConfig.LINGER_MS_CONFIG, linger_ms_config);
configs.put(ProducerConfig.BUFFER_MEMORY_CONFIG, buffer_memory_config);
// 4、创建生产者工厂
ProducerFactory<String, String> producerFactory = new DefaultKafkaProducerFactory(configs);
// 5、再把工厂传递给Template构造方法
// 表示需要返回一个 kafkaTemplate 对象
return new KafkaTemplate(producerFactory);
}
```
3. 在`test`测试源码中创建一个Junit测试用例
- 整合 Spring Boot Test
- 注入`kafkaTemplate`
- 测试发送100条消息到`test` Topic
```java
@RunWith(SpringRunner.class)
@SpringBootTest
public class KafkaTest {
@Autowired
KafkaTemplate kafkaTemplate;
@Test
public void sendMsg(){
for (int i = 0; i < 100; i++)
kafkaTemplate.send("test", "key","this is test msg") ;
}
}
```
4. KafkaManager创建`test` topic,三个分区,两个副本
创建连接kafka集群
![](screenshot/544d0e7a.png)
![](screenshot/0b4ea4e1.png)
![](screenshot/cba7b53e.png)
创建连接成功
![](screenshot/7cf4425b.png)
![](screenshot/a2ab75e3.png)
创建topic
![](screenshot/5326b634.png)
![](screenshot/8f89e666.png)
创建topic成功
![](screenshot/13c61ea9.png)
5. 启动`kafka-conslole-consumer`
```bash
/usr/local/src/kafka_2.11-1.1.0/bin
./kafka-console-consumer.sh --zookeeper master:2181 --from-beginning --topic test
```
运行 test 程序,报错如下:
![](screenshot/abb5e847.png)
添加序列化器代码:
```java
// 设置 keyvalue 的序列化器
configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG , StringSerializer.class);
configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG , StringSerializer.class);
```
6. 打开kafka-managerconsumer监控页面,查看对应的`logsize`参数,消息是否均匀的分布在不同的分区中
添加序列化器后重新运行,消费者终端打印消息:
![](screenshot/7fe930e0.png)
打开页面管理:
![](screenshot/2b7f3937.png)
消息全落在了一个分区上,这样会影响kafka性能
![](screenshot/6ac8e320.png)
解决的最简单的方法:将 "key" 去掉
```java
@Test
public void sendMsg(){
for (int i = 0; i < 100; i++)
kafkaTemplate.send("test","this is test msg") ;
// kafkaTemplate.send("test", "key","this is test msg") ;
}
```
#### 5.8、自定义分区的实现
```java
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
// 获取分区的数量
Integer partitions = cluster.partitionCountForTopic(topic) ;
int curpartition = counter.incrementAndGet() % partitions ; // 当前轮询的 partition
if(counter.get() > 65535){
counter.set(0);
}
return curpartition;
}
```
![](screenshot/8fe964b8.png)
#### 5.9、上报服务开发
上报服务系统要能够接收 http 请求,并将 http 请求中的数据写入到 kafka 中。
![](screenshot/64a0b856.png)
步骤:
1. 创建`Message`实体类对象
所有的点击流消息都会封装带 Message 实体类中
2. 设计一个 Controller 来接收 http 请求
3. 将 http 请求发送的消息封装到一个`Message`实体类对象
4. 使用`FastJSON`将`Message`实体类对象转换为JSON字符串
5. 将JSON字符串使用`KafkaTemplate`写入到`kafka`
6. 返回给客户端一个写入结果JSON字符串
#### 5.10、模拟生产点击流日志消息到Kafka
为了方便调试可以使用一个消息生成工具来生产点击流日志然后发送个上报服务系统。该消息生成工具可以一次性生产100条
Clicklog 信息,并转换成 JSON ,通过 HTTPClient 把消息内容发送到编写的 ReportController 上。
![](screenshot/3ab50051.png)
步骤:
1. 导入 ClickLog 实体类ClickLog.java)
2. 导入点击流日志生成器ClickLogGenerator.java)
3. 创建 Kafka 的 Topicpyg
4. 使用 `kafka-console-sonsumer.sh` 消费 topic 中的数据
5. 启动上报服务
6. 执行 `ClickLogGenerator`的main方法生成 100 条用户浏览数据消息发送到 Kafka
实现:
1. 创建 kafka topic
```jshelllanguage
./kafka-topics.sh --create --zookeeper master:2181 --replication-factor 2 --partitions 3 --topic pyg
```
2. 启动消费者
```jshelllanguage
./kafka-console-consumer.sh --zookeeper master:2181 --from-beginning --topic pyg
```
运行消息模拟器,运行结果如下(此时,上报服务也是在运行中):
![](screenshot/565c64ed.png)
### 6、Flink 实时数据分析系统开发
前边已经开发完成了`上报服务系统`,可以通过上报服务系统把电商页面中的点击流数据发送到 Kafka 中。那么,接下来就是开发
`Flink 实时分析系统`,通过流的方式读取 kafka 中的消息,进而分析数据。
![](screenshot/98ddfe9a.png)
**业务**
- 实时分析频道热点
- 实时分析频道PV/UV
- 实时分析频道新鲜度
- 实时分析频道地域分布
- 实时分析运营商平台
- 实时分析浏览器类型
**技术**
- Flink 实时处理算子
- 使用`CheckPoint`和`水印`解决Flink生产中遇到的问题网络延迟、丢数据
- Flink整合Kafka
- Flink整合HBase
#### 6.1、搭建 Flink 实时数据分析系统 环境
##### 6.1.1 导入Maven项目依赖
##### 6.1.2 创建项目包结构
包名 | 说明
---|---
`com.henry.realprocess.util` | 存放存放相关的工具类
`com.henry.realprocess.bean` | 存放相关的实体类
`com.henry.realprocess.task` | 存放具体的分析任务,每一个业务都是一个任务,对应的分析处理都写在这里
##### 6.1.3 导入实时系统Kafka/Hbase配置
1. 将`application.conf`导入到`resources`目录
2. 将`log4j.properties`导入到`resources`目录
> 注意修改`kafka服务器` 和`hbase服务器` 的机器名称
##### 6.1.4 获取配置文件API介绍
`ConfigFactory.load()`介绍
![](screenshot/d6cc806c.png)
常用 API
![](screenshot/df332a64.png)
##### 6.1.5 编写 Scala 读取代码配置工具类
`com.henry.realprocess.util.GlobalConfigutil`
#### 6.2 初始化Flink流式计算环境
`com.henry.realprocess.App`
#### 6.3 Flink添加checkPoint容错支持
![](screenshot/75fcc253.png)
增量更新的不会因为创建了很多状态的快照导致快照数据很庞大存储到HDFS中。
**实现**
1. 在Flink流式处理环境中添加一下`checkpoint`的支持确保Flink的高容错性数据不丢失。
![](screenshot/2193cbd1.png)
#### 6.4 Flink整合Kafka
##### 6.4.1 Flink读取Kafka数据
![](screenshot/54187145.png)
**实现**
1、启动上报服务系统 `ReportApplication`
2、启动kafka
3、启动kafka消息生成模拟器
4、启动App.scala
消息生成模拟器发送的消息:
![](screenshot/831e1859.png)
App实时分析系统接收到的消息
![](screenshot/5a321628.png)
消息者接收到的消息:
![](screenshot/b77622b6.png)
##### 6.4.2 Kafka消息解析为元组
步骤:
- 使用map算子遍历kafka中消费到的数据
- 使用FastJSON转换为JSON对象
- 将JSON的数据解析为一个元组
代码:
1. 使用map算子将Kafka中消费到的数据使用FastJSON转换为JSON对象
2. 将JSON的数据解析为一个元组
3. 打印经过map映射后的元组数据测试能否正确解析
App实时分析系统接收到的消息Tuple类型
![](screenshot/6f897038.png)
![](screenshot/14679e84.png)
##### 6.4.3 Flink封装点击流消息为样例类
**步骤**
1. 创建一个`ClikLog`样例类来封装消息
2. 使用map算子将数据封装到`ClickLog`样例类
**代码**
1. 在bean包中创建`ClikLog`样例类,添加以下字段
- 频道IDchannelID
- 产品类别IDcategoryID
- 产品IDproduceID
- 国家country
- 省份province
- 城市city
- 网络方式network
- 来源方式source
- 来源方式browserType
- 进入网站时间entryTime
- 离开网站时间leaveTime
- 用户IDuserID
2. 在`ClikLog`半生对象中实现`apply`方法
3. 使用FastJSON的`JSON.parseObject`方法将JSON字符串构建一个`ClikLog`实例对象
4. 使用map算子将数据封装到`ClikLog`样例类
5. 在样例类中编写一个main方法传入一些JSON字符串测试是否能够正确解析
6. 重新运行Flink程序测试数据是否能够完成封装
App实时分析系统接收到的消息样例类
![](screenshot/d452de1b.png)
##### 6.4.4 封装KafKa消息为Message样例类
**步骤**
1. 创建一个`Message`样例类将ClickLog、时间戳、数量封装
2. 将Kafka中的数据整个封装到`Message`类中
3. 运行Flink测试
App实时分析系统接收到的消息Message样例类
![](screenshot/0fcd02b7.png)
#### 6.5 Flink添加水印支持
![](screenshot/e751cb2d.png)
![](screenshot/c6d0728b.png)
![](screenshot/9e4179c5.png)
![](screenshot/72d64e76.png)
### 7、HBaseUtil 工具类开发
#### 7.1、HBase工具类介绍
前面实现了Flink整合Kafka可以从Kafka中获取数据进行分析分析之后把结果存入HBase
为了方便提前编写一个操作HBase工具类。HBase作为一个数据库面肯定需要进行数据的增删改差
那么就需要围绕这几个进行开发。
![](screenshot/c84f6044.png)
##### 7.1.1、API介绍
![](screenshot/d1a2dc81.png)
![](screenshot/d457be6b.png)
**HBase操作基本类**
![](screenshot/2f5a312e.png)
##### 7.1.2、获取表
代码实现添加HBase配置信息
```scala
val conf:Configuration = HBaseConfiguration.create()
```
![](screenshot/0ced234a.png)
![](screenshot/e4022013.png)
不存在表时,则创建表
![](screenshot/908989c5.png)
创建后:
![](screenshot/69907922.png)
![](screenshot/8cca6196.png)
##### 7.1.3、存储数据
创建`putData`方法
- 调用 getTable获取表
- 构建`put`对象
- 添加列、列值
- 对 table 进行 put 操作
- 启动编写 main 进行测试
![](screenshot/af73ebaa.png)
##### 7.1.4、获取数据
1、 使用Connection 获取表
2、 创建 getData 方法
- 调用 getTable 获取表
- 构建 get 对象
- 对 table 执行 get 操作,获取 result
- 使用 Result.getValue 获取列族列对应的值
- 捕获异常
- 关闭表
##### 7.1.5、批量存储数据
创建 putMapData 方法
- 调用 getTable 获取表
- 构建 get 对象
- 添加 Map 中的列、列值
- 对 table 执行 put 操作
- 捕获异常
- 关闭表
![](screenshot/ea8764de.png)
##### 7.1.6、批量获取数据
创建 getMapData 方法
- 调用 getTable 获取表
- 构建 get 对象
- 根据 get 对象查询表
- 构建可变 Map
- 遍历查询各个列的列值
- 过滤掉不符合的结果
- 把结果转换为 Map 返回
- 捕获异常
- 关闭表
- 启动编写 main 进行测试
![](screenshot/a35893be.png)
##### 7.1.7、删除数据
创建 deleteData 方法
- 调用 getTable 获取表
- 构建 Delete 对象
- 对 table 执行 delete 操作
- 捕获异常
- 关闭表
- 启动编写 main 进行测试
![](screenshot/d99a61f4.png)
![](screenshot/d068b5c0.png)
### 8、实时数据分析业务目标
![](screenshot/520fd656.png)
### 9、业务开发一般流程
![](screenshot/79c600b1.png)
**一般流程**
![](screenshot/e6130b81.png)
### 10、点击流日志实时数据预处理
#### 10.1、业务分析
为了方便后续分析,需要对点击流日志,使用 Flink 进行实时预处理。在原有点击流日志的基础上添加
一些字段,方便进行后续业务功能的统计开发。
以下为 kafka 中消费得到的原始点击流日志字段:
![](screenshot/e61c1e01.png)
需要在原有点击流日志字段基础上,再添加以下字段:
![](screenshot/201507bb.png)
不能直接从点击流日志中直接计算得到上述后4个字段的值。而是需要在 hbase 中有一个 **历史记录表**
,来保存用户的历史访问状态才能计算得到。
**历史记录表** 表结构:
![](screenshot/76c4fbf8.png)
#### 10.2、创建 ClickLogWide 样例类
使用 ClickLogWide 样例类来保存拓宽后的点击流日志数据。直接**复制**原有的`ClickLog`样例类,
然后给它额外加上下列额外的字段;
**步骤*
![](screenshot/0b4d0c1b.png)
![](screenshot/0e6080a2.png)
#### 10.3、预处理isNew字段处理
isNew 字段是判断某个`用户ID`,是否已经访问过`某个频道`。
**实现思路**
![](screenshot/1d504cce.png)
user_history 表的列
- 用户ID频道ID(rowkey)
- 用户IDuserID
- 频道IDchannelid
- 最后访问时间时间戳lastVisitedTime
![](screenshot/a560cff6.png)
### 11、实时频道热点分析业务开发
#### 11.1、业务介绍
频道热点,就是要统计频道访问(点击)的数量。
分析得到以下的数据:
![](screenshot/cdefdf02.png)
> 需要将历史的点击数据进行累加
![](screenshot/fc27880f.png)
其中, 第一步预处理已经完成。
```scala
// 转换
ChannelRealHotTask.process(clickLogWideDateStream).print()
```
![](screenshot/b35e8d12.png)
落地 HBase
```scala
// 落地 HBase
ChannelRealHotTask.process(clickLogWideDateStream)
```
```
hbase shell
scan 'channel'
```
![](screenshot/3254e2ca.png)
### 6、实时频道PV/UV分析
针对频道的PV、UV进行不同维度的分析有以下三个维度
- 小时
-
-
#### 6.1、业务介绍
PV(访问量)
即Page View页面刷新一次计算一次
UV(独立访客)
即Unique Visitor指定时间内相同的客户端只被计算一次
统计分析后得到的数据如下所示:
![](screenshot/6f5af076.png)
#### 6.2、小时维度PV/UV
```scala
// 落地 HBase
ChannelPvUvTask.process(clickLogWideDateStream)
```
```
hbase shell
scan 'channel_pvuv'
```
![](screenshot/cf67e612.png)
#### 6.3、天维度PV/UV业务开发
按天的维度来统计 PV、UV 与按小时维度类似,就是分组字段不一样。可以直接复制按小时维度的
PV/UV ,然后修改就可以。
#### 6.4、小时/天/月维度PV/UV业务开发
将按**小时**、**天**、**月** 三个时间维度的数据放在一起来进行分组。
**思路**
![](screenshot/aa3dbfbf.png)
![](screenshot/fe002ea4.png)
```scala
// 落地 HBase
ChannelPvUvTaskMerge.process(clickLogWideDateStream)
```
```
hbase shell
scan 'channel_pvuv'
```
![](screenshot/12f712f9.png)
### 7、实时频道用户新鲜度分析
#### 7.1、业务介绍
用户新鲜度,即分析网站每个小时、每天、每月活跃用户的新老用户占比
可以通过新鲜度;
- 从宏观层面上了解每天的新老用户比例以及来源结构
- 当天新增用户与当天`推广行为`是否相关
统计分析要得到的数据如下:
![](screenshot/0bd763d1.png)
![](screenshot/6c99f78b.png)
## Day 04
### 1、模板方法提取公共类
**模板方法:**
模板方法模式是在父类中定义算法的骨架,把具体实现到子类中去,可以在不改变一个算法的结构时
可重定义该算法的某些步骤。
前面我们已经编写了三个业务的分析代码代码结构都是分为5个部分非常的相似。针对这样
的代码,我们可以进行优化,提取模板类,让所有的任务类都按照模板的顺序去执行。
![](screenshot/277372f9.png)
继承父类方法:
```scala
ChannelFreshnessTaskTrait.scala
```
![](screenshot/3d2cda96.png)
```scala
// 重构模板方法
ChannelFreshnessTaskTrait.process(clickLogWideDateStream)
```
![](screenshot/32a6daaf.png)
### 2、实时频道低于分析业务开发
#### 2.1、业务介绍
通过地域分析可以帮助查看地域相关的PV/UV、用户新鲜度。
需要分析出来指标
- PV
- UV
- 新用户
- 老用户
统计分析后的结果如下:
![](screenshot/2d11fecd.png)
#### 2.2、 业务开发
**步骤**
1. 创建频道地域分析样例类频道、地域国省市、时间、PV、UV、新用户、老用户
2. 将预处理后的数据,使用 flatMap 转换为样例类
3. 按照 频道 、 时间 、 地域 进行分组(分流)
4. 划分时间窗口3秒一个窗口
5. 进行合并计数统计
6. 打印测试
7. 将计算后的数据下沉到Hbase
**实现**
1. 创建一个 ChannelAreaTask 单例对象
2. 添加一个 ChannelArea 样例类它封装要统计的四个业务字段频道IDchannelID、地域area、日期
datepv、uv、新用户newCount、老用户oldCount
3. 在 ChannelAreaTask 中编写一个 process 方法,接收预处理后的 DataStream
4. 使用 flatMap 算子,将 ClickLog 对象转换为三个不同时间维度 ChannelArea
5. 按照 频道ID 、 时间 、 地域 进行分流
6. 划分时间窗口3秒一个窗口
7. 执行reduce合并计算
8. 打印测试
9. 将合并后的数据下沉到hbase
- 准备hbase的表名、列族名、rowkey名、列名
- 判断hbase中是否已经存在结果记录
- 若存在,则获取后进行累加
- 若不存在,则直接写入
`ChannelAreaTask` 测试
![](screenshot/e219a541.png)
### 3、 实时运营商分析业务开发
#### 3.1、 业务介绍
根据运营商来统计相关的指标。分析出流量的主要来源是哪个运营商的,这样就可以进行较准确的网络推广。
**需要分析出来指标**
- PV
- UV
- 新用户
- 老用户
**需要分析的维度**
- 运营商
- 时间维度(时、天、月)
统计分析后的结果如下:
![](screenshot/ff2dcb9b.png)
#### 3.2、 业务开发
**步骤**
1. 将预处理后的数据转换为要分析出来数据频道、运营商、时间、PV、UV、新用户、老用户样例类
2. 按照 频道 、 时间 、 运营商 进行分组(分流)
3. 划分时间窗口3秒一个窗口
4. 进行合并计数统计
5. 打印测试
6. 将计算后的数据下沉到Hbase
**实现**
1. 创建一个 ChannelNetworkTask 单例对象
2. 添加一个 ChannelNetwork 样例类它封装要统计的四个业务字段频道IDchannelID、运营商
network、日期datepv、uv、新用户newCount、老用户oldCount
3. 在 ChannelNetworkTask 中编写一个 process 方法,接收预处理后的 DataStream
4. 使用 flatMap 算子,将 ClickLog 对象转换为三个不同时间维度 ChannelNetwork
5. 按照 频道ID 、 时间 、 运营商 进行分流
6. 划分时间窗口3秒一个窗口
7. 执行reduce合并计算
8. 打印测试
9. 将合并后的数据下沉到hbase
- 准备hbase的表名、列族名、rowkey名、列名
- 判断hbase中是否已经存在结果记录
- 若存在,则获取后进行累加
- 若不存在,则直接写入
`ChannelNetworkTask` 测试
报错:
![](screenshot/3936fce5.png)
```scala
// 错误代码:
// totalPv
if (resultMap != null && resultMap.size > 0 && StringUtils.isNotBlank(resultMap(pvColName))) {
totalPv = resultMap(pvColName).toLong + network.pv
}
else {
totalPv = network.pv
}
...
// 正确代码: 即列空的时候写入一个 "" 空字符串
// totalPv
if (resultMap != null && resultMap.size > 0 && StringUtils.isNotBlank(resultMap.getOrElse(pvColName,""))) {
totalPv = resultMap(pvColName).toLong + network.pv
}
else {
totalPv = network.pv
}
```
![](screenshot/2c0ad8e2.png)
### 4、 实时频道浏览器分析业务开发
#### 4.1、 业务介绍
需要分别统计不同浏览器(或者客户端)的占比
**需要分析出来指标**
- PV
- UV
- 新用户
- 老用户
**需要分析的维度**
- 浏览器
- 时间维度(时、天、月)
统计分析后的结果如下:
![](screenshot/3b6d6d1f.png)
#### 4.2、 业务开发
**步骤**
1. 创建频道浏览器分析样例类频道、浏览器、时间、PV、UV、新用户、老用户
2. 将预处理后的数据,使用 flatMap 转换为要分析出来数据样例类
3. 按照 频道 、 时间 、 浏览器 进行分组(分流)
4. 划分时间窗口3秒一个窗口
5. 进行合并计数统计
6. 打印测试
7. 将计算后的数据下沉到Hbase
**实现**
1. 创建一个 ChannelBrowserTask 单例对象
2. 添加一个 ChannelBrowser 样例类它封装要统计的四个业务字段频道IDchannelID、浏览器
browser、日期datepv、uv、新用户newCount、老用户oldCount
3. 在 ChannelBrowserTask 中编写一个 process 方法,接收预处理后的 DataStream
4. 使用 flatMap 算子,将 ClickLog 对象转换为三个不同时间维度 ChannelBrowser
5. 按照 频道ID 、 时间 、 浏览器 进行分流
6. 划分时间窗口3秒一个窗口
7. 执行reduce合并计算
8. 打印测试
9. 将合并后的数据下沉到hbase
- 准备hbase的表名、列族名、rowkey名、列名
- 判断hbase中是否已经存在结果记录
- 若存在,则获取后进行累加
- 若不存在,则直接写入
对重复使用的代码整合到`BaseTask`中进行重构
// ChannelBrowserTask 测试
`ChannelBrowserTask.process(clickLogWideDateStream)`
![](screenshot/58945558.png)
5.2.4、 canal 解决方案三
![](screenshot/65e75e0f.png)
1. 通过 canal 来解析mysql中的 binlog 日志来获取数据
2. 不需要使用sql 查询mysql不会增加mysql的压力
> binlog : mysql 的日志文件,手动开启,二进制文件,增删改命令
1. 通过 canal 来解析mysql中的 binlog 日志来获取数据
2. 不需要使用sql 查询mysql不会增加mysql的压力
> MySQL 的主从复制,因为 canal 为伪装成 MySQL 的一个从节点,
这样才能获取到 bilog 文件
### 6、Canal数据采集平台
接下来我们去搭建Canal的数据采集平台它是去操作Canal获取MySql的binlog文件解析之后再把数据存入到Kafka
中。
![](screenshot/62c03232.png)
**学习顺序:**
- 安装MySql
- 开启binlog
- 安装canal
- 搭建采集系统
#### 6.1、 MySql安装
#### 6.2、 MySql创建测试表
**步骤**
1. 创建 pyg 数据库
2. 创建数据库表
**实现**
推荐使用 sqlyog 来创建数据库、创建表
1. 创建 pyg 数据库
2. 将 资料\mysql脚本\ 下的 创建表.sql 贴入到sqlyog中执行创建数据库表
#### 6.3、 binlog 日志介绍
- 用来记录mysql中的 增加 、 删除 、 修改 操作
- select操作 不会 保存到binlog中
- 必须要 打开 mysql中的binlog功能才会生成binlog日志
- binlog日志就是一系列的二进制文件
```
-rw-rw---- 1 mysql mysql 669 11⽉11日 10 21:29 mysql-bin.000001
-rw-rw---- 1 mysql mysql 126 11⽉11日 10 22:06 mysql-bin.000002
-rw-rw---- 1 mysql mysql 11799 11⽉11日 15 18:17 mysql-bin.00000
```
#### 6.4、 开启 binlog
步骤
1. 修改mysql配置文件添加binlog支持
2. 重启mysql查看binlog是否配置成功
实现
1. 使用vi打开 /etc/my.cnf
2. 添加以下配置
```
[mysqld]
log-bin=/var/lib/mysql/mysql-bin
binlog-format=ROW
server_id=1
```
> 注释说明
> 配置binlog日志的存放路径为/var/lib/mysql目录文件以mysql-bin开头 log-bin=/var/lib/mysql/mysql-bin
> 配置mysql中每一行记录的变化都会详细记录下来 binlog-format=ROW
> 配置当前机器器的服务ID如果是mysql集群不能重复 server_id=1
3. 重启mysql
`service mysqld restart`
`systemctl restart mysqld.service`
4. mysql -u root -p 登录到mysql执行以下命令
`show variables like '%log_bin%';`
5. mysql输出以下内容表示binlog已经成功开启
![](screenshot/48cd018e.png)
6. 进入到 /var/lib/mysql 可以查看到mysql-bin.000001文件已经生成
![](screenshot/e44c5879.png)
### 6.5. 安装Canal
#### 6.5.1. Canal介绍
- canal是 阿里巴巴 的一个使用Java开发的开源项目
- 它是专门用来进行 数据库同步 的
- 目前支持 mysql 、以及(mariaDB)
> MariaDB数据库管理系统是MySQL的一个分支主要由开源社区在维护采用GPL授权许可 MariaDB的目的是完
全兼容MySQL包括API和命令行使之能轻松成为MySQL的代替品。
#### 6.5.2. MySql主从复制原理
mysql主从复制用途
- 实时灾备,用于故障切换
- 读写分离,提供查询服务
- 备份,避免影响业务
主从形式
- 一主一从
- 一主多从--扩展系统读取性能
> 一主一从和一主多从是最常见的主从架构实施起来简单并且有效不仅可以实现HA而且还能读写分离进而提升集群
的并发能力。
- 多主一从--5.7开始支持
> 多主一从可以将多个mysql数据库备份到一台存储性能比较好的服务器上。
- 主主复制
> 双主复制也就是互做主从复制每个master既是master又是另外一台服务器的slave。这样任何一方所做的变更
都会通过复制应用到另外一方的数据库中。
- 联级复制
> 级联复制模式下部分slave的数据同步不连接主节点而是连接从节点。因为如果主节点有太多的从节点就会损耗一
部分性能用于replication那么我们可以让3~5个从节点连接主节点其它从节点作为二级或者三级与从节点连接
样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。
![](screenshot/a8d36972.png)
主从部署必要条件:
- 主库开启 binlog 日志
- 主从 server-id 不同
- 从库服务器能连通主库
主从复制原理图:
![](screenshot/7cd00637.png)
1. master 将改变记录到二进制日志( binary log )中(这些记录叫做二进制日志事件, binary log
events ,可以通过 show binlog events 进行查看);
2. slave 的I/O线程去请求主库的binlog拷贝到它的中继日志( relay log )
3. master 会生成一个 log dump 线程用来给从库I/O线程传输binlog
4. slave重做中继日志中的事件将改变反映它自己的数据。
#### 6.5.3. Canal原理
![](screenshot/a13d8808.png)
1. Canal模拟mysql slave的交互协议伪装自己为mysql slave
2. 向mysql master发送dump协议
3. mysql master收到dump协议发送binary log给slavecanal)
4. canal解析binary log字节流对象
#### 6.5.4. Canal架构设计
![](screenshot/946fe86f.png)
说明:
- server 代表一个canal运行实例对应于一个jvm
- instance 对应于一个数据队列 1个server对应1..n个instance)
instance模块一个数据队列
- eventParser (数据源接入模拟slave协议和master进行交互协议解析)
- eventSink (Parser和Store链接器进行数据过滤加工分发的工作)
- eventStore (数据存储)
- metaManager (增量订阅[读完之后,之前读过的就不再读了]&消费信息管理器)
EventParser
![](screenshot/c33fe1b4.png)
整个parser过程大致可分为六步
1. Connection获取上一次解析成功的位置
2. Connection建立连接发送BINLOG_DUMP命令
3. Mysql开始推送Binary Log
4. 接收到的Binary Log通过Binlog parser进行协议解析补充一些特定信息
5. 传递给EventSink模块进行数据存储是一个阻塞操作直到存储成功
6. 存储成功后定时记录Binary Log位置
EventSink设计
![](screenshot/ebf3c65b.png)
说明:
- 数据过滤:支持通配符的过滤模式,表名,字段内容等
- 数据路由/分发解决1:n (1个parser对应多个store的模式)
- 数据归并解决n:1 (多个parser对应1个store)
- 数据加工在进入store之前进行额外的处理比如join
EventStore设计
目前实现了Memory内存、本地file存储以及持久化到zookeeper以保障数据集群共享。 Memory内存的RingBuwer设
计:
![](screenshot/9e67979f.png)
定义了3个cursor
- Put : Sink模块进行数据存储的最后一次写入位置
- Get : 数据订阅获取的最后一次提取位置
- Ack : 数据消费成功的最后一次消费位置
#### 6.5.5. 安装Canal
**步骤**
1. 上传canal安装包
2. 解压canal
3. 配置canal
4. 启动canal
**实现**
1. 上传 \资料\软件包\canal.deployer-1.0.24.tar.gz 到 /export/software 目录
2. 在 /export/servers 下创建 canal 目录一会直接将canal的文件解压到这个目录中
```
cd /export/servers
mkdir canal
```
3. 解压canal到 /export/servers 目录
```
tar -xvzf canal.deployer-1.0.24.tar.gz -C ../servers/canal
```
4. 修改 canal/conf/example 目录中的 instance.properties 文件
```
## mysql serverId
canal.instance.mysql.slaveId = 1234
 
# position info
canal.instance.master.address = node01:3306
canal.instance.dbUsername = root
canal.instance.dbPassword = 123456
```
> 1. canal.instance.mysql.slaveId这个ID不能与之前配置的 service_id 重复
> 2. canal.instance.master.address配置为mysql安装的机器名和端口号
5. 执行/export/servers/canal/bin目录中的 startup.sh 启动canal
> cd /export/servers/canal/bin
> ./startup.sh
6. 控制台如果输出如下表示canal已经启动成功
![](screenshot/820fe570.png)
![](screenshot/342dcc3e.png)
### 6.6. Canal数据采集系统 - 项目初始化
**步骤**
1. 导入Maven依赖
2. 拷贝 资料\工具类\03.Canal数据采集系统 中的 pom.xml 的依赖到 canal-kakfa 项目的pom.xml文件中
3. 拷贝 资料\工具类\03.Canal数据采集系统 中的 log4j.properties 配置文件
4. 拷贝 资料\工具类\03.Canal数据采集系统 中的 application.properties 文件
### 6.7. Canal采集程序搭建
使用java语言将canal中的binlog日志解析并写入到Kafka中
![](screenshot/d9fcfcf5.png)
在canal-kafka项目的 java 目录中,创建以下包结构:
![](screenshot/dc64a356.png)
#### 6.7.1. 编写配置文件加载代码
**步骤**
1. 创建 GlobalConfigUtil 工具类,读取 application.properties 中的 canal 和 kafka 配置
2. 添加main方法测试是否能正确读取配置
**实现**
1. 在 util 包中创建 GlobalConfigUtil ,用来读取 application.properties 中的配置。我们使用以下代
码来读取 application.properties 中的配置
```
ResourceBundle bundle = ResourceBundle.getBundle("配置文件名"
, Locale.ENGLISH);
String host = bundle.getString("属性key");
```
将 application.properties 中的 canal 和 kafka 配置读取出来
2. 编写main方法测试是否能够正确读取配置
GlobalConfigUtil.java
![](screenshot/04e25b5a.png)
> 注意:
使用ResourceBundle.getBundle("application", Locale.ENGLISH); 读取 application.properties 时 不需要 写
后缀名
#### 6.7.2. 导入Kafka工具类代码
KafkaSender.java
#### 6.7.3. 导入Canal解析binlog日志工具类代码
- 将mysql中的 binlog 日志解析
- 将解析后的数据写入到 Kafka
CanalClient.java
#### 6.7.4. 测试工具类代码
**步骤**
1. 启动 mysql
2. 启动 canal
3. 启动 zookeeper 集群
4. 启动 kafka 集群
5. 在kafka创建一个 canal topic
```
bin/kafka-topics.sh --create --zookeeper node01:2181 --replication-factor 2 --partitions 3
--topic canal
```
6. 启动kafka的控制台消费者程序
```
bin/kafka-console-consumer.sh --zookeeper node01:2181 --from-beginning --topic canal
```
7. 启动工具类 canal同步程序
8. 打开 navicat 往mysql中插入一些数据
```sql
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (1 , '耐克' , 1 , 888.00 , 820.00);
 
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (2 , '阿迪达斯' , 1 , 900.00 , 870.00);
 
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (3 , 'MacBook Pro' , 2 , 18000.00 , 17500.00);
 
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (4 , '联想' , 2 , 5500.00 , 5320.00);
 
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (5 , '索菲亚' , 3 , 35000.00 , 30100.00);
 
INSERT INTO commodity(commodityId , commodityName , commodityTypeId , originalPrice ,
activityPrice) VALUES (6 , '欧派' , 3 , 43000.00 , 40000.00);
```
9. 如果kafka中能看到打印以下消息表示canal已经正常工作
![](screenshot/d42bd3f1.png)
### 1. Flink实时数据同步系统开发
![](screenshot/3f08b9d0.png)
其中MySQL连接CannalCanal操作MySQL的binlog文件。
实时同步系统Flink将Kafka中的Json数据读取过来进行转换存入HBase。
#### 1.1. binlog日志格式分析
测试日志数据
```json
{
"emptyCount": 2,
"logFileName": "mysql-bin.000002",
"dbName": "pyg",
"logFileOffset": 250,
"eventType": "INSERT",
"columnValueList": [
{
"columnName": "commodityId",
"columnValue": "1",
"isValid": "true"
},
{
"columnName": "commodityName",
"columnValue": "耐克",
"isValid": "true"
},
{
"columnName": "commodityTypeId",
"columnValue": "1",
"isValid": "true"
},
{
"columnName": "originalPrice",
"columnValue": "888.0",
"isValid": "true"
},
{
"columnName": "activityPrice",
"columnValue": "820.0",
"isValid": "true"
}
],
"tableName": "commodity",
"timestamp": 1553741346000
}
```
格式分析
字段以及说明
![](screenshot/4cf81224.png)
#### 1.2. Flink实时同步应用开发
整体架构
![](screenshot/3c8d398c.png)
Kafka的数据来源于binlog当Flink同步程序拿到binlog之后会进行处理和转换然后
写入到HBase中。
具体架构
![](screenshot/cfd8e121.png)
1. Flink对接Kafka
2. 对数据进行预处理将原始样例类转换成HBase可以操作的样例类存储到HBase
3. 将数据落地到Hbase
数据同步说明
![](screenshot/7cba404f.png)
> 要确保hbase中的rowkey是唯一的数据落地不能被覆盖
#### 1.3. 实时数据同步项目初始化
在sync-db项目的scala 目录中,创建以下包结构:
![](screenshot/c1186185.png)
步骤
1. 将资料\工具类\04.Flink数据同步系统目录的pom.xml文件中的依赖导入到sync-db 项目的pom.xml
2. sync-db 模块添加scala支持
3. main和test创建scala 文件夹,并标记为源代码和测试代码目录
4. 将资料\工具类\04.Flink数据同步系统目录的application.conf 和log4j.properties 配置文件
5. 复制之前Flink项目中的GlobalConfigUtil 和HBaseUtil
#### 1.4. Flink程序开发
步骤
1. 编写App.scala 初始化Flink环境
2. 运行Flink程序测试是否能够消费到kafka中topic为canal 的数据
3. 编写FlinkUtils.scala
`App.scala`
![](screenshot/1a3addd7.png)
整合kafka
![](screenshot/036a079d.png)
#### 1.4.1. 定义原始Canal消息样例类
步骤
1. 在bean 包下创建Canal原始消息映射样例类
2. 在Cannal样例类中编写apply方法使用FastJSON来解析数据转换为Cannal样例类对象
3. 编写main 方法测试是否能够成功构建样例类对象
#### 1.4.2. 解析Kafka数据流为Canal样例类
步骤
1. 在map 算子将消息转换为Canal样例类对象
2. 打印测试,如果能输出以下信息,表示成功
![](screenshot/03ef7ace.png)
#### 1.4.3. 添加水印支持
步骤
1. 使用Canal中的timestamp 字段,生成水印数据
2. 重新运行Flink打印添加水印后的数据
![](screenshot/22cd7b3c.png)
#### 1.4.4. 定义HBaseOperation样例类
HbaseOperation样例类主要封装对Hbase的操作主要封装以下字段
- 操作类型opType= INSERT/DELETE/UPDATE
- 表名tableName= mysql.binlog数据库名.binlog表名
- 列族名cfName= 固定为info
- rowkey = 唯一主键取binlog中列数据的第一个
- 列名colName= binlog中列名
- 列值colValue= binlog中列值
![](screenshot/58926ce0.png)
#### 1.4.5. 将Canal样例类转换为HBaseOperation样例类
一个binlog消息中有会有多个列的操作。它们的映射关系如下
可以使用flatMap 算子来生成一组HBaseOperation 操作
步骤
1. 创建一个预处理任务对象
2. 使用flatMap对水印数据流转换为HBaseOperation
- 根据eventType分别处理HBaseOperation 列表
- 生成的表名为mysql.数据库名.表名
- rowkey就是第一个列的值
- INSERT操作 -> 将所有列值转换为HBaseOperation
- UPDATE操作 -> 过滤掉isValid字段为false 的列再转换为HBaseOperation
- DELETE操作 -> 只生成一条DELETE的HBaseOperation的List
- INSERT操作记录
![](screenshot/34f66a92.png)
![](screenshot/a47efd66.png)
实现
1. 在task 包下创建PreprocessTask 单例对象添加process 方法
2. 使用flatMap对Canal样例类进行扩展
3. 使用FastJSON 解析Canal样例类中的列值列表数据并存储到一个Seq中
4. 遍历集合构建HBaseOperation 样例类对象
5. 打印测试
6. 启动Flink验证程序是否正确处理
> JSON字符串转List
List<T> parseArray(String text, Class<T> clazz)
classOf[T] : 获取class对象
Java的List转Scala的集合
注意要导入: import scala.collection.JavaConverters._
var scalaList: mutable.Buffer[T] = javaList.asScala
#### 1.4.6. Flink数据同步到hbase
步骤
1. 分两个落地实现一个是delete 一个是insert/update 因为hbase中只有一个put操作所以只要是
2. 启动hbase
3. 启动flink 测试
#### 1.4.7. 验证Flink同步数据功能
步骤
1. 启动mysql
2. 启动canal
3. 启动zookeeper 集群
4. 启动kafka 集群
5. 启动hdfs 集群
6. 启动hbase 集群
7. 启动Flink数据同步程序
8. 启动Canal数据同步程序
9. 在mysql中执行insert、update、delete语句查看hbase 数据是否落地
insert/update都转换为put操作
执行插入:
![](screenshot/4b18ecbe.png)
修改数据:
![](screenshot/880c750d.png)
![](screenshot/07a78b77.png)
删除操作:
![](screenshot/ec1f3fda.png)
落地HBase
删除动作:
![](screenshot/dedf144c.png)
![](screenshot/8c5fa195.png)
修改动作:
![](screenshot/21733492.png)
![](screenshot/6c04e485.png)