搜狗搜索日志传输与分析

日志在四台不同主机上模拟实际生产环境,hive并不适合在线服务,因为调用mapreduce任务比较慢。

出现的坑

1.如何把gbk转换成utf-8,解决方法是在flume配置文件中inputcharset=gbk,outputcharset=utf-8

2.kafka partition :为了均衡数据的压力,会将一个topic分为多个partition,分给不同的机器处理。consumer里面可以多线程消费topic,线程同时运行的最大数量与partition数量相同

3.识别新文件的产生:flume的source识别 SpoolDirectorySource

4.流程的自动化:文件的产生,flume读取新文件,flume到kafka(kafkasink),kafka到hdfs,hdfs中的文件load到hive中,以上则几个过程都是自动化执行的。

5.flume 的event分为header和body,将文件名放到header中,保留文件名的日期信息。

6.在kafka中,一个topic的数据能否均衡传输到不同的partition是不一定的。

a.传到kafka的消息没有key的时候,kafaka会自己算出一个key,而这个key的更新时间默认是10分钟,所以在数据量比较小的时候,数据可能都传到同一个partition中去了,没有达到想要的负载均衡的效果。

b.在有key的时候,partitioner就会根据key来决定分到哪个partition.

hdfs中的数据自动化load数据到hive中,hive有分区,看最后一部分.

flume-->kafka:三种信息 topic key body,key就是文件名信息,在flume中配置的,可以保证kafka的partition负载均衡

 

1.项目框架

  实现了flume->Kafka->HDFS->Hive,后面还没有实现。

2.用户搜索日志的数据结构

访问时间\t用户ID\t[查询词]\t该URL在返回结果中的排名\t用户点击的顺序号\t用户点击的URL

3.功能

      topN搜索词查询

  日/周/月topN搜索词详情查询

  搜索量topN的用户个人搜索词查询

4.模块承担角色及开发功能

   Flume:日志收集,从源端机器磁盘文件,将数据传入Kafka。开发功能:Flume到kafka数据录入

   Kafka:日志传输及加载,将数据加载到HDFS。开发功能:Kafka HDFS consumer。

   HDFS:数据存储,存储Kafka传入的数据。

   Hive:日/周/月topN搜索词查询及搜索词详情查询,开发功能:模块查询需求,制定partition及bucket定义方式及源数据存储方法,保证查询高性能。

5.表结构及查询语句

describe sogouqueryfish;
time varchar(8)
userid varchar(30)
query string
pagerank int
clickrank int
site string

搜索量topN的用户个人搜索词查询
– select sf.userid, sf.query, count(sf.query) sco, tt.co from (select userid, count(3) co from sogouqueryfish group by userid order by co desc) as tt join sogouqueryfish as sf on tt.userid = sf.userid group by sf.userid,sf.query,tt.co order by tt.co desc,sco desc limit 10;

选出查询数量最多的查询词及次数 top10

select query,count(query) from sogouqueryfish group by query order by count(query) desc limit 10;

6.产生日志文件脚本

每隔五秒钟产生一个文件,给每个文件加上一个时间戳,产生的文件放到generated目录。

 

7.文件到Kafka

7.1Flume配置文件

 7.2读取自动产生的文件配置项SpoolDirectorySource

 

 7.3 编解码问题:日志文件中含有中文字符,如果不处理的话会出现乱码
  – 读入:InputCharset
    • ReliableSpoolingFileEventReader

  

  • SpoolDirectorySourceConfigurationConstants
    – public static final String INPUT_CHARSET = "inputCharset"; 
    – public static final String DEFAULT_INPUT_CHARSET = "UTF-8";

源代码给出了设置编码的参数是"inputCharset",并且默认的编码方式是"UTF-8",所以我们在Flume的配置文件中手动设置输入编码为GBK

   

输出部分

 – 输出
• 需要:utf-8

flume配置项设置输出为UTF-8

7.4文件名转为目录

 文件名是类似于temp20161221.txt的,要求做到文件名信息可以传递到Kafka并由其依据名称生成目标目录2016/12/21这种按年月日分级的目录形式。

解决办法是利用Flume的event中的header传递文件名信息

• SpoolDirectorySource源代码

• ReliableSpoolingFileEventReader

对应flume中的配置为

– Flume event中的header将形如
• key=/a/b/c
– fileHeaderKey默认值为file,为何改为key
• 与下面将提到的Kafka sink有关

8.Kafka到HDFS

主要分为三步:

  • 主循环中提取数据
  • 依据消息key将数据存入相应位置,文件传输完毕后重命名为.Done结尾,这里在转换文件名为目录的时候并不是像前面所说的key=/a/b/c,而是字符串处理成为了/a/b/c形式,key好像还是abc的形式,看代码就知道了。
  • 独立线程完成文件刷写

