flume学习(六):使用hive来分析flume收集的日志数据

问题导读

1.遇到无法转换成JSON对象的字符串时应如何处理?
2.遇到非JSON格式输入的时候应如何处理?







前面已经讲过如何将log4j的日志输出到指定的hdfs目录,我们前面的指定目录为/flume/events。
如果想用hive来分析采集来的日志,我们可以将/flume/events下面的日志数据都load到hive中的表当中去。

如果了解hive的load data原理的话,还有一种更简便的方式,可以省去load data这一步,就是直接将sink1.hdfs.path指定为hive表的目录。
下面我将详细描述具体的操作步骤。

我们还是从需求驱动来讲解,前面我们采集的数据,都是接口的访问日志数据,数据格式是JSON格式如下:

{"requestTime":1405651379758,"requestParams":{"timestamp":1405651377211,"phone":"02038824941","cardName":"测试商家名称","provinceCode":"440000","cityCode":"440106"},"requestUrl":"/reporter-api/reporter/reporter12/init.do"}

现在有一个需求,我们要统计接口的总调用量。

我第一想法就是,hive中建一张表:test             然后将hdfs.path指定为tier1.sinks.sink1.hdfs.path=hdfs://master68:8020/user/hive/warehouse/besttone.db/test

然后select  count(*) from test;   完事。
这个方案简单,粗暴,先这么干着。于是会遇到一个问题,我的日志数据时JSON格式的,需要hive来序列化和反序列化JSON格式的数据到test表的具体字段当中去。

这有点糟糕,因为hive本身没有提供JSON的SERDE,但是有提供函数来解析JSON字符串,

第一个是(UDF):
get_json_object(string json_string,string path) 从给定路径上的JSON字符串中抽取出JSON对象,并返回这个对象的JSON字符串形式,如果输入的JSON字符串是非法的,则返回NULL。

第二个是表生成函数(UDTF):json_tuple(string jsonstr,p1,p2,...,pn) 本函数可以接受多个标签名称,对输入的JSON字符串进行处理,这个和get_json_object这个UDF类似,不过更高效,其通过一次调用就可以获得多个键值,例:select b.* from test_json a lateral view json_tuple(a.id,'id','name') b as f1,f2;通过lateral view行转列。

最理想的方式就是能有一种JSON SERDE,只要我们LOAD完数据,就直接可以select * from test,而不是select get_json_object这种方式来获取,N个字段就要解析N次,效率太低了。

