利用Flume拦截器(interceptors)实现Kafka Sink的自定义规则多分区写入

13 篇文章 0 订阅

本文大部分内容来自:http://lxw1234.com/archives/2015/11/547.htm,非常感谢原作者


我们目前的业务场景如下:前端的5台日志收集服务器产生网站日志,使用Flume实时收集日志,并将日志发送至Kafka,然后Kafka中的日志一方面可以导入到HDFS,另一方面供实时计算模块使用。

前面的文章《Kafka分区机制介绍与示例》介绍过Kafka的分区机制。我们对Kafka中存储日志的Topic指定了多个分区,默认情况下,Kafka Sink在收到events之后,将会随机选择一个该Topic的分区来存储数据,但我们不想这么做,我们需要根据网站日志中的cookieid来决定events存储到哪个分区中,简单来说,就是对cookieid计算hashcode,取绝对值,然后和Topic的分区数做模运算,这样,即实现了多分区的负载均衡,又确保相同的cookieid会写入同一个分区中,这样的处理,对后续的实时计算模块大有好处。

而这样的需求,利用Flume的拦截器即可实现。前面有两篇文章《Flume中的拦截器(Interceptor)介绍与使用(一)》Flume中的拦截器(Interceptor)介绍与使用(二)介绍了Flume的拦截器和使用示例,这里我们使用的拦截器是Regex Extractor Interceptor。即从原始events中抽取出cookieid,放入到header中,而Kafka Sink在写入Kafka的时候,会从header中获取指定的key,然后根据分区规则确定该条events写入哪个分区中。


网站日志格式
假设原始网站日志有三个字段,分别为 时间|cookieid|ip,中间以单竖线分隔,比如:

2015-10-30 16:00:00| 967837DE00026C55D8DB2E|127.0.0.1
2015-10-30 16:05:00| 967837DE00026C55D8DB2E|127.0.0.1
2015-10-30 17:10:00| AC19BBDC0002A955A4A47F|127.0.0.1
2015-10-30 17:15:00| AC19BBDC0002A955A4A47F|127.0.0.1

注意:我这里稍微改了一下第三、四行的cookieid,我这里是 AC19BBDC0002A955A4A47F而原文章是 AC19BBDC0002A955A4A48F,我这么做是为了最后这四行数据会均匀插入两个分区内,显示出更好的测试效果,否则的话都插入[1]分区了


编辑flume配置文件:
[hadoop@h71 ~]$ cd /home/hadoop/apache-flume-1.6.0-cdh5.5.2-bin/conf

[hadoop@h71 conf]$ vi fenqu.conf
a1.sources = s1
a1.sinks = k1  
a1.channels = c1 

#source 配置
a1.sources.s1.type = com.lxw1234.flume17.TaildirSource
a1.sources.s1.positionFile = /home/hadoop/hui/taildir_position.json  
a1.sources.s1.filegroups = f1
a1.sources.s1.filegroups.f1 = /home/hadoop/q1/test.*.log
a1.sources.s1.batchSize = 100
a1.sources.s1.backoffSleepIncrement = 1000
a1.sources.s1.maxBackoffSleep = 5000
a1.sources.s1.channels = c1

a1.sources.s1.interceptors = i1
a1.sources.s1.interceptors.i1.type = regex_extractor
a1.sources.s1.interceptors.i1.regex = .*?\\|(.*?)\\|.*
a1.sources.s1.interceptors.i1.serializers = s1
a1.sources.s1.interceptors.i1.serializers.s1.name = key
#该拦截器(Regex Extractor Interceptor)用于从原始日志中抽取cookieid,访问到events header中,header名字为key。

#k1 配置
a1.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
a1.sinks.k1.brokerList = h71:9092,h72:9092,h73:9092
a1.sinks.k1.topic = hui
a1.sinks.k1.channel = memoryChannel
a1.sinks.k1.batch-size = 100
a1.sinks.k1.requiredAcks = -1
a1.sinks.k1.kafka.partitioner.class = com.lxw1234.flume17.SimplePartitioner

#channel 配置
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

# Bind the source and sink to the channel
a1.sources.s1.channels = c1  
a1.sinks.k1.channel = c1

注:该Sink配置为Kafka Sink,将接收到的events发送至kafka集群的topic:hui中。
其中topic:hui创建时候指定了2个分区(原博主用的是四个分区),Kafka Sink使用的分区规则为com.lxw1234.flume17.SimplePartitioner,它会读取events header中的key值(即cookieid),然后对cookieid应用于分区规则,以便确定该条events发送至哪个分区中。
我已将com.lxw1234.flume17.SimplePartitioner和com.lxw1234.flume17.TaildirSource的代码打包成flumee.jar,你只需将该jar包上传到flume安装目录下的lib目录下就行,下载地址: http://download.csdn.net/detail/m0_37739193/9920190


开启kafka集群并建立相应的topic:
[hadoop@h71 kafka_2.10-0.8.2.0]$ bin/kafka-topics.sh --create --zookeeper h71:2181,h72:2181,h73:2181 --replication-factor 3 --partitions 2 --topic hui

启动flume进程:
[hadoop@h71 apache-flume-1.6.0-cdh5.5.2-bin]$ bin/flume-ng agent -c conf/ -f conf/fenqu.conf -n a1 -Dflume.root.logger=INFO,console

Kafka消费者:
使用下面的Java程序从Kafka中消费数据,打印出每条events所在的分区。
并从events中抽取cookieid,然后根据com.lxw1234.flume17.SimplePartitioner中的分区规则(Math.abs(cookieid.hashCode()) % 2)测试分区,看是否和获取到的分区一致。

package com.lxw1234.flume17;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
 
import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;
 
public class MyConsumer {
	public static void main(String[] args) {
		String topic = "hui";
		ConsumerConnector consumer = Consumer.createJavaConsumerConnector(createConsumerConfig()); 
		Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
		topicCountMap.put(topic, new Integer(1));
		Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
		KafkaStream<byte[], byte[]> stream =  consumerMap.get(topic).get(0);
		ConsumerIterator<byte[], byte[]> it = stream.iterator();
	    while(it.hasNext()) {
	    	MessageAndMetadata<byte[], byte[]> mam = it.next();
	    	String msg = new String(mam.message());
	    	String cookieid = msg.split("\\|")[1];
	    	int testPartition = Math.abs(cookieid.hashCode()) % 2;
	    	System.out.println("consume: Partition [" + mam.partition() + "] testPartition [" + testPartition + "] Message: [" + new String(mam.message()) + "] ..");
	    }
	}
	
	private static ConsumerConfig createConsumerConfig() {
		Properties props = new Properties();
		props.put("group.id","group_lxw_test");
//在zookeeper的/consconsumers目录下会生成该组:group_lxw_test
		props.put("zookeeper.connect", "h71:2181,h72:2181,h73:2181");  
		props.put("metadata.broker.list","h71:9092,h72:9092,h73:9092");
		props.put("zookeeper.session.timeout.ms", "4000");
		props.put("zookeeper.sync.time.ms", "200");
		props.put("auto.commit.interval.ms", "1000");
		props.put("auto.offset.reset", "smallest");
		return new ConsumerConfig(props);
	}
}

再往/home/hadoop/q1/test.1.log中传入模拟数据:
[hadoop@h71 q1]$ echo "2015-10-30 16:00:00| 967837DE00026C55D8DB2E|127.0.0.1" >> test.1.log
[hadoop@h71 q1]$ echo "2015-10-30 16:05:00| 967837DE00026C55D8DB2E|127.0.0.1" >> test.1.log
[hadoop@h71 q1]$ echo "2015-10-30 17:10:00| AC19BBDC0002A955A4A47F|127.0.0.1" >> test.1.log

[hadoop@h71 q1]$ echo "2015-10-30 17:15:00| AC19BBDC0002A955A4A47F|127.0.0.1" >> test.1.log


myeclipse中运行结果:


如图中红框所示,实际events所在的分区和期望分区(testPartition)的结果完全一致,由此可见,所有的events已经按照既定的规则写入Kafka分区中。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
要将CSV文件写入MySQL,可以使用Flume自定义Sink。以下是一些基本步骤: 1. 创建一个自定义Sink类,继承AbstractSink类。 2. 在该类中实现process方法,在该方法中编写将CSV文件数据写入MySQL的逻辑。 3. 在Flume配置文件中指定自定义Sink类,并设置相关参数,例如CSV文件路径、MySQL连接信息等。 下面是一个简单的示例: ```java public class CsvToMysqlSink extends AbstractSink { private String csvPath; private String mysqlUrl; private String mysqlUsername; private String mysqlPassword; private String mysqlTable; private Connection connection; private PreparedStatement statement; @Override public void configure(Context context) { csvPath = context.getString("csvPath"); mysqlUrl = context.getString("mysqlUrl"); mysqlUsername = context.getString("mysqlUsername"); mysqlPassword = context.getString("mysqlPassword"); mysqlTable = context.getString("mysqlTable"); } @Override public void start() { try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection(mysqlUrl, mysqlUsername, mysqlPassword); statement = connection.prepareStatement("INSERT INTO " + mysqlTable + " VALUES (?, ?, ?)"); } catch (Exception e) { e.printStackTrace(); } } @Override public void stop() { try { statement.close(); connection.close(); } catch (Exception e) { e.printStackTrace(); } } @Override public Status process() throws EventDeliveryException { Status status = null; try { File csvFile = new File(csvPath); BufferedReader br = new BufferedReader(new FileReader(csvFile)); String line; while ((line = br.readLine()) != null) { String[] values = line.split(","); statement.setString(1, values[0]); statement.setString(2, values[1]); statement.setString(3, values[2]); statement.executeUpdate(); } br.close(); status = Status.READY; } catch (Exception e) { e.printStackTrace(); status = Status.BACKOFF; } return status; } } ``` 在Flume配置文件中,可以这样指定自定义Sink类: ```properties agent.sinks.mysqlSink.type = com.example.CsvToMysqlSink agent.sinks.mysqlSink.csvPath = /path/to/csv/file.csv agent.sinks.mysqlSink.mysqlUrl = jdbc:mysql://localhost:3306/mydatabase agent.sinks.mysqlSink.mysqlUsername = myusername agent.sinks.mysqlSink.mysqlPassword = mypassword agent.sinks.mysqlSink.mysqlTable = mytable ``` 这个示例假设CSV文件每行有三个值,分别对应MySQL表中的三个字段。在process方法中,将读取CSV文件中的每一行,并将其分割为三个值,然后使用PreparedStatement将这些值插入到MySQL表中。 注意,这个示例没有包含一些必要的异常处理和错误处理逻辑,需要根据实际情况进行完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小强签名设计

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值