先来看下面一段代码
@Autowired
private RabbitTemplate rabbitTemplate;
private void sendKernel(Message message){
String topic=message.getTopic();
String routingKey=message.getRoutingKey();
CorrelationData correlationData=new CorrelationData(
String.format("%s#%s", message.getMessageId(), System.currentTimeMillis())
);
rabbitTemplate.convertAndSend(topic,routingKey,message,null,correlationData);
log.info("#RabbitBrokerImpl.sendKernel# send to rabbitmq, messageid: {}",
message.getMessageId());
}
sendKernel()
方法很容易理解,根据传入消息使用rabbitTemplate
来发送消息。这样做的效率并不是很高,我们可以使用线程池来异步发送消息。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
当然,也可以不使用Executors
类,自己创建一个ExecutorService
。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Creates a new ThreadPoolExecutor with the given initial parameters.
Parameters:
corePoolSize - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
maximumPoolSize - the maximum number of threads to allow in the pool
keepAliveTime - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit - the time unit for the keepAliveTime argument
workQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
threadFactory - the factory to use when the executor creates a new thread
handler - the handler to use when execution is blocked because the thread bounds and queue capacities are reached
前面几个参数很好理解,这里主要解释下后面几个参数。
workQueue
任务队列:用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。
theadFactory
看名字就明白是用于创建Thread类的。
RejectedExecutionHandler
:当Executor已经关闭(即执行了executorService.shutdown()方法后),并且Executor将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute()中提交的新任务将被拒绝.
在以上述情况下,execute 方法将调用其 RejectedExecutionHandler 的RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。
最终,我们实现的线程池如下:
@Slf4j
public class AsyncBaseQueue {
public static final int THREAD_SIZE = Runtime.getRuntime().availableProcessors();
public static final int QUEUE_SIZE = 10000;
public static ExecutorService executorService =
new ThreadPoolExecutor(THREAD_SIZE, THREAD_SIZE, 60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_SIZE), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread();
t.setName("rabbitmq_client_async_sender");
return t;
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.error("async sender is error rejected, runnable:{}, executor:{}",
r, executor);
}
});
public static void submit(Runnable r){
executorService.submit(r);
}
}
所以在sendKernel()
方法中,可以有如下调用:
private void sendKernel(Message message){
//使用线程池进行异步提交
AsyncBaseQueue.submit(new Runnable() {
@Override
public void run() {
String topic=message.getTopic();
String routingKey=message.getRoutingKey();
CorrelationData correlationData=new CorrelationData(
String.format("%s#%s", message.getMessageId(), System.currentTimeMillis())
);
rabbitTemplate.convertAndSend(topic,routingKey,message,null,correlationData);
log.info("#RabbitBrokerImpl.sendKernel# send to rabbitmq, messageid: {}",
message.getMessageId());
}
});
}
不过即使实现了异步提交,还有需要改进的地方。我们看到,首先我们注入了:
@Autowired
private RabbitTemplate rabbitTemplate;
虽然实现了线程池,完成了多线程,但是RabbitTemplate是由Spring帮我们注入的,默认是单例的。所以,我们还需要把RabbitTemplate池化。
池化有两个好处:
实现方式直接看代码:
@Slf4j
@Component
public class RabbitTemplateContainer implements RabbitTemplate.ConfirmCallback {
private Map<String /* TOPIC */, RabbitTemplate> rabbitMap = Maps.newConcurrentMap();
private Splitter splitter=Splitter.on("#");
/* 连接工厂 */
@Autowired
private ConnectionFactory connectionFactory;
public RabbitTemplate getTemplate(Message message) throws MessageRuntimeException {
Preconditions.checkNotNull(message);
String topic = message.getTopic();
RabbitTemplate rabbitTemplate = rabbitMap.get(topic);
if (rabbitTemplate != null) {
return rabbitTemplate;
}
log.info("#RabbitTemplateContainer.getTemplate# topic: {} is not exist, create one",topic);
RabbitTemplate newRabbitTemplate=new RabbitTemplate(connectionFactory);
newRabbitTemplate.setRetryTemplate(new RetryTemplate());
newRabbitTemplate.setRoutingKey(message.getRoutingKey());
newRabbitTemplate.setExchange(topic);
// TODO: 2020-02-27 对message的序列化方式
// newRabbitTemplate.setMessageConverter();
String messageType=message.getMessageType();
if(!MessageType.RAPID.equals(messageType)){
//需要设置callback
newRabbitTemplate.setConfirmCallback(this);
}
rabbitMap.put(topic,newRabbitTemplate);
return rabbitMap.get(topic);
}
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
// TODO: 2020-02-27 具体的应答
List<String> strings=splitter.splitToList(correlationData.getId());
String messageId=strings.get(0);
long sendTime=Long.parseLong(strings.get(1));
if (b){
log.info("send message is OK, confirm messageId: {}, send time: {}",messageId,sendTime);
}else{
log.error("send message is Failed, confirm messageId: {}, send time: {}",messageId,sendTime);
}
}
}
我们首先解释,为什么使用Map
,使用Map可以将不同topic的路由规则与rabbitTemplate绑定,我们可以为每一种不同的topic来定制化不同的rabbitTemplate,比如有的rabbitTemplate可以设置回调setConfirmCallback()
,有的不需要设置回调。
其次,需要解释为什么使用的是ConcurrentMap
(代码中使用的google api来初始化的,不影响我们的理解)。
java8是这么解释的:
ConcurrentMap : A Map providing thread safety and atomicity guarantees.
在多线程环境下,使用普通的Map进行put操作会引起死循环,导致CPU利用率接近100%,普通的Map在并发执行put操作时会引起死循环,是因为多线程会导致普通的Map的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。所以这里使用线程安全且高效的ConcurrentMap(一般是ConcurrentHashMap) 即可。
其他代码就是为了实现不同topic定制化的rabbitTemplate而写的了,不做赘述。