好在cloudrea wiki里提供了一个json serde类(这个类没有在发行的hive的jar包中),于是我把它搬来了,如下:
  1. package com.besttone.hive.serde;

  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.Properties;

  8. import org.apache.hadoop.conf.Configuration;
  9. import org.apache.hadoop.hive.serde.serdeConstants;
  10. import org.apache.hadoop.hive.serde2.SerDe;
  11. import org.apache.hadoop.hive.serde2.SerDeException;
  12. import org.apache.hadoop.hive.serde2.SerDeStats;
  13. import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector;
  14. import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector;
  15. import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
  16. import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
  17. import org.apache.hadoop.hive.serde2.objectinspector.StructField;
  18. import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
  19. import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo;
  20. import org.apache.hadoop.hive.serde2.typeinfo.MapTypeInfo;
  21. import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo;
  22. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
  23. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
  24. import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils;
  25. import org.apache.hadoop.io.Text;
  26. import org.apache.hadoop.io.Writable;
  27. import org.codehaus.jackson.map.ObjectMapper;

  28. /**
  29. * This SerDe can be used for processing JSON data in Hive. It supports
  30. * arbitrary JSON data, and can handle all Hive types except for UNION. However,
  31. * the JSON data is expected to be a series of discrete records, rather than a
  32. * JSON array of objects.

  33. * The Hive table is expected to contain columns with names corresponding to
  34. * fields in the JSON data, but it is not necessary for every JSON field to have
  35. * a corresponding Hive column. Those JSON fields will be ignored during
  36. * queries.

  37. * Example:

  38. * { "a": 1, "b": [ "str1", "str2" ], "c": { "field1": "val1" } }

  39. * Could correspond to a table:

  40. * CREATE TABLE foo (a INT, b ARRAY<STRING>, c STRUCT<field1:STRING>);

  41. * JSON objects can also interpreted as a Hive MAP type, so long as the keys and
  42. * values in the JSON object are all of the appropriate types. For example, in
  43. * the JSON above, another valid table declaraction would be:

  44. * CREATE TABLE foo (a INT, b ARRAY<STRING>, c MAP<STRING,STRING>);

  45. * Only STRING keys are supported for Hive MAPs.
  46. */
  47. public class JSONSerDe implements SerDe {

  48.         private StructTypeInfo rowTypeInfo;
  49.         private ObjectInspector rowOI;
  50.         private List<String> colNames;
  51.         private List<Object> row = new ArrayList<Object>();

  52.         //遇到非JSON格式输入的时候的处理。
  53.         private boolean ignoreInvalidInput;

  54.         /**
  55.          * An initialization function used to gather information about the table.
  56.          * Typically, a SerDe implementation will be interested in the list of
  57.          * column names and their types. That information will be used to help
  58.          * perform actual serialization and deserialization of data.
  59.          */
  60.         @Override
  61.         public void initialize(Configuration conf, Properties tbl)
  62.                         throws SerDeException {
  63.                 // 遇到无法转换成JSON对象的字符串时,是否忽略,默认不忽略,抛出异常,设置为true将跳过异常。
  64.                 ignoreInvalidInput = Boolean.valueOf(tbl.getProperty(
  65.                                 "input.invalid.ignore", "false"));

  66.                 // Get a list of the table's column names.

  67.                 String colNamesStr = tbl.getProperty(serdeConstants.LIST_COLUMNS);
  68.                 colNames = Arrays.asList(colNamesStr.split(","));

  69.                 // Get a list of TypeInfos for the columns. This list lines up with
  70.                 // the list of column names.
  71.                 String colTypesStr = tbl.getProperty(serdeConstants.LIST_COLUMN_TYPES);
  72.                 List<TypeInfo> colTypes = TypeInfoUtils
  73.                                 .getTypeInfosFromTypeString(colTypesStr);

  74.                 rowTypeInfo = (StructTypeInfo) TypeInfoFactory.getStructTypeInfo(
  75.                                 colNames, colTypes);
  76.                 rowOI = TypeInfoUtils
  77.                                 .getStandardJavaObjectInspectorFromTypeInfo(rowTypeInfo);
  78.         }

  79.         /**
  80.          * This method does the work of deserializing a record into Java objects
  81.          * that Hive can work with via the ObjectInspector interface. For this
  82.          * SerDe, the blob that is passed in is a JSON string, and the Jackson JSON
  83.          * parser is being used to translate the string into Java objects.
  84.          * 
  85.          * The JSON deserialization works by taking the column names in the Hive
  86.          * table, and looking up those fields in the parsed JSON object. If the
  87.          * value of the field is not a primitive, the object is parsed further.
  88.          */
  89.         @Override
  90.         public Object deserialize(Writable blob) throws SerDeException {
  91.                 Map<?, ?> root = null;
  92.                 row.clear();
  93.                 try {
  94.                         ObjectMapper mapper = new ObjectMapper();
  95.                         // This is really a Map<String, Object>. For more information about
  96.                         // how
  97.                         // Jackson parses JSON in this example, see
  98.                         // http://wiki.fasterxml.com/JacksonDataBinding
  99.                         root = mapper.readValue(blob.toString(), Map.class);
  100.                 } catch (Exception e) {
  101.                         // 如果为true,不抛出异常,忽略该行数据
  102.                         if (!ignoreInvalidInput)
  103.                                 throw new SerDeException(e);
  104.                         else {
  105.                                 return null;
  106.                         }
  107.                         
  108.                 }

  109.                 // Lowercase the keys as expected by hive
  110.                 Map<String, Object> lowerRoot = new HashMap();
  111.                 for (Map.Entry entry : root.entrySet()) {
  112.                         lowerRoot.put(((String) entry.getKey()).toLowerCase(),
  113.                                         entry.getValue());
  114.                 }
  115.                 root = lowerRoot;

  116.                 Object value = null;
  117.                 for (String fieldName : rowTypeInfo.getAllStructFieldNames()) {
  118.                         try {
  119.                                 TypeInfo fieldTypeInfo = rowTypeInfo
  120.                                                 .getStructFieldTypeInfo(fieldName);
  121.                                 value = parseField(root.get(fieldName), fieldTypeInfo);
  122.                         } catch (Exception e) {
  123.                                 value = null;
  124.                         }
  125.                         row.add(value);
  126.                 }
  127.                 return row;
  128.         }

  129.         /**
  130.          * Parses a JSON object according to the Hive column's type.
  131.          * 
  132.          * @param field
  133.          *            - The JSON object to parse
  134.          * @param fieldTypeInfo
  135.          *            - Metadata about the Hive column
  136.          * @return - The parsed value of the field
  137.          */
  138.         private Object parseField(Object field, TypeInfo fieldTypeInfo) {
  139.                 switch (fieldTypeInfo.getCategory()) {
  140.                 case PRIMITIVE:
  141.                         // Jackson will return the right thing in this case, so just return
  142.                         // the object
  143.                         if (field instanceof String) {
  144.                                 field = field.toString().replaceAll("\n", "\\\\n");
  145.                         }
  146.                         return field;
  147.                 case LIST:
  148.                         return parseList(field, (ListTypeInfo) fieldTypeInfo);
  149.                 case MAP:
  150.                         return parseMap(field, (MapTypeInfo) fieldTypeInfo);
  151.                 case STRUCT:
  152.                         return parseStruct(field, (StructTypeInfo) fieldTypeInfo);
  153.                 case UNION:
  154.                         // Unsupported by JSON
  155.                 default:
  156.                         return null;
  157.                 }
  158.         }

  159.         /**
  160.          * Parses a JSON object and its fields. The Hive metadata is used to
  161.          * determine how to parse the object fields.
  162.          * 
  163.          * @param field
  164.          *            - The JSON object to parse
  165.          * @param fieldTypeInfo
  166.          *            - Metadata about the Hive column
  167.          * @return - A map representing the object and its fields
  168.          */
  169.         private Object parseStruct(Object field, StructTypeInfo fieldTypeInfo) {
  170.                 Map<Object, Object> map = (Map<Object, Object>) field;
  171.                 ArrayList<TypeInfo> structTypes = fieldTypeInfo
  172.                                 .getAllStructFieldTypeInfos();
  173.                 ArrayList<String> structNames = fieldTypeInfo.getAllStructFieldNames();

  174.                 List<Object> structRow = new ArrayList<Object>(structTypes.size());
  175.                 for (int i = 0; i < structNames.size(); i++) {
  176.                         structRow.add(parseField(map.get(structNames.get(i)),
  177.                                         structTypes.get(i)));
  178.                 }
  179.                 return structRow;
  180.         }

  181.         /**
  182.          * Parse a JSON list and its elements. This uses the Hive metadata for the
  183.          * list elements to determine how to parse the elements.
  184.          * 
  185.          * @param field
  186.          *            - The JSON list to parse
  187.          * @param fieldTypeInfo
  188.          *            - Metadata about the Hive column
  189.          * @return - A list of the parsed elements
  190.          */
  191.         private Object parseList(Object field, ListTypeInfo fieldTypeInfo) {
  192.                 ArrayList<Object> list = (ArrayList<Object>) field;
  193.                 TypeInfo elemTypeInfo = fieldTypeInfo.getListElementTypeInfo();

  194.                 for (int i = 0; i < list.size(); i++) {
  195.                         list.set(i, parseField(list.get(i), elemTypeInfo));
  196.                 }

  197.                 return list.toArray();
  198.         }

  199.         /**
  200.          * Parse a JSON object as a map. This uses the Hive metadata for the map
  201.          * values to determine how to parse the values. The map is assumed to have a
  202.          * string for a key.
  203.          * 
  204.          * @param field
  205.          *            - The JSON list to parse
  206.          * @param fieldTypeInfo
  207.          *            - Metadata about the Hive column
  208.          * @return
  209.          */
  210.         private Object parseMap(Object field, MapTypeInfo fieldTypeInfo) {
  211.                 Map<Object, Object> map = (Map<Object, Object>) field;
  212.                 TypeInfo valueTypeInfo = fieldTypeInfo.getMapValueTypeInfo();

  213.                 for (Map.Entry<Object, Object> entry : map.entrySet()) {
  214.                         map.put(entry.getKey(), parseField(entry.getValue(), valueTypeInfo));
  215.                 }
  216.                 return map;
  217.         }

  218.         /**
  219.          * Return an ObjectInspector for the row of data
  220.          */
  221.         @Override
  222.         public ObjectInspector getObjectInspector() throws SerDeException {
  223.                 return rowOI;
  224.         }

  225.         /**
  226.          * Unimplemented
  227.          */
  228.         @Override
  229.         public SerDeStats getSerDeStats() {
  230.                 return null;
  231.         }

  232.         /**
  233.          * JSON is just a textual representation, so our serialized class is just
  234.          * Text.
  235.          */
  236.         @Override
  237.         public Class<? extends Writable> getSerializedClass() {
  238.                 return Text.class;
  239.         }

  240.         /**
  241.          * This method takes an object representing a row of data from Hive, and
  242.          * uses the ObjectInspector to get the data for each column and serialize
  243.          * it. This implementation deparses the row into an object that Jackson can
  244.          * easily serialize into a JSON blob.
  245.          */
  246.         @Override
  247.         public Writable serialize(Object obj, ObjectInspector oi)
  248.                         throws SerDeException {
  249.                 Object deparsedObj = deparseRow(obj, oi);
  250.                 ObjectMapper mapper = new ObjectMapper();
  251.                 try {
  252.                         // Let Jackson do the work of serializing the object
  253.                         return new Text(mapper.writeValueAsString(deparsedObj));
  254.                 } catch (Exception e) {
  255.                         throw new SerDeException(e);
  256.                 }
  257.         }

  258.         /**
  259.          * Deparse a Hive object into a Jackson-serializable object. This uses the
  260.          * ObjectInspector to extract the column data.
  261.          * 
  262.          * @param obj
  263.          *            - Hive object to deparse
  264.          * @param oi
  265.          *            - ObjectInspector for the object
  266.          * @return - A deparsed object
  267.          */
  268.         private Object deparseObject(Object obj, ObjectInspector oi) {
  269.                 switch (oi.getCategory()) {
  270.                 case LIST:
  271.                         return deparseList(obj, (ListObjectInspector) oi);
  272.                 case MAP:
  273.                         return deparseMap(obj, (MapObjectInspector) oi);
  274.                 case PRIMITIVE:
  275.                         return deparsePrimitive(obj, (PrimitiveObjectInspector) oi);
  276.                 case STRUCT:
  277.                         return deparseStruct(obj, (StructObjectInspector) oi, false);
  278.                 case UNION:
  279.                         // Unsupported by JSON
  280.                 default:
  281.                         return null;
  282.                 }
  283.         }

  284.         /**
  285.          * Deparses a row of data. We have to treat this one differently from other
  286.          * structs, because the field names for the root object do not match the
  287.          * column names for the Hive table.
  288.          * 
  289.          * @param obj
  290.          *            - Object representing the top-level row
  291.          * @param structOI
  292.          *            - ObjectInspector for the row
  293.          * @return - A deparsed row of data
  294.          */
  295.         private Object deparseRow(Object obj, ObjectInspector structOI) {
  296.                 return deparseStruct(obj, (StructObjectInspector) structOI, true);
  297.         }

  298.         /**
  299.          * Deparses struct data into a serializable JSON object.
  300.          * 
  301.          * @param obj
  302.          *            - Hive struct data
  303.          * @param structOI
  304.          *            - ObjectInspector for the struct
  305.          * @param isRow
  306.          *            - Whether or not this struct represents a top-level row
  307.          * @return - A deparsed struct
  308.          */
  309.         private Object deparseStruct(Object obj, StructObjectInspector structOI,
  310.                         boolean isRow) {
  311.                 Map<Object, Object> struct = new HashMap<Object, Object>();
  312.                 List<? extends StructField> fields = structOI.getAllStructFieldRefs();
  313.                 for (int i = 0; i < fields.size(); i++) {
  314.                         StructField field = fields.get(i);
  315.                         // The top-level row object is treated slightly differently from
  316.                         // other
  317.                         // structs, because the field names for the row do not correctly
  318.                         // reflect
  319.                         // the Hive column names. For lower-level structs, we can get the
  320.                         // field
  321.                         // name from the associated StructField object.
  322.                         String fieldName = isRow ? colNames.get(i) : field.getFieldName();
  323.                         ObjectInspector fieldOI = field.getFieldObjectInspector();
  324.                         Object fieldObj = structOI.getStructFieldData(obj, field);
  325.                         struct.put(fieldName, deparseObject(fieldObj, fieldOI));
  326.                 }
  327.                 return struct;
  328.         }

  329.         /**
  330.          * Deparses a primitive type.
  331.          * 
  332.          * @param obj
  333.          *            - Hive object to deparse
  334.          * @param oi
  335.          *            - ObjectInspector for the object
  336.          * @return - A deparsed object
  337.          */
  338.         private Object deparsePrimitive(Object obj, PrimitiveObjectInspector primOI) {
  339.                 return primOI.getPrimitiveJavaObject(obj);
  340.         }

  341.         private Object deparseMap(Object obj, MapObjectInspector mapOI) {
  342.                 Map<Object, Object> map = new HashMap<Object, Object>();
  343.                 ObjectInspector mapValOI = mapOI.getMapValueObjectInspector();
  344.                 Map<?, ?> fields = mapOI.getMap(obj);
  345.                 for (Map.Entry<?, ?> field : fields.entrySet()) {
  346.                         Object fieldName = field.getKey();
  347.                         Object fieldObj = field.getValue();
  348.                         map.put(fieldName, deparseObject(fieldObj, mapValOI));
  349.                 }
  350.                 return map;
  351.         }

  352.         /**
  353.          * Deparses a list and its elements.
  354.          * 
  355.          * @param obj
  356.          *            - Hive object to deparse
  357.          * @param oi
  358.          *            - ObjectInspector for the object
  359.          * @return - A deparsed object
  360.          */
  361.         private Object deparseList(Object obj, ListObjectInspector listOI) {
  362.                 List<Object> list = new ArrayList<Object>();
  363.                 List<?> field = listOI.getList(obj);
  364.                 ObjectInspector elemOI = listOI.getListElementObjectInspector();
  365.                 for (Object elem : field) {
  366.                         list.add(deparseObject(elem, elemOI));
  367.                 }
  368.                 return list;
  369.         }
  370. }
