一个Mule应用就是一个service集合的协作体,service按以下3个阶段处理消息:
Connector连接器 接收阶段
Service component组件 处理阶段
Connector连接器 分发阶段
Mule性能调优就是针对每个service分析和改进上述3个阶段,你可以对所有service采用统一的调优方式、进一步对每个service定义独立非更优化的方式。
关于线程池
来到mule的每个请求都被一个专属线程处理(类似webserver),连接器的接收器使用线程池来接收并处理入站请求。注意,mule可以同步或者异步的发送消息,默认情况下,消息是异步的,也就是不要求响应的消息是“fire-and-forget——发射后不管”方式(类似于自动制导导弹),如果需要响应,你必须把service配置成同步的。如果配置是异步的,要使用连接器的接收器和分发器,如果是同步的只使用连接器的接收器。
如果使用同步方式,处理一条消息将只使用一个线程贯穿mule的内部。
如果使用异步方式,接收器用一个线程把消息发送到component、随后就转移到component线程、然后接收器线程就被交还给接收器线程池了(接收器将重用这条线程)
component处理完异步消息以后,消息就被发送给一个dispatcher分发器线程,接收器、component组件、分发器都拥有各自的线程池。如果是同步的那么只有接收器具备线程池。
关于Threading Profiles线程描述
线程描述定义了mule中的线程池的行为。针对每个接收器线程池、组件线程池和分发器线程池你都可以单独定义它。最重要的设置是maxThreadsActive:最大活跃线程数。
关于Pooling Profiles池描述
和单组件不同,每个PooledJavaComponent池化组件都有一个组件池,容纳多个组件实例来处理并发请求。service的pooling profile配置了这个组件池。最重要的设置是maxActive:最大活跃数、指定了最多的用于处理并发请求的组件实例数量——也就是支持的最大并发数。注意这个设置要与接收器线程池的maxThreadsActive设置一致。你可以使用mule HQ来监控组件池、监视正在使用的组件实例的最大数量,以帮助你调优组件和线程的数量。
计算线程
要计算线程数必须考虑以下因素:
并发用户请求数:通常情况,对service来说就是inbound入站能够同时处理的请求数。在连接器级别,并发用户请求就是所有使用了这个连接器的service的并发用户请求数之和。典型情况下,你应该从业务需求视角决定这个并发用户请求数。
处理时间:从连接器的接收器开始执行到结束处理消息、连接器的分发器发送响应给outbound出站、或者接收器响应回给客户端的平均处理时间。一般你应该根据单元测试决定这个处理时间。
响应时间:如果一个service以同步模式运行,响应时间就是客户端等待响应到达的实际时间。如果是异步的那么就是请求到达mule直到从outbound出去的总时间。在线程池场景下,并不一定总是有线程可用,如果是这样的话,请求会被扔到内部线程池的work queue作业队列、等待下一个可用线程(类似ThreadPoolExecutor的BlockingQueue workQueue)。因此,响应时间这么算:Response time = average of thread pool waiting time in work queue + average of processing time。
超时时间:如果你的业务需求指定了最大等待时间,那就是它了。
明确上述因素以后,你可以估计出你的service线程池和接收器线程池的maxThreadsActive 和 maxBufferSize,推导公式:
Concurrent user requests = maxThreadsActive + maxBufferSize
maxThreadsActive 就是活跃线程数、maxBufferSize 就是可以在队列中等待被处理的请求数。
计算service线程
业务需求决定了每个service处理的并发数。例如,一个service可能需要处理50个并发、另一个需要40个。这个数值一般可以通过service标签的maxThreadsActive属性设置。如果你需要对同步处理设置超时,你得对每个service做些计算估计:
1. 运行同步测试用例来求出响应时间
2. 从业务需求能允许的超时时间中扣除响应时间就是你的最大等待时间(maximum wait time = timeout time - response time).
3. 用maximum wait time最大等待时间除以response time响应时间来求得顺序执行的batches批次(batches =
maximum wait time / response time). 请求在队列中等待,直到第一批次完成、然后第一批次的线程可以再回收给下一批次利用。
4. 并发用户请求数除以批次以求得线程数、也就是service的maxThreadsActive设置(where maxThreadsActive = concurrent user requests / processing batches). 这就是可以并发运行一个service的总线程数。
5. maxBufferSize等于concurrent user requests并发用户请求数减去maxThreadsActive(maxBufferSize = concurrent user requests - maxThreadsActive). 这就是允许在队列中等待可用线程的请求数——应该就是在构造ThreadPoolExecutor时代第五个参数:BlockingQueue workQueue阻塞队列,这个阻塞队列实际上就是在一个对象monitor上等待获取对象执行权/锁的线程队列,它属于Java.util.concurrent包的并发集合范畴。
穿插队列Queue与阻塞队列BlockingQueue详解
普通队列测试:
Java代码 收藏代码
package com.test;
import java.util.LinkedList;
import java.util.Queue;
public class QueueTest {
public static void main(String s[]){
Queue queue = new LinkedList();/*把这里换为PriorityQueue()则下属输出顺序变为:
Four、One、Three、Three —— 按字符串自然排序顺序(通过其java.util.Comparable实现或者根据传递给构造函数的java.util.Comparator 实现*/
queue.offer(“One”);
queue.offer(“Two”);
queue.offer(“Three”);
queue.offer(“Four”);
System.out.println(“Head of queue is: ” + queue.poll());//Head of queue is: One
System.out.println(“Head of queue is: ” + queue.poll());//Head of queue is: Two
System.out.println(“Head of queue is: ” + queue.peek());//Head of queue is: Three
System.out.println(“Head of queue is: ” + queue.peek());//Head of queue is: Three
}
}
java.util.concurrent.ConcurrentLinkedQueue是基于链接节点的、线程安全的队列。并发访问不需要同步。因为它在队列的尾部添加元素并从头部删除它们,所以只要不需要知道队列的大小,ConcurrentLinkedQueue 对公共集合的共享访问就可以工作得很好。收集关于队列大小的信息会很慢,需要遍历队列。
新的java.util.concurrent包加入了BlockingQueue接口和5个阻塞队列类。阻塞队列实质上是一种带有一点扭曲的FIFO数据结构,不是立即从队列中添加或者删除元素,线程执行操作被阻塞,直到有空间或者元素可用。 BlockingQueue接口的Javadoc 给出了阻塞队列的基本用法,生产者中的 put() 操作会在没有空间可用时阻塞,而消费者的take() 操作会在队列中没有任何东西时阻塞——这么看实际上BlockingQueue的作用在于替你完成了传统的生产者消费者模型里面你自己建立的同步信号量。
五个队列所提供的各有不同:
ArrayBlockingQueue :一个由数组支持的有界队列
LinkedBlockingQueue :一个由链接节点支持的可选有界队列
PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列,它利用所包含元素的 Comparable 排序顺序来以逻辑顺序维护元素。可以将它看作TreeSet的可能替代
DelayQueue :一个由优先级堆支持的、基于时间的调度队列,新的 DelayQueue 实现可能是其中最有意思(也是最复杂)的一个。加入到队列中的元素必须事先新的Delayed接口(只有一个方法:long getDelay(java.util.concurrent.TimeUnit unit) )。因为队列的大小没有界限,添加可以立即返回,但是在延迟时间过去之前,不能从队列中取出元素。如果多个元素完成了延迟,那么最早失效/失效时间最长的元素将第一个取出。
SynchronousQueue :一个利用 BlockingQueue 接口的简单聚集(rendezvous)机制,最简单,它没有内部容量,它就像线程之间的手递手机制,在队列中加入一个元素的生产者会等待另一个线程的消费者,当这个消费者出现,这个元素就直接在消费者和生产者之间传递,永远不会加入到阻塞队列。
BlockingQueue示例:
Java代码 收藏代码
package com.test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue queue = new ArrayBlockingQueue(5);
Producer p = new Producer(queue);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
new Thread(p).start();
new Thread(c1, “c1”).start();
new Thread(c2, “c2”).start();
}
}
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
for (int i = 0; i < 100; i++) {
queue.put(produce());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
String produce() {
String temp = "" + (char) ('A' + (int) (Math.random() * 26));
System.out.println("produce " + temp);
return temp;
}
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) {
queue = q;
}
public void run() {
try {
for (int i = 0; i < 100; i++) {
consume(queue.take());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
void consume(String x) {
System.out.println(Thread.currentThread().getName() + " consume: " + x);
}
}
回到性能调优主题
假定一个service必须能够处理200个并发用户请求,你的超时时间是10秒,响应时间是2秒,让你的最大等待时间为8秒(10 - 2 = 8),最大等待时间除以响应时间则得到批次为4(8 / 2 = 4)。最终,并发用户请求除以批次得到service的maxThreadsActive设置为50(200 / 4 = 50),再用并发用户请求减去maxThreadsActive得到maxBufferSize设置为150。总结上述限定超时的同步处理参数求取公式:
• Maximum wait time = timeout time - response time
• Batches = maximum wait time / response time
• maxThreadsActive = concurrent user requests / batches
• maxBufferSize = concurrent user requests - maxThreadsActive