内容平台消息队列异步静态化实现
一、重构前系统面对的问题
随着分公司站点的建设,后台稿件的数量越来越多,加上自动签发的需求,
大量的静态化请求非常容易造成系统宕机,于是引入消息队列做异步处理,平滑请求处理曲线。
二、静态化消息发送端工程
首先并不是全部的静态化请求都需要通过消息队列来处理,在项目配置文件中添加了一个static_mq的参数控制是否静态化。
(1)处理请求,并对静态化相关的数据进行封装
收到静态化请求后,后台首先进行判断,是否开启消息队列静态化,
确认后实例化一个MQ的线程,将相关的静态化信息保存到线程信息中,然后将线程提交给线程池调度,如果未开启消息队列,则按照原有的逻辑进行静态化。
以首页静态化为例:
String isMQStatic = ConfUtil.getString("static_mq"); if (isMQStatic != null && "true".equals(isMQStatic.trim())) { // 如果启用了消息队列,则发起一个mq的线程执行 IndexStaticPageThread indexStaticPage = new IndexStaticPageThread(site.getId(), jmsTemplate); // 将线程提交到线程池,提高执行的效率 StaticThreadPool.threadPool().execute(indexStaticPage); } else { staticPageSvc.index(site); }
栏目和稿件的类似,但是需要将静态化的信息添加到线程实例中,下面分别是栏目消息封装:
if (isMQStatic != null && "true".equals(isMQStatic.trim())) { // 增加MQ的消息发送方式 begin Map<Object, Object> paramMap = new HashMap<Object, Object>(); paramMap.put("siteId", site.getId()); paramMap.put("contextPath", request.getContextPath()); paramMap.put("isStaticFirst", staticFirst); paramMap.put("isContainChild", containChild); paramMap.put("startDate", startDate); ChannelStaticPageThread channelStaticPage = new ChannelStaticPageThread(channelId, jmsTemplate, paramMap); StaticThreadPool.threadPool().execute(channelStaticPage); // 增加MQ的消息发送方式 end } else { count = staticPageSvc.channel(staticFirst, site.getId(), channelId, startDate, containChild, request); }
稿件消息封装:
if (isMQStatic != null && "true".equals(isMQStatic.trim())) { Map<Object, Object> paramMap = new HashMap<Object, Object>(); paramMap.put("siteId", siteId); paramMap.put("startDate", startDate); paramMap.put("endDate", endDate); paramMap.put("needRegenerateAll", needRegenerateAll); paramMap.put("subCommand", "pages"); paramMap.put("isContainChild", true); ChannelStaticPageThread channelStaticPage = new ChannelStaticPageThread(channelId, jmsTemplate, paramMap); StaticThreadPool.threadPool().execute(channelStaticPage); } else { count = staticPageSvc.content(siteId, channelId, startDate, endDate, needRegenerateAll); }
(2)发送消息到ActiveMQ中
消息线程通过传入的静态化信息构造并且实例化以后,在线程的执行体中做具体的消息绑定和发送工作。
下面以ChannelStaticPageThread为例来看一下具体是如何对消息队列进行封装和发送的:
public class ChannelStaticPageThread implements Runnable { private static final Logger log = LoggerFactory.getLogger(ChannelStaticPageThread.class); private Integer channelId; private JmsTemplate jmsTemplate; private Map<Object, Object> paramMap; private boolean needRegenerateAll, isStaticFirst, isContainChild = false; // 构造方法 public ChannelStaticPageThread(Integer channelId, JmsTemplate jmsTemplate, Map<Object, Object> paramMap) { this.channelId = channelId; this.jmsTemplate = jmsTemplate; this.paramMap = paramMap; } public void run() { log.info("thread...send message,channel static,channel_id:" + channelId); // 需要根据不同的情形,调用不同的queue String QueueName = "xhCloud.STATIC.AUTO"; Integer siteId = (Integer) paramMap.get("siteId"); String contextPath = (String) paramMap.get("contextPath"); Date startDate = (Date) paramMap.get("startDate"); Date endDate = (Date) paramMap.get("endDate"); if (paramMap.get("isStaticFirst") != null) isStaticFirst = (Boolean) paramMap.get("isStaticFirst"); if (paramMap.get("isContainChild") != null) isContainChild = (Boolean) paramMap.get("isContainChild"); if (paramMap.get("needRegenerateAll") != null) needRegenerateAll = (Boolean) paramMap.get("needRegenerateAll"); // 用包装过的taskNode发送 ChannelTaskEntity taskEntity = new ChannelTaskEntity(); taskEntity.setCommand("channel"); String subCommand = (String) paramMap.get("subCommand"); if (subCommand != null && subCommand.trim().length() > 0) { taskEntity.setSubCommand(subCommand); } else { taskEntity.setSubCommand("default"); } taskEntity.setChannelId(channelId); taskEntity.setSiteId(siteId); taskEntity.setContextPath(contextPath); taskEntity.setStaticFirst(isStaticFirst); taskEntity.setContainChild(isContainChild); taskEntity.setStartDate(startDate); taskEntity.setEndDate(endDate); taskEntity.setRegenerateAll(needRegenerateAll); InstantTaskNode<ChannelTaskEntity> instTaskNode = new InstantTaskNode<ChannelTaskEntity>(); instTaskNode.setMainPriority(TaskMainPriority.MEDIUM); instTaskNode.setTaskType(TaskType.INSTANT); instTaskNode.setEntity(taskEntity); jmsTemplate.convertAndSend(QueueName, instTaskNode); log.info("send success!channelid:" + channelId + " subCommand:" + subCommand); } }
这里的JmsTemplate来自于org.springframework.jms.core.JmsTemplate,
是一个消息传递的标准,具体的分析可以查看博文。
为了区分消息的优先级,在MQ中创建了若干个不同的队列,
不同的任务通过不同的消息队列发送;
jms.queue.static.auto=xhCloud.STATIC.AUTO jms.queue.static.medium=xhCloud.STATIC.MEDIUM jms.queue.static.high=xhCloud.STATIC.HIGH jms.queue.static.critical=xhCloud.STATIC.CRITICAL
(3)对线程池的处理
代码中维护了一个用于处理静态化操作的线程池,
是通过java.util.concurrent.ThreadPoolExecutor来创建的。
看一下具体的代码实现:
public class StaticThreadPool { private static int corePoolSize;// 池中所保存的线程数,包括空闲线程 private static int maximumPoolSize;// 池中允许的最大线程数 private static int keepAliveTime;// 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 private static int workQueue; // 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable // 任务。 private static ThreadPoolExecutor executor = null; private static String resource = "activeMQStaticThreadPool"; private static ResourceBundle rb = null; public static ThreadPoolExecutor threadPool() { if (rb == null) { rb = ResourceBundle.getBundle(resource); if (rb != null) { corePoolSize = Integer.parseInt(rb.getString("corePoolSize")); maximumPoolSize = Integer.parseInt(rb.getString("maximumPoolSize")); keepAliveTime = Integer.parseInt(rb.getString("keepAliveTime")); workQueue = Integer.parseInt(rb.getString("workQueue")); } else { System.out.println("thread pool property initialized failed!"); } } if (executor == null) { System.out.println(" Initialize StaticThreadPool Executor"); executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(workQueue), new ThreadPoolExecutor.AbortPolicy()); } return executor; } }
可以看到通过读取配置文件的形式设置了线程池的相关参数,
下面是具体的配置文件:
#线程池维护线程的最少数量 corePoolSize=10 #线程池维护线程的最大数量 maximumPoolSize=50 #线程池维护线程所允许的空闲时间 keepAliveTime=60000 #线程池所使用的缓冲队列数 workQueue=10
线程池的任务队列使用了java.util.concurrent.ArrayBlockingQueue,
这是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
在代码中添加了一个threadPool()的静态方法,
通过传入配置文件中的参数,返回ThreadPoolExecutor实例,
使用了如下的构造方法:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
下面简单对参数进行说明:
corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。
runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。
maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。
ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。
RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。
以下是JDK1.5提供的四种策略。
AbortPolicy:直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务。
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉。
当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。
keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
另外这里线程池的创建可以看做是单例的应用。
(4)编写并提供首页/栏目/详情页等静态化的Dubbo接口
根据目前系统中对静态化请求类型的划分,对外提供了
staticIndexPageService、staticChannelPageService、
staticContentPageService等静态化服务。
下面是截取provider.xml的配置:
<!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="${dubbo.port}" /> <!-- 使用zookeeper注册中心暴露服务地址 --> <dubbo:registry protocol="zookeeper" address="192.168.75.138:2181,192.168.75.139:2181,192.168.75.140:2181,192.168.75.141:2181,192.168.75.142:2181"/> <!-- 静态化服务 --> <dubbo:service interface="com.bingyue.dubbo.StaticChannelPageService" ref="staticChannelPage" timeout="50000" version="2.2.006" /> <bean id="staticChannelPage" class="com.xinhuanet.statictpl.dubbo.impl.StaticChannelPageServiceImpl" /> <dubbo:service interface="com.bingyue.dubbo.StaticIndexPageService" ref="staticIndexPage" timeout="50000" version="2.2.006" /> <bean id="staticIndexPage" class="com.bingyue.dubbo.impl.StaticIndexPageServiceImpl" />
简单看一下Dubbo接口的编写:
public interface StaticContentPageService { public TaskEntity[] staticizeContentPage(Integer contentId, boolean isCheckedBeforePublished, boolean isBatchChange) throws StaticPageException, UnrecoverableException; }
这个Dubbo接口是对原有静态化服务的封装,最终的实现还是通过项目最初的静态化服务。
三、静态化消息处理端工程
回顾完了静态化消息发送端的设计和实现,再来看一下消费端工程是如何实现的。
消费端工程是一个Maven管理的多项目聚合工程,加上后期的功能扩展,形成了现在一个较大的工程:
(1)父工程下各个Maven项目的作用如下:
static-beans:
主要封装了基础的消息实体,主要是TaskEntity等几个抽象类,自定义了程序执行期间相关的异常,依赖static-utils项目。
下面以TaskEntity为例:
public abstract class TaskEntity implements Serializable { private final static Logger logger = LoggerFactory .getLogger(TaskEntity.class); private static final long serialVersionUID = 6139912897589483499L; final ObjectMapper mapper = ObjectMapperFactory.get(); TaskEntityType entityType; String command; // 静态化执行类型 String subCommand; // 静态化的二级执行类型,对于属性页静态化的任务,即为属性的类型 public String getSubCommand() { return subCommand; } public void setSubCommand(String subCommand) { this.subCommand = subCommand; } public String getCommand() { return command; } public void setCommand(String command) { this.command = command; } public TaskEntityType getEntityType() { return entityType; } public void setEntityType(TaskEntityType entityType) { this.entityType = entityType; } public String toJson() { try { return mapper.writeValueAsString(this); } catch (JsonProcessingException e) { logger.error("[{}]:[{}]", e.getClass().getName(), e.getMessage()); } return this.getCommand() + "," + this.getSubCommand(); } }
static-cachemanager:
配置了Infinispan 缓存模式,主要就是包装了org.infinispan.manager.DefaultCacheManager,代码很简单:
public class OptimizeCacheManager { public static DefaultCacheManager cacheManager = new DefaultCacheManager(); }
关于Infinispan ,简单了解一下就好,这里使用的是Infinispan的本地模式。
Infinispan 是个开源的数据网格平台。它公开了一个简单的数据结构(一个Cache)来存储对象。
看一下pom.xml的配置:
<dependencies> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-cachestore-leveldb</artifactId> <version>7.0.2.Final</version> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-spring</artifactId> <version>7.0.2.Final</version> </dependency> </dependencies>
static-dubboinvoke:
测试项目,主要测试Dubbo服务端提供的静态化接口是否有效。
static-event:
继承org.springframework.context.ApplicationEvent,实现了一个
TaskProcessErrorEvent,用来监听消息处理中的错误事件。
public class TaskProcessErrorEvent<T extends TaskEntity> extends ApplicationEvent { private static final long serialVersionUID = -4883903824786622364L; TaskNode<T> taskNode; public TaskProcessErrorEvent(TaskNode<T> source) { super(source); this.taskNode = source; } public TaskNode<T> getTaskNode() { return taskNode; } public void setTaskNode(TaskNode<T> taskNode) { this.taskNode = taskNode; } }
依赖static-beans项目。
static-event-channelhandler:
继承ApplicationListener,实现了一个ChannelTaskOccurredEventHandler。
static-event-handler:
类似static-event-channelhandler项目。
static-executor:
维护了一个任务调度的线程池,
实现方式和前面发起静态化线程的线程池类似,不同的是
这里使用Executors获得线程池实例的方法,
封装了两个线程池,一个是
java.util.concurrent.ThreadPoolExecutor
java.util.concurrent.ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,
被用来执行定时任务。
看一下代码:
public class OptimizeScheduledExecutor { public static ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1); public static ThreadPoolExecutor excutorBatch = (ThreadPoolExecutor) Executors.newSingleThreadExecutor(); static{ OptimizeScheduledExecutor.executor.setMaximumPoolSize(1); } }
static-process:
定义了消息转换和发送等的接口。
static-utils:
项目工具类。
static-xuan-beans:
这个项目在代码层面是继承static-beans工程的,
其中继承抽象类TaskEntity实现了针对首页、栏目和稿件静态化的三个消息实体对象:
ChannelTaskEntity、IndexTaskEntity和ContentTaskEntity。
继承抽象包装类TaskEntityWrapper实现了
ChannelTaskEntityWrapper、ContentTaskEntityWrapper和IndexTaskEntityWrapper。
继承ApplicationEvent实现了ChannelTaskOccurredEvent。
其他的mobile等项目是后期扩展添加,不在我们的讨论范围中。
下面重点看一下static-starter模块,分析一下相关流程,这是消费端项目启动的入口。
(2)从MQ中接收消息并处理
下面我们分析static-starter项目的配置和启动。
(3)相关的配置文件
ActiveMQ配置文件,application.properties:
jms.broker.url=failover:(tcp://192.168.xx.xx:61616?wireFormat.maxInactivityDuration=0,tcp://192.168.xx.xx:61617?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=1000 jms.username=test jms.password=test jms.queue.static.auto=STATIC.AUTO jms.queue.static.medium=STATIC.MEDIUM jms.queue.static.high=STATIC.HIGH jms.queue.static.critical=STATIC.CRITICAL
Dubbo消费端配置文件,consumer.xml:
Redis配置文件,供jesque读取,redis.properties;
redis.host=192.168.xx.xx redis.port=6379 redis.timeout=5000 redis.password=**** redis.namespace=resque redis.database=0 jesque.queue.name=bingyue
配置jesque信息,jesque.xml:
<bean id="config" class="net.greghaines.jesque.Config"> <constructor-arg value="${redis.host}" /> <constructor-arg value="${redis.port}" /> <constructor-arg value="${redis.timeout}" /> <constructor-arg value="${redis.password}" /> <constructor-arg value="${redis.namespace}" /> <constructor-arg value="${redis.database}" /> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg> <bean class="net.greghaines.jesque.utils.PoolUtils" factory-method="getDefaultPoolConfig" /> </constructor-arg> <constructor-arg value="${redis.host}" /> <constructor-arg value="${redis.port}" /> <constructor-arg value="${redis.timeout}" /> <constructor-arg value="${redis.password}" /> <constructor-arg value="${redis.database}" /> </bean> <bean id="jesqueClient" class="net.greghaines.jesque.client.ClientImpl"> <constructor-arg ref="config" /> <constructor-arg value="true" /> </bean> <bean id="failureDAO" class="net.greghaines.jesque.meta.dao.impl.FailureDAORedisImpl"> <constructor-arg ref="config" /> <constructor-arg ref="jedisPool" /> </bean> <bean id="keysDAO" class="net.greghaines.jesque.meta.dao.impl.KeysDAORedisImpl"> <constructor-arg ref="config" /> <constructor-arg ref="jedisPool" /> </bean> <bean id="queueInfoDAO" class="net.greghaines.jesque.meta.dao.impl.QueueInfoDAORedisImpl"> <constructor-arg ref="config" /> <constructor-arg ref="jedisPool" /> </bean> <bean id="workerInfoDAO" class="net.greghaines.jesque.meta.dao.impl.WorkerInfoDAORedisImpl"> <constructor-arg ref="config" /> <constructor-arg ref="jedisPool" /> </bean> <bean id="queueName" class="java.lang.String"> <constructor-arg value="${jesque.queue.name}"/> </bean>
配置加载MQ连接池等,mq-config.xml:
<amq:connectionFactory brokerURL="${jms.broker.url}" id="amqConnectionFactory" userName="${jms.username}" password="${jms.password}"> <amq:redeliveryPolicyMap> <amq:redeliveryPolicyMap> <amq:defaultEntry> <!-- default policy, 5 times with 10s delay each --> <amq:redeliveryPolicy maximumRedeliveries="3" initialRedeliveryDelay="1000" /> </amq:defaultEntry> <amq:redeliveryPolicyEntries> <!-- three times with exponential back-off, that is, 1s, 2s, 4s, 8s. "queue" references to the "physicalName" defined in amq:queue --> <amq:redeliveryPolicy queue="${jms.queue.static.auto}" maximumRedeliveries="3" initialRedeliveryDelay="1000" backOffMultiplier="3" useExponentialBackOff="true" maximumRedeliveryDelay="60000" /> <!-- another policy mapping --> <amq:redeliveryPolicy queue="${jms.queue.static.high}" maximumRedeliveries="3" initialRedeliveryDelay="2000" /> </amq:redeliveryPolicyEntries> </amq:redeliveryPolicyMap> </amq:redeliveryPolicyMap> </amq:connectionFactory> <!-- Default Destination Queue Definition 设置预读取条数 --> <bean id="destinationAuto" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg index="0" value="${jms.queue.static.auto}?consumer.prefetchSize=1" /> </bean> <bean id="destinationMedium" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg index="0" value="${jms.queue.static.medium}?consumer.prefetchSize=1" /> </bean> <bean id="destinationHigh" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg index="0" value="${jms.queue.static.high}?consumer.prefetchSize=1" /> </bean> <bean id="destinationCritical" class="org.apache.activemq.command.ActiveMQQueue"> <constructor-arg index="0" value="${jms.queue.static.critical}?consumer.prefetchSize=1" /> </bean> <!-- Text message converter --> <bean id="textMessageConverter" class="test.task.process.jms.internal.TextTaskMessageConverter"> </bean> <!-- Map message converter --> <bean id="mapMessageConverter" class="test.task.process.jms.internal.MapTaskMessageConverter"> </bean> <!-- Message Receiver Definition --> <bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter"> <constructor-arg> <bean class="test.task.process.handle.internal.DefaultHandleTaskMessageEntry"> <property name="taskMessageSender" ref="textTaskMessageSender" /> </bean> </constructor-arg> <property name="defaultListenerMethod" value="processTask" /> <!-- property name="messageConverter"><null/></property --> <property name="messageConverter" ref="textMessageConverter" /> </bean> <bean id="errorHandler" class="test.task.process.handle.internal.DefaultTaskProcessErrorHandler" /> <bean id="taskMessageListenerContainerAuto" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="sessionAcknowledgeMode" value="2" /> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="destination" ref="destinationAuto" /> <property name="messageListener" ref="messageListener" /> <property name="concurrentConsumers" value="8" /> <property name="errorHandler" ref="errorHandler" /> </bean> <bean id="taskMessageListenerContainerMedium" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <!-- property name="sessionTransacted" value="true"/ --> <property name="sessionAcknowledgeMode" value="2" /><!-- 2-client-ack 0 transaction --> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="destination" ref="destinationMedium" /> <property name="messageListener" ref="messageListener" /> <property name="concurrentConsumers" value="1" /> <property name="errorHandler" ref="errorHandler" /> <!-- property name="maxMessagesPerTask" value="1" / --> </bean> <bean id="taskMessageListenerContainerHigh" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <!-- property name="sessionTransacted" value="true"/ --> <property name="sessionAcknowledgeMode" value="2" /><!-- 2-client-ack 0 transaction --> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="destination" ref="destinationHigh" /> <property name="messageListener" ref="messageListener" /> <property name="concurrentConsumers" value="1" /> <property name="errorHandler" ref="errorHandler" /> <!-- property name="maxMessagesPerTask" value="1" / --> </bean> <bean id="taskMessageListenerContainerCritical" class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <!-- property name="sessionTransacted" value="true"/ --> <property name="sessionAcknowledgeMode" value="2" /><!-- 2-client-ack 0 transaction --> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="destination" ref="destinationCritical" /> <property name="messageListener" ref="messageListener" /> <property name="concurrentConsumers" value="1" /> <property name="errorHandler" ref="errorHandler" /> <!-- property name="maxMessagesPerTask" value="1" / --> </bean> <bean id="textJmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="messageConverter" ref="textMessageConverter" /> </bean> <bean id="mapJmsTemplate" class="org.springframework.jms.core.JmsTemplate"> <property name="connectionFactory" ref="amqConnectionFactory" /> <property name="messageConverter" ref="mapMessageConverter" /> </bean> <bean id="textTaskMessageSender" class="test.task.process.handle.internal.DefaultTaskMessageSender"> <property name="jmsTemplate" ref="textJmsTemplate" /> </bean> <bean id="mapTaskMessageSender" class="test.task.process.handle.internal.DefaultTaskMessageSender"> <property name="jmsTemplate" ref="mapJmsTemplate" /> </bean>
最后是Spring CTX启动加载文件,spring-config.xml:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>application.properties</value> <value>redis.properties</value> </list> </property> </bean> <bean id="priorityChannel" class="test.task.process.bean.internal.DefaultTaskPriorityChannel"> <property name="AUTO_CHANNEL" value="${jms.queue.static.auto}" /> <property name="MEDIUM_CHANNEL" value="${jms.queue.static.medium}" /> <property name="HIGH_CHANNEL" value="${jms.queue.static.high}" /> <property name="CRITICAL_CHANNEL" value="${jms.queue.static.critical}" /> </bean> <bean class="test.task.process.utils.ApplicationContextRegister" /> <import resource="process-mapping.xml" /> <bean class="test.task.process.utils.ProcessEntityMap"> <property name="processMap" ref="processMap" /> <property name="processWrapperMap" ref="processWrapperMap" /> </bean> <import resource="mq-config.xml" /> <import resource="consumer.xml" /> <import resource="jesque.xml" /> <bean id="taskEventHandler" class="test.task.event.listener.TaskProcessErrorEventHandler"></bean> <bean id="channelTaskEventHandler" class="test.task.event.listener.ChannelTaskOccurredEventHandler"></bean>
(4)启动和部署方式
最后的打包方式是jar,启动脚本:
java -cp "libs/*:xinhua-static-starter-0.0.1.jar" Main
(5)ActiveMQ集群配置
静态化消息吞吐量较大,单个MQ实例不能满足要求,系统配置了多台MQ的集群,
消费端使用了MQ内置的failover断线重连机制:
jms.broker.url=failover:(tcp://192.168.xx.xx:61616?wireFormat.maxInactivityDuration=0,tcp://192.168.xx.xx:61617?wireFormat.maxInactivityDuration=0)&maxReconnectDelay=1000
采用的是单台物理机上配置多台ActiveMQ实例开放不同端口的伪集群。
(6)配置Dubbo消费端,调用系统的静态化Dubbo服务
四、使用消息队列异步后对系统的优化
对静态化请求的解耦和异步处理,很好的平滑了高并发场景下短时间内大量的服务请求,比如稿件模板修改以后的批量静态化,
经常有上万个静态化的请求,提高了系统的可用性和稳定性,对一些对优先级要求比较高的请求,通过加入到不同的队列中来处理,
兼顾了及时性和异步性。
本文来自博客园,作者:邴越,转载请注明原文链接:https://www.cnblogs.com/binyue/p/3751903.html