复制代码
我稍微修改了一点东西,多加了一个参数input.invalid.ignore,对应的变量为:


//遇到非JSON格式输入的时候的处理。
private boolean ignoreInvalidInput;

在deserialize方法中原来是如果传入的是非JSON格式字符串的话,直接抛出了SerDeException,我加了一个参数来控制它是否抛出异常,在initialize方法中初始化这个变量(默认为false):

// 遇到无法转换成JSON对象的字符串时,是否忽略,默认不忽略,抛出异常,设置为true将跳过异常。
ignoreInvalidInput = Boolean.valueOf(tbl.getProperty(
"input.invalid.ignore", "false"));

好的,现在将这个类打成JAR包: JSONSerDe.jar,放在hive_home的auxlib目录下(我的是/etc/hive/auxlib),然后修改hive-env.sh,添加HIVE_AUX_JARS_PATH=/etc/hive/auxlib/JSONSerDe.jar,这样每次运行hive客户端的时候都会将这个jar包添加到classpath,否则在设置SERDE的时候会报找不到类。

现在我们在HIVE中创建一张表用来存放日志数据:
  1. create table test(
  2. requestTime BIGINT,
  3. requestParams STRUCT<timestamp:BIGINT,phone:STRING,cardName:STRING,provinceCode:STRING,cityCode:STRING>,        
  4. requestUrl STRING)
  5. row format serde "com.besttone.hive.serde.JSONSerDe" 
  6. WITH SERDEPROPERTIES(
  7. "input.invalid.ignore"="true",
  8. "requestTime"="$.requestTime",
  9. "requestParams.timestamp"="$.requestParams.timestamp",
  10. "requestParams.phone"="$.requestParams.phone",
  11. "requestParams.cardName"="$.requestParams.cardName",
  12. "requestParams.provinceCode"="$.requestParams.provinceCode",
  13. "requestParams.cityCode"="$.requestParams.cityCode",
  14. "requestUrl"="$.requestUrl");