完整代码:

  1 package cn.chinahadoop.kafka.hadoop_consumer;
  2 
  3 import java.io.IOException;
  4 import java.util.concurrent.ScheduledExecutorService;
  5 import java.util.concurrent.Executors;
  6 import org.apache.hadoop.fs.FSDataOutputStream;
  7 import org.apache.hadoop.fs.FileSystem;
  8 import org.apache.hadoop.fs.Path;
  9 
 10 import kafka.consumer.ConsumerIterator;
 11 import kafka.consumer.KafkaStream;
 12 
 13 public class SubTaskConsumer implements Runnable {
 14     private KafkaStream m_stream;
 15     private int m_threadNumber;
 16     String m_destDir = "/user/wwx/kafka2hdfs";
 17     String outputFileName = null;
 18     FSDataOutputStream dos = null;
 19     FileSystem hdfs;
 20     public SubTaskConsumer(KafkaStream a_stream, int a_threadNumber,FileSystem fs) {
 21         m_threadNumber = a_threadNumber;
 22         m_stream = a_stream;
 23         hdfs = fs;
 24         try {
 25             init();
 26         } catch (IOException e) {
 27             e.printStackTrace();
 28         }
 29         System.out.println("come in 11111111");        
 30     }
 31     class refreshThread implements Runnable {
 32         @Override
 33         public void run() {
 34             //refresh the contents to hdfs
 35             try {
 36                 synchronized (this) {
 37                     if (dos != null) {
 38                         dos.hsync();
 39                     }
 40                 }
 41             } catch (IOException e) {
 42                 e.printStackTrace();
 43             }
 44         }
 45     }
 46     private void init() throws IOException{
 47         ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
 48         refreshThread refresher = new refreshThread();
 49         scheduler.scheduleAtFixedRate(refresher, 0, 1000, TimeUnit.MILLISECONDS);
 50     }
 51     private void dumpToFile(String dateString, byte[] message) {
 52         String year = dataString.substring(0, 4);
 53         String month = dateString.substring(4, 6);
 54         String day = dateString.substring(6);
 55         String pathString = m_destDir + "/" + year + "/" + month + "/" + day + "/kafkadata" + m_threadNumber;
 56         Path path = new Path(pathString);
 57         if (outputFileName == null || dos == null || !pathString.equals(outputFileName)) {
 58             if (dos != null) {
 59                 synchronized (this) {
 60                     dos.flush();
 61                     dos.close();
 62                 }
 63                 //mark the old one as done
 64                 hdfs.rename(new Path(outputFileName), new Path(outputFileName + "." + System.currentTimeMillis() + ".Done"));
 65             }
 66             if (hdfs.exists(path)) {
 67                 synchronized (this) {
 68                     dos = hdfs.append(path);
 69                 }
 70                 System.out.println("Open the output in the append mode: " + path);
 71             } else {
 72                 synchronized (this) {
 73                     dos = hdfs.create(path);
 74                 }
 75                 System.out.println("create the new output file: " + path);
 76             }
 77             outputFileName = pathString;
 78         }
 79         dos.write(message);
 80         dos.write("\n".getBytes());
 81     }
 82     public void run() {
 83         //Path path = new Path("/user/shen/tt/test.txt");
 84         // writing
 85         try {
 86             //FSDataOutputStream dos = hdfs.create(path);
 87             ConsumerIterator<byte[], byte[]> it = m_stream.iterator();
 88             while (it.hasNext()){
 89                 byte[] message = it.next().message();
 90                 byte[] decodedKeyBytes = mam.key();
 91                 String dateString = "20160101"; //default value
 92                 if (decodedKeyBytes != null) {
 93                     String sourceFileName = new String(decodedKeyBytes);
 94                     int lastDotIndex = sourceFileName.lastIndexOf(".");
 95                     dateString = sourceFileName.substring(lastDotIndex + 1);
 96                 }
 97                 dumpToFile(dateString, message);                
 98             }            
 99         } catch (IOException e) {
100             // TODO Auto-generated catch block
101             e.printStackTrace();
102         } finally {
103             System.out.println("Shutting down Thread: " + m_threadNumber);
104             try {
105                 if (dos != null) {
106                     dos.flush();
107                     dos.close();
108                 }
109                 hdfs.close();
110             } catch (IOException e) {
111                 e.printStackTrace();
112             }
113         }            
114     }
115 }
View Code

 

9.HDFS到Hive

这里采用了自动化的脚本来执行数据的load,新数据会自动load到hive中,hive中进行了分区 year,month,day的分区.

 

posted @ 2016-12-21 10:30  fisherinbox  阅读(471)  评论(0编辑  收藏  举报