HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统

内容简介

    • 一、项目内容深度分析
        • 1. 项目内容概览
        • 2.数据的大致流向分析
        • 3. 涉及的知识难点分析
    • 二、项目所用到的框架清单
    • 三、项目实战代码
      • 1. 后端开发
        • 1. 构建工程项目模块
        • 2.开发通话记录生成模块
        • 3.开发通话记录收集模块
        • 4.开发协处理器模块
        • 5.将通话记录生成模块打包上集群并测试
        • 6.将协处理器模块打包提交到集群并配置集群协处理器
        • 7.将通话记录收集模块打包上集群
        • 8.测试数据是否正常插入HBase中
      • 2. 前端开发
        • 1.整合开发Spring + Spring MVC +MyBatis
        • 2.在Hive中建立外部表关联HBase
        • 3.启动hiveserver2服务
    • 四、项目结果演示
        • 1.打印所有的通话记录信息
        • 2.查询某个号码在某个时间段内的通话记录
        • 3.查询某个用户最后三次的通话记录信息
        • 4.统计所有用户的所有通话信息
    • 五、总结

一、项目内容深度分析

1. 项目内容概览

  • 电信大数据通话信息实时读写定位系统,即是为了解决实时产生的海量通话记录的实时读写与随机定位的问题,该系统涵盖了数据的产生,数据的收集,数据的处理及入库,内容包括以下四个模块:
    1. 电话通话记录的产生模块
    2. 电话通话记录的收集模块
    3. 电话通话记录的处理及入库模块
    4. 读写定位系统的前端平台模块

2.数据的大致流向分析

  • 首先由电话通话记录的产生模块源源不断地产生数据,模拟在交换机上源源不断产生的电话通话日志,这些日志会被追加存储在一个特定的文件中,然后启动Flume监控该文件进行收集,收集后的通话记录数据将会进入Kafa中进行缓存,缓存后的电话通话记录会被HBase进行消费,此步会对数据进行处理,包括RowKey的生成及盐析处理避免热点问题等,将处理后的数据插入HBase的表,然后会在Hive中建立一张映射HBase的外部表,因为HBase是NoSQL,无法通过SQL语句进行查询信息,但是经过Hive的建模则可以直接通过类SQL语句进行查询信息了,然后开启hiveserver2服务,最后构建SSM平台,连接hiveserver2服务进行交互。

3. 涉及的知识难点分析

  • HBase中RowKey的设计。这是本项目非常关键的一个步骤,假设现在HBase中用以存储通话记录的表有100个region,这些region分布在整个HBase的集群中,现在存在一个问题,当源源不断的通话记录被插入到HBase中若是不对RowKey进行设计则很有可能造成热点问题,使得某些RegionServer被高频访问,造成集群资源利用不充分。其次也不能随机将通话记录放入到各个region中,这样的话,同一个人同一时间段的通话记录被打散,查询起来就会跨多个region,效率低下。因此RowKey的设计是一个难点。
  • 被叫记录的实时插入。当产生一个主叫记录时也必然会产生一个被叫记录,但是数据的收集处只有主叫记录,因此需要在插入一条主叫信息时实时插入一条被叫的通话记录,这就需要使用HBase的协处理器来完成,在检测到是通话记录表有插入操作且是主叫插入时会触发插入一条对应的被叫通话记录。在查询某个用户的通话记录时,应该要把该用户的主叫及被叫都查询出来。
  • 合理在Hive中建立HBase的外部表。在外部执行某些条件查询时,使用HBase不好查询,因为HBase是非关系型数据库,因此不能使用SQL语句进行查询,需要使用Hive对HBase的通话记录表建模,创建一张外部表关联HBase的表,然后使用Hive进行查询。
  • 本次项目会涉及HBase的过滤器的使用、协处理器的设计与使用、RowKey的设计等三大HBase的经典需求,以及Hive与HBase的交互,如何建立外部表关联HBase的表以及如何通过启动hivserver2这个交互式查询引擎与web服务对接进行实时查询。

二、项目所用到的框架清单

  • 要完成该项目,需要确保你的虚拟机(或服务器)已经安装了以下大数据框架:

    1. Hadoop集群,本次演示使用的Hadoop版本是hadoop-2.6.0-cdh5.7.0
    2. HBase集群,本次演示使用的HBase版本是hbase-1.2.0-cdh5.7.0
    3. ZooKeeper集群,本次演示使用的Zookeeper版本是zookeeper-3.4.5-cdh5.7.0
    4. Kafka集群,本次演示使用的Kafka版本是kafka_2.11-1.0.0
    5. Flume,本次演示使用的Flume版本是apache-flume-1.9.0-bin
    6. Hive,本次演示使用的Hive版本是apache-hive-2.1.0-bin

    前端构建查询系统使用了SSM框架:

    1. Spring,本次演示使用的Spring版本是4.3.3.RELEASE
    2. Spring MVC,本次演示使用的Spring MVC版本是4.3.3.RELEASE
    3. MyBatis,本次演示使用的MyBatis版本是3.2.1
    4. MySQL,本次演示使用的MySQL版本是5.6.26

    本次项目使用IEDA2018作为开发工具。

三、项目实战代码

