Архитекторам и дизайнерам веб-приложений было больно обрабатывать длительные онлайн-задачи в веб-контейнерах, пока не была введена асинхронная обработка сервлета (спецификация Servlet 3.0). В этой статье будет представлено решение для использования этого технического преимущества для создания гибкого механизма обработки задач без потери масштабируемости веб-контейнера.
Длительные задачи, такие как длительный вызов веб-службы или длительный запрос, используемые для длительного блокирования текущего потока в ожидающем запросе. Следовательно, это может значительно снизить пропускную способность веб-контейнера и снизить масштабируемость системы. Есть несколько других решений, которые обсуждались в прикрепленных ресурсах, но это решение нуждается в помощи контейнера EJB; следовательно, в случае веб-контейнера, такого как Tomcat, он не применим.
Мое решение основано на стандартах Servlet спецификации 3.0, части выпуска Java EE 6. Следующая диаграмма иллюстрирует эту архитектуру.
Архитектурная схема
Механизм задач в основном состоит из пула потоков. Чтобы повысить производительность обработки задач, создается пул потоков для одновременной обработки нескольких задач, и пул потоков также позволяет иметь очередь блокировки для хранения задач, ожидающих обработки. Этот механизм задач фактически делегирует обработку своих задач этому пулу потоков и предоставляет параллельные представления задач, инициированные веб-клиентами.
Диаграмма классов механизма задач
Особенности дизайна
1. Использование слушателя ServletContext. Это правильное место для подключения этого многопоточного обработчика задач к веб-контейнеру, поскольку он слабо связан с сервлетом для выполнения переработки атрибутов и выделенных объектов пула потоков; и сделать его доступным в области применения. Кроме того, с помощью аннотации @WebListener мы можем сделать ее подключаемой к любому сервлету.
2. Отделение логики обработки задач от других. Интерфейс Task — это абстрактный слой над конкретной обработкой в каждом потоке в пуле потоков. Методы beforeRun () и afterRun () в этом интерфейсе предназначены для размещения логики, связанной с сервлетами, такой как обработка запросов / ответов, тогда как чистая обработка задач предназначена для унаследованного метода Run (), поэтому расширяемость и возможность повторного использования достигаются в некоторой степени.
3. Общение с клиентом. Благодаря поддержке Ajax и асинхронной обработки пользователи не будут блокироваться от дальнейшего взаимодействия с веб-страницами после отправки долго выполняемой задачи, и эта обработка запросов в контейнере сервлета не будет блокироваться до тех пор, пока запрошенный ресурс больше не станет доступным. Контейнер сервлетов может перерабатывать этот поток запросов для обслуживания других запросов, и позже, при необходимости, этот запрос может быть возобновлен для передачи данных, полученных из процесса обработки задач, клиенту.
Когда для атрибута asyncSupported установлено значение true, объект ответа не фиксируется при выходе из метода. Вызов startAsync () возвращает объект AsyncContext, который кэширует пару объектов запрос / ответ. Затем объект AsyncContext служит контекстом задачи для инициализации объекта задачи и помещается в обработчик задач области приложения, ожидающий обработки. Исходная ветка запроса затем перерабатывается. Когда обработка задачи заканчивается или задача отклоняется механизмом задач, мы можем либо вызвать AsyncContext.getResponse (). GetWriter (). Print (…), а затем complete (), чтобы зафиксировать ответ, либо вызвать dispatch (), чтобы направить поток к другой странице JSP в результате.
4. Политика для решения вопросов перегрузки.В большинстве случаев использование неограниченной очереди в пуле потоков достаточно для веб-сервера. Новые задачи будут поставлены в очередь, если в пуле потоков нет свободных потоков, что может эффективно сгладить переходный пакет задач. Но в некоторых случаях, например, когда в вашей системе ежедневно наблюдается значительное время пиковых запросов или если обработка серверных задач некоторое время замедляется, эта неограниченная очередь может продолжать расти, что может привести к исчерпанию ресурсов и окончательному уничтожению системы. , Тогда на помощь приходит ограниченная очередь. Механизм задач с ограниченной очередью может быть спроектирован так, чтобы отклонять любую новую задачу после ее заполнения, что делает систему достаточно стабильной. К тому же,самая важная вещь, чтобы заставить эту стратегию работать, — то, что с помощью AsynContext и Ajax клиент мог быть немедленно признан об этом отклонении, сказав, что система испытывает большой объем в настоящее время; пожалуйста, повторите вашу задачу позже. Это приемлемо в большинстве случаев с точки зрения клиента, поскольку им разрешено продолжать работу на других страницах.
Существует четыре реализации политики для пула потоков полностью в Java 6 при заполнении ограниченной рабочей очереди: AbortPolicy, CallerRunsPolicy, DiscardPolicy и DiscardOldestPolicy. Прекращение политики по умолчанию приводит к тому, что execute вызывает исключение Rejected-ExecutionException. Наша система принимает эту политику, а затем перехватывает это исключение и реализует здесь нашу собственную обработку переполнения. Пожалуйста, обратитесь к следующей диаграмме состояний для деталей. CallerRunsPolicy позволяет потоку, который вызывает выполнение, выполнить задачу. Это обеспечивает простой механизм управления с обратной связью, который замедляет скорость отправки новых задач. Политика отмены молча отбрасывает вновь отправленную задачу, если она не может быть поставлена в очередь для выполнения;политика discard-old отбрасывает задачу, которая в противном случае была бы выполнена следующей, и пытается повторно отправить новую задачу.
Диаграмма состояний для обработки задач на основе политики прерывания
Образцы фрагментов кода (принятие политики прерывания):
@WebListener( )
public class TaskEngineListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
logger.info("Start Task Engine...");
TaskEngine taskengine= new TaskEngine();
sce.getServletContext().setAttribute("TaskEngine", taskengine);
}
public void contextDestroyed(ServletContextEvent sce) {
logger.info("Try to shut down Task Engine");
ServletContext ctx= sce.getServletContext();
TaskEngine taskengine=(TaskEngine)ctx.getAttribute("TaskEngine");
if(taskengine!=null){
taskengine.shutdown();
}
ctx.removeAttribute("TaskEngine");
}
}
@WebServlet (asyncSupported = true)
public class AsyncServlet extends HttpServlet {
…
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Cache-Control", "private");
response.setHeader("Pragma", "no-cache");
final AsyncContext ac = request.startAsync(request, response);
ac.setTimeout(10 * 60 * 1000);
Task task= new AsyncServletTask(ac);
TaskEngine taskengine = (TaskEngine) request.getServletContext().getAttribute("TaskEngine");
if (!(taskengine.addTask(task)))
{ PrintWriter out = null;
try {
String name = ac.getRequest().getParameter("name");
out = ac.getResponse().getWriter();
out.println("Task: " + name + " is declined, Please submit later");
out.close();
} catch (IOException ex) {
logger.log(Level.SEVERE, "Task Rejection Exception",ex);
} finally {
ac.complete();
}
}
}
}
public class TaskEngine {
private final static int POOLMAX=4;
private final static int TASKQUEUELENGTH=3;
private final ExecutorService jobExecutor
= new TaskEngineThreadPool(POOLMAX, POOLMAX,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(TASKQUEUELENGTH), new ThreadPoolExecutor.AbortPolicy());
public TaskEngine(){
}
public synchronized boolean addTask(Task task){
boolean done=true;
try{
jobExecutor.execute(task);
}catch(RejectedExecutionException ex){
done=false;
}
return done;
}
...
}
public class AsyncServletTask implements Task {
private static final Logger logger = Logger.getLogger(AsyncServletTask.class.getName());
private AsyncContext ac=null;
private String taskName=null;
public AsyncServletTask(AsyncContext in){
ac=in;
}
@Override
public void run() {
//handle task processing here
}
@Override
public void beforeRun() {
this.taskName=ac.getRequest().getParameter("name");
}
@Override
public void afterRun() {
try {
PrintWriter out = ac.getResponse().getWriter();
//logger.info("Get Task:"+taskName);
out.println("Servlet AsyncServlet "+taskName+" response");
out.close();
} catch(IOException ex) {
logger.log(Level.SEVERE,"AfterRun() exception",ex);
}
ac.complete();
}
}
В заключении
В этой статье представлен дизайн решения для обработки онлайн-задач в веб-контейнере с учетом масштабируемости и расширяемости системы. Преимущества этого решения:
- Сделайте так, чтобы контейнер сервлета более эффективно управлял своими потоками при обработке долго выполняющихся задач.
- Упрощенная реализация механизма задач позволяет централизованно настраивать контроль над долгосрочными задачами, что повышает общую эффективность системы.
- Предоставьте клиенту гибкое подтверждение обработки и, следовательно, сделайте страницу пользователя более отзывчивой.
Недостатки:
- Предлагается настройка пула потоков.
Об авторе
Он спроектировал, спроектировал и разработал множество приложений J2EE / JEE для Websphere и Weblogic.
Ресурсы:
- http://java.sun.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html
- http://blogs.sun.com/enterprisetechtips/entry/asynchronous_support_in_servlet_3
- http://www.javaranch.com/journal/2004/03/AsynchronousProcessingFromServlets.html
- http://www.javaworld.com/javaworld/jw-02-2009/jw-02-servlet3.html
- Java-параллелизм на практике Автор: Брайан Гетц