复制代码
这个表结构就是按照日志格式设计的,还记得前面说过的日志数据如下:

{"requestTime":1405651379758,"requestParams":{"timestamp":1405651377211,"phone":"02038824941","cardName":"测试商家名称","provinceCode":"440000","cityCode":"440106"},"requestUrl":"/reporter-api/reporter/reporter12/init.do"}

我使用了一个STRUCT类型来保存requestParams的值,row format我们用的是自定义的json serde:com.besttone.hive.serde.JSONSerDe,SERDEPROPERTIES中,除了设置JSON对象的映射关系外,我还设置了一个自定义的参数:"input.invalid.ignore"="true",忽略掉所有非JSON格式的输入行。

这里不是真正意义的忽略,只是非法行的每个输出字段都为NULL了,要在结果集上忽略,必须这样写:select * from test where requestUrl is not null;
OK表建好了,现在就差数据了,我们启动flumedemo的WriteLog,往hive表test目录下面输出一些日志数据,然后在进入hive客户端,select * from test;所以字段都正确的解析,大功告成。

flume.conf如下:
  1. tier1.sources=source1
  2. tier1.channels=channel1
  3. tier1.sinks=sink1

  4. tier1.sources.source1.type=avro
  5. tier1.sources.source1.bind=0.0.0.0
  6. tier1.sources.source1.port=44444
  7. tier1.sources.source1.channels=channel1

  8. tier1.sources.source1.interceptors=i1 i2
  9. tier1.sources.source1.interceptors.i1.type=regex_filter
  10. tier1.sources.source1.interceptors.i1.regex=\\{.*\\}
  11. tier1.sources.source1.interceptors.i2.type=timestamp

  12. tier1.channels.channel1.type=memory
  13. tier1.channels.channel1.capacity=10000
  14. tier1.channels.channel1.transactionCapacity=1000
  15. tier1.channels.channel1.keep-alive=30

  16. tier1.sinks.sink1.type=hdfs
  17. tier1.sinks.sink1.channel=channel1
  18. tier1.sinks.sink1.hdfs.path=hdfs://master68:8020/user/hive/warehouse/besttone.db/test
  19. tier1.sinks.sink1.hdfs.fileType=DataStream
  20. tier1.sinks.sink1.hdfs.writeFormat=Text
  21. tier1.sinks.sink1.hdfs.rollInterval=0
  22. tier1.sinks.sink1.hdfs.rollSize=10240
  23. tier1.sinks.sink1.hdfs.rollCount=0
  24. tier1.sinks.sink1.hdfs.idleTimeout=60

复制代码
besttone.db是我在hive中创建的数据库,了解hive的应该理解没多大问题。

OK,到这篇文章为止,整个从LOG4J生产日志,到flume收集日志,再到用hive离线分析日志,一整套流水线都讲解完了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值