1. 后端开发

  • 1. 构建工程项目模块

    打开IDEA创建普通的Java工程,取名为CallLogsSystem,并添加Maven支持,然后在本工程下添加新的模块:
    在这里插入图片描述
    按照以上步骤,依次添加CallLogGenModule、CallLogWeb、CallCoprosser、KafkaConsumerModule四个模块,添加完成的模块如图:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第1张图片
  • 2.开发通话记录生成模块

    进入CallLogGenModule模块,添加Maven支持,目的是为了方便使用Maven自带的打包工具进行打包。在该模块的src下创建如下目录路径:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第2张图片
    在包com.project.callloggenmodule下创建APP类,该类用于生成通话记录,这些通话 日志是最原始的记录:
    APP类代码如下:
    /**
    * 主呼叫记录生成类
    * 模拟一个主动呼叫的电话
    */
    public class App {
       //初始化工作
       public static Map<String,String> allCallers = new HashMap();
       public static List<String> phoneNumber = new ArrayList();
       static {
           //加载所有用户号码和姓名信息
           allCallers.put("15810092493", "萧邦主");
           allCallers.put("18000696806", "赵贺彪");
           allCallers.put("15151889601", "张倩");
           allCallers.put("13269361119", "王世昌");
           allCallers.put("15032293356", "张涛");
           allCallers.put("17731088562", "张阳");
           allCallers.put("15338595369", "李进全");
           allCallers.put("15733218050", "杜泽文");
           allCallers.put("15614201525", "任宗阳");
           allCallers.put("15778423030", "梁鹏");
           allCallers.put("18641241020", "郭美彤");
           allCallers.put("15732648446", "刘飞飞");
           allCallers.put("13341109505", "段光星");
           allCallers.put("13560190665", "唐会华");
           allCallers.put("18301589432", "杨力谋");
           allCallers.put("13520404983", "温海英");
           allCallers.put("18332562075", "朱尚宽");
           allCallers.put("15902136987", "张翔");
           allCallers.put("13801358247", "杨超凡");
           allCallers.put("15975500987", "何潮辉");
           allCallers.put("13013685036", "庄银泳");
           allCallers.put("15019933667", "萧金辉");
           allCallers.put("18301930136", "黄海锋");
           //加载所有用户号码到此集合
           phoneNumber.addAll(allCallers.keySet());
       }
    
       public static void main(String[] args) throws Exception{
    
           while (true){
               if(args.length != 1){
                   System.err.println("You should input a path to save the logs");
                   System.exit(1);
               }
               // 第一个参数即是存储电话号码的文件
               genCallLog(args[0]);
           }
        }
    
        public static void genCallLog (String logFile) throws Exception{
                   Random r = new Random();
                   //文件输出流,追加写入文件
                   FileWriter writer = new FileWriter(logFile,true);
                   //产生一个主叫
                   String callerNumber = phoneNumber.get(r.nextInt(phoneNumber.size()));
                   String callerName = allCallers.get(callerNumber);
                   String calleeNumber;
                   String calleeName;
                   //产生一个被叫
                   while (true) {
                       calleeNumber = phoneNumber.get(r.nextInt(phoneNumber.size()));
                       //主叫与被叫默认不为同一个人
                       if (!calleeNumber.equals(callerNumber)) {
                           calleeName = allCallers.get(calleeNumber);
                           break;
                       }
                   }
           //通话时间生成
           int year = r.nextInt(2) == 0 ? 2018 : 2019;
           int month = r.nextInt(12);
           int day = r.nextInt(getDay(month)) + 1;
           int hour = r.nextInt(24);
           int min = r.nextInt(60);
           int sec = r.nextInt(60);
           //使用日期类格式化时间
           Calendar calendar = Calendar.getInstance();
           calendar.set(Calendar.YEAR, year);
           calendar.set(Calendar.MONTH, month);
           calendar.set(Calendar.DAY_OF_MONTH, day);
           calendar.set(Calendar.HOUR_OF_DAY, hour);
           calendar.set(Calendar.MINUTE, min);
           calendar.set(Calendar.SECOND, sec);
           Date timeDate = calendar.getTime();
           SimpleDateFormat sdf = new SimpleDateFormat();
           sdf.applyPattern("yyyy/MM/dd hh:mm:ss");
           String time = sdf.format(timeDate);
           //通话时长
           DecimalFormat df = new DecimalFormat();
           df.applyPattern("000");
           // 默认通话时间范围[0,1800)秒
           int dur = r.nextInt(60 * 30);
           String duration = df.format(dur);
           //通话记录
            String log = callerNumber + ","  + calleeNumber + "," + time + "," + duration;
           //将通话记录写入到外部存储系统
            writer.write(log + "\r\n");
            writer.flush();
           //休眠200ms
           Thread.sleep(200);
       }
    
       // 根据月份返回该月最多的天数
       public static int getDay(int month){
           if(month == 2)
               return 28;
           if(month == 1 || month == 3 || month == 5 || month == 7
                   || month == 8 || month == 10 || month == 12)
               return 31;
           return 30;
       }
    }
    
  • 3.开发通话记录收集模块

    该模块是本项目核心项目之一,对RowKey的设计在本模块中完成。开发本模块之前,首先需要在HBase中建立一张表存储通话记录:
    进入HBase的终端内,执行命令create 'ns1:calllog','f1',在名字空间ns1中创建一张calllog表,列族为f1。
    然后进入KafkaConsumerModule模块下,并为该目录添加Maven支持,本模块完整的Maven依赖如下:
     <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
        </repository>
    </repositories>
    
    <dependencies>
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka_2.11</artifactId>
            <version>1.0.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>1.2.0-cdh5.7.0</version>
        </dependency>
    
    </dependencies>
    
    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.7.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.20.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <!--  <sourceDirectory>src/main/scala</sourceDirectory>
          <testSourceDirectory>src/test/scala</testSourceDirectory>-->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-eclipse-plugin</artifactId>
                <configuration>
                    <downloadSources>true</downloadSources>
                    <buildcommands>
                        <buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand>
                    </buildcommands>
                    <additionalProjectnatures>
                        <projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature>
                    </additionalProjectnatures>
                    <classpathContainers>
                        <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
                        <classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer>
                    </classpathContainers>
                </configuration>
            </plugin>
    
             <plugin>
                 <artifactId>maven-assembly-plugin</artifactId>
                 <executions>
                     <execution>
                         <phase>package</phase>
                         <goals>
                             <goal>single</goal>
                         </goals>
                     </execution>
                 </executions>
                 <configuration>
                     <descriptorRefs>
                         <descriptorRef>jar-with-dependencies</descriptorRef>
                     </descriptorRefs>
                 </configuration>
             </plugin>
    
        </plugins>
    </build>
    
    在resources目录下创建project.properties文件,该文件是一个该模块下的全局配置文件,用于该模块对Kafka,HBase等的配置:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第3张图片
    project.properties内容如下,注意要将某些配置如集群的IP等改为符合你的配置:
    #----- kafka配置 ----#
    #zk服务器
    zookeeper.connect=hadoop00:2181
    #消费者组
    group.id=g1
    zookeeper.session.timeout.ms=500
    zookeeper.sync.time.ms=250
    auto.commit.interval.ms=1000
    #消费偏移量,从头消费smallest
    auto.offset.reset=smallest
    #主题
    kafka.topic=calllog
    
    #----- HBase配置 ------#
    hbase.zookeeper.quorum=hadoop00:2181
    hbase.tablename=ns1:calllog
    partition.num=100
    
    #---- 其他配置 ----#
    #呼叫标志位,1代表主叫,0代表被叫
    callerflag=1
    calleeflag=0
    
    在java目录下创建类PropertiesUtil,用于解析配置文件,代码如下:
    /**
     * 配置文件工具类
     */
    public class PropertiesUtil {
        //单例模式
        private PropertiesUtil(){}
        //静态配置对象
        public static Properties props = null;
        //初始化对象
        static {
            InputStream in = ClassLoader.getSystemResourceAsStream("project.properties");
            props = new Properties();
            try {
                props.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //获得配置属性
        public static String getProperty(String key){
            return props.getProperty(key);
        }
    }
    
    接着在java目录下创建类HbaseDao用于处理通话记录,主要负责三个工作:计算该通话记录存储的region,产生RowKey,数据入库。HbaseDao代码如下:
    /**
     * 访问Hbase
     */
    public class HbaseDao {
    
        private Table table = null;
        private TableName tableName = null;
        // 是否是主叫标志位
        private String flag = null;
        // HBase的分区个数
        private int partitonsNum = 0;
        //初始化
        public HbaseDao(){
            Configuration conf = HBaseConfiguration.create();
            conf.set("hbase.zookeeper.quorum",PropertiesUtil.getProperty("hbase.zookeeper.quorum"));
            try {
               Connection con =  ConnectionFactory.createConnection(conf);
                tableName = TableName.valueOf(PropertiesUtil.getProperty("hbase.tablename"));
                table = con.getTable(tableName);
                flag = PropertiesUtil.getProperty("callerflag");
                partitonsNum = Integer.parseInt(PropertiesUtil.getProperty("partition.num"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //将通话记录插入到HBase,设计RowKey
        public void put(String log){
            String[] arr = log.split(",");
            String caller = arr[0];
            String callee = arr[1];
            String callDate = arr[2];
            //对日期格式化
            String callDateFormat = callDate.replace("/",""); //删除/
            callDateFormat = callDateFormat.replace(" ","");  //删除空格
            callDateFormat = callDateFormat.replace(":","");  //删除:
            String duration = arr[3];
            // 根据主叫号码以及呼叫日期生成哈希码
            // 可以确保同一个用户的同一个月的通话记录会被放在同一个region中
            // 不同用户或者同一用户的不同月份的通话记录尽可能发散存储,避免出现热点
            String hashCode = getHashCode(caller,callDateFormat);
            // 拼接HBase的RowKey
            String rowKey = hashCode + "," + caller + "," + callDateFormat + "," + flag + "," + callee + "," + duration;
            //创建Put对象
            Put put = new Put(rowKey.getBytes());
            // 在列族添中加相应的列
            put.addColumn("f1".getBytes(),"caller".getBytes(),caller.getBytes());
            put.addColumn("f1".getBytes(),"callee".getBytes(),callee.getBytes());
            put.addColumn("f1".getBytes(),"callDate".getBytes(),callDateFormat.getBytes());
            put.addColumn("f1".getBytes(),"callDuration".getBytes(),duration.getBytes());
           //插入表中
            try {
                table.put(put);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        // 该方法用于计算出该通话记录所在的region区域号
        private String getHashCode(String caller,String callDateFormat){
            //取电话号码最后5位
            int len = caller.length();
            String number = caller.substring(len - 5);
            //取年份和月份
            String date = callDateFormat.substring(0,6);
            //取哈希值
            int code = (Integer.parseInt(date) ^ Integer.parseInt(number) ) % partitonsNum;
            //格式化后返回
            DecimalFormat df = new DecimalFormat();
            df.applyPattern("00");
    
            return df.format(code);
        }
    }
    
    最后再在java目录下创建HbaseConsumer类,该类用于消费Flume收集的通话记录日志,并调用HbaseDao,将数据写入HBase,完整代码如下:
    /**
     * 1.启动kafka消费者,从kafka订阅消息(通话记录)
     * 2.将数据插入HBase中
     */
    public class HbaseConsumer {
        public static void main(String[] args) {
            //创建HbaseDao,与Hbase交互
            HbaseDao hbaseDao = new HbaseDao();
            //消费者配置对象
            ConsumerConfig config = new ConsumerConfig(PropertiesUtil.props);
            //获得主题
            String topic = PropertiesUtil.getProperty("kafka.topic");
            //创建Map集合存放订阅的主题
            Map<String, Integer> map = new HashMap<String, Integer>();
            //设置主题,可以订阅多个主题
            map.put(topic, new Integer(1));
            //传入主题,得到订阅的消息,kafka流集合
            //<主题,消息>
            Map<String, List<KafkaStream<byte[], byte[]>>> msgs = Consumer.createJavaConsumerConnector(config).createMessageStreams(map);
            //根据主题得到消息集合(只订阅了一个主题,直接get)
            List<KafkaStream<byte[], byte[]>> msgList = msgs.get(topic);
            //迭代消息集合
            for(KafkaStream<byte[],byte[]> stream : msgList){
                ConsumerIterator<byte[],byte[]> it = stream.iterator();
                while(it.hasNext()){
                    byte[] log = it.next().message();
                    //将数据写入Hbase
                    hbaseDao.put(new String(log));
                }
            }
        }
    }
    
  • 4.开发协处理器模块

    该模块也是项目的核心模块之一,负责在往HBase中插入主叫信息的同时,生成一条对应的被叫信息并往HBase中插入该被叫通话记录。进入CallCoprosser模块,为此模块添加Maven支持,完整的Maven依赖如下:
    	  <repositories>
            <repository>
                <id>cloudera</id>
                <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
            </repository>
        </repositories>
    <dependencies>
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>1.2.0-cdh5.7.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-server</artifactId>
            <version>1.2.0-cdh5.7.0</version>
        </dependency>
    </dependencies>
    
        <build>
            <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
                <plugins>
                    <plugin>
                        <artifactId>maven-clean-plugin</artifactId>
                        <version>3.0.0</version>
                    </plugin>
                    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                    <plugin>
                        <artifactId>maven-resources-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.7.0</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.20.1</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-install-plugin</artifactId>
                        <version>2.5.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-deploy-plugin</artifactId>
                        <version>2.8.2</version>
                    </plugin>
                </plugins>
            </pluginManagement>
            <!--  <sourceDirectory>src/main/scala</sourceDirectory>
              <testSourceDirectory>src/test/scala</testSourceDirectory>-->
            <plugins>
                <!--  <plugin>
                      <groupId>org.scala-tools</groupId>
                      <artifactId>maven-scala-plugin</artifactId>
                      <executions>
                          <execution>
                              <goals>
                                  <goal>compile</goal>
                                  <goal>testCompile</goal>
                              </goals>
                          </execution>
                      </executions>
                      <configuration>
                          <scalaVersion>${scala.version}</scalaVersion>
                          <args>
                              <arg>-target:jvm-1.5</arg>
                          </args>
                      </configuration>
                  </plugin>-->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-eclipse-plugin</artifactId>
                    <configuration>
                        <downloadSources>true</downloadSources>
                        <buildcommands>
                            <buildcommand>ch.epfl.lamp.sdt.core.scalabuilder</buildcommand>
                        </buildcommands>
                        <additionalProjectnatures>
                            <projectnature>ch.epfl.lamp.sdt.core.scalanature</projectnature>
                        </additionalProjectnatures>
                        <classpathContainers>
                            <classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER</classpathContainer>
                            <classpathContainer>ch.epfl.lamp.sdt.launching.SCALA_CONTAINER</classpathContainer>
                        </classpathContainers>
                    </configuration>
                </plugin>	
            </plugins>
    </build>
    
    并在该模块的java目录下创建如下的包:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第4张图片
    在util目录下,创建Util类,该类很重要,用于为被叫通话记录生成一个region号,可以确保该号码的被叫与主叫的通话记录存储在同一个region区域,完整代码如下:
    /**
     * 协处理工具类
     */
    public class Util {
    
        // RowKey的盐析处理
        // 由电话号码及通话的时间算出一个region号
        public static String getHashCode(String caller,String callDateFormat,int partitonsNum){
            //创建格式化类
            DecimalFormat df = new DecimalFormat();
            //取年份和月份
            String date = callDateFormat.substring(0,6);
            //取电话号码最后5位
            int len = caller.length();
            String number = caller.substring(len - 5);
            //取哈希值
            int code = (Integer.parseInt(date) ^ Integer.parseInt(number) ) % partitonsNum;
            //格式化后返回
            df.applyPattern("00");
    
            return df.format(code);
        // 拼接RowKey
        public static String getRowKey(String hashCode,String caller,String callDate,String flag,String callee,String callDuration){
            return hashCode + "," + caller + "," + callDate + "," + flag  + "," + callee + "," + callDuration;
        }
    }
    
    在App目录下创建类CallCoprocessor,该类是HBase的协处理器,完整代码如下:
    /**
     * 通话记录协处理器
     * 1.在插入一条主叫通话记录的同时插入一条被叫通话记录
     * 2.在查被叫信息时通过value找出主叫
     */
    public class CallCoprocessor extends BaseRegionObserver {
    
        //重写父类方法,在插入一条主叫通话记录的同时插入一条被叫通话记录
        @Override
        public void postPut(ObserverContext<RegionCoprocessorEnvironment> e, Put put, WALEdit edit, Durability durability) throws IOException {
            super.postPut(e, put, edit, durability);
            //获得表名
            String tableName = e.getEnvironment().getRegion().getRegionInfo().getTable().getNameAsString();
            //判断吧是否为通话记录表,如果不是则跳过
            if(!tableName.equals("calllog")){
                return;
            }
            //获得当前插入的RowKey
            String callerRow = Bytes.toString(put.getRow());
            //切割
            String[] row = callerRow.split(",");
            //判断是否是主叫插入,若不是则退出
            if(row[3].equals("0")){
                return;
            }
            //获得各个属性值
            String caller = row[1];
            String callDate = row[2];
            String callee = row[4];
            String callDuration = row[5];
            //设置为被叫插入
            String flag = "0";
            //取得哈希值
            String hashCode = Util.getHashCode(callee,callDate,100);
            //被叫RowKey
            String calleeRow = Util.getRowKey(hashCode,callee,callDate,flag,caller,callDuration);
            //创建Put对象
            Put putCallee = new Put(calleeRow.getBytes());
            //被叫存储
            putCallee.addColumn("f1".getBytes(),"caller".getBytes(),callee.getBytes());
            putCallee.addColumn("f1".getBytes(),"callDate".getBytes(),callDate.getBytes());
            putCallee.addColumn("f1".getBytes(),"callee".getBytes(),caller.getBytes());
            putCallee.addColumn("f1".getBytes(),"callDuration".getBytes(),callDuration.getBytes());
            //插入表中
            Table table = e.getEnvironment().getTable(TableName.valueOf(tableName));
            table.put(putCallee);
        }
    }
    
  • 5.将通话记录生成模块打包上集群并测试

    在本项目的右测,点击MavenProjects,然后选择CallLogGenModule模块的Maven配置进行如下操作即可完成打包:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第5张图片
    将打包好的jar包上传到虚拟机(或服务器)上,并使用命令
    java -cp jar包 类名 存储通话日志文件的绝对路径
    即可源源不断地产生通话日志并存储在特定的文件中,在本次演示中,我使用的命令为:
    在这里插入图片描述
    去查看存储日志的文件,可以发现如下的数据:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第6张图片
    至此,数据生成模块已经开发完成。
  • 6.将协处理器模块打包提交到集群并配置集群协处理器

    在本项目的右测,点击MavenProjects,然后选择CallCoprosser模块的Maven配置进行如下操作即可完成打包:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第7张图片
    将打成的jar包上传到HBase集群的每一个节点$HBASE_HOME/lib目录下。然后进入$HBASE_HOME/conf目录下,编辑hbase-site.xml文件,添加如下配置:
    <property>
    	<name>hbase.coprocessor.region.classes</name>
    	<value>com.project.coprocessor.App.CallCoprocessor</value>
    </property>
    
    其中value的值是你的协处理器代码所在的全类名,该配置在所有HBase的节点都要修改,然后重启HBase集群
  • 7.将通话记录收集模块打包上集群

    在本项目的右测,点击MavenProjects,然后选择KafkaConsumerModule模块的Maven配置进行如下操作即可完成打包:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第8张图片
    配置Flume来监控存储通话记录的文件calllog.log(演示所用的文件,与数据生成模块的存储文件一致),进入$FlUME_HOME/conf目录下创建Calllog.conf配置文件,该文件内容如下:
    a1.sources=execSource
    a1.sinks=kafkaSink
    a1.channels=memoryChannel
    
    a1.sources.execSource.type = exec
    a1.sources.execSource.command = tail -F /home/hadoop/data/calllog.log
    
    a1.sinks.kafkaSink.type = org.apache.flume.sink.kafka.KafkaSink
    a1.sinks.kafkaSink.kafka.topic = calllog
    a1.sinks.kafkaSink.kafka.bootstrap.servers = hadoop01:9092
    a1.sinks.kafkaSink.kafka.flumeBatchSize = 20
    a1.sinks.kafkaSink.kafka.producer.acks = 1
    
    a1.channels.memoryChannel.type=memory
    
    a1.sources.execSource.channels=memoryChannel
    a1.sinks.kafkaSink.channel=memoryChannel
    
    其中:
    execSource.command是Flume要监控的文件路径,该路径必须与数据生成模块的文件存储路径一致。
    kafkaSink.kafka.bootstrap.servers是你Kafka集群的某一个节点的地址。
    afkaSink.kafka.topic是你创建的某一个Kafka的主题,关于Kafka的主题,稍后会创建。
    创建Kafka的主题,去到$KAFKA_HOME/bin目录下执行命令:
    ./kafka-topics.sh --create --zookeeper hadoop00:2181 --topic calllog --replication-factor 3 --partitions 3
    注意参数中topic的名字要与Flume中配置的topic名字一致。
  • 8.测试数据是否正常插入HBase中

    (1).启动数据产生模块,使用命令:
    java -cp jar包 类名 存储通话日志文件的绝对路径
    jar包是你第5步所打的jar包
    类名是数据产生类的全类名
    (2).启动Flume,开始在后台收集通话记录日志,使用命令:
    flume-ng agent -f $FLUME_HOME/conf/calllog.conf -n a1 &
    (3).启动数据收集模块,使用命令:
    java -cp jar包 类名
    jar包是第7步所打的jar包
    类名是HBase消费者类的全类名
    至此,通话日志的产生到处理和最后的入库工作已全部完成,进入HBase的shell中,执行命令 scan 'ns1:calllog',会发现如下的记录:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第9张图片
    至此,后端开发工作已经完成。

2. 前端开发

  • 1.整合开发Spring + Spring MVC +MyBatis

    进入CallLogWeb模块,并添加Web支持和Maven支持,因为这是一个Web模块,负责与用户交互,完整的Maven依赖如下:
    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos</url>
        </repository>
    </repositories>
    
    <dependencies>
    
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.10</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
    
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.hbase</groupId>
            <artifactId>hbase-client</artifactId>
            <version>1.2.0-cdh5.7.0</version>
        </dependency>
    
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-jdbc</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>
    
    然后在java目录下,创建如下的包:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第10张图片
    1.在resources目录下新建如下配置文件:
    (1).beans.xml(Spring的核心配置文件)
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                               http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context
                               http://www.springframework.org/schema/context/spring-context-4.3.xsd
                               http://www.springframework.org/schema/tx
                               http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
                               http://www.springframework.org/schema/aop
                               http://www.springframework.org/schema/aop/spring-aop-4.3.xsd" default-autowire="byType">
        <!-- 配置事务特征 -->
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT"/>
            </tx:attributes>
        </tx:advice>
    
        <!-- 配置事务切面 -->
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* *..*Service.*(..))" />
        </aop:config>
    
        <!-- 扫描包 -->
        <context:component-scan base-package= "com.project.ssm.dao,com.project.ssm.service" />
    
        <!-- 数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql://hadoop00:3306/mybatis?user=root&password=root&useUnicode=true&characterEncoding=utf8"/>
            <property name="user" value="root"/>
            <property name="password" value="root"/>
            <property name="maxPoolSize" value="10"/>
            <property name="minPoolSize" value="2"/>
            <property name="initialPoolSize" value="3"/>
            <property name="acquireIncrement" value="2"/>
        </bean>
    
        <!-- mybatis整合spring的核心类。 -->
        <bean id="sf" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 指定数据源 -->
            <property name="dataSource" ref="dataSource" />
            <!-- 指定mybatis配置文件 -->
             <property name="configLocation" value="classpath:mybatis-config.xml"/>
        </bean>
    
        <!-- 数据源事务管理器 -->
        <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </beans>
    
    (2).mybatis-config.xml(MyBatis的核心配置文件)
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <typeAliases>
            <typeAlias type="com.project.ssm.domain.Person" alias="_person"/>
        </typeAliases>
        <!-- 引入映射文件 -->
        <mappers>
            <mapper resource="PersonMapper.xml"/>
        </mappers>
    </configuration>
    
    (3).PersonMapper.xml(PersonDao的MyBatis配置文件)
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="person">
        <insert id="insert">
          insert into persons(name,phone) values(#{name},#{phone})
        </insert>
    
        <!-- selectOne -->
        <select id="selectOne" parameterType="String" resultType="_person">
            select
              id,
              name,
              phone
              from persons where phone = #{phone}
        </select>
    
        <!-- selectList -->
        <select id="selectList" resultType="_person">
              select
              id,
              name,
              phone
              from persons
        </select>
    </mapper>
    
    (4).project.properties(访问HBase要用到的配置文件,要将相关配置改为你的配置)
    #----- kafka配置 ----#
    #zk服务器
    zookeeper.connect=hadoop01:2181
    #消费者组
    group.id=g1
    zookeeper.session.timeout.ms=500
    zookeeper.sync.time.ms=250
    auto.commit.interval.ms=1000
    #消费偏移量,从头消费
    auto.offset.reset=smallest
    #主题
    kafka.topic=calllog
    
    #----- HBase配置 ------#
    hbase.zookeeper.quorum=hadoop01:2181
    hbase.tablename=ns1:calllog
    partition.num=100
    
    #---- 其他配置 ----#
    #呼叫标志位,1代表主叫,0代表被叫
    callerflag=1
    calleeflag=0
    
    在web/WEB-INF下新建一个Spring MVC的配置文件dispatcher-servlet.xml:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/mvc
                            http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context-4.3.xsd">
        <!-- 配置扫描路径 -->
        <context:component-scan base-package="com.project.ssm.web.controller" />
        <!-- 使用注解驱动 -->
        <mvc:annotation-driven   />
    
        <mvc:resources mapping="/js/**" location="/js/"/>
        <mvc:resources mapping="/css/**" location="/css/"/>
        <mvc:resources mapping="/images/**" location="/images/"/>
        <mvc:resources mapping="/html/**" location="/html/"/>
    
    
        <!-- 内部资源视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/" />
            <property name="suffix" value=".jsp"/>
        </bean>
    </beans>
    
    2.在dao目录下创建一个接口PersonDao,该接口用于查询MySQL中电话用户的基本信息:
    /**
     * 通讯人信息Dao,在MySQL中查询用户信息
     */
    public interface PersonDao {
    
        //插入一条信息
        public void addOne(Person person);
    
        //根据电话号码查询信息
        public Person findByPhone(String phone);
    
        //查询所有
        public List<Person> findAll();
    }
    
    在dao目录包下创建impl包,再在impl包中创建类PersonDaoImpl,该类实现PersonDao接口:
    /**
     * PersonDao实现类
     */
    @Repository("PersonDao")
    public class PersonDaoImpl extends SqlSessionDaoSupport implements PersonDao {
    
        public void addOne(Person person) {
            getSqlSession().insert("person.insert",person);
        }
    
        public Person findByPhone(String phone) {
            return getSqlSession().selectOne("person.selectOne",phone);
        }
    
        public List<Person> findAll() {
            return getSqlSession().selectList("person.selectList");
        }
    }
    
    3.在domain包下创建如下几个类:
    在这里插入图片描述
    其中Person类用于封装查询的用户信息。
    CallLog类用于封装通话记录的详细信息。
    CallLogRage用于封装按时间段查询用户信息的参数。
    CallMessageStat用于封装用户通话记录统计信息。
    各个类代码如下:
    (1).Person
    /**
     * 通讯人信息
     */
    public class Person {
    
        private Integer id;
        private String name;
        private String phone;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPhone() {
            return phone;
        }
    
        public void setPhone(String phone) {
            this.phone = phone;
        }
    }
    
    (2).CallLog
    /**
     * 通话记录的全部信息
     */
    public class CallLog {
        private String caller ;
        private String callee ;
        private String callDate ;
        private String callDuration ;
        private String callerName;
        private String calleeName;
        private boolean flag;
    
        public String getCallerName() {
            return callerName;
        }
    
        public void setCallerName(String callerName) {
            this.callerName = callerName;
        }
    
        public String getCalleeName() {
            return calleeName;
        }
    
        public void setCalleeName(String calleeName) {
            this.calleeName = calleeName;
        }
    
        public boolean isFlag() {
            return flag;
        }
    
        public void setFlag(boolean flag) {
            this.flag = flag;
        }
    
        public String getCaller() {
            return caller;
        }
    
        public void setCaller(String caller) {
            this.caller = caller;
        }
    
        public String getCallee() {
            return callee;
        }
    
        public void setCallee(String callee) {
            this.callee = callee;
        }
    
        public String getCallDate() {
            //格式化时间
            try {
                SimpleDateFormat sf1 = new SimpleDateFormat();
                SimpleDateFormat sf2 = new SimpleDateFormat();
                sf1.applyPattern("yyyyMMddHHmmss");
                sf2.applyPattern("yyyy-MM-dd HH:mm:ss");
                return sf2.format(sf1.parse(callDate));
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
        public void setCallDate(String callDate) {
            this.callDate = callDate;
        }
    
        public String getCallDuration() {
            return callDuration;
        }
    
        public void setCallDuration(String callDuration) {
            this.callDuration = callDuration;
        }
    }
    
    (3).CallLogRage
    	/**
     * 封装了按时段查询通话记录的参数
     */
    public class CallLogRange {
        private String startPoint ;
        private String endPoint ;
    
        public String getStartPoint() {
            return startPoint;
        }
    
        public void setStartPoint(String startPoint) {
            this.startPoint = startPoint;
        }
    
        public String getEndPoint() {
            return endPoint;
        }
    
        public void setEndPoint(String endPoint) {
            this.endPoint = endPoint;
        }
    
        public String toString() {
            return startPoint + " - " + endPoint ;
        }
    }
    
    (4).CallMessageStat
    /**
     * 统计电话用户通话记录类
     */
    public class CallMessageStat {
    
        String caller;
        String callerName;
        Integer callNum;
        Integer callDuration;
    
        public String getCaller() {
            return caller;
        }
    
        public void setCaller(String caller) {
            this.caller = caller;
        }
    
        public String getCallerName() {
            return callerName;
        }
        public void setCallerName(String callerName) {
            this.callerName = callerName;
        }
    
        public Integer getCallNum() {
            return callNum;
        }
    
        public void setCallNum(Integer calleeNum) {
            this.callNum = calleeNum;
        }
    
        public Integer getCallDuration() {
            return callDuration;
        }
    
        public void setCallDuration(Integer calleeDuration) {
            this.callDuration = calleeDuration;
        }
    }	
    
    4.在util包下创建两个类:
    (1).PropertiesUtil类,该类用于读取我们自定义的配置文件project.properties:
    **
     * 配置文件工具类
     */
    public class PropertiesUtil {
        //单例模式
        private PropertiesUtil(){}
        //静态配置对象
        public static Properties props = null;
        //初始化对象
        static {
            InputStream in = ClassLoader.getSystemResourceAsStream("project.properties");
            props = new Properties();
            try {
                props.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //获得配置属性
        public static String getProperty(String key){
            return props.getProperty(key);
        }
    }
    
    (2).CallLogUtil类,该类为后续的查询提供了:计算region区域号,按时间段计算region区域范围等功能,是该模块的核心类之一:
    /**
     * CallLog工具类
     */
    public class CallLogUtil {
    
        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        private static SimpleDateFormat sdfFriend = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    
        //格式化
        private static DecimalFormat df = new DecimalFormat();
    
         public static String getHashCode(String caller,String callDateFormat,int partitonsNum) {
             //取年份和月份
             String date = callDateFormat.substring(0, 6);
             //取电话号码最后5位
             int len = caller.length();
             String number = caller.substring(len - 5);
             //取哈希值
             int code = (Integer.parseInt(date) ^ Integer.parseInt(number) ) % partitonsNum;
             //格式化后返回
             DecimalFormat df = new DecimalFormat();
             df.applyPattern("00");
    
             return df.format(code);
         }
        //得到起始RowKey
        public static String getStartRow(String caller,String startDate,int partitonsNum){
             String hashCode = getHashCode(caller,startDate,partitonsNum);
             return hashCode + "," + caller + "," + startDate;
        }
        //得到结束RowKey
        //注意,StopKey的哈希值由startKey计算得来!!!!
        public static String getStopRow(String caller,String startDate,String stopDate,int partitonsNum){
            String hashCode = getHashCode(caller,startDate,partitonsNum);
            return hashCode + "," + caller + "," + stopDate;
        }
    
        /**
         * 计算查询时间范围
         */
        public static List<CallLogRange> getCallLogRanges(String startStr ,String endStr){
            try{
                SimpleDateFormat sdfYMD = new SimpleDateFormat("yyyyMMdd");
                SimpleDateFormat sdfYM = new SimpleDateFormat("yyyyMM");
                DecimalFormat df00 = new DecimalFormat("00");
    
                //
                List<CallLogRange> list = new ArrayList<CallLogRange>();
                //字符串时间
                String startPrefix = startStr.substring(0, 6);
    
                String endPrefix = endStr.substring(0, 6);
                int endDay = Integer.parseInt(endStr.substring(6, 8));
                //结束点
                String endPoint = endPrefix + df00.format(endDay + 1);
    
                //日历对象
                Calendar c = Calendar.getInstance();
    
                //同年月
                if (startPrefix.equals(endPrefix)) {
                    CallLogRange range = new CallLogRange();
                    range.setStartPoint(startStr);          //设置起始点
    
                    range.setEndPoint(endPoint);            //设置结束点
                    list.add(range);
                } else {
                    //1.起始月
                    CallLogRange range = new CallLogRange();
                    range.setStartPoint(startStr);
    
                    //设置日历的时间对象
                    c.setTime(sdfYMD.parse(startStr));
                    c.add(Calendar.MONTH, 1);
                    range.setEndPoint(sdfYM.format(c.getTime()));
                    list.add(range);
    
                    //是否是最后一月
                    while (true) {
                        //到了结束月份
                        if (endStr.startsWith(sdfYM.format(c.getTime()))) {
                            range = new CallLogRange();
                            range.setStartPoint(sdfYM.format(c.getTime()));
                            range.setEndPoint(endPoint);
                            list.add(range);
                            break;
                        } else {
                            range = new CallLogRange();
                            //起始时间
                            range.setStartPoint(sdfYM.format(c.getTime()));
    
                            //增加月份
                            c.add(Calendar.MONTH, 1);
                            range.setEndPoint(sdfYM.format(c.getTime()));
                            list.add(range);
                        }
                    }
                }
                return list ;
            }
            catch(Exception e){
                e.printStackTrace();
            }
            return null ;
        }
    }
    
    5.在services包下添加服务接口:
    (1).CallLogService
    /**
     *与HBase直接交互的服务
     */
    public interface CallLogService {
        public List<CallLog> findAll();
    
        public  List<CallLog> findByDate(String caller, List<CallLogRange> list);
    }
    
    (2).HiveCallLogService
    /**
     * 与Hive交互的服务
     */
    public interface HiveCallLogService {
    
        public List<CallLog> findLatestCallLog(String phoneNum);
    
        public List<CallMessageStat> findCallMessageStat();
    }	
    
    (3).PersonService
    /**
     * 交互MySQLService类
     */
    public interface PersonService {
    
        //插入一条信息
        public void addOne(Person person);
    
        //根据电话号码查询信息
        public Person findByPhone(String phone);
    
        //查询所有
        public List<Person> findAll();
    }
    
    然后在该包下创建impl包,以实现以上3个接口,在impl包下创建以下类:
    (1).PersonDaoImpl
    /**
     * PersonService的实现类
     */
    @Service("PersonService")
    public class PersonServiceImpl implements PersonService {
    
        @Resource(name = "PersonDao")
        private PersonDao personDao;
    
        public void addOne(Person person) {
            personDao.addOne(person);
        }
    
        public Person findByPhone(String phone) {
            return personDao.findByPhone(phone);
        }
    
        public List<Person> findAll() {
            return personDao.findAll();
        }
    }
    
    (2).CallLogServiceImpl
    /**
     * 交互HBase提供查询服务
     */
    @Service("callLogService")
    public class CallLogServiceImpl implements CallLogService{
    
        @Resource(name = "PersonService")
        private PersonService personService;
    
        private Table table ;
        public CallLogServiceImpl(){
            try {
                Configuration conf = HBaseConfiguration.create();
                conf.set("hbase.zookeeper.quorum", "hadoop01:2181");
                Connection conn = ConnectionFactory.createConnection(conf);
                TableName name = TableName.valueOf("ns1:calllog");
                table = conn.getTable(name);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         *查询所有log
         */
        public List<CallLog> findAll() {
            List<CallLog> list = new ArrayList<CallLog>();
            try {
                Scan scan = new Scan();
                ResultScanner rs = table.getScanner(scan);
                Iterator<Result> it = rs.iterator();
                boolean flag = false;
                CallLog log = null ;
                String callerName = null;
                String calleeName = null;
                while(it.hasNext()){
                    log = new CallLog();
                    Result r = it.next();
                    String rowKey = Bytes.toString(r.getRow());
                    String[] row = rowKey.split(",");
                    //查询主叫的姓名
                    callerName = personService.findByPhone(row[1]).getName();
                    //查询被叫的姓名
                    calleeName = personService.findByPhone(row[4]).getName();
                    flag = row[3].equals("1")?true:false;
                    log.setCallDate(row[2]);
                    log.setCallee(row[4]);
                    log.setCaller(row[1]);
                    log.setCallerName(callerName);
                    log.setCalleeName(calleeName);
                    log.setCallDuration(row[5]);
                    log.setFlag(flag);
                    list.add(log);
                }
                return list ;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public List<CallLog> findByDate(String call, List<CallLogRange> ranges) {
            List<CallLog> list = new ArrayList<CallLog>();
            try {
                for(CallLogRange range : ranges) {
                    Scan scan = new Scan();
                    //设置起始RowKey
                    scan.setStartRow((CallLogUtil.getStartRow(call,range.getStartPoint(),100)).getBytes());
                    //设置结束RowKey
                    scan.setStopRow((CallLogUtil.getStopRow(call,range.getStartPoint(),range.getEndPoint(),100)).getBytes());
                    ResultScanner rs = table.getScanner(scan);
                    Iterator<Result> it = rs.iterator();
                    CallLog log = null;
                    boolean flag = false;
                    while (it.hasNext()) {
                        log = new CallLog();
                        Result r = it.next();
                        String rowKey = Bytes.toString(r.getRow());
                        String[] row = rowKey.split(",");
                        flag = row[3].equals("1")?true:false;
                        log.setCaller(row[1]);
                        log.setCallDate(row[2]);
                        log.setCallee(row[4]);
                        log.setCallDuration(row[5]);
                        log.setFlag(flag);
                        list.add(log);
                    }
                }
                return list;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    (3).HiveCallLogServiceImpl
    /**
     * 使用Hive对通话日志统计实现类
     */
    @Service("hiveCallLogService")
    public class HiveCallLogServiceImpl implements HiveCallLogService {
    
        @Resource(name = "PersonService")
        private PersonService personService;
    
        //hiveserver2连接串
        private static String url = "jdbc:hive2://hadoop00:10000/mydb";
        //驱动程序类
        private static String driverClass = "org.apache.hive.jdbc.HiveDriver" ;
        // 与Hive的JDBC连接
        private static Connection connection = null;
        //加载hive驱动,初始化JDBC连接
        static{
            try {
                Class.forName(driverClass);
                connection = DriverManager.getConnection(url);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 查询最近的3个通话记录,使用hive进行mr查询.
         */
        public List<CallLog> findLatestCallLog(String phoneNum){
            try {
                List<CallLog> list = new ArrayList<CallLog>();
                String sql = "select * " +
                        "from calllogs_in_hbase where caller like '%"+ phoneNum +"%' limit 3" ;
                PreparedStatement ps = connection.prepareStatement(sql);
                ResultSet rs = ps.executeQuery();
                CallLog log = null ;
                String callerName = null;
                String calleeName = null;
                String id = null;
                String[] arr = null;
                Boolean flag = null;
                while (rs.next()){
                    log = new CallLog();
                    id = rs.getString("id");
                    arr = id.split(",");
                    callerName = personService.findByPhone(arr[1]).getName();
                    calleeName = personService.findByPhone(arr[4]).getName();
                    flag = arr[3].equals("1")?true:false;
                    log.setCaller(arr[1]);
                    log.setCallee(arr[4]);
                    log.setCallDate(arr[2]);
                    log.setCallDuration(arr[5]);
                    log.setCallerName(callerName);
                    log.setCalleeName(calleeName);
                    log.setFlag(flag);
                    list.add(log);
                }
                rs.close();
                return list ;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null ;
        }
    
        /**
         * 查询每个用户的所有通话记录的个数,以及通话时长
         * @return
         */
        public List<CallMessageStat> findCallMessageStat() {
            List<CallMessageStat> callMessageStats = new ArrayList<CallMessageStat>();
            CallMessageStat callMessageStat ;
            try {
                String sql = "select caller, count(caller) as callNum,sum(callduration) as callDurations " +
                        "from calllogs_in_hbase group by caller";
                 PreparedStatement ps = connection.prepareStatement(sql);
                 ResultSet resultSet = ps.executeQuery();
                 while (resultSet.next()){
                   String caller = resultSet.getString("caller");
                   // 查询用户的姓名
                   String callerName = personService.findByPhone(caller).getName();
                   Integer callNum = resultSet.getInt("callNum");
                   Integer callDurations = resultSet.getInt("callDurations");
                   // 将统计信息封装
                   callMessageStat = new CallMessageStat();
                   callMessageStat.setCaller(caller);
                   callMessageStat.setCallerName(callerName);
                   callMessageStat.setCallNum(callNum);
                   callMessageStat.setCallDuration(callDurations);
                   // 将封装好的对象放入集合
                   callMessageStats.add(callMessageStat);
                   System.out.println(callMessageStat);
                 }
            }catch (Exception e){
                e.printStackTrace();
            }
            return callMessageStats;
        }
    }
    
    6.在web.controller包下创建类CallLogController用于接收前端页面的请求并提供服务:
    /**
     *通话记录查询服务Controller类
     */
    @Controller
    public class CallLogController {
    
        // 注入callLogService,此服务直接与HBase交互
        @Resource(name="callLogService")
        private CallLogService cs ;
    
        //注入hiveservice,此服务与hiveServer2交互
        @Resource(name="hiveCallLogService")
        private HiveCallLogService hcs ;
    
        //全表扫描,查询所有用户的通话记录信息
        @RequestMapping("/callLog/findAll")
        public String findAll(Model m){
            List<CallLog> list = cs.findAll();
            m.addAttribute("callLogs",list);
            return "callLog/callLogList" ;
        }
    
        //跳转至查询页面
        @RequestMapping("/callLog/LogByDate")
        public String LogByDatePage(){
            return  "callLog/LogByDatePage" ;
        }
    
        //按时间段查询通话记录
        @RequestMapping(value = "/callLog/findLogByDate",method = RequestMethod.POST)
        public String findLogByDate(Model m,@RequestParam("caller") String caller ,@RequestParam("startDate") String startDate
                ,@RequestParam("endDate") String endDate){
            //得到所有的时间段
            List<CallLogRange> list = CallLogUtil.getCallLogRanges(startDate,endDate);
            //按时间得到所有的通话记录
            List<CallLog> logList = cs.findByDate(caller,list);
            m.addAttribute("callLogs",logList);
            return "callLog/callLogList" ;
        }
    
        //根据号码查找最近的三次通话页面跳转
        @RequestMapping("/callLog/toFindLatestCallLog")
        public String toFindLatestCallLog(){
            return "/callLog/findLatestCallLog";
        }
    
        //根据号码查找最近的三次通话
        @RequestMapping(value = "/callLog/findLatestCallLog",method = RequestMethod.POST)
        public String findLatestCallLog(Model m,@RequestParam("caller") String caller){
            List<CallLog> list  = hcs.findLatestCallLog(caller);
            System.out.println(list.size());
            m.addAttribute("callLogs",list);
            return  "callLog/latestCallLog" ;
        }
    
        // 查找一个用户所有的用户通话记录统计
        @RequestMapping("/callLog/toFindCallMessage")
        public String toFindCallMessage(Model m){
            List<CallMessageStat> list = hcs.findCallMessageStat();
            m.addAttribute("callMessages",list);
            return "/callLog/callMessageLogList";
        }
    }
    
    7.在web目录下创建三个目录用以存放前端界面相关的文件:
    (1).创建css目录,并在该目录下创建my.css样式文件
    body{
    border : 1px solid blue ;
    margin: 0px;
    /*background-color: aquamarine;*/
    }
    
    table {
        border: 1px solid cadetblue;
        border-collapse: collapse;
        width: 400px;
        margin: 50px auto;
    }
    
    table td {
        text-align: center;
        height: 45px;
    }
    
    table tr:nth-child(even) {
        background-color: beige;
    }
    
    再创建一个js目录,里面放入jQuery的框架即可,我使用的是jquery-3.2.0.min.js,可以自行下载。
    最后创建一个callLog目录放查询页面:
    (1).callLogList.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <html>
    <head>
        <title>所有通话记录</title>
        <link rel="stylesheet" type="text/css" href="../css/my.css">
    </head>
    <body>
        <table id="t1" border="1px" class="t-1" style="width: 800px">
            <tr>
                <td>电话1</td>
                <td>姓名1</td>
                <td>电话2</td>
                <td>姓名2</td>
                <td>()</td>
                <td>通话时间</td>
                <td>通话时长</td>
            </tr>
            <c:forEach items="${callLogs}" var="log">
                <tr>
                    <td><c:out value="${log.caller}"/></td>
                    <td><c:out value="${log.callerName}"/></td>
                    <td><c:out value="${log.callee}"/></td>
                    <td><c:out value="${log.calleeName}"/></td>
                    <td>
                    <c:if test="${log.flag == true}">主叫</c:if>
                    <c:if test="${log.flag == false}">被叫</c:if>
                    </td>
                    <td><c:out value="${log.callDate}"/></td>
                    <td><c:out value="${log.callDuration}"/></td>
                </tr>
            </c:forEach>
            <tr>
                <td colspan="7" style="text-align: right">
                </td>
            </tr>
        </table>
    </body>
    </html>
    
    (2).callMessageLogList.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <html>
    <head>
        <title>所有通话记录统计</title>
        <link rel="stylesheet" type="text/css" href="../css/my.css">
    </head>
    <body>
        <table id="t1" border="1px" class="t-1" style="width: 800px">
            <tr>
                <td>号码</td>
                <td>姓名</td>
                <td>通话个数</td>
                <td>通话总时长()</td>
            </tr>
            <c:forEach items="${callMessages}" var="message">
                <tr>
                    <td><c:out value="${message.caller}"/></td>
                    <td><c:out value="${message.callerName}"/></td>
                    <td><c:out value="${message.callNum}"/></td>
                    <td><c:out value="${message.callDuration}"/></td>
                </tr>
            </c:forEach>
            <tr>
                <td colspan="4" style="text-align: right">
                </td>
            </tr>
        </table>
    </body>
    </html>
    
    (3).findLatestCallLogList.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <html>
    <head>
        <title>查询最近三次通话记录</title>
        <link rel="stylesheet" type="text/css" href="../css/my.css">
    </head>
    <body>
    <form action='' method="post">
        <table>
            <tr>
                <td>电话号码 :</td>
                <td><input type="text" name="caller"></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="查询"/>
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>
    
    (4).latestCallLog.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <html>
    <head>
        <title>最近三次通话记录</title>
        <link rel="stylesheet" type="text/css" href="../css/my.css">
    </head>
    <body>
        <c:if test="${callLogs == null}">
            无记录!
        </c:if>
        <c:if test="${callLogs != null}">
            <c:forEach items="${callLogs}" var="log">
            <table id="t1" border="1px" class="t-1" style="width: 800px">
                <tr>
                    <td>电话1</td>
                    <td><c:out value="${log.caller}" /></td>
                </tr>
                <tr>
                    <td>姓名1</td>
                    <td><c:out value="${log.callerName}" /></td>
                </tr>
                <tr>
                    <td>电话2</td>
                    <td><c:out value="${log.callee}"/></td>
                </tr>
                <tr>
                    <td>姓名2</td>
                    <td><c:out value="${log.calleeName}"/></td>
                </tr>
                <tr>
                    <td>被叫/主叫</td>
                     <c:if test="${log.flag == true}"><td>主叫</td></c:if>
                     <c:if test="${log.flag == false}"><td>被叫</td></c:if>
                </tr>
                <tr>
                    <td>时间</td>
                    <td><c:out value="${log.callDate}"/></td>
                </tr>
                <tr>
                    <td>时长</td>
                    <td><c:out value="${log.callDuration}"/></td>
                </tr>
            </table>
            </c:forEach>
        </c:if>
    </body>
    </html>
    
    (5).LogByDatePage.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <html>
    <head>
        <title>时间段查询通话记录</title>
        <link rel="stylesheet" type="text/css" href="../css/my.css">
    </head>
    <body>
    <form action='' method="post">
        <table>
            <tr>
                <td>电话号码 :</td>
                <td><input type="text" name="caller"></td>
            </tr>
            <tr>
                <td>起始时间 :</td>
                <td><input type="text" name="startDate"></td>
            </tr>
            <tr>
                <td>结束时间:</td>
                <td><input type="text" name="endDate"></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="查询"/>
                </td>
            </tr>
        </table>
    </form>
    </body>
    </html>
    
    (8).在MySQL中创建一张表用于存储用户的基本信息,有三个字段:id(int),
    name(varchar(25)),phone(varchar(15)),插入如下的数据:
    HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第11张图片
  • 2.在Hive中建立外部表关联HBase

    进入Hive的终端下,执行命令:
    create external table calllogs_in_hbase(id string, caller string,callDate string,callee string,callDuration string) STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler' WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,f1:caller,f1:callDate,f1:callee,f1:callDuration") TBLPROPERTIES ("hbase.table.name" = "ns1:calllog")
    注意表所在的库,要与hive的服务连接串中的一致,否则web服务没法读到这张表。
  • 3.启动hiveserver2服务

    进入$HIVE_HOME/bin目录下,执行命令:
    ./hive --service hiveserver2
    即可启动服务,在Linux终端执行命令:
    netstat -anop|grep 10000
    看到如下结果说明服务已经启动:
    在这里插入图片描述
    最后启动CallLogWeb模块的web服务。

四、项目结果演示

1.打印所有的通话记录信息

HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第12张图片

2.查询某个号码在某个时间段内的通话记录

HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第13张图片
HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第14张图片

3.查询某个用户最后三次的通话记录信息

HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第15张图片
HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第16张图片

4.统计所有用户的所有通话信息

HBase项目实战:HBase+Flume+Kafka+Hive+SSM实现电信大数据通话信息实时读写定位系统_第17张图片

五、总结

  • 本篇文章非常长,涵盖的知识点很多,也是本人在完成一遍之后与大家分享的,有许多细微的点比如通话日志的具体的信息字段,及设计后的RowKey具体是什么我没有写出来,碍于篇幅,如果展开来讲就会非常非常之长。总的来说本文涵盖了HBase过滤器的使用,协处理的设计与使用,RowKey的设计三大HBase的经典需求,以及使用Flume收集通话日志,Kafka缓存数据等等,以及HBase与Hive的整合,通过建立外部表关联HBase以使用hiveserver2提供web服务查询等,内容很丰富。感谢大家的阅读,如有错误请不吝赐教!
  • 更多精彩内容请查看 萧邦主的技术博客导航

你可能感兴趣的:(大数据项目实战)