DDMQ作为典型的消息队列,现简单分析使用过程如下。
准备:
group: cg_whb_test
topic:whb_test_topic1
topic:whb_test_topic2
topic:whb_test_topic3
Producer
生产者使用比较简单,直接调用send方法即可。一般情况下,可以自行封装一层send。如:
/**
* 最简单的用法,只指定topic和字符串消息体(默认按UTF-8编码)。
* @param body body
* @param topic topic
* @return boolean
*/
public boolean send(String body, String topic) {
try {
CarreraProducer producer = this.buildCarreraProducer(topic);
//指定iplist方式,服务发现方式不满足时,再用此方式
//CarreraConfig config = getNoSDConfig();
//CarreraProducer producer = new CarreraProducer(config);
producer.start(); // 启动生产实例。这里会进行一些初始化操作。
//0.最简单的用法,只指定topic和字符串消息体(默认按UTF-8编码)。
Result result = producer.send(topic, body);
//
//--------------- 关闭生产实例 ---------------
//
producer.shutdown();
if (null == result) {
log.error("消息发送失败,body:[{}]", body);
return false;
}
//强烈建议一定要在日志中打印生产的结果。
// OK 和 CACHE_OK 两个结果都可以认为是生产成功了。
if (result.getCode() == CarreraReturnCode.OK
|| result.getCode() == CarreraReturnCode.CACHE_OK) {
log.info("produce success");
return true;
}
if (result.getCode() > CarreraReturnCode.CACHE_OK) {
log.info("produce failure, body[{}], message:{}", body, result.getMsg()); // 失败的情况,根据code和msg做相应处理。
return false;
}
return false;
} catch (Exception e) {
log.error("发送消息异常,body:{}, e:{}", body, e);
return false;
}
}
Comsumer
1. 单元测试
测试代码分为3部分:
- 配置方法:配置消费组等。
- 消费方法:主要的消费逻辑,接收到message后的处理。
- main主方法:用于启动消费方法。
public class CarreraConsumerExampleTest1 {
private static final Logger LOGGER = LoggerFactory.getLogger(CarreraConsumerExampleTest1.class);
private static final String groupName = "cg_whb_test";
public static void simpleExample(long consumeTime) throws InterruptedException {
//CarreraConfig config = getConfig("cg_your_groupName", "127.0.0.1:9713");
CarreraConfig config = getSDConfig();
final CarreraConsumer consumer = new CarreraConsumer(config);
consumer.startConsume(new MessageProcessor() {
@Override
public Result process(Message message, Context context) {
LOGGER.info("收到如下消息:{}",message.value); //收到如下消息:java.nio.HeapByteBuffer[pos=83 lim=109 cap=139]
LOGGER.info("process key:{}, value.length:{}, offset:{}, context:{}", message.getKey(),
message.getValue().length, message.getOffset(), context);
return Result.SUCCESS;
}
}, 5); // 每台server有2个线程,额外的一个随机分配。
Thread.sleep(consumeTime); // consume for 10 seconds
consumer.stop(); //1分钟便于测试,可以注释掉
}
public static void main(String[] args) throws TTransportException, InterruptedException {
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
LogManager.shutdown(); //shutdown log4j2.
}
}));
LOGGER.info("start simpleExample...");
simpleExample(60 * 1000);
}
// 不使用服务发现
public static CarreraConfig getConfig(String groupName, String ipList) {
CarreraConfig config = new CarreraConfig(groupName, ipList);
config.setRetryInterval(500); // 拉取消息重试的间隔时间,单位ms。
config.setMaxBatchSize(8); //一次拉取的消息数量。
return config;
}
// 使用服务发现
private static CarreraConfig getSDConfig() {
CarreraConfig config = new CarreraConfig(groupName, Env.valueOf("Test"));
config.setRetryInterval(500); // 拉取消息重试的间隔时间,单位ms。
config.setMaxBatchSize(8); //一次拉取的消息数量。
return config;
}
}
启动main方法,并在控制台发送一条消息,看到结果:
INFO EndpointManagerSD:129 - update service completely
10:11:17,110 INFO CarreraConsumerExampleTest1:32 - 收到如下消息:java.nio.HeapByteBuffer[pos=82 lim=108 cap=138]
10:11:17,113 INFO CarreraConsumerExampleTest1:33 - process key:1nDFsGfZ0Q9jz0fo, value.length:26, offset:1, context:Context(groupId:cg_whb_test, topic:whb_test_topic1, qid:R_test_test-02_0)
2. springboot测试
在没有main
方法启动消费方法的情况下,需要将消费方法发布为bean
,并依靠容器来启动消费方法。
在前一节的例子中,主要是运行simpleExample
方法。在spring bean注入的理念下,将该方法作为bean发布。
并且,我们注意到,消费配置中,我们仅仅配置了消费组,而没有配置topic,这意味着所有绑定该组的topic,将会被所有的startConsume
随机消费。我们一般需要使用独立的方法消费指定的topic。官方例子有如下说明:
/**
* 如果某些topic中消息流量特别大,可以使用独立的线程消费该topic。
*
* @param consumeTime 消费的时间
* @throws InterruptedException
*/
public static void exampleWithExtraThreadsForSomeTopic(long consumeTime) throws InterruptedException {
CarreraConfig config = getConfig("cg_your_groupName", "127.0.0.1:9713");
final CarreraConsumer consumer = new CarreraConsumer(config);
Map<String/*Topic*/, Integer/*该topic额外的线程数*/> extraThreads = new HashMap<String, Integer>();
extraThreads.put("test-0", 2);
//该配置下有6个消费线程,有4个消费test-0和test-1的消息, 另外2个线程只消费test-0.
consumer.startConsume(new MessageProcessor() {
@Override
public Result process(Message message, Context context) {
LOGGER.info("process key:{}, value.length:{}, offset:{}, context:{}", message.getKey(),
message.getValue().length, message.getOffset(), context);
return Result.SUCCESS;
}
}, 4, extraThreads);
Thread.sleep(consumeTime);
consumer.stop();
}
注意上述中 该注释对应的消费关系
//该配置下有6个消费线程,有4个消费test-0和test-1的消息, 另外2个线程只消费test-0
- 当匿名内部类后第一个参数为0时,仅消费指定的topic;
- 当匿名内部类后第一个参数不为0时,有其他topic时也会消费。
以下测试代码中,topic1
,topic2
为指定方法消费;topic3
由于没有指定,会随机选一个startConsume
消费。
package cn.whbing.mqboot.test;
import com.xiaojukeji.carrera.consumer.thrift.Context;
import com.xiaojukeji.carrera.consumer.thrift.Message;
import com.xiaojukeji.carrera.consumer.thrift.client.CarreraConfig;
import com.xiaojukeji.carrera.consumer.thrift.client.CarreraConsumer;
import com.xiaojukeji.carrera.consumer.thrift.client.MessageProcessor;
import com.xiaojukeji.carrera.sd.Env;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.HashMap;
import java.util.Map;
@SpringBootConfiguration
public class CarreraConsumerExampleTest1 {
private static final Logger LOGGER = LoggerFactory.getLogger(CarreraConsumerExampleTest1.class);
private static String groupName = "cg_whb_test";
private String topic1 = "whb_test_topic1";
private String topic2 = "whb_test_topic2";
@Bean
public CarreraConsumer getCarreraConsumer() {
CarreraConfig config = getSDConfig();
//指定iplist方式,服务发现方式不满足时,再用此方式
//CarreraConfig config = getNoSDConfig("cg_capricornus_client", "10.95.121.43:9713;10.95.137.66:9713");
CarreraConsumer carreraConsumer = new CarreraConsumer(config);
//指定某topic,消费进程
Map<String/*Topic*/, Integer/*该topic额外的线程数*/> extraThreads = new HashMap<>();
extraThreads.put(topic1, 1);
carreraConsumer.startConsume(new MessageProcessor() {
@Override
public Result process(Message message, Context context) {
LOGGER.info("测试1收到如下消息:{}",message.value); //收到如下消息:java.nio.HeapByteBuffer[pos=83 lim=109 cap=139]
LOGGER.info("process key:{}, value.length:{}, offset:{}, context:{}", message.getKey(),
message.getValue().length, message.getOffset(), context);
return Result.SUCCESS;
}
}, 0,extraThreads);
return carreraConsumer;
}
@Bean
public CarreraConsumer getCarreraConsumer2() {
CarreraConfig config = getSDConfig();
//指定iplist方式,服务发现方式不满足时,再用此方式
//CarreraConfig config = getNoSDConfig("cg_capricornus_client", "10.95.121.43:9713;10.95.137.66:9713");
CarreraConsumer carreraConsumer = new CarreraConsumer(config);
//指定某topic,消费进程
Map<String/*Topic*/, Integer/*该topic额外的线程数*/> extraThreads = new HashMap<>();
extraThreads.put(topic2, 1);
carreraConsumer.startConsume(new MessageProcessor() {
@Override
public Result process(Message message, Context context) {
LOGGER.info("测试2收到如下消息:{}",message.value); //收到如下消息:java.nio.HeapByteBuffer[pos=83 lim=109 cap=139]
LOGGER.info("process key:{}, value.length:{}, offset:{}, context:{}", message.getKey(),
message.getValue().length, message.getOffset(), context);
return Result.SUCCESS;
}
}, 0,extraThreads);
return carreraConsumer;
}
// 使用服务发现
private static CarreraConfig getSDConfig() {
CarreraConfig config = new CarreraConfig(groupName, Env.valueOf("Test"));
config.setRetryInterval(500); // 拉取消息重试的间隔时间,单位ms。
config.setMaxBatchSize(8); //一次拉取的消息数量。
return config;
}
}
其他说明:spring bean
在初始化时,即@Bean
的方法里,会被执行一次。
@Bean
public String test(){
System.out.println("测试测试测试");
return "返回结果";
}