一般来说,对于那些处理时间比较长,需要马上响应并且做成异步后不会影响其它流程的做成异步。比如用户注册成功后发邮件给用户,这个可以在用户注册成功后马上返回,而不需要等到邮件发送成功才返回。异步有二种方式:内存异步和JMS异步。
内存异步即把要处理的请求放到内存队列中,然后由多个线程去消费。这种方式性能比较高,但是会存在请求丢失和内存溢出的风险。比如服务器突然down机,那么队列中未处理完的请求就会丢失;如果请求处理时间太长,并且请求一直在增加,即生产者速度要大于消费者速度时,就存在内存溢出的风险。这种适合对数据丢失不敏感,并且生产者速度要小于消费者速度的场合。
JMS异步即把要处理的请求先持久化到数据库,然后多个线程去消费。这种方式性能相对来说要慢一些,但是不会出现请求丢失和内存溢出的情况。这种适合不允许数据丢失,请求处理时间比较长的场合。
异步框架,使用元数据和AOP的方式,将内存异步和JMS异步统一起来。
package com.konceptusa.infinet.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 方法异步
* @author Jwin
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target( {ElementType.METHOD })
@Documented
public @interface Async
{
//异步队列名称,默认为类名.方法名
String queueName() default "";
//消费者线程数
int threadCount() default 10;
//队列报警值,仅对内存异步有效
int warningQueueSize() default 100;
//是否使用jms异步,默认为内存异步
boolean jmsAsync() default false;
}
package com.konceptusa.infinet.imsupport.aop;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import org.apache.activemq.command.ActiveMQQueue;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.listener.DefaultMessageListenerContainer;
import com.konceptusa.framework.asyn.AsynCallInfo;
import com.konceptusa.framework.asyn.jms.AsynExecuteMessageListener;
import com.konceptusa.framework.core.jmsservice.ObjectMessageSenderImpl;
import com.konceptusa.framework.core.jmsservice.SendMessageFailedException;
import com.konceptusa.infinet.annotation.Async;
/**
* 异步AOP
* @author Jwin
*
*/
public class AsyncInterceptor implements DisposableBean
{
private final static Log LOG = LogFactory.getLog(AsyncInterceptor.class);
private Map<String, ThreadPoolExecutor> executorMap = new HashMap<String, ThreadPoolExecutor>();
private Map<String, ObjectMessageSenderImpl> messageSenderMap = new HashMap<String, ObjectMessageSenderImpl>();
private Map<String, DefaultMessageListenerContainer> listerContainerMap = new HashMap<String, DefaultMessageListenerContainer>();
private ConnectionFactory connectionFactory;
public Object async(final ProceedingJoinPoint pjp, Async async) throws Throwable
{
if(!async.jmsAsync())
{
memAsync(pjp, async);
}
else
{
jmsAsync(pjp,async);
}
return null;
}
private void jmsAsync(ProceedingJoinPoint pjp, Async async)
{
ObjectMessageSenderImpl objectMessageSender = null;
String queueName = getQueueName(pjp, async);
synchronized (messageSenderMap)
{
objectMessageSender = messageSenderMap.get(queueName);
if(objectMessageSender == null)
{
LOG.info("init jms queueName " + queueName + " thread count " + async.threadCount() );
objectMessageSender = new ObjectMessageSenderImpl();
Queue queue = new ActiveMQQueue(queueName);
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory);
jmsTemplate.setDefaultDestination(queue);
objectMessageSender.setJmsTemplate(jmsTemplate);
messageSenderMap.put(queueName, objectMessageSender);
DefaultMessageListenerContainer defaultMessageListenerContainer = new DefaultMessageListenerContainer();
defaultMessageListenerContainer.setConcurrentConsumers(async.threadCount());
defaultMessageListenerContainer.setConnectionFactory(connectionFactory);
defaultMessageListenerContainer.setDestination(queue);
defaultMessageListenerContainer.setCacheLevelName("CACHE_CONSUMER");
defaultMessageListenerContainer.setMaxMessagesPerTask(20);
AsynExecuteMessageListener messageListener = new AsynExecuteMessageListener();
messageListener.setTarget(pjp.getTarget());
defaultMessageListenerContainer.setMessageListener(messageListener);
defaultMessageListenerContainer.afterPropertiesSet();
listerContainerMap.put(queueName, defaultMessageListenerContainer);
}
}
AsynCallInfo asynCallInfo = new AsynCallInfo(pjp.getSignature().getName(),pjp.getArgs());
objectMessageSender.asynSendMessage(asynCallInfo);
}
private String getQueueName(ProceedingJoinPoint pjp, Async async)
{
String queueName = async.queueName();
//如果没有指定队列名,则以 类名.方法名 为队列名
if(StringUtils.isBlank(queueName))
{
queueName = pjp.getTarget().getClass().getSimpleName() + "." + pjp.getSignature().getName();
}
return queueName;
}
private void memAsync(final ProceedingJoinPoint pjp, Async async)
{
ThreadPoolExecutor executor = null;
final String queueName = getQueueName(pjp, async);
synchronized (executorMap)
{
executor = executorMap.get(queueName);
if(executor == null)
{
LOG.info("init mem queueName " + queueName + " thread count " + async.threadCount() + " warning queue size " + async.warningQueueSize());
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(async.threadCount());
executorMap.put(queueName, executor);
}
}
Thread thread = new Thread(){
@Override
public void run()
{
try
{
pjp.proceed();
} catch (Throwable e)
{
LOG.error("async call fail", e);
}
}
};
executor.execute(thread);
int size = executor.getQueue().size();
if( size >= async.warningQueueSize())
{
LOG.warn("queueName " + queueName + " exceeds warning queue size " + async.warningQueueSize() + " current size " + size);
}
}
public void destroy() throws Exception
{
for(String key : executorMap.keySet())
{
LOG.info("destroy mem queue " + key);
ThreadPoolExecutor executor = executorMap.get(key);
executor.shutdown();
executor.awaitTermination(2, TimeUnit.SECONDS);
}
for(String key : listerContainerMap.keySet())
{
LOG.info("destroy jms queue " + key);
DefaultMessageListenerContainer container = listerContainerMap.get(key);
container.destroy();
}
executorMap.clear();
listerContainerMap.clear();
}
public void setConnectionFactory(ConnectionFactory jmsConnectionFactory)
{
this.connectionFactory = jmsConnectionFactory;
}
}
对需要异步的方法加入@Aysnc 即可实现异步
内存异步
@Async
public String memAsyncFind(Long userid)
{
System.out.println("mem asyncFind userid " + userid);
return userid.toString();
}
JMS异步
@Async(jmsAsync=true)
public String jmsAsyncFind(Long userid)
{
System.out.println("jms asyncFind userid " + userid);
return userid.toString();
}
AOP配置
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd " default-autowire="byName"> <bean id="asyncInterceptor" class="com.konceptusa.infinet.imsupport.aop.AsyncInterceptor"> </bean> <aop:config> <aop:pointcut id="asyncPointcut" expression="execution(* com.konceptusa.infinet.test.service..*.*(..)) and @annotation(async)" /> <aop:aspect id="asyncAspect" ref="asyncInterceptor"> <aop:around method="async" pointcut-ref="asyncPointcut" /> </aop:aspect> </aop:config> </beans>
Activemq配置
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:amq="http://activemq.org/config/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://activemq.org/config/1.0 http://activemq.apache.org/schema/activemq-core.xsd http://activemq.apache.org/camel/schema/spring http://activemq.apache.org/camel/schema/spring/camel-spring.xsd"> <broker xmlns="http://activemq.org/config/1.0" brokerName="test" dataDirectory="${activemq.data}/data/test"> <persistenceAdapter> <journaledJDBC journalLogFiles="10" dataDirectory="${activemq.data}/persistence" dataSource="#activemq-ds" createTablesOnStartup="true" useDatabaseLock="false" /> </persistenceAdapter> <systemUsage> <systemUsage> <memoryUsage> <memoryUsage limit="100 mb" percentUsageMinDelta="20" /> </memoryUsage> <storeUsage> <storeUsage limit="1 gb" /> </storeUsage> </systemUsage> </systemUsage> </broker> <bean id="activemq-ds" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${database.driverName.mq}" /> <property name="jdbcUrl" value="${database.url.mq}" /> <property name="user" value="${database.user.mq}" /> <property name="password" value="${database.password.mq}" /> <property name="initialPoolSize" value="${database.initialSize}" /> <property name="minPoolSize" value="${database.initialSize}" /> <property name="maxPoolSize" value="${database.maxActive}" /> <property name="maxIdleTime" value="${database.maxIdleTime}" /> <property name="acquireIncrement" value="${database.acquireIncrement}" /> <property name="numHelperThreads" value="${database.numHelperThreads}" /> <property name="automaticTestTable" value="${database.automaticTestTable}" /> <property name="maxStatements" value="${database.maxStatements}" /> <property name="maxStatementsPerConnection" value="${database.maxStatementsPerConnection}" /> <property name="idleConnectionTestPeriod" value="${database.idleConnectionTestPeriod}" /> </bean> <connectionFactory xmlns="http://activemq.org/config/1.0" id="jmsConnectionFactory" brokerURL="vm://test" /> <bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory"> <property name="targetConnectionFactory" ref="jmsConnectionFactory" /> </bean> </beans>