有的時候希望通過Flume將讀取的檔案再細分儲存,比如講source的資料按照業務型別分開儲存,具體一點比如類似:將source中web、wap、media等的內容分開儲存;比如丟棄或修改一些資料。這時可以考慮使用攔截器Interceptor。
flume通過攔截器實現修改和丟棄事件的功能。攔截器通過定義類繼承org.apache.flume.interceptor.Interceptor介面來實現。使用者可以通過該節點定義規則來修改或者丟棄事件。Flume支援鏈式攔截,通過在配置中指定構建的攔截器類的名稱。在source的配置中,攔截器被指定為一個以空格為間隔的列表。攔截器按照指定的順序呼叫。一個攔截器返回的事件列表被傳遞到鏈中的下一個攔截器。當一個攔截器要丟棄某些事件時,攔截器只需要在返回事件列表時不返回該事件即可。若攔截器要丟棄所有事件,則其返回一個空的事件列表即可。
先解釋一下一個重要物件Event:event是flume傳輸的最小物件,從source獲取資料後會先封裝成event,然後將event傳送到channel,sink從channel拿event消費。event由頭(Map<String, String> headers)和身體(body)兩部分組成:Headers部分是一個map,body部分可以是String或者byte[]等。其中body部分是真正存放資料的地方,headers部分用於本節所講的interceptor。
Flume-NG自帶攔截器有多種:
1、HostInterceptor:使用IP或者hostname攔截;
2、TimestampInterceptor:使用時間戳攔截;
3、RegexExtractorInterceptor:該攔截器提取正規表示式匹配組,通過使用指定的正規表示式並追加匹配組作為事件的header。它還支援可插拔的serializers用於在新增匹配組作為事件header之前格式化匹配組;
4、RegexFilteringInterceptor:該攔截器會選擇性地過濾事件。通過以文字的方式解析事件主體,用配置好的規則表示式來匹配文字。提供的正規表示式可以用於包含事件或排除事件;這個和上面的那個區別是這個會按照正規表示式選擇性的讓event通過,上面那個是提取event.body符合正則的內容作為headers的value。
5、StaticInterceptor:可以自定義event的header的value。
這些類都在org.apache.flume.interceptor包下。
這些interceptor都比較簡單我們選取HostInterceptor來講解interceptor的原理,以及如何自己定製interceptor。
這些interceptor都實現了org.apache.flume.interceptor.Interceptor介面,該介面有四個方法以及一個內部介面:
1、public void initialize()執行前的初始化,一般不需要實現(上面的幾個都沒實現這個方法);
2、public Event intercept(Event event)處理單個event;
3、public List<Event> intercept(List<Event> events)批量處理event,實際上市迴圈呼叫上面的2;
4、public void close()可以做一些清理工作,上面幾個也都沒有實現這個方法;
5、 public interface Builder extends Configurable 構建Interceptor物件,外部使用這個Builder來獲取Interceptor物件。
如果要自己定製,必須要完成上面的2,3,5。
下面,我們來看看org.apache.flume.interceptor.HostInterceptor,其全部程式碼如下:
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.flume.interceptor; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.Map; import org.apache.flume.Context; import org.apache.flume.Event; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.flume.interceptor.HostInterceptor.Constants.*; /** * Simple Interceptor class that sets the host name or IP on all events * that are intercepted.<p> * The host header is named <code>host</code> and its format is either the FQDN * or IP of the host on which this interceptor is run. * * * Properties:<p> * * preserveExisting: Whether to preserve an existing value for 'host' * (default is false)<p> * * useIP: Whether to use IP address or fully-qualified hostname for 'host' * header value (default is true)<p> * * hostHeader: Specify the key to be used in the event header map for the * host name. (default is "host") <p> * * Sample config:<p> * * <code> * agent.sources.r1.channels = c1<p> * agent.sources.r1.type = SEQ<p> * agent.sources.r1.interceptors = i1<p> * agent.sources.r1.interceptors.i1.type = host<p> * agent.sources.r1.interceptors.i1.preserveExisting = true<p> * agent.sources.r1.interceptors.i1.useIP = false<p> * agent.sources.r1.interceptors.i1.hostHeader = hostname<p> * </code> * */ public class HostInterceptor implements Interceptor { private static final Logger logger = LoggerFactory .getLogger(HostInterceptor.class); private final boolean preserveExisting; private final String header; private String host = null; /** * Only {@link HostInterceptor.Builder} can build me */ private HostInterceptor(boolean preserveExisting, boolean useIP, String header) { this.preserveExisting = preserveExisting; this.header = header; InetAddress addr; try { addr = InetAddress.getLocalHost(); if (useIP) { host = addr.getHostAddress(); } else { host = addr.getCanonicalHostName(); } } catch (UnknownHostException e) { logger.warn("Could not get local host address. Exception follows.", e); } } @Override public void initialize() { // no-op } /** * Modifies events in-place. */ @Override public Event intercept(Event event) { Map<String, String> headers = event.getHeaders(); if (preserveExisting && headers.containsKey(header)) { return event; } if(host != null) { headers.put(header, host); } return event; } /** * Delegates to {@link #intercept(Event)} in a loop. * @param events * @return */ @Override public List<Event> intercept(List<Event> events) { for (Event event : events) { intercept(event); } return events; } @Override public void close() { // no-op } /** * Builder which builds new instances of the HostInterceptor. */ public static class Builder implements Interceptor.Builder { private boolean preserveExisting = PRESERVE_DFLT; private boolean useIP = USE_IP_DFLT; private String header = HOST; @Override public Interceptor build() { return new HostInterceptor(preserveExisting, useIP, header); } @Override public void configure(Context context) { preserveExisting = context.getBoolean(PRESERVE, PRESERVE_DFLT); useIP = context.getBoolean(USE_IP, USE_IP_DFLT); header = context.getString(HOST_HEADER, HOST); } } public static class Constants { public static String HOST = "host"; public static String PRESERVE = "preserveExisting"; public static boolean PRESERVE_DFLT = false; public static String USE_IP = "useIP"; public static boolean USE_IP_DFLT = true; public static String HOST_HEADER = "hostHeader"; } }
Constants類是引數類及預設的一些引數:
Builder類是構造HostInterceptor物件的,它會首先通過configure(Context context)方法獲取配置檔案中interceptor的引數,然後方法build()用來返回一個HostInterceptor物件:
1、preserveExisting表示如果event的header中包含有本interceptor指定的header,是否要保留這個header,true則保留;
2、useIP表示是否使用本機IP地址作為header的value,true則使用IP,預設是true;
3、header是event的headers的key,預設是host。
HostInterceptor:
1、建構函式除了賦值外,還有就是根據useIP獲取IP或者hostname;
2、intercept(Event event)方法是設定event的header的地方,首先是獲取headers物件,然後如果同時滿足preserveExisting==true並且headers.containsKey(header)就直接返回event,否則設定headers:headers.put(header, host)。
3、intercept(List<Event> events)方法是迴圈呼叫上述2的方法。
顯然其他幾個Interceptor也就類似這樣。在配置檔案中配置source的interceptor時,如果是自己定製的interceptor,則需要對type引數賦值:完整類名+¥Builder,比如com.MyInterceptor$Builder即可。
這樣設定好headers後,就可以在後續的流轉中通過selector實現細分儲存。
歡迎大夥交流
感謝XX峰的flume使用者指南的中文翻譯。