此框架代码为单线程收发, 适用于用kafka转送消息的业务,
如果要发送大量数据, 并且发送端有大量并发请求, 应当修改发送代码.
代码可以免费应用于商业代码, 但请保留创作者信息.
本框架包含如下内容:
下面就把各类完整代码发上来
AbstractConfig类:
package org.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* 消息配置
* @see KafkaTemplate
* @author guishuanglin 2019-09-5
*/
public abstract class AbstractConfig {
//必须配置
protected String projectName; //接收消息的项目名称
protected String servers; //主机地址, 集群设置也只要填写其中一个,如: 127.0.0.1:9092
protected String groupId; //接收组id, 不是做集群,则ID不能相同.
protected String clientId; //客户端ID, 都不能相同,一般用 [标识_本机IP]方式
protected List<String> topics = new ArrayList<String>(); //接收主题列表
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getServers() {
return servers;
}
public void setServers(String servers) {
this.servers = servers;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public List<String> getTopics() {
return topics;
}
public void setTopics(List<String> topics) {
this.topics = topics;
}
/**
* 把 [a;b;c] 格式的主题转换成数组
*/
public int setTopics(String str){
int size = 0;
if(str.length()>0){
String[] sArray=str.split(";");
for(String ts:sArray){
this.topics.add(ts);
}
}
return size;
}
/**
* 初始化配置参数
*/
public abstract boolean initConfig();
/**
* 把字段组成 Properties 格式的配置属性
*/
public abstract Properties getPropertiesConfig();
/**
* 把字段组成 Map 格式的配置属性
*/
public abstract Map<String,Object> getMapConfig();
/**
* 检查配置是否正常
*/
public abstract boolean checkConfig();
}
DemoReceiveCallback 类:
package org.test;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.fastjson.JSON;
/**
* 业务处理实现例子: 收发双向场景回调处理 <br>
*
* <br>只发不收业务直接使用 KafkaTemplate 模板即可,不需要回调.<br>
*
* <br> 本例子实现了重发/超时/清理等业务:<br>
*
* <br>如果要提高处理速度可采用如下方式:<br>
* 1, receiveProcess 只接收消息,并放入本地接收队列.<br>
* 2, 启用本类中的处理线程,独立处理[本地接收队列].<br>
*
* <br>重点说明:<br>
* 增加或修改 IReceiveCallback 回调实现, 一定要同步修改 MsgReceivePreprocessor 接收处理判断逻辑.
*
* @author guishuanglin 2019-09-5
*
*/
public class DemoReceiveCallback implements IReceiveCallback{
private Log logger = LogFactory.getLog(this.getClass());
private String fn ="[读开关命令]";
//发送主题
private String topic = MsgTopic.TOPIC_COMMAND_SEND;
//发送子主题
private String subTopic = "r-switch";
//接收时消息主题
private String recTopic = MsgTopic.TOPIC_COMMAND_RECE;
//接收时消息子主题
private String recSubTopic = "r-switch";
//发送列表, 以便进行超时/收到回复时处理 Object 是根据业务定义的bean
public final static Map<String, MsgRecord> sendMsgMap = new ConcurrentHashMap<String, MsgRecord>();
//接收列表, 用来存放处理好的接收结果
public final static Map<String, MsgRecord> receMsgMap = new ConcurrentHashMap<String, MsgRecord>();
//接收队列(可以不需要), 如果消息太多无法快速处理, 则先放入本地队列, 处理完之后结果放入receMsgMap
private final static LinkedBlockingQueue< MsgRecord > receiveQueue = new LinkedBlockingQueue< MsgRecord >();
//线程是否在运行
private static boolean isRuning = false;
//运行接收线程
private boolean receNext =true;
//运行重发/超时线程
private boolean sendNext =true;
//运行清理线程
private boolean clearNext =true;
//消息清理,最长保留时间(毫秒),默认5分钟
private long clearTime =300000 ;
public DemoReceiveCallback() {
//回调增加到工厂
ReceiveProcessFactory.setReceiveCallback(this, this.getRecTopic(), this.getRecSubTopic());
//运行处理线程
runThread();
}
//--------------------- 业务处理子类, 发送消息实现 -----------------------------------------
/**
* 发送消息处理
* @param msgId 消息唯一ID
* @param key 消息key关键标签
* @param message 消息体
* @return 返回msgId
*/
public String sendMessage(String topic, String key, String message, String msgId) {
KafkaTemplate.getInstance().sendMessage(topic, key, message, this);
MsgRecord msgRecord = new MsgRecord(topic, key, message, msgId);
sendMsgMap.put(msgId, msgRecord);
return msgId;
}
/**
* 发送消息处理
* @param MsgRecord 本地消息对象
* @return 返回msgId
*/
public String sendMessage(MsgRecord msgRecord) {
if(msgRecord.getMsgId() == null) {
logger.error(fn + ",发送消息错误: MsgId不能为空");
return null;
}
KafkaTemplate.getInstance().sendMessage(msgRecord, this);
sendMsgMap.put(msgRecord.getMsgId(), msgRecord);
return msgRecord.getMsgId();
}
/**
* 查询发送消息
*/
public MsgRecord getSendMessage(String msgId) {
return sendMsgMap.get(msgId);
}
/**
* 查询回复消息
*/
public MsgRecord getReceMessage(String msgId) {
return receMsgMap.get(msgId);
}
/**
* 查询回复消息
* @param msgId 消息唯一ID
* @param timeout 如果没有取到消息,需要等待的最长时间 (ms)
* @return MsgRecord 查询超时则返回空
*/
public MsgRecord getReceMessage(String msgId, int timeout) {
MsgRecord msgRecord = receMsgMap.get(msgId);
long tvb = System.currentTimeMillis();
while(msgRecord == null) {
msgRecord = receMsgMap.get(msgId);
if(msgRecord == null) {
long tve = System.currentTimeMillis();
if((tve - tvb) > timeout) {
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if(msgRecord != null) {
if(logger.isInfoEnabled()) {
logger.info(fn +",查到消息:key = "+ msgId +", value = "+msgRecord.getValue());
}
}else {
if(logger.isInfoEnabled()) {
logger.info(fn +",查到消息:key = "+ msgId +", value = 空");
}
}
return msgRecord;
}
//--------------------- 业务处理子类, 接收消息回调 -----------------------------------------
@Override
public void receiveProcess(MsgRecord msgRecord) {
//1,直接入 接收 receMsgMap; 2,入接收队列receiveQueue,
logger.info(fn +",接收消息:topic ="+ msgRecord.getTopic() +",key ="+ msgRecord.getKey() +",value ="+msgRecord.getValue());
//receMsgMap.put(msgRecord.getMsgId(), msgRecord);
try {
receiveQueue.put(msgRecord);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String getRecSubTopic() {
return this.recSubTopic;
}
@Override
public String getRecTopic() {
return this.recTopic;
}
/**
* 运行处理线程,如果已经运行,则不再重复运行.
*/
private void runThread() {
if(isRuning) return;
isRuning = true;
new Thread(new receiveThread()).start(); // 运行接收处理线程
//new Thread(new sendThread()).start(); // 重发,超时
//new Thread(new clearThread()).start(); // 清理缓存数据
}
/**
* 处理接收的消息线程, 独立处理 receiveProcess 方法收到的消息,如果不需要处理则直接放入 receMsgMap 列表
*/
private class receiveThread implements Runnable{
@Override
public void run() {
MsgRecord msgRecord = null;
while (receNext) {
try {
// 出队方法 receiveQueue.take(), 入队方法 receiveQueue.put(E o) 必须配对使用, 否则队列接收后可能不会及时触发;
msgRecord = receiveQueue.take();
if(msgRecord != null) {
//处理消息....
receMsgMap.put(msgRecord.getMsgId(), msgRecord);
} else {
Thread.sleep(2000);
}
}catch (Exception e) {
logger.error(fn+",接收线程异常", e);
} finally {
}
}
}
}
/**
* 重发,超时(如果需要重发时用)
*/
private class sendThread implements Runnable{
@Override
public void run() {
long tvb = System.currentTimeMillis();
MsgRecord msgRecord = null;
boolean br = false;
while (sendNext) {
try {
//20秒运行一次
Thread.sleep(20000);
//
for(Map.Entry<String, MsgRecord> et : sendMsgMap.entrySet()){
msgRecord = et.getValue();
//是否收到
if(receMsgMap.containsKey(msgRecord.getMsgId())) {
continue;
}
if(msgRecord.getCount() > 3 ) {
//重发3次,则超时
msgRecord.setStatus(2);
}else {
br = KafkaTemplate.getInstance().sendMessage(topic, msgRecord.getKey(), msgRecord.getValue());
if(br) {
//重发成功
msgRecord.setStatus(1);
msgRecord.setCount(msgRecord.getCount() +1);
}else {
//重发失败
msgRecord.setStatus(3);
msgRecord.setCount(msgRecord.getCount() +1);
}
}
}
}catch (Exception e) {
logger.error(fn+",重发线程异常", e);
} finally {
}
}
}
}
/**
* 清理线程
*/
private class clearThread implements Runnable{
@Override
public void run() {
long tvb = System.currentTimeMillis();
MsgRecord msgRecord = null;
boolean br = false;
while (clearNext) {
try {
for(Map.Entry<String, MsgRecord> et : sendMsgMap.entrySet()){
msgRecord = et.getValue();
if(msgRecord != null && (tvb - msgRecord.getMsgTime()) > clearTime ) {
//清理垃圾时间到
sendMsgMap.remove(et.getKey());
}
}
for(Map.Entry<String, MsgRecord> et : receMsgMap.entrySet()){
msgRecord = et.getValue();
if(msgRecord != null && (tvb - msgRecord.getMsgTime()) > clearTime ) {
//清理垃圾时间到
receMsgMap.remove(et.getKey());
}
}
//5分钟运行一次
Thread.sleep(300000);
}catch (Exception e) {
logger.error(fn+",清理线程异常", e);
} finally {
}
}
}
}
public void close() {
receNext =false;
sendNext =false;
clearNext =false;
sendMsgMap.clear();
receMsgMap.clear();
receiveQueue.clear();
isRuning = false;
}
//测试
public static void main(String[] args) {
KafkaTemplate tlp = KafkaTemplate.getInstance();
//接收配置
MsgConsumerConfig cConfig = new MsgConsumerConfig();
//接收预处理
MsgReceivePreprocessor prep = new MsgReceivePreprocessor();
cConfig.setTopics("command-send;command-rece;data-up");
//启用接收线程
tlp.runListener(cConfig, prep);
//调用发送方法
// for(int i = 0; i < 5; i++) {
// System.out.println("KafkaProduce ->"+i);
// 发送命令:读取[上海项目]的[10000898]设备的[0号]开关状态
// tlp.sendMessage("command-send", "r-switch", "{\"project\":\"sh\",\"id\":10000898,\"seq\":1567647660299001,\"command\":\"r-switch\",\"value\":0}");
//
// try {
// Thread.sleep(1 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
//
// try {
// Thread.sleep(3 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// //关闭接收/发送
// tlp.closeListener();
// tlp.closeProducer();
System.out.println("===========end=============");
}
}
DemoReceiveCallback2 类:
package org.test;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.alibaba.fastjson.JSON;
/**
* 业务处理实现例子: 只收不发的场景回调处理 ,<br>
*
* <br>只发不收业务直接使用 KafkaTemplate 模板即可,不需要回调.<br>
*
* <br>如果要提高处理速度可采用如下方式:<br>
* 1, receiveProcess 只接收消息,并放入本地接收队列.<br>
* 2, 启用本类中的处理线程,独立处理[本地接收队列].<br>
*
* <br>重点说明:<br>
* 增加或修改 IReceiveCallback 回调实现, 一定要同步修改 MsgReceivePreprocessor 接收处理判断逻辑.
*
* @author guishuanglin 2019-09-5
*
*/
public class DemoReceiveCallback2 implements IReceiveCallback{
private Log logger = LogFactory.getLog(this.getClass());
private String fn ="[上报警告数据]";
//接收时消息主题
private String recTopic = MsgTopic.TOPIC_DATA_UP;
//接收时消息子主题
private String recSubTopic = MsgTopic.DATA_UP_ALARM;
//接收列表, 用来存放处理好的接收结果
public final static Map<String, MsgRecord> receMsgMap = new ConcurrentHashMap<String, MsgRecord>();
//接收队列(可以不需要), 如果消息太多无法快速处理, 则先放入本地队列, 处理完之后结果放入receMsgMap
private final static LinkedBlockingQueue< MsgRecord > receiveQueue = new LinkedBlockingQueue< MsgRecord >();
//线程是否在运行
private static boolean isRuning = false;
//运行接收线程
private boolean receNext =true;
//运行清理线程
private boolean clearNext =true;
//消息请理,最长保留时间(毫秒),默认5分钟
private long clearTime =300000 ;
public DemoReceiveCallback2() {
//增加到工厂
ReceiveProcessFactory.setReceiveCallback(this, this.getRecTopic(), this.getRecSubTopic());
//运行处理线程
runThread();
}
//--------------------- 业务处理子类, 接收消息回调 -----------------------------------------
@Override
public void receiveProcess(MsgRecord msgRecord) {
//1,直接入 接收 receMsgMap; 2,入接收队列receiveQueue,
logger.info(fn +",接收消息:topic ="+ msgRecord.getTopic() +",key ="+ msgRecord.getKey() +",value ="+msgRecord.getValue());
//receMsgMap.put(msgRecord.getMsgId(), msgRecord);
try {
receiveQueue.put(msgRecord);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String getRecSubTopic() {
return this.recSubTopic;
}
@Override
public String getRecTopic() {
return this.recTopic;
}
/**
* 运行处理线程,如果已经运行,则不再重复运行.
*/
private void runThread() {
if(isRuning) return;
isRuning = true;
new Thread(new receiveThread()).start(); // 运行接收处理线程
new Thread(new clearThread()).start(); // 清理缓存数据
}
/**
* 处理接收的消息线程
*/
private class receiveThread implements Runnable{
@Override
public void run() {
MsgRecord msgRecord = null;
while (receNext) {
try {
// 出队方法 receiveQueue.take(), 入队方法 receiveQueue.put(E o) 必须配对使用, 否则队列接收后可能不会及时触发;
msgRecord = receiveQueue.take();
if(msgRecord != null) {
//处理消息...
receMsgMap.put(msgRecord.getMsgId(), msgRecord);
} else {
Thread.sleep(2000);
}
}catch (Exception e) {
logger.error(fn+",接收线程异常", e);
} finally {
}
}
}
}
/**
* 清理线程
*/
private class clearThread implements Runnable{
@Override
public void run() {
long tvb = System.currentTimeMillis();
MsgRecord msgRecord = null;
boolean br = false;
while (clearNext) {
try {
for(Map.Entry<String, MsgRecord> et : receMsgMap.entrySet()){
msgRecord = et.getValue();
if(msgRecord != null && (tvb - msgRecord.getMsgTime()) > clearTime ) {
//清理垃圾时间到
receMsgMap.remove(et.getKey());
}
}
//5分钟运行一次
Thread.sleep(300000);
}catch (Exception e) {
logger.error(fn+",清理线程异常", e);
} finally {
}
}
}
}
public void close() {
receNext =false;
clearNext =false;
receMsgMap.clear();
receiveQueue.clear();
isRuning = false;
}
//测试
public static void main(String[] args) {
KafkaTemplate tlp = KafkaTemplate.getInstance();
//接收配置
MsgConsumerConfig cConfig = new MsgConsumerConfig();
//接收预处理
MsgReceivePreprocessor prep = new MsgReceivePreprocessor();
cConfig.setTopics("command-send;command-rece;data-up");
//启用接收线程
tlp.runListener(cConfig, prep);
//调用发送方法
// for(int i = 0; i < 5; i++) {
// System.out.println("KafkaProduce ->"+i);
// 发送命令:读取[上海项目]的[10000898]设备的[0号]开关状态
// tlp.sendMessage("command-send", "r-switch", "{\"project\":\"sh\",\"id\":10000898,\"seq\":1567647660299001,\"command\":\"r-switch\",\"value\":0}");
//
// try {
// Thread.sleep(1 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
//
// try {
// Thread.sleep(3 * 1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// //关闭接收/发送
// tlp.closeListener();
// tlp.closeProducer();
System.out.println("===========end=============");
}
}
IKafkaOperation 接口:
package org.test;
/**
* kafka 操作模板接口
*
* @see KafkaTemplate
* @author guishuanglin 2019-09-5
*
*/
public interface IKafkaOperation {
//------------------------发送--------------------------------
/**
* kafka 发送数据
* @param topic 主题名称
* @param message 消息内容
*/
public abstract boolean sendMessage(String topic, String message);
/**
* kafka 发送数据
* @param topic 主题名称
* @param message 消息内容
* @param callback 回调处理,发送消息后,需要接收方回复时则提供回调处理
*/
public abstract boolean sendMessage(String topic, String message, IReceiveCallback callback);
/**
* kafka 发送数据
* @param topic 主题名称
* @param key 消息key
* @param message 消息内容
*/
public abstract boolean sendMessage(String topic, String key, String message);
/**
* kafka 发送数据
* @param topic 主题名称
* @param key 消息key
* @param message 消息内容
* @param callback 回调处理, 发送消息后, 需要接收方回复时, 则提供回调处理
*/
public abstract boolean sendMessage(String topic, String key, String message, IReceiveCallback callback);
/**
* kafka 发送数据
* @param msgRecord 本地消息对象
*/
public abstract boolean sendMessage(MsgRecord msgRecord);
/**
* kafka 发送数据
* @param msgRecord 本地消息对象
* @param callback 回调处理, 发送消息后, 需要接收方回复时, 则提供回调处理
*/
public abstract boolean sendMessage(MsgRecord msgRecord, IReceiveCallback callback);
//-------------------------接收监听线程-------------------------------
/**
* 注册回调类,会在接收监听时进行回调.
*/
public abstract void addReceiveCallback(IReceiveCallback callback);
/**
* 启动 kafka 的 Consumer 数据监听对象线程.
* 启动前请调用 setConsumerConfig 设置参数
*/
public abstract boolean runListener(AbstractConfig config, IReceivePreprocessor prepr);
/**
* 关闭 kafka 的 Consumer 数据监听对象线程.
*/
public abstract boolean closeListener();
/**
* 关闭 kafka 的 Producer 发送数据对象.
*/
public abstract boolean closeProducer();
}
IReceiveCallback 接口:
package org.test;
/**
* 接收消息,回调处理接口 <br>
* 一般由同个业务类来实现[发送]/[接收]处理一体化处理, 同时在业务类中有自己的发送/接收列表, <br>
* 等收到回复消息后配合发送消息列表, 以实现消息交互处理<br>
* 接收消息之前,请把回调类增加到 ReceiveProcessFactory 中.
* <br>
* 在发送消息的业务中,分以下几种情况:<br>
* 1 定时任务发送方式, 2 发送线程方式, 3 页面请求方式.<br>
* <br>
* 在接收消息的业务中,分以下几种情况:<br>
* 1 普通接收处理方式, 2 接收消息线程方式.<br>
*
* @author guishuanglin 2019-09-5
*
*/
public interface IReceiveCallback {
/**
* 接收消息回调方法
*/
public void receiveProcess(MsgRecord record);
/**
* 接收主题的名称( 发送与接收主题可能不同 ), 比如:<br>
* 发送 device-control 主题<br>
* 接收 device-state 主题<br>
*/
public String getRecTopic();
/**
* 接收子主题名称, 或叫分类名称 ( 发送子主题,与接收主子题可能不同, 尽量要相同为好 ), 比如:<br>
* device-control 主题中: Switch子类,<br>
* device-state 主题中 : switch子类<br>
* <br>
* 消息子主题应用场景:<br>
* 1, 子主题主要是为了把同一主题下不同业务分开处理, 如果不需要子主题, 则返回主题名=主题名.<br>
*
* 提醒: 子主题用来在消息回调时, 用来获取回调业务类.
*/
public String getRecSubTopic();
}
IReceivePreprocessor 接口:
package org.test;
import java.util.Map;
import org.apache.kafka.clients.consumer.ConsumerRecord;
/**
* 接收消息,预处理处理接口<br>
* 提取/解析一些常用的关键数据,方便后处理/判断.
*
* @author guishuanglin 2019-09-5
*
*/
public interface IReceivePreprocessor {
/**
* 接收预处理
*/
public MsgRecord preprocessor(ConsumerRecord<String, String> inRecord, Map<String,Object> inMap);
}