EMQ实例01-EMQ的搭建及java环境本地客户端的使用
EMQ的安装
环境:
CentOS 7 64位
emqttd-centos7-v2.3.6.zip
Erlang //由于emqttd是用Erlang语言编写的,所以,在Linux下安装时,需要先安装Erlang
安装Erlang命令 #sudo yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
EMQ的安装
安装命令:
mkdir emq
cd emq
wget http://emqtt.com/static/brokers/emqttd-centos7-v2.3.6.zip
unzip emqttd-centos7-v2.3.6.zip
EMQ启动
方式一:
cd emqttd
./bin/emqttd console //会在控制台打印日志 Ctrl+c结束
方式二:
./bin/emqttd start //不会在控制台打印日志
./bin/emqttd status
./bin/emqttd stop
注意:EMQ中有两个端口18083和1883需要打开。
java客户端使用
@Component // 不加这个注解的话, 使用@Autowired 就不能注入进去了
@PropertySource("classpath:application.yml") //指定要读取的配置文件
@ConfigurationProperties(prefix = "mqtt") // 配置文件中的前缀
public class MqttConfiguration {
private String host;
private String username;
private String password;
private Integer qos;
private String[] hosts;
private Integer connectionTimeout;
private Integer keepAliveInterval;
private String publishClientId;
private String subscribeClientId;
private boolean retained;
/**
* 发布消息的回调类
*
* 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。
* 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。
* 在回调中,将它用来标识已经启动了该回调的哪个实例。
* 必须在回调类中实现三个方法:
*
* public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。
*
* public void connectionLost(Throwable cause)在断开连接时调用。
*
* public void deliveryComplete(MqttDeliveryToken token))
* 接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。
* 由 MqttClient.connect 激活此回调。
*/
public class PushCallback implements MqttCallback {
@Override
public void connectionLost(Throwable throwable) {
/**
* 连接丢失后,一般在这里面进行重连
*/
System.out.println("连接断开,可以做重连");
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// subscribe后得到的消息会执行到这里面
System.out.println("Server 接收消息主题 : " + topic);
System.out.println("Server 接收消息Qos : " + message.getQos());
System.out.println("Server 接收消息内容 : " + new String(message.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("deliveryComplete---------" + token.isComplete());
}
}
@RestController
public class MQTTServerController {
@Autowired
private IEmqService iEmqService;
String TOPIC = "MQTT_PRODUCER_TOPIC";
@RequestMapping("/")
public String sayHello() {
return "Hello !";
}
@RequestMapping("/send")
public void send(String msg) throws MqttException {
System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName() + "==========:" + msg);
iEmqService.publish(TOPIC, msg);
}
}
@Service
@Slf4j
@EnableConfigurationProperties({MqttConfiguration.class})
public class IEmqServiceImpl implements IEmqService {
@Autowired
private MqttConfiguration mqttConfiguration;
@Override
public Boolean publish(String topic, String content) {
log.info("MQ===public=== 入参:topic:{};content:{}", topic, content);
MqttMessage message = new MqttMessage(content.getBytes());
message.setQos(mqttConfiguration.getQos());
/**
* Retained为true时MQ会保留最后一条发送的数据,当断开再次订阅即会接收到这最后一次的数据
*/
message.setRetained(true);
try {
MqttClient mqttClient = this.connect(mqttConfiguration.getPublishClientId(), mqttConfiguration.getUsername(),
mqttConfiguration.getPassword());
// 判定是否需要重新连接
/*String clientId = UUID.randomUUID().toString() +
"[" + InetAddress.getLocalHost().getHostAddress() + "]";*/
if (mqttClient == null || !mqttClient.isConnected() || !mqttClient.getClientId().equals(mqttConfiguration.getPublishClientId())) {
mqttClient = this.connect(mqttConfiguration.getPublishClientId(), mqttConfiguration.getUsername(),
mqttConfiguration.getPassword());
}
mqttClient.publish(topic, message);
log.info("emq已发topic: {} - message: {}", topic, message);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
public Boolean subscribe(String topic) {
return null;
}
public MqttClient connect(String clientId, String userName, String password) throws MqttException {
MemoryPersistence persistence = new MemoryPersistence();
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(userName);
options.setPassword(password.toCharArray());
options.setConnectionTimeout(mqttConfiguration.getConnectionTimeout());
options.setKeepAliveInterval(mqttConfiguration.getKeepAliveInterval());
MqttClient client = new MqttClient(mqttConfiguration.getHost(), clientId, persistence);
client.setCallback(new PushCallback());
client.connect(options);
return client;
}
}
/**
*
* EMQ服务管理接口。
*
*
* @author zhangs
* @createDate 2018/04/09
*/
public interface IEmqService {
/**
* 发布消息
*
* @param topic
* @param content
* @return
*/
Boolean publish(String topic, String content);
/**
* 订阅消息
*
* @param topic
* @return
*/
Boolean subscribe(String topic);
}
receive
@Service
@Slf4j
@EnableConfigurationProperties({MqttConfiguration.class})
public class IMqttWrapperServiceImpl implements IMqttWrapperService {
@Autowired
private MqttConfiguration mqttConfiguration;
@Autowired
private SubscribeConn subscribeConn;
@Override
public Boolean publish(String topic, String content) {
return true;
}
@Override
public Boolean subscribe(String topic) {
log.info("MQ===subscribe=== 入参:topic:{}", topic);
MqttClient mqttClient = subscribeConn.getMqttClient();
// 判定是否需要重新连接
if (mqttClient==null || !mqttClient.isConnected() || !mqttClient.getClientId().equals(mqttConfiguration.getSubscribeClientId())) {
mqttClient = subscribeConn.getConn();
}
try {
// 订阅消息
int[] qos = {mqttConfiguration.getQos()};
mqttClient.subscribe(new String[]{topic},qos);
} catch (MqttException e) {
e.printStackTrace();
return false;
}
return true;
}
}
/**
*
* EMQ服务管理接口。
*
*
* @author zhangs
* @createDate 2018/04/09
*/
public interface IMqttWrapperService {
/**
* 发布消息
*
* @param topic
* @param content
* @return
*/
Boolean publish(String topic, String content);
/**
* 订阅消息
*
* @param topic
* @return
*/
Boolean subscribe(String topic);
}
/**
* @author zhangs
*/
@Component // 不加这个注解的话, 使用@Autowired 就不能注入进去了
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "mqtt") // 配置文件中的前缀
@Data
public class MqttConfiguration {
private String host;
private String username;
private String password;
private Integer qos;
private String[] hosts;
private Integer connectionTimeout;
private Integer keepAliveInterval;
private String publishClientId;
private String subscribeClientId;
private boolean retained;
}
/**
* @author zhangs
* @createDate 2018/5/21
*
* 发布消息的回调类
*
* 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。
* 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。
* 在回调中,将它用来标识已经启动了该回调的哪个实例。
* 必须在回调类中实现三个方法:
*
* public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。
*
* public void connectionLost(Throwable cause)在断开连接时调用。
*
* public void deliveryComplete(MqttDeliveryToken token))
* 接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。
* 由 MqttClient.connect 激活此回调。
*
*/
@Slf4j
@Component
public class PushCallback implements MqttCallback {
/**
* 消息订阅者配置类
*/
@Autowired
private SubscribeConn subscribeConn;
@Autowired
private QueueHandleUtil queueHandleUtil;
@Override
public void connectionLost(Throwable cause) {
log.info("连接断开,进行重连");
subscribeConn.getConn();
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
log.info("接收消息主题:{},接收消息Qos:{},接收消息内容:{}",topic,mqttMessage.getQos(),new String(mqttMessage.getPayload()));
String content = new String(mqttMessage.getPayload());
log.info("MQ消费者接收消息:" + content);
// Thread.sleep(10000);
queueHandleUtil.advanceDisruptor(content);
// queueHandleUtil.advanceDisruptor2(content);
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("deliveryComplete:{}",iMqttDeliveryToken.isComplete());
}
}
/**
*
* 消息订阅者配置类
*
*
* @author zhangs
* @createDate 2018/8/30
*/
@Data
@Slf4j
@Component
@EnableConfigurationProperties({MqttConfiguration.class})
public class SubscribeConn {
private MqttClient mqttClient;
private MqttConnectOptions mqttConnectOptions;
@Autowired
private ServiceInfoUtil serviceInfoUtil;
@Autowired
private MqttConfiguration mqttConfiguration;
@Autowired
private PushCallback pushCallback;
/**
* 连接emq服务器
*/
public MqttClient getConn() {
try {
// host为主机名,clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
String clientId = UUID.randomUUID().toString() + "[" + InetAddress.getLocalHost().getHostAddress() +
"-" + serviceInfoUtil.getPort() + "]";
mqttClient = new MqttClient(mqttConfiguration.getHost(), clientId, new MemoryPersistence());
// MQTT的连接设置
mqttConnectOptions = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
mqttConnectOptions.setCleanSession(true);
// 设置连接的用户名
mqttConnectOptions.setUserName(mqttConfiguration.getUsername());
// 设置连接的密码
mqttConnectOptions.setPassword(mqttConfiguration.getPassword().toCharArray());
// 设置超时时间 单位为秒
mqttConnectOptions.setConnectionTimeout(mqttConfiguration.getConnectionTimeout());
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*keepAliveInterval秒的时间向客户端发送个消息判断客户端是否在线,
// 但这个方法并没有重连的机制
mqttConnectOptions.setKeepAliveInterval(mqttConfiguration.getKeepAliveInterval());
//配置多个服务器列表,一个挂掉会自动切换到其他正常的服务器,挂掉的服务器正常后,客户端会再次切换回来
if (mqttConfiguration.getHosts() != null && mqttConfiguration.getHosts().length > 0) {
mqttConnectOptions.setServerURIs(mqttConfiguration.getHosts());
}
// 设置回调
mqttClient.setCallback(pushCallback);
/* if(topic.length>0){
MqttTopic mqttTopic = mqttClient.getTopic(topic[0]);
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
mqttConnectOptions.setWill(mqttTopic, "close".getBytes(), mqttConfiguration.getQos(), true);
}*/
// mqttClient.connect(mqttConnectOptions);
IMqttToken iMqttToken = mqttClient.connectWithResult(mqttConnectOptions);
log.info("连接服务器成功...");
} catch (Exception e) {
System.out.println(e);
log.info("连接服务器失败...");
}
return mqttClient;
}
}
/**
*
* 启动MQTT订阅
*
* @author zhangs
* @createDate 2018/08/30
*/
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Autowired
private IMqttSubscribe iMqttSubscribe;
String TOPIC = "MQTT_PRODUCER_TOPIC";
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
log.info(" MQTT Subscribe Server 开始...");
iMqttSubscribe.subscribe(TOPIC);
}
}
public interface IMqttSubscribe {
Boolean subscribe(String topic);
}
`@Service
@Slf4j
public class MqttSubscribeImpl implements IMqttSubscribe {
@Autowired
private IMqttWrapperService iMqttWrapperService;
@Override
public Boolean subscribe(String topic) {
return iMqttWrapperService.subscribe(topic);
}
}
import lombok.Data;
/**
* 1.建Event类(数据对象)
* 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
* 3.监听事件类(处理Event数据)
* 4.实例化Disruptor,配置参数,绑定事件;
* 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
*/
@Data
public class ObjectEvent {
private Object event;
}
/**
* 1.建Event类(数据对象)
* 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
* 3.监听事件类(处理Event数据)
* 4.实例化Disruptor,配置参数,绑定事件;
* 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
*/
@Slf4j
public class ObjectEventConsumer implements EventHandler {
@Override
public void onEvent(ObjectEvent objectEvent, long l, boolean b) throws Exception {
log.info("Disruptor===事件消费者:" + JSON.toJSONString(objectEvent.getEvent()));
Thread.sleep(10000);
}
}
/**
* 1.建Event类(数据对象)
* 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
* 3.监听事件类(处理Event数据)
* 4.实例化Disruptor,配置参数,绑定事件;
* 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
*/
@Slf4j
public class ObjectEventConsumer2 implements EventHandler {
@Override
public void onEvent(ObjectEvent objectEvent, long sequence, boolean endOfBatch) throws Exception {
log.info("Disruptor2===事件消费者:" + JSON.toJSONString(objectEvent.getEvent()));
Thread.sleep(10000);
}
}
/**
* 1.建Event类(数据对象)
* 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
* 3.监听事件类(处理Event数据)
* 4.实例化Disruptor,配置参数,绑定事件;
* 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
*/
public class ObjectEventFactory implements EventFactory {
@Override
public Object newInstance() {
return new ObjectEvent();
}
}
public class ObjectEventMain {
public static void main(String[] args) throws Exception{
/**
* 创建缓冲池
/
ExecutorService executor = Executors.newCachedThreadPool();
/*
* 创建工厂
/
ObjectEventFactory factory = new ObjectEventFactory();
/*
* 创建bufferSize,即RingBuffer大小(必须是2的N次方)
*/
int ringBufferSize = 1024 * 1024;
/**
* BlockingWaitStrategy 是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现
* WaitStrategy BLOCKING_WAIT = new BlockingWaitStrategy();
* SleepingWaitStrategy 的性能表现跟BlockingWaitStrategy差不多,对CPU的消耗也类似,但其对生产者线程的影响最小,适合用于异步日志类似的场景
* WaitStrategy SLEEPING_WAIT = new SleepingWaitStrategy();
* YieldingWaitStrategy 的性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性
* WaitStrategy YIELDING_WAIT = new YieldingWaitStrategy();
*/
/**
* 创建disruptor
*/
Disruptor disruptor =
new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
/**
* 连接消费事件方法
*/
disruptor.handleEventsWith(new ObjectEventConsumer());
/**
* 启动
*/
disruptor.start();
RingBuffer ringBuffer = disruptor.getRingBuffer();
/*ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
ByteBuffer byteBuffer = ByteBuffer.allocate(20);
for (long l = 0;l < 100;l++) {
String content = "===Producer:" + l;
byteBuffer.putLong(0, l);
producer.onData(byteBuffer);
}*/
ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
for (long l = 0;l < 100;l++) {
String content = "===Producer:" + l;
producer.onData(content);
}
}
/**
* 1.建Event类(数据对象)
* 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
* 3.监听事件类(处理Event数据)
* 4.实例化Disruptor,配置参数,绑定事件;
* 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
*
* 当用一个简单队列来发布事件的时候会牵涉更多的细节,这是因为事件对象还需要预先创建。
* 发布事件最少需要两步:获取下一个事件槽并发布事件(发布事件的时候要使用try/finnally保证事件一定会被发布)。
* 如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。
* 如果不能发布事件,那么就会引起Disruptor状态的混乱。
* 尤其是在多个事件生产者的情况下会导致事件消费者失速,从而不得不重启应用才能会恢复。
*/
@Slf4j
public class ObjectEventProducer {
private final RingBuffer ringBuffer;
public ObjectEventProducer(RingBuffer ringBuffer) {
this.ringBuffer = ringBuffer;
}
/**
* onData用来发布事件,每调用一次就发布一次事件
*/
public void onData(ByteBuffer byteBuffer) {
/**
* 1.可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
*/
long sequence = ringBuffer.next();
try {
/**
* 用上面的索引取出一个空的时间用于填充(获取该序号对应的事件对象)
*/
ObjectEvent event = ringBuffer.get(sequence);
/**
* 获取要通过事件传递的业务数据
*/
event.setEvent(byteBuffer.getLong(0));
} finally {
/**
* 发布事件
* 最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;
* 如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
*/
ringBuffer.publish(sequence);
}
}
public void onData(String content) {
long sequence = ringBuffer.next();
try {
ObjectEvent event = ringBuffer.get(sequence);
event.setEvent(content);
} finally {
ringBuffer.publish(sequence);
log.info("Disruptor===publish");
}
}
}
@Slf4j
@Component
public class QueueHandleUtil {
/**
* 创建缓冲池
*/
ExecutorService executor = Executors.newCachedThreadPool();
public void advanceDisruptor(String content) {
/**
* 创建工厂
*/
ObjectEventFactory factory = new ObjectEventFactory();
/**
* 创建bufferSize,即RingBuffer大小(必须是2的N次方)
*/
int ringBufferSize = 1024 * 1024;
/**
* 创建disruptor
*/
Disruptor disruptor =
new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
RingBuffer ringBuffer = disruptor.getRingBuffer();
disruptor.handleEventsWith(new ObjectEventConsumer());
disruptor.start();
ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
log.info("MQ消费者将接收的消息放入Disruptor===content:" + content);
producer.onData(content);
}
public void advanceDisruptor2(String content) {
/**
* 创建工厂
*/
ObjectEventFactory factory = new ObjectEventFactory();
/**
* 创建bufferSize,即RingBuffer大小(必须是2的N次方)
*/
int ringBufferSize = 1024 * 1024;
/**
* 创建disruptor
*/
Disruptor disruptor =
new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
RingBuffer ringBuffer = disruptor.getRingBuffer();
disruptor.handleEventsWith(new ObjectEventConsumer2());
disruptor.start();
ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
log.info("MQ消费者将接收的消息放入Disruptor2===content:" + content);
producer.onData(content);
}
}
/**
* 监听WebServerInitializedEvent事件,用来获取Servlet容器初始化的端口
*/
@Slf4j
@Component
public class ServiceInfoUtil implements ApplicationListener {
private static WebServerInitializedEvent event;
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
ServiceInfoUtil.event = event;
}
public int getPort() {
int port = event.getWebServer().getPort();
return port;
}
}