Я вот все озадачиваюсь одним интересным вопросом. Ведь вроде как уже давно в нашу жизнь пришли Agile подходы, разработчики начали писать тесты, много и на разных уровнях, заказчики начали более внимательно относиться к качеству и брать на себя часть работы тестировщиков, многие команды стали сильно заботиться о качестве кода и даже жить без тестировщиков. Почему об этом не говорят на встречах и тусовках тестировщиков? Почему на конференциях для тестировщиков так мало докладов и выступлений на тему интеграции тестировщика с разработчиками, их совместной работе, распределении обязанностей в свете изменений?
Я несколько раз выступал на конференции SQA Days и каждый раз был чуть ли не белой вороной – единственным разработчиком (именно реальным разработчиком, а не в прошлом), который делал доклад. Да и среди участников разработчиков мной было замечено немного. Почему так происходит? Во всех веселых историях про разработчиков и тестировщиков первые всегда выставляются в дураках. И некому защитить альтернативную точку зрения. А как же интеграция и совместная работа?
А за границей все гораздо более радостно. Много докладов об автоматизации как от разработчиков так и от тестировщиков. Мало докладов о ручном тестировании во всех его формах. Инструменты часто представляют разработчики а не тестировщики. Что же у нас то так все несовременно?
Я уже на третью конференцию для тестировщиков пытаюсь подать свой доклад “Бытовая классификация тестировщиков с точки зрения разработчика” и его не берут в программу. 🙁 Тестировщики часто говорят о противостоянии и конфликтах с разработчиками. Но ведь есть команды, где все живут в мире и согласии. Видимо что-то тут не так? Я хочу поговорить о том, как тестировщиков видят сами разработчики. В докладе будет проведена забавная классификация. Кроме известного всем тестировщика-обезьянки будут представлены тестировщик-муха, тестировщик-нацист, тестировщик-панда и многие другие герои. Вы сможете лишний раз задуматься над тем, как вас видят со стороны и, возможно, изменить ситуацию к лучшему. Доклад может быть полезен не только тестировщикам, но и менеджерам проектов, лидерам команд. Вы сможете быстрее распознавать те или иные шаблоны поведения тестировщиков и принимать меры по повышению уровня командной работы.
Я пока думаю где еще его можно было бы рассказать, чтобы целевая аудитория была более соответствующей и смеялась над очередной шуткой формата “разработчик сказал, что это не баг а фича”. Чтобы открыть глаза та то, как выглядят тестировщики с другой стороны, с точки зрения разработчиков. Без осознания этой правды очень тяжело работать вместе. Ведь совместная работа должна основываться на прозрачности и доверии с обеих сторон.
Так что ждите в ближайшее время на одной из сцен Украины или ближнего зарубежья! Заглянем по ту сторону правды… 🙂
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Много кто меня спрашивает, почему я в последнее время не хожу на разнообразные Agile тусовки и конференции. И я решил поделиться своей историей по этому поводу. Сами Agile, Scrum, Kanban, XP, Lean тут вовсе ни при чем, я по-прежнему считаю эти подходы к разработке самыми современными и правильными. Возможно, кто-то тоже заметил определенные изменения в самой Agile тусовке и разделит мое мнение.
Итак, когда-то давно мне очень нравилась одна фишка – мы собирались на встречи, участвовали в различных Agile мероприятиях и везде были реальные проблемы от реальных людей. Собирались и выступали люди, которые действительно работали над реальным внедрением гибких подходов и практик у себя на проекте. Было круто услышать реальные истории из нашего мира аутсорсинга, какие трудности испытывают команды в реальных условиях на реальных проектах в нашей стране. Сами темы выступлений и обсуждаемых проблем были гораздо интереснее и ярче, люди делились своими успехами, победами и поражениями. И создавалось приятное ощущение, что все движется вперед, развивается, не стоит на месте. Можно было на следующей конференции встретить тех же докладчиков и послушать как изменилась ситуация у них, чему они научились, что нового применили. Это давало толчок к собственному развитию, а также возможность поделиться своими опытом и знаниями с увлеченной аудиторией.
А что происходит теперь? В подавляющем большинстве выступают консультанты, коучи, тренера и евангелисты. Серьезно, около 90%. Кроме шуток, эта тенденция прослеживается не только в Украине, но и за границей. Только в Украине она более усугублена погоней за известными именами в программе – ведь мы все еще страна третьего мира, жители которой сильно ведутся на блестящие бусики (вспоминаем туземцев, а также как наш народ “хавает” золотые айфоны по невероятным ценам). Изредка консультанты разбавляются менеджерами (которых в грамотно построенном Agile процессе вообще и быть не должно, по крайней мере в этой роли). И что при таком составе выступающих можно услышать? Чем могут поделиться зарубежные “гуру”? Обычно, своими измышлениями и философскими рассуждениями, которые они вынесли из опыта консультирования НЕ В НАШЕЙ СТРАНЕ.
Я каждый раз смотрю на список тем докладов и задаю себе вопрос о практической применимости для меня лично и для людей, которых я знаю. И не вижу ее. Все философствования и идеи консультанты давно описали у себя в блогах, поэтому с ними я давно знаком. Вероятнее всего, видео подобного выступления проскакивало у меня в RSS ленте и я его уже видел, если конечно оно хоть чем-то меня заинтересовало. Так что мне там делать? Я теперь даже не подаю заявки на доклады – мои темы не вписываются в тематику. Я люблю рассказывать практические вещи, которые помогают людям что-то менять у себя и которые я попробовал сам.
Я в какой-то момент даже задумался, может реально уровень сильно вырос в Украине и у всех все хорошо. Но, общаясь с очень многими представителями различных компаний, я понимаю, что это не так. У многих как были проблемы с внедрением Agile, так и остались. Средняя температура по палате немного улучшилась, но незначительно. Зато сильно выросло число разочаровавшихся в Agile. И неужели им помогут очередные философские рассуждения от зарубежных “гуру”? Сомневаюсь.
Вот такая вот история… А вы что думаете по этому поводу?
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Я буду время от времени писать интересные технические заметки, мало ли разработчики тоже читают наш блог. 🙂 Итак, сегодня попытаемся решить очень простую задачку – организовать счетчики в MySQL. Для чего это нужно? Примеров использования очень много: нужно для каждой страницы сайта хранить количество обращений, при обработке большого объема данных нужно посчитать частоты встречаемости элементов, нужно контролировать количество запросов для каждого пользователя в системе… В общем, задача частенько встречается.
Если точность результата вам не особо важна и потеря части данных не является критичной проблемой, то моя вам рекомендация хранить данные не в БД. Вариантов организации счетчика очень много, какие-то чуть быстрее, какие-то чуть медленнее. Вот лишь несколько из них:
Во всех этих вариантах добиться идеальной точности и избежать потенциальной потери данных или двойного подсчета не удастся. Но для многих задач этого хватает с головой.
Вам все таки нужны точные счетчики, привязанные к бизнес-транзакции и вы решили реализовать их на уровне БД. От этой точки и до конца статьи все примеры будут приводиться для MySQL. Решение напрашивается, потому что БД у вас уже есть и почему бы не использовать ее и для этой цели. Первым решением, которое приходит в голову, будет создание отдельной таблички:
CREATE TABLE resource_counter ( resource_id BIGINT NOT NULL, count_of INT UNSIGNED NOT NULL, PRIMARY KEY (resource_id) ) ENGINE=InnoDB;
Ну и чтобы обновить счетчик, нужно вызвать банальный код:
UPDATE resource_counter SET count_of = count_of + :delta WHERE resource_id = :resourceId;
У этого решения есть несколько “сюрпризов”. Первый из них очень простой и понятен каждому, кто знает как работают базы данных. В случае обновления счетчика из нескольких потоков все они будут заблокированы и выполнятся в порядке очереди. Поэтому производительность будет не совсем радостной. Но есть и еще один интересный “сюрприз” – наличие deadlock-ов в банальном коде. Они могут проявляться сразу в нескольких случаях:
В общем, вариант решения так себе и однозначно подойдет только при слабой нагрузке.
И тут вам приходит в голову оптимизация. Вы вспоминаете, что вставки практически не блокируют друг друга, в отличие от обновлений данных. Поэтому вы решаете убрать PK с resource_id и вместо обновлений счетчика добавлять новые данные. Для подсчета же общего значения счетчика вы будете просто использовать следующий запрос:
SELECT sum(count_of) FROM resource_counter WHERE resource_id = :resourceId;
Некоторое время все даже будет работать достаточно быстро, но со временем производительность будет потихоньку деградировать, а общее количество “мусорных” исторических данных будет все время расти. И вы начинаете думать дальше…
Чтобы избавиться от “мусорных” исторических данных существует множество однотипных простых техник. Нужно время от времени агрегировать данные и удалять или “виртуально удалять” старые. Давайте рассмотрим немного детальнее. Вы запускаете job в отдельном потоке либо на уровне БД либо на уровне вашего приложения. Этот job бежит по таблице счетчиков, подсчитывает сумму по каждому ресурсу и вместо набора записей оставляет ровно одну со значением суммы. Как это реализовать технически:
Третий способ, пожалуй, является самым быстрым и безопасным из перечисленных в плане блокировок. Подобное решение является “виртуальным удалением” и часто используется для подсчета баланса за определенные периоды. Агрегированные записи могут как удаляться так и оставаться в системе для построения временных графиков. Подсчет значения счетчика более-менее фиксирован по времени и зависит от периода агрегации.
И тут вам на ум приходит воспоминание, что удалять данные из таблицы вообще не очень кошерная операция и ее лучше избегать. Как же быть?
Чтобы избежать удаления данных, нужно немного напрячься и вспомнить об операции TRUNCATE TABLE. Она очищает данные очень быстро. Но не все так просто, придется немного изменить алгоритм обновления значений счетчиков.
Для этого нам понадобятся вместо одной сразу 2 или 3 таблички одинаковой структуры: resource_counter, resource_counter_shadow, resource_counter_total (эта табличка является необязательной). Каждая из них будет поддерживать только вставки. Ваше приложение пишет все изменения значения счетчика в виде дополнительных записей в таблицу resource_counter и только в нее. Параллельно работает отдельный поток, который раз в определенное время производит замену таблиц:
RENAME TABLE resource_counter TO resource_counter_tmp, resource_counter_shadow TO resource_counter, resource_counter_tmp TO resource_counter_shadow;
Запрос является атомарным и по сути меняет местами таблицы resource_counter и resource_counter_shadow. Получается, что с таблицей resource_counter_shadow никто не работает и можно быстро сделать агрегацию данных в ней. Полученные результаты можно добавить в таблицу resource_counter запросом:
INSERT INTO resource_counter (resource_id, count_of) SELECT resource_id, sum(count_of) FROM resource_counter_shadow GROUP BY resource_id ORDER BY NULL;
Работать подобный запрос будет достаточно быстро, потому что таблица resource_counter_shadow маленькая и контролируется интервалом агрегации. Можно также использовать необязательную таблицу resource_counter_total, которая заведена для оптимизации (чтобы избежать переливания данных из одной таблички в другую, если они не меняются). Сделать это можно следующим запросом:
UPDATE TABLE resource_counter_total rct INNER JOIN (SELECT resource_id, sum(count_of) AS delta FROM resource_counter_shadow GROUP BY resource_id ORDER BY NULL) deltas ON rct.resource_id = deltas.resource_id SET rct.count_of = rct.count_of + deltas.delta;
Есть еще более симпатичная версия этого же запроса:
INSERT INTO resource_counter_total (resource_id, count_of) SELECT resource_id, sum(count_of) FROM resource_counter_shadow rcs GROUP BY resource_id ORDER BY NULL ON DUPLICATE KEY UPDATE resource_counter_total.count_of = resource_counter_total.count_of + rcs.count_of;
Ну и конечно же, после использования таблицы resource_counter_shadow она очищается. За исключением необязательной таблицы, данный подход требует минимальное количество блокировок, но повышает количество “переливаний данных”.
В какой-то момент времени вы задумаетесь, а не слишком ли все стало сложно и обратитесь к изначальной проблеме. Она заключалась в том, что запись в таблице счетчика блокируется при обновлении. Но в то же время, формирование отдельных записей для каждого обновления приводит к росту их количества и необходимости чистить данные. Тогда надо использовать золотую середину. Для этого в таблицу счетчика добавляется новая колонка counter_index, которая включается в состав PK:
CREATE TABLE resource_counter ( resource_id BIGINT NOT NULL, counter_index INT UNSIGNED NOT NULL, count_of INT UNSIGNED NOT NULL, PRIMARY KEY (resource_id, counter_index) ) ENGINE=InnoDB;
Теперь наша цель распределить обновление по нескольким записям таблицы, сделав возможность блокировки менее вероятной. От качества распределения будет зависеть количество блокировок по одному счетчику. Для простенькой версии можно использовать следующий запрос:
INSERT INTO resource_counter (resource_id, counter_index, count_of) VALUES (resource_id, rand() * 10, :countOf) WHERE resource_id = :resourceId ON DUPLICATE KEY UPDATE count_of = count_of + :countOf;
Для получения значения счетчика по определенному ресурсу по-прежнему нужно будет использовать сумму, но по фиксированному количеству строк (максимум 10 в нашем примере):
SELECT sum(count_of) FROM resource_counter WHERE resource_id = :resourceId;
Работает быстро как на обновление так и на получение значения. Балансировать можно количеством счетчиков на один ресурс.
Как видите, решений такой простенькой задачи достаточно много, если знать предметную область и тщательно тестировать ваши решения. Надеюсь, это сэкономит кому-то время. 🙂
P.S. Все запросы писались напрямую в текстовом редакторе, поэтому мелкие ошибки и опечатки просьба указывать в комментариях, но в нежной форме. 😉
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Что-то я подзабил на рубрику “полезное чтиво”. Вроде и что-то интересное начало встречаться реже и времени не хватает. Но надо иногда делиться с другими полезными ссылками, поэтому вот очередной выпуск:
А вот список интересного видео для просмотра:
Читайте и набирайтесь новых знаний!
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Только вчера прошла 32-ая встреча “Клуба анонимных разработчиков” и мы уже готовы анонсировать следующую встречу. Она состоится 1 октября и пройдет в формате практического мастер-класса.
Темой мастер-класса будет “TDD в Java для начинающих”. Многие слышали о том, что TDD – это круто и с помощью этой практики можно писать код лучше, быстрее и качественнее. Но в теории все звучит просто, на практике оказывается не так уж легко начать. Виктор Кучин подготовил практическое введение в TDD для тех, кто хотел бы попробовать этот подход, используя Java. На мастер-классе вы услышите небольшое теоретическое введение, а остальное время проведете за реальной разработкой по TDD. Поэтому все участники должны иметь с собой ноутбук с предустановленным ПО.
Встреча пройдет во вторник 1 октября. Местом проведения мы выбрали уютный Киевский офис компании DataArt. Этот офис полюбился членам клуба своей обстановкой и наличием всего необходимого для продуктивного общения.
Официальное начало встречи по-прежнему в 19:00, завершение в 23:00. Стоимость участия 80 гривен при оплате заранее, 120 гривен при оплате на месте. Пива, пиццы и кофе с печеньками хватит на всех. Регистрация обязательна. Все детали по оплате будут высланы вам после успешного прохождения регистрации. Количество мест ограничено 30 участниками.
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Недавно прочитал любопытную статейку от Максима Дорофеева с письмом от одного из его знакомого менеджера. В двух словах проблема примерно выглядит так:
Как и практически любой человек, который прочитал книжку Голдратта, менеджер из письма во всем видит потенциальное поле для улучшения эффективности. Причем, сразу и с текущего положения. В разы, чтобы как в книжке…
Лирическое отступление. Я когда-то давно прочитал “Цель” и “Цель-2” (кстати, всем настоятельно рекомендую) и до такой степени проникся идеями эффективности, что планировал чуть ли не по руководителям IT компаний ходить и “открывать им глаза” на неэффективность. Но когда начал больше расспрашивать в процессе консалтинга о деталях работы некоторых компаний, команд и проектов, осознал, что не все так просто и быстро. У некоторых царил полный хаос и они не были готовы к культуре “экономного эффективного производства”. Низкий уровень квалификации, бюрократия, навязанные извне правила и обязательства, специфические детали культуры компании и ситуация на рынке – все это требовало гораздо более примитивных мер на первых этапах борьбы за эффективность…
Scrum со своей итеративностью разработки позволяет перейти от хаоса к чуть более контролируемому процессу. Scrum далеко не идеален и, чем быстрее двигается бизнес, тем более очевидны становятся недостатки той же итеративности. Но когда у вас нет ничего, никто не может дать ответ о сроках выполнения задач, в команде разброд и шатание, заказчик не понимает что творится в команде, невозможно сделать хоть немного реалистичный план, команда не готова работать как команда, то “прогрессивные подходы” наподобие агрессивного планирования с отслеживанием выполненных работ и добавлением новой по факту или перехода на Kanban просто обречены на провал.
Scrum пытается научить команду давать осязаемые оценки своей продуктивности, выбрать самостоятельно объем работ на итерацию и сделать его успешно. Это очень важно! Во-первых, появляется предсказуемость для представителей бизнеса. Они теперь могут хоть как-то планировать. Во-вторых, появляется понятие командной ответственности за свои собственные решения и обратная связь по результатам выполнения работ (успели или нет, была ли возможность взять еще работ, нет ли проблем с качеством). Команда начинает работать как команда. И тут очень важно, чтобы начало хоть что-то получаться. Для некоторых команд непростой задачей является взять одну User Story и довести ее до конца в итерации. ОДНУ! Scrum же не исправляет ваши проблемы, а показывает их как в зеркале. Он дает вам данные, чтобы задуматься о причинах провала и неудач. Поэтому нет ничего зазорного или плохого в том, что команда ввела для себя (или кто-то другой ввел) жесткий критерий “все к концу итерации должно быть готово и качество должно быть на уровне”.
И вот, когда команда научилась давать нормальные оценки и делать хоть какую-то работу в срок от итерации к итерации, стоит задуматься об эффективности. Для этого в Scrum есть замечательные инструменты – burndown chart и ретроспектива. Первый может подтвердить описанное в статье подозрение, что команда первые две трети времени занимается чем угодно но не работой. А второй позволяет задать вопрос об эффективности и обсудить картину burndown chart за предыдущую итерацию. И команда сама примет решение, может ли она увеличить количество задач на итерацию или это слишком рискованно. В конце концов иногда проще брать на себя надежные обязательства и, если остается время, добавлять новые задачи. Чтобы ни при каких обстоятельствах не поломать ожидания представителей бизнеса. Все проекты разные и для некоторых это ну оооочень важно.
Все перечисленное будет работать, если в команде есть хотя бы кто-то ответственный. Если вся команда безответственная, то она будет филонить при любом процессе. Любая задача может быть искусственно затянута и оценки повышены. Поэтому сам процесс или подход не является решением. В Agile очень многое зависит от “правильных людей”, а иначе просто не работает, как бы грамотно не насаживались сверху практики замера и улучшения эффективности… 🙂
Через некоторое время необходимость в итерациях перестанет быть такой острой, и тут уже можно начать применять другие подходы к планированию. Еще через некоторое время итерации начнут просто мешать работать более эффективно для хороших команд. И тут время искать или строить более эффективный процесс разработки, например Kanban. Но рубить с плеча сразу может быть очень вредным для команды…
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Мы обсуждали тему вреда наследования в рамках недавней встречи клуба (уже доступно видео для желающих послушать про беды ООП, а также правильные подходы к дизайну и архитектуре). Я решил для закрепления темы и разжигания очередного холивара повторить основные тезисы в статье. 🙂
Итак, вроде бы наследование входит в состав трех китов, на которых держится весь ООП. Тем не менее, про прошествии лет люди осознали, что вреда от наследования обычно больше чем пользы и на самом деле наследование нужно в исключительных случаях. Что же не так с наследованием?
Дело в том, что наследование не учитывает будущие изменения родительского класса. Ведь когда вы наследуете новый класс от существующего, вы подписываете контракт о том, что новый класс всегда будет вести себя как существующий, возможно расширяя его поведение в некоторых местах. Для простоты понимания представьте себе, что вы разработчик и встретили своего друга, тоже разработчика. Поболтали, обсудили работу и поняли, что занимаетесь одним и тем же, только вы еще немного управляете командой. И тут вы можете сделать вывод – вы такой же как друг (вот оно наследование), но только делаете немного больше (расширение в дочернем классе). Прошло время и ваш друг немного забросил разработку и начал заниматься больше менеджментом (но по-прежнему время от времени пишет код, да и писать не разучился). Получается, у родительского класса появились новые функции, которые все дочерние классы по умолчанию наследуют. И вы нежданно-негаданно начинаете тоже заниматься менеджментом (тут может ничего плохого и нет), по крайней мере так следует из вашего “отношения наследования”. 🙂
В языках программирования такой эффект наблюдается за счет отсутствия необходимости переопределять публичные методы родительского класса. А это значит, что их с момента наследования могут добавить сколько угодно и вам никто, включая компилятор, об этом не скажет. А это, в свою очередь, может поломать вашу логику наследования с расширением возможностей родительского класса. На самом деле варианты есть – написать модульный тест, который посчитает количество публичных и полупубличных методов и проверит их количество. Это тоже не даст полной гарантии уведомления, но уже лучше. В идеале, стоило бы при наследовании во избежание сторонних эффектов добавлять тест, фиксирующий сигнатуры методов родительского класса. Но кто это делает? Я не встречал на практике. Более подробно с примерами о плохом наследовании можно прочитать в “Effective Java”. Надеюсь, эту книгу и так все Java разработчики прочли. 🙂
Это не все минусы наследования. Когда у класса появляется несколько наследников, то у них есть общее поведение, а есть расширенное. И зачастую приходится извращаться с тестами, чтобы избежать дубликатов. Это вырождается в абстрактный тест, который каждый тест на дочерний класс должен наследовать. А что делать, если вы наследуете класс из сторонней библиотеки. В этом случае нужно ли писать тесты на базовое поведение, дублируя существующие тесты в библиотеке? Или стоит оставить их как “надежные” без тестов?
Но ведь есть же шаблоны проектирования, которые предрасполагают нас к использованию абстрактных классов и наследования. Есть, и это большое зло. Мало кто правильно реализует тот же Template Method, делая публичные методы final и давая ровно столько точек для расширения дочерним классам, сколько требует базовый алгоритм. Но и правильная реализация не защитит от добавления новых точек для расширения при модификации алгоритма или рефакторинге. И ни один из наследников не получит уведомления от компилятора об этом. В данном случае гораздо лучше работает шаблон Strategy, где шаги алгоритма расписаны в интерфейсе, и обычный класс с реализацией самого алгоритма. Таким образом, каждая реализация интерфейса стратегии будет давать конкретную реализацию шагов алгоритма.
В Java нельзя разорвать наследование состояния от наследования поведения. Получается, что при желании наследовать состояние (набор полей и действия над ними) вы вынуждены тянуть за собой и все поведение, что приводит к нежелательным эффектам при будущих изменениях. Возможно, наличие других типов данных наподобие структур решило бы эту проблему.
Практически любое использование наследования можно заменить на композицию. Для этого нужен только интерфейс. Тестировать становится значительно проще, гибкости становится больше, уведомления об изменениях будет давать компилятор, код становится менее связанным… Одни преимущества! Почему же так не делают на практике? Ответ простой – из-за лени. Гораздо проще добавить еще одного наследника и дописать строчку кода, перекрыв один из методов. Это требует меньших усилий. А на то, что есть какие-то сложности в будущем, плевать. В будущем поддерживать этот код будет кто-то другой. 🙂
Может быть у вас есть возражения или удачные применения наследования? Напишите о них в комментариях!
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
В последнее время кое-кто нам твердит, что выгоднее всего для компании “привозить” конференцию или выступление к себе в офис посредством онлайн вещания. Ведь тогда сотрудники компании находятся в безопасности: их никто не схантит, никто не узнает их контакты и им никуда не нужно ехать… И можно сэкономить еще на билетах, отелях, накладных расходах и т.д. Заметьте, ни слова про эффективность самого участия в такой “конференции”. Ни слова про то, насколько это будет вдохновляющим и полезным для сотрудников. Только экономия и безопасность. 🙂
Я поделюсь с вами страшным секретом от Капитана Очевидность. На самом деле самым выгодным с точки зрения как затрат так и принесенной пользы является приглашение экспертов к себе в офис на семинар. Алгоритм очень простой:
Вуаля! Вы имеете отличное локальное мероприятие при минимальном бюджете. Из моего опыта, $1-2K хватает с головой, если вы не приглашаете “звезду” международного масштаба. А если вы будете отслеживать различные мероприятия в вашей местности, то можете еще больше сэкономить, совместив прилет эксперта к кому-то со своим мероприятием. Некоторым, благодаря таким хитростям, удавалось заполучить даже Боба Мартина и Мартина Фаулера. Важной особенностью будет живое общение с приглашенным экспертом, возможность позадавать вопросы и обсудить проблемы именно вашей компании или команды, программа выступления с учетом ваших потребностей и пожеланий. И главное, все в безопасности и комфортно себя чувствуют.
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Пристыдили меня сегодня, что мол не пишу технических статей, а все больше про Agile глупости и злой ужасный ООП. Поэтому я решил поделиться маленьким элегантным решением к одной интересной задаче, которая возникла у нас на днях. Если вы занимаетесь разработкой кода на для многопоточного окружения, то статья может вас заинтересовать.
Итак, начнем с задачи. Она звучит очень просто. Есть некий сервис, к которому не рекомендуется делать больше чем X обращений в секунду. Понятное дело, что от небольшой погрешности никто не умрет и задача не имеет супер-критичной формулировки. Но надо приложить максимум возможных усилий, чтобы избежать перегрузки сервиса. Обращения к сервису идут с многих потоков (для примера 50), поэтому механизм контроля должен максимально избегать тяжелой синхронизации.
Звучит действительно несложно. В первую очередь на ум тем, кто не сачковал на курсе по теории алгоритмов или уже не первый год пишет код для многопоточного доступа, приходит Semaphore. В Java есть его реализация в пакете java.util.concurrent. Он отлично подходит для синхронизации доступа к ресурсу – можно задать количество разрешенных заходов, а когда они закончатся, остальные потоки мирно уснут в ожидании освободившихся мест. Проблема заключается в том, что в отличие от классического использования, поток не может сам по завершению задачи вернуть свой заход назад в семафор. Ведь на каждую секунду количество заходов должно быть лимитировано. То есть, нам нужен некий временной семафор.
Прежде чем идти дальше, любой опытный разработчик постарается поискать, не решал ли кто-то уже подобную задачу. Поиск привел нас к TimedSemaphore из commons-lang и RateLimiter из google-guava. Первый имеет сразу две проблемы: слишком прямолинейная реализация с синхронизированным медленным методом aquire и добавление ресурсов в семафор с помощью ScheduledThreadPoolExecutor, что совершенно не гарантирует добавление в начале секунды и отсутствие простоев по несколько секунд. Второй использует несколько странное решение для поставленной задачи – при необходимости обеспечить лимит в N запросов в секунду он ждет после каждого из них по 1/N части секунды. Получается, что наши запросы будут распределяться по секунде вместо того, чтобы заниматься обработкой результатов. Это явно не наш кандидат.
Как же обновлять в семафоре количество возможных заходов? Первой решили опробовать все тот же выделенный поток с расписанием “раз в секунду”. Поток ни с кем не конкурирует, поэтому синхронизировать ничего не нужно. Он просто возвращает значение семафора в равное лимиту на секунду. Данному потоку для пущего устрашения был дан максимальный приоритет. Реализация заведомо ненадежная, но интересовало насколько именно в наших условиях. Оказалось, что немного раз дистанция между обновлениями составляла несколько секунд (потоку просто не давали времени на выполнение) и очень много раз она сбивалась на сотни миллисекунд. А для нас это сулило две проблемы: простой работающих потоков или двойная норма запросов в одну секунду. Обе проблемы совершенно не радовали.
В запасе была оптимизация – семафор пополнялся не только по расписанию, но и по запросу любого работающего потока, который пришел узнать можно ли ему сделать запрос и обнаружил, что секунда с прошлого обновления уже прошла. Реализация требует синхронизации, но она может быть облегчена за счет использования AtomicInteger и его метода compareAndSet. Каждый поток проверяет время последнего обновления по отношению к текущему. Если секунда прошла, то пытается установить свое время вместо старого. Это удастся только одному потоку – именно он и будет отвечать за добавления запросов в семафор. Но получается из одной легковесной структуры для синхронизации мы уже используем две, причем в большой части случаев делая проверки по времени совершенно напрасными. Но в среднем случае ситуация улучшилась. Зато, если все потоки успели заснуть на семафоре в ожидании свободных заходов, то ничего не изменилось и проблемы с обновлением по расписанию все те же.
И тут на помощь пришло альтернативное решение, которое рассматривалось с самого начала – работа через счетчики. Заводим AtomicInteger для хранения количества осуществленных запросов в секунду и дополнительно храним текущую секунду (мы потом придумали как кодировать информацию о текущей секунде сразу в счетчик, чтобы операции были атомарными, но это не так важно). Теперь любой поток проверяет не изменилась ли секунда. Пробует ее обновить и если у него получается, то он сбрасывает счетчик все тем же методом compareAndSet. Иначе, он просто увеличивает счетчик и смотрит, не превышен ли лимит на данную секунду. Если превышен, то засыпает на время до конца секунды. Код такого решения явно сложнее и каждый поток теперь должен сам засыпать вместо ожидания на объекте синхронизации. Вот пример реализации такого подхода:
private static final int LIMIT = 20; private final AtomicInteger second = new AtomicInteger(getCurrentSecond()); private final AtomicInteger counter = new AtomicInteger(0); public void accessResource() { int currentSecond = checkCurrentSecondIsCorrect(); int requestsCount = counter.incrementAndGet(); if (requestsCount > LIMIT) { sleepTillNextSecond(currentSecond); } } private int checkCurrentSecondIsCorrect() { while (true) { int currentSecond = getCurrentSecond(); int lastSecond = second.get(); if (currentSecond == lastSecond || tryToChangeSecond(currentSecond, lastSecond)) { return currentSecond; } } } private boolean tryToChangeSecond(int currentSecond, int lastSecond) { boolean changed = second.compareAndSet(lastSecond, currentSecond); if (changed) { counter.set(0); } return changed; }
Это решение можно улучшить, если завести не один AtomicInteger, а N. Представьте для простоты их в виде массива и пусть N = 60 (количество секунд в минуте). Тогда каждый поток приходит и первым делом определяет какая сейчас секунда. После этого берет из массива счетчик по индексу равному секунде и дальше работает с ним. Получается, что все потоки работают просто со счетчиками и нет необходимости им их сбрасывать. А кто же тогда будет их обнулять? Эту задачу как раз и можно повесить на отдельный поток, который будет раз в 5 секунд просыпаться и обнулять счетчики в “безопасной зоне” (скажем для примера, все кроме пары секунд до и после текущей секунды). Ему тоже не нужно ни с кем синхронизироваться, а размер массива и “безопасной зоны” могут быть подобраны так, что даже опоздание этого потока на 30-40 секунд ни на что не влияет.
И тут пришла идея скомбинировать данный подход с семафорами. Алгоритм получился очень простой. Вначале заводится массив из N семафоров и все они сразу имеют установленное количество заходов равное лимиту в секунду. Когда поток приходит, то он высчитывает секунду, берет по ней из массива семафор и пытается получить заход с его помощью tryAcquire, передавая туда время до конца текущей секунды. Если метод вернет TRUE, то можно смело делать запрос. Если он вернул FALSE (не получилось захватить заход за заданное время), то надо просто вернуться на первый шаг и взять другой семафор. Реализация алгоритма представляет простейший цикл. Параллельно действует восстанавливающий семафоры поток. Он представляет массив в виде замкнутого кольца (так как после 59 секунды снова идет 0 секунда) и раз в 5 секунд восстанавливает значения семафоров в “безопасной зоне” (все кроме 5 секунд до и после по кольцу от текущей секунды). Пример реализации:
private static final int RING_SIZE = 60; private static final int LIMIT = 20; private final Semaphore[] ring = new Semaphore[RING_SIZE]; public AccessManager() { for (int i = 0; i < ring.length; i++) { ring[i] = new Semaphore(LIMIT); } } public void accessResource() { while (!acquire()) {} } private boolean acquire() { try { return tryToAcquire(getActiveRingCell()); } catch (InterruptedException e) { return false; } } private boolean tryToAcquire(int cell) throws InterruptedException { Semaphore semaphore = ring[cell]; long timeoutMs = calculateDelay(); return semaphore.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); } private int getActiveRingCell() { return (int) ((Clock.getTimeMillis() / 1000) % RING_SIZE); }
Пока на тестах в реальном окружении алгоритм показывает себя очень хорошо, но дальнейшую его судьбу определят большая нагрузка и мониторинг... Надеюсь, описанные подходы помогут кому-то реализовать элегантное решения в своем проекте.
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!
Тематика архитектуры и дизайна похоже очень популярна – второй раз на встречу «Клуба анонимных разработчиков» собирается около 100 человек. Мы уже выложили видео последних двух встреч и уже готовы огласить темы следующей встречи. Она состоится во вторник 17 сентября.
На встрече будут представлены две темы. С первым докладом под названием “Объективное чувство стиля в ООП и закон Деметры” выступит постоянный член клуба Владимир Цукур. Цель доклада – познакомить аудиторию с законом Деметры. Это очень простое правило, следуя которому можно значительно понизить связанность классов и уменьшить количество зависимостей, а значит сократить количество ошибок и упростить тестирование. Хорошие ООП разработчики следуют этому закону в полной или частичной мере. Владимир покажет пример на Java, расскажет “правила игры” и осветит практическую сторону вопроса. Также аудитории будут представлены результаты исследований, доказывающих положительное воздействие следования этому закону в реальных проектах.
Второй доклад называется “Священный уровень DAO” и представит его Николай Алименков. С приходом JPA в мире Java многие начали писать о том, что уровень DAO умер и больше нет смысла поддерживать его в архитектуре приложений. Николай постарается убедить аудиторию в обратном, продемонстрировав многочисленные преимущества использования данного уровня, а также на практических примерах раскроет суть использования DAO в современных приложениях.
Итак, 17 сентября пройдет 32-ая встреча клуба. Местом проведения пока выбран офис компании ЕПАМ, который располагается по адресу ул. Кудряшова 14-Б. Наши встречи собирают все больше и больше участников, поэтому мы снова начинаем не влезать даже в более просторные залы. Возможно, ближе к дате встречи место будет изменено.
Внимание!!! Всем участникам нужно иметь при себе документ, удостоверяющий личность для прохождения поста охраны.
Официальное начало встречи по-прежнему в 19:00, завершение в 23:00. Стоимость участия 80 гривен при оплате заранее, 120 гривен при оплате на месте. Пива, пиццы и кофе с печеньками хватит на всех. Регистрация обязательна. Все детали по оплате будут высланы вам после успешного прохождения регистрации. Количество мест ограничено 90 участниками.
Не хочешь пропускать ничего интересного? Подпишись на ленту RSS или следи за нами в Twitter!