Определить причину сотен потоков AJP в Tomcat - PullRequest
3 голосов
/ 29 января 2010

У нас есть два сервера Tomcat 6.0.20, на которых работает Apache, и связь между ними осуществляется с помощью AJP. Tomcat, в свою очередь, использует веб-службы в кластере JBoss.

Этим утром одна из машин Tomcat использовала 100% ЦП на 6 из 8 ядер нашей машины. Мы взяли дамп кучи с помощью JConsole, а затем попытались подключить JVisualVM, чтобы получить профиль, чтобы увидеть, что занимает весь процессор, но это вызвало сбой Tomcat. По крайней мере, у нас была свалка кучи!

Я загрузил дамп кучи в Eclipse MAT, где я обнаружил, что у нас есть 565 экземпляров java.lang.Thread. Некоторые из них, очевидно, полностью законны, но подавляющее большинство названо «ajp-6009-XXX», где XXX - это число.

Я довольно хорошо знаю, как обходить Eclipse MAT, но не смог найти объяснения этому. Если у кого-то есть некоторые указания относительно того, почему Tomcat может делать это, или некоторые советы по выяснению причин использования Eclipse MAT, это будет оценено!

Ответы [ 2 ]

1 голос
/ 12 февраля 2010

Это не прямой ответ, я думаю, но, возможно, в качестве смягчающего подхода в производстве, вы можете ограничить ущерб, ограничив maxThreads для AJP в вашей конфигурации, на http://tomcat.apache.org/tomcat-6.0-doc/config/ajp.html?

По умолчанию 200, что, конечно, много потоков - но это, возможно, не объясняет 565 выше. Очевидно, что это может подтолкнуть проблему в другом месте, но, возможно, вам лучше будет отладить проблему там, или она проявится по-другому. Возможно ли, что вы находитесь под большим количеством нагрузки? Есть ли что-нибудь примечательное в поведении Apache в периоды, приводящие к проблемам, с которыми вы сталкиваетесь?

0 голосов
/ 06 апреля 2010

Невозможно знать наверняка, если вам не удалось получить дамп потока, но однажды я столкнулся с подобной проблемой, когда все 8 ядер были заняты на 100% тысячами потоков (однако это не было в Tomcat).

В нашем случае каждый поток застрял внутри java.util.HashMap в методе get (), плотно вращаясь в цикле for:

   public V get(Object key) {
       if (key == null)
           return getForNullKey();
       int hash = hash(key.hashCode());
       for (Entry<K,V> e = table[indexFor(hash, table.length)];
            e != null;
            e = e.next) {
           Object k;
           if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
               return e.value;
       }
       return null;
   }

Наша теория заключалась в том, что каким-то образом связанный список записей в конкретном сегменте был поврежден и указывал на себя, поэтому никогда не мог выйти из цикла. Поскольку ни одна работа не была завершена, все больше и больше потоков потреблялось из пула по мере того, как было сделано больше запросов.

Это может произойти, если размер таблицы должен быть изменен при добавлении новых записей, но есть неохраняемый доступ для чтения / записи несколькими потоками; один поток может расширять связанный список в определенном месте, в то время как другой занят попыткой его перемещения. Если доступ к хэш-карте не синхронизирован, то он может быть поврежден (хотя, как правило, не воспроизводится).

Проверьте, есть ли общий HashMap (или HashSet), к которому могут одновременно обращаться несколько потоков. Если это так, и это легко сделать, либо замените на ConcurrentHashMap, либо используйте ReentrantReadWriteLock для защиты доступа для чтения / записи к карте. Вы, конечно, тоже можете попробовать Collections.synchronizedMap(), но это не будет таким масштабируемым.

Любое из предложенных исправлений должно предотвратить проблему, если она является основной причиной вашей проблемы.

Смотри также:

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html

...