参与实现的模块的功能:
基于Kafka low level API实现一个消费者,消费到消息先反序列化,之后通过HTTPClient发送给指定的url。
一、KafkaConsumer
KafkaConsumer基于之前项目中的HadoopConsumer的框架实现,但一个partition开启了一个消费者线程,同时修改了持续消费的实现逻辑:不停地轮询offsetRange是否改变,是否生成了新的消息;
DataGenerator:
从配置文件中获取topic、brokerlist等信息,给每个topic的每个partition创建文件offset.dat,保存offset值;写入的是leader broker;
KafkaConsumerMain:
维护一个线程池,线程数为topicCount * brokerCount;
为每个topic创建一个SimpleKafkaETLJob对象,SimpleKafkaETLJob类中,给topic的每个分区创建一个线程,启动一个消费者消费消息;
类KafkaETLRecordReader继承了thread,实现run方法,在run中:
1) 首先,读取offset.dat,得到上一次消费的offset值,继续消费,每个线程操作自己的partition自己的文件,不存在线程安全问题;
2) 创建一个KafkaETLContext对象,该对象中创建了consumer,并消费消息;
3) 死循环,不停地消费消息:函数hasMoreNewMessages获取该分区最新的offsetrange,看是否生产了新的消息,如果没有,休眠interval;有新的消息,则调用currContext.getNext()一条一条循环消费消息,get()中操作具体的消息,把消息放入message_list中,并更新offset;
4) 现在的逻辑是:第一次消费完累积的所有消息,之后,每隔interval消费一次,如果一个interval过后没有新消息,则再等一个interval;也就是按照固定的时间间隔消费消息并更新offset。
5) 先把消息反序列化发送之后,再更新offset,可能的异常是:消息已经发出去了,写offset文件的时候宕机,重启后会重复消费,所以Restful server端会收到重复的消息,是否需要去重;可靠性保证是,至少消费一次;
二、使用Log4j写日志
maven依赖:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
两个配置文件:
使用:Log logger = LogFactory.getLog(DataGenerator.class);
三、使用Maven管理依赖的jar包并将程序打包
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<mainClass>consumer.projectkafka.KafkaConsumerMain</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
直接在Run as中
Maven install 即可。标签
manifest用来指定导出的Jar包中main函数所在的类。
四、把String类型的json串转换为JSONobject并输出至Map
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<classifier>jdk15</classifier>
<version>2.4</version>
</dependency>
public Map<String, String> parserToMap(String s){
Map<String, String> map = new HashMap<String, String>();
JSONObject json=JSONObject.fromObject(s);
@SuppressWarnings("unchecked")
Iterator<String> keys=json.keys();
while(keys.hasNext()){
String key=(String) keys.next();
String value=json.get(key).toString();
map.put(key, value);
}
return map;
}
设置jdk18不成功,还不知道原因
五、使用HTTPClient把消息发送给Server
public CloseableHttpResponse msgSendbyHttpClient(String url, Map<String,String> map,byte[] messagebody) throws ParseException, IOException{
//创建httpclient对象
//CloseableHttpClient client = HttpClients.createDefault();
//创建post方式请求对象
System.out.println("msgSendbyHttpClient");
RequestConfig config = RequestConfig.custom().setConnectTimeout(60*1000) // 创建连接的最长时间
.setConnectionRequestTimeout(5000) // 从连接池中获取到连接的最长时间
.setSocketTimeout(60 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(config);
for (Entry<String, String> entry : map.entrySet()) {
httpPost.addHeader(entry.getKey(), entry.getValue());
}
ByteArrayEntity myEntity = new ByteArrayEntity(messagebody);
httpPost.setEntity(myEntity);
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = client.execute(httpPost);
return response;
}
public CloseableHttpResponse fileSendbyHttpClient(String url, Map<String,String> map,File file) throws ParseException, IOException{
System.out.println("fileSendbyHttpClient");
if (!file.exists() || file.isDirectory()) {
return null;
}
String contenttype = map.get("Content-Type");
String boundary = contenttype.substring(contenttype.indexOf("boundary")+9);
//System.out.println("boundary:"+boundary);
HttpEntity myentity = MultipartEntityBuilder.create().addBinaryBody("file", file)
.setBoundary(boundary).build();
//创建post方式请求对象
HttpPost httpPost = new HttpPost(url);
RequestConfig config = RequestConfig.custom().setConnectTimeout(60*1000) // 创建连接的最长时间
.setConnectionRequestTimeout(5000) // 从连接池中获取到连接的最长时间
.setSocketTimeout(60 * 1000) // 数据传输的最长时间
.setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用
.build();
httpPost.setConfig(config);
for (Entry<String, String> entry : map.entrySet()) {
httpPost.addHeader(entry.getKey(), entry.getValue());
}
httpPost.setEntity(myentity);
//执行请求操作,并拿到结果(同步阻塞)
CloseableHttpResponse response = client.execute(httpPost);
return response;
}