大数据项目之_15_电信客服分析平台_01&02_项目背景+项目架构+项目实现+数据生产+数据采集/消费(存储)

本文介绍了电信客服分析平台的项目背景、架构及实现细节。项目涉及数据生产、数据采集与消费,使用Hadoop、Hbase和Flume、kafka进行数据处理。数据生产部分包括数据结构设计和Java代码实现,数据采集涉及Flume从日志文件到kafka的传输,以及HBase的消费和存储。整个流程展示了大数据实时处理的典型应用场景。
摘要由CSDN通过智能技术生成

一、项目背景

  通信运营商每时每刻会产生大量的通信数据,例如:通话记录,短信记录,彩信记录,第三方服务资费等等繁多信息。数据量如此巨大,除了要满足用户的实时查询和展示之外,还需要定时定期的对已有数据进行离线的分析处理。例如:当日话单,月度话单,季度话单,年度话单,通话详情,通话记录等等。我们以此为背景,寻找一个切入点,学习其中的方法论

二、项目架构

三、项目实现

系统环境:

系统 版本
windows 10 专业版(建议)
linux CentOS 6.8 or CentOS 7.2(1611 内核)

开发工具:

工具 版本
idea 2017.2.5 旗舰版
maven 3.3.9
JDK 1.8+

尖叫提示:idea2017.2.5 必须使用 maven3.3.9,不要使用 maven3.5,有部分兼容性问题。

集群环境(CDH版):
尖叫提示:学习的时候使用的普通版本的,企业开发中使用的是 CDH 版本的。

框架 版本
hadoop cdh5.3.6-2.5.0
zookeeper cdh5.3.6-3.4.5
hbase cdh5.3.6-0.98
hive cdh5.3.6-0.13.1
flume cdh5.3.6-1.5.0(学习使用版本 1.7.0)
kafka kafka_2.10-0.8.2.1(学习使用版本 2.11-0.11.0.2)

硬件环境:

硬件 hadoop102 hadoop103 hadoop104
内存 4G 2G 2G
CPU 2核 1核 1核
硬盘 50G 50G 50G

3.1、数据生产

  此情此景,对于该模块的业务,即数据生产过程,一般并不会让你来进行操作,数据生产是一套完整且严密的体系,这样可以保证数据的鲁棒性。但是如果涉及到项目的一体化方案的设计(数据的产生、存储、分析、展示),则必须清楚每一个环节是如何处理的,包括其中每个环境可能隐藏的问题;数据结构,数据内容可能出现的问题。

3.1.1、数据结构

  我们将在 HBase 中存储两个电话号码,以及通话建立的时间和通话持续时间,最后再加上一个 flag 作为判断第一个电话号码是否为主叫。姓名字段的存储我们可以放置于另外一张表做关联查询,当然也可以插入到当前表中。如下图所示:

数据结构如下:

列名 解释 举例
call1 第一个手机号码 15369468720
call1_name 第一个手机号码人姓名(非必须) 李雁
call2 第二个手机号码 19920860202
call2_name 第二个手机号码人姓名(非必须) 卫艺
build_time 建立通话的时间 20171017081520
build_time_ts 建立通话的时间(时间戳形式) 毫秒数
duration 通话持续时间(秒) 0600
flag 用于标记本次通话第一个字段(call1)是主叫还是被叫 1为主叫,0为被叫
3.1.2、编写代码

思路:
  a) 创建 Java 集合类存放模拟的电话号码和联系人;
  b) 随机选取两个手机号码当做“主叫”与“被叫”(注意判断两个手机号不能重复),产出 call1 与 call2 字段数据;
  c) 创建随机生成通话建立时间的方法,可指定随机范围,最后生成通话建立时间,产出 date_time 字段数据;
  d) 随机一个通话时长,单位:秒,产出 duration 字段数据;
  e) 将产出的一条数据拼接封装到一个字符串中;
  f) 使用 IO 操作将产出的一条通话数据写入到本地文件中。(一定要手动 flush,这样能确保每条数据写入到文件一次)

新建 module 项目:ct_producer
pom.xml 文件配置:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.12.4</version>
                <configuration>
                    <!-- 设置打包时跳过test包里面的代码 -->
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
  1. 随机输入一些手机号码以及联系人,保存于 Java 的集合中。
      新建类:ProductLog
/**
 * @author chenmingjun
 * 2019-03-13 13:35
 */
public class ProductLog {
   

    /**
     * 生产数据
     */

    private String startTime = "2017-01-01";
    private String endTime = "2017-12-31";

    // 用于存放待随机的联系人电话
    private List<String> phoneList = new ArrayList<>();

    // 用于存放联系人电话与姓名的映射
    private Map<String, String> phoneNameMap = new HashMap<>();

    /**
     * 初始化随机的电话号码和姓名
     */
    public void initPhone() {
   
        phoneList.add("13242820024");
        phoneList.add("14036178412");
        phoneList.add("16386074226");
        phoneList.add("13943139492");
        phoneList.add("18714767399");
        phoneList.add("14733819877");
        phoneList.add("13351126401");
        phoneList.add("13017498589");
        phoneList.add("16058589347");
        phoneList.add("18949811796");
        phoneList.add("13558773808");
        phoneList.add("14343683320");
        phoneList.add("13870632301");
        phoneList.add("13465110157");
        phoneList.add("15382018060");
        phoneList.add("13231085347");
        phoneList.add("13938679959");
        phoneList.add("13779982232");
        phoneList.add("18144784030");
        phoneList.add("18637946280");

        phoneNameMap.put("13242820024", "李雁");
        phoneNameMap.put("14036178412", "卫艺");
        phoneNameMap.put("16386074226", "仰莉");
        phoneNameMap.put("13943139492", "陶欣悦");
        phoneNameMap.put("18714767399", "施梅梅");
        phoneNameMap.put("14733819877", "金虹霖");
        phoneNameMap.put("13351126401", "魏明艳");
        phoneNameMap.put("13017498589", "华贞");
        phoneNameMap.put("16058589347", "华啟倩");
        phoneNameMap.put("18949811796", "仲采绿");
        phoneNameMap.put("13558773808", "卫丹");
        phoneNameMap.put("14343683320", "戚丽红");
        phoneNameMap.put("13870632301", "何翠柔");
        phoneNameMap.put("13465110157", "钱溶艳");
        phoneNameMap.put("15382018060", "钱琳");
        phoneNameMap.put("13231085347", "缪静欣");
        phoneNameMap.put("13938679959", "焦秋菊");
        phoneNameMap.put("13779982232", "吕访琴");
        phoneNameMap.put("18144784030", "沈丹");
        phoneNameMap.put("18637946280", "褚美丽");
    }
  1. 创建随机生成通话时间的方法:randomBuildTime()
      该时间生成后的格式为:yyyy-MM-dd HH:mm:ss,并使之可以根据传入的起始时间和结束时间来随机生成。
    /**
     * 根据传入的时间区间,在此范围内随机产生通话建立的时间
     * 公式:startDate.getTime() + (endDate.getTime() - startDate.getTime()) * Math.random()
     *
     * @param startTime
     * @param endTime
     * @return
     */
    public String randomBuildTime(String startTime, String endTime) {
   
        try {
   
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
            Date startDate = sdf1.parse(startTime);
            Date endDate = sdf1.parse(endTime);

            if (endDate.getTime() <= startDate.getTime()) {
   
                return null;
            }

            long randomTS = startDate.getTime() + (long) ((endDate.getTime() - startDate.getTime()) * Math.random());

            Date resultDate = new Date(randomTS);
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String resultTimeString = sdf2.format(resultDate);

            return resultTimeString;

        } catch (ParseException e) {
   
            e.printStackTrace();
        }

        return null;
    }
  1. 创建生产日志一条日志的方法:productLog()
      随机抽取两个电话号码,随机产生通话建立时间,随机通话时长,将这几个字段拼接成一个字符串,然后return,便可以产生一条通话的记录。需要注意的是,如果随机出的两个电话号码一样,需要重新随机(随机过程可优化,但并非此次重点)。通话时长的随机为30分钟以内,即:60秒 * 30,并格式化为4位数字,例如:0600(10分钟)。
    /**
     * 生产数据的形式:13651311090,18611213803,2017-10-17 08:15:20,0360
     */
    public String productLog() {
   

        String caller = null;
        String callee = null;

        String callerName = null;
        String calleeName = null;

        // 随机获取主叫手机号
        int callerIndex = (int) (Math.random() * phoneList.size()); // [0, 20)
        caller = phoneList.get(callerIndex);
        callerName = phoneNameMap.get(caller);

        // 随机获取被叫手机号
        while (true) {
   
            int calleeIndex = (int) (Math.random() * phoneList.size()); // [0, 20)
            callee = phoneList.get(calleeIndex);
            calleeName = phoneNameMap.get(callee);

            if (!caller.equals(callee)) {
   
                break;
            }
        }

        // 随机获取通话建立的时间
        String buildTime = randomBuildTime(startTime, endTime);

        // 随机获取通话的时长
        DecimalFormat df = new DecimalFormat("0000");
        String duration = df.format((int) (30 * 60 * Math.random()));

        StringBuilder sb = new StringBuilder();
        sb.append(caller + ",").append(callee + ",").append(buildTime + ",").append(duration);
        return sb.toString();

        // System.out.println(caller + "," + callerName + "," + callee + "," + calleeName + "," + buildTime + "," + duration);
    }
  1. 创建写入日志方法:writeLog()
      productLog() 方法每产生一条日志,便将日志写入到本地文件中,所以建立一个专门用于日志写入的方法,需要涉及到 IO 操作,需要注意的是,输出流每次写一条日之后需要 flush,不然可能导致积攒多条数据才输出一次。最后需要将 productLog() 方法放置于 while 死循环中执行。
    /**
     * 将数据写入到文件中
     */
    public void writeLog(String filePath) {
   
        try {
   
            OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8");

            while (true) {
   
                Thread.sleep(200);

                String log = productLog();
                System.out.println(log);

                osw.write(log + "\n");
                osw.flush(); // 一定要手动flush,这样能确保每条数据写入到文件一次
            }

        } catch (IOException e) {
   
            e.printStackTrace();
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
  1. 在主函数中初始化以上逻辑,并测试:
    public static void main(String[] args) throws InterruptedException {
   

        if (args == null || args.length <= 0) {
   
            System.out.println("No arguments");
            return;
        }

        ProductLog productLog = new ProductLog();
        productLog.initPhone();
        productLog.writeLog(args[0]);

        // 测试
        // String logPath = "d:\\temp\\ct_log\\log.csv";
        // productLog.writeLog(logPath);
    }
3.1.3、打包测试
  1. 打包方式
    如果在 eclipse 中,则需要如下 maven 参数进行打包:
-P local clean package:不打包第三方依赖
-P dev clean package install:打包第三方依赖

如果在 idea 中,则需要在 maven project 视图中一次选择如下按钮进行打包:详细操作请参看课堂演示

LifeCycle --> package(双击)

分别在 Windows 上和 Linux 中进行测试:

Windows:

java -cp ct_producer-1.0-SNAPSHOT.jar producer.ProductLog /本地目录/callLog.csv
  1. 为日志生成任务编写 bash 脚本:productLog.sh,文件内容如下,该文件放在 /opt/module/flume/job/ct/ 目录下,并授予执行权限。
#!/bin/bash
java -cp /opt/module/flume/job/ct/ct_producer-1.0-SNAPSHOT.jar com.china.producer.ProductLog /opt/module/flume/job/ct/calllog.csv

3.2、数据采集/消费(存储)

  欢迎来到数据采集模块(消费),在企业中你要清楚流式数据采集框架 flume 和 kafka 的定位是什么。我们在此需要将实时数据通过 flume 采集到 kafka 然后供给给 hbase 消费。

flume:Cloudera 公司研发
  适合采集文件中的数据;
  适合下游数据消费者不多的情况;
  适合数据安全性要求不高的操作;
  适合与 Hadoop 生态圈对接的操作。

kafka:Linkedin 公司研发
  适合数据下游消费众多的情况;
  适合数据安全性要求较高的操作(支持 replication(副本))。

HBase:实时保存一条一条流入的数据(万金油)
情景:
  适用于在线业务
  适用于离线业务
  适用于非结构化数据
  适用于结构化数据

因此我们常用的一种模型是:
  线上数据 --> flume --> kafka --> flume(根据情景增删该流程) --> HDFS (最常用)
  线上数据 --> flume --> kafka --> 根据kafka的API自己写 --> HDFS
  线上数据 --> kafka --> HDFS
  线上数据 --> kafka --> Spark/Storm

消费存储模块流程图:

公司中的业务情景:
  1、公司已经设计好架构了,耐心了解每一个框架应对的是哪一个业务的功能,之后按照框架进行分层。
  2、公司没有架构,需要自己搭建,需要按照客户的需求,先对需求进行分层,根据需求用对应的框架实现,之后对框架进行分层。(架构师的思想:宏观格局,5万的月薪,这样才刺激!)

3.2.1、数据采集:采集实时产生的数据到 kafka 集群

思路:
  a) 配置 kafka,启动 zookeeper 和 kafka 集群;
  b) 创建 kafka 主题;
  c) 启动 kafka 控制台消费者(此消费者只用于测试使用);
  d) 配置 flume,监控日志文件;
  e) 启动 flume 监控任务;
  f) 运行日志生产脚本;
  g) 观察测试。

  1. 配置 kafka
    使用新版本 kafka_2.11-0.11.0.2,不使用老版本 kafka_2.10-0.8.2.1

新旧版本的区别:
新:能配置 delete.topic.enable=true 删除topic功能使能,老版本没有,不过配置了也生效。
旧:需要配置 port=9092,host.name=hadoop102,新版本的不需要。
新:设置读取偏移地址的位置 auto.offset.reset 默认值是 latest,还可以填写 earliest。
旧:设置读取偏移地址的位置 auto.offset.reset 默认值是 largest,还可以填写 smallest。
server.properties

############################# Server Basics #############################

# The id of the broker. This must be set to a unique integer for each broker.
broker.id=0

# Switch to enable topic deletion or not, default value is false(此处的配置打开)
delete.topic.enable=true

############################# Socket Server Settings #############################

# The address the socket server listens on. It will get the value returned from 
# java.net.InetAddress.getCanonicalHostName() if not configured.
#   FORMAT:
#     listeners = listener_name://host_name:port
#   EXAMPLE:
#     listeners = PLAINTEXT://your.host.name:9092
#listeners=PLAINTEXT://:9092

# Hostname and port the broker will advertise to producers and consumers. If not set, 
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值