Тынц
Мелочь, а приятно :)
Так получилось, что я работаю в мире Ынтерпрайза. Java, J2EE, Oracle и все такое. По этой части я интересуюсь HighLoad-ом и распределенными приложениями. Помимо этого, в свободное время занимаюсь интересными языками под JVM - Groovy, Scala, Clojure, а так же интересуюсь внутренностями Java и разработкой компиляторов и виртуальных машин.
пятница, 28 декабря 2012 г.
четверг, 27 декабря 2012 г.
Resizing existing virtual disk in Virtual Box
Today I had to resize the existing virtual hard drive on the virtual machine with all stuff on it second time in my life. Unfortunately, the first time was some time back and I didn't bother to make notes for myself, so this time it again took me more than 1 hour to get it all done. This process sounds like something really simple and easy, something thousands of programmers probably want to know how to do, so you may assume all required steps are well-known, ordered, and show up in Google when you search for something like "virtual box existing disk resize". However, most of the instructions found are either wrong or incomplete, so it may take quite some time for googling before you really get it done.
So so I want to share it here in case. Steps below were tested with Centos 6.3 as guest OS and Windows 8 as host OS (this time, and before I used to do it with Centos 6.2 as guest OS and Ubuntu 12.10 as host OS).\
1) Resize the physical file containing virtual hard drive in host OS:
- on Linux host just run something like:
sudo VBoxManage modifyhd /.../mydrive.vdi --resize 20480
where last number is size in MB.
On Windows host, do something like:
C:\Program Files\Oracle\VirtualBox>VBoxManage modifyhd D:\VM\Ubuntu\Ubuntu.vdi --resize 20480
2) Allocate the space on the virtual hard drive using GParted or similar tool.
Download .ISO image of it from http://gparted.sourceforge. net/download.php, mount it in your VM settings as a live CD, load from it, then resize the needed partition (most likely it will be root partition you need to enlarge, something like /dev/sda2 or so)
3) Load the guest OS and resize the partition within LVM group (you'll need to find exact LVM device used for mapping! this is just example how it is on my Centos 6.3 VM):
sudo lvm lvresize /dev/mapper/vg_<myhostname>- lv_root –size 20G
4) Resize the filesystem on partition:
sudo resize2fs -p /dev/mapper/vg_<myhostname>- lv_root 20G
Now, run df command and you are all set- should see new size as output.
Hope if will help someone.
среда, 29 февраля 2012 г.
На чем писать Ынтерпрайз проекты?
Немного грубого троллинга на утро :)
Давайте представим, что мы с вами собираемся начать крупный и серьезный Ынтерпрайз проект. И перед нами встает вопрос выбора основного языка (понятно, если у нас есть rich client нам понадобится Javascript / Flash в том или ином виде, для БД нам почти наверняка понадобится SQL, вероятнее всего с его процедурными расширениями и т.д., но речь об основном языке).
Википедия подсказывает, что языков программирования нынче развелось много - тысячи их. Но что можно использовать?
Давайте представим, что мы с вами собираемся начать крупный и серьезный Ынтерпрайз проект. И перед нами встает вопрос выбора основного языка (понятно, если у нас есть rich client нам понадобится Javascript / Flash в том или ином виде, для БД нам почти наверняка понадобится SQL, вероятнее всего с его процедурными расширениями и т.д., но речь об основном языке).
Википедия подсказывает, что языков программирования нынче развелось много - тысячи их. Но что можно использовать?
- Прежде всего, отбросим unmanaged языки. Писать Ынтерпрайз софт в 2012 году на С или С++, возиться с указателями и ручным управлением памятью, закрашивать свежую седину после очередного хитрого бага вызванного разничной реализации того или иного поведения в разных версиях компилятора или чем-то подобным... Не тянет. Будь это обособленная железка типа игровой консоли или PC для игр, будь это системные утилиты или что-то подобное, где совершенно критично выжать 99% возможного из доступной аппаратной конфигурации - да, один этот аргумент перевесил бы все остальные. Но в Ынтепрайз проекте я выжму 85%, а потом подбавлю серверов в кластер. Седобородые археологи и геронтофилы, прощу прощения, с глубоким уважением к вам... на выход.
- Далее, отбросим динамические языки, такие, как Python, Ruby и им подобные. Даже не принимая во внимание скорость их выполнения (будем считать, что зрелые современные динамические языки имеют в той или иной степени развитый JIT), писать на них крупные проекты затруднительно именно из-за их динамической природы. Поддержка со стороны IDE (рефакторинги, статический анализ и миллион мелочей, из-за которых мы выбираем IndelliJ IDEA, а не windows notepad) существенно хуже, чем для языков с простой статической системой типов и простой строгой объектной моделью. Ряд вещей, концептуально крутых, может совершенно убивать саму возможность нормальной поддержки языка со стороны IDE - например, метаобъектный протокол. За исключением Kotlin (на F# близко не смотрел, возможно, там есть?), пока что я не видел, например, комбинаций языков и сред разработки, в которых поддерживалась бы полноценно умное автодополнение, подсветка и прочее внутри функциональных литералов (лямбда функций) в коде. В результате, там, где в языке с простой статической типизацией ошибки находятся фоновым процессом компиляции в IDE, в динамических языках надо писать unit test. Только не говорите мне, что писать их все равно нужно всегда и везде :) Сторонники динамических языков, аджайла, meet-up-ов.. Вы умные и часто прикольные, но сегодня не ваша ночь.
- Отбросим C#, F# и группу языков, имеющих основные реализации на платформе .NET. Я не слишком верю в Mono и то, что это полноценный эквивалент для Microfost .NET под Windows, а идея писать Ынтепрайз софт, работающий только под Windows, мне представляется странной. Дотнетчики, я сам считаю эту платформу очень мощной, а C# - развивающимся, вполне продуманным, современным и мощным языком, но..увы-увы. До лучших дней.
- Отбросим академические функциональные языки, языки, используемые для обкатки разных концепций в теории систем типов и новых парадигм, языки, используемые редкими исследователями-энтузиастами в университетах, языки, по которым нереально найти в проект 20 человек за сколько-нибудь разумное время. Отбросим с затаенным сожалением. Отбросим Haskell, OCaml, Erlang, Lisp...Покойся с миром, славный МакКарти, твои труды не забыты и еще сослужат добрую службу.. но не нам и не сегодня :(.
- Отбросим языки, на которые просто писать нормальный поддерживаемый и понятный код нормальному человеку трудно. Перловики, просто бесшумно спрячьтесь под стулья.
И что остается? Правильно, Java.
BO-OO-O-OO-O-R-I-I-I-NG!!!
И вот именно поэтому, принимая решения о том, какой язык использовать в подсистеме крупного проекта, я посматриваю на языки на платформе JVM.
- Groovy, который мы уже используем, и, надеюсь, будем использовать все больше и больше.
- Scala, ты загадочна, очаровательна и сложновата...Но в некоторых случаях пойдет. Ты все-же, приятнее Haskell-a :)
- Closure? Lisp, я знаю, ты бессмертен, посмотрим, получится ли у этого твоего потомка взлететь.
- Kotlin? Андрей Бреслав и прочие, ребята, вы делаете язык, который пофиксит две критические проблемы Groovy - более динамическая, чем статическая типизация типизация и, частично как следствие этого, плохая поддержка со стороны IDE. Получится у вас - повешу вашу фотку над рабочим столом.
- Есть еще немало славных языков в этом семействе - Ceylon, Fantom и прочие. Каждый из них нелеп и крив по своему, но у каждого есть какая-то изюминка.
Вот на них я и гляжу с некоторой надеждой :)
пятница, 20 января 2012 г.
Несколько мыслей про CAP теорему
Навеяно обсуждением на Хабре (Недопонимание CAP-теоремы) и постами Ивана Сагалаева в его блоге - раз, два. Я решил вынести мои комментарии в отдельной пост, чтобы они воспринимались более целостно. К тому же, я хотел бы обобщить свое понимание некоторых аспектов этой теоремы своими словами, максимально просто и доступно.
Прежде всего, что такое CAP-теорема - читать тут. Если совсем вкратце, одним предложением - "В (идеальной) распределенной системе можно обеспечить одновременно выполнение только двух из следующих трех свойств - Consistency, Availability, и Partition tolerance", откуда, собственно, и название теоремы - CAP.
Под Consistency в этой теореме понимается целостность данных, так, как они видимы для пользователей системы. Т.е. не должно быть такого, что пользователь А логинится и видит одно, а пользователь Б, смотрящиq на те же (логически) данные в тот же момент времени - видит другое. Здесь важный момент - что такое логические данные. Допустим, оба пользователя смотрят на статус некоторого третьего пользователя С. Поскольку в распределенной системе для обеспечения сохранности данных при выходе из строя узлов кусочки данных (chunks) обычно хранятся в нескольких копиях на разных узлах системы, очевидно, что пользователь А может смотреть на одну копию данных, где хранится статус С, а пользователь Б может смотреть на другую копию (например, если это пользователи из разных регионов, и в системе присутствует load balancing по примерному географическому адресу, который установливается на основе IP адреса из http-запроса). Тогда, consistency требует, чтобы в любой момент времени, если пользователи А и Б смотрят на статус С, они видели один и тот же статус.
Возникает вопрос - а как обеспечить consistency? Очевидно, существует два базовых способа - это синхронная и асинхронная репликация данных при каждом их изменении.
Пример синхронной репликации - это двухфазный коммит транзакции в среде, которая поддерживает транзакционность в смысле ACID. Предположим, пользователь С обновляет свой статус. Запрос на обновление статуса приходит на некоторый промежуточный мастер-сервер. Мастер сервер находит два узла, на которых лежат две копии данных текущего статуса С.
Дальше первая фаза - мастер-нод отдает обоим нодам с данными команду применить обновление, но не коммитить транзакцию, и сообщить мастер-ноду, как только все будет готово и останется только сделать коммит (понятие "транзакция готова, останется только сделать коммит", само по себе может быть непростым.. Например, для СУБД Oracle транзакция считается закоммиченной, когда сгенерирован SCN (уникальный system change number, и соответствующий redo-log файл с информацией, необходимой для наката транзакции с нуля, сброшен на диск).
Вторая фаза - как только мастер-нод дождался подтверждения готовности обоих нодов закоммитить свою часть транзакции, мастер-нод отдает обоим нодам указание осуществить коммит и дожидается ответа, что коммит произведен на всех нодах. После этого транзакция считается законченной.
Итак, очевидно, синхронная репликация позволяет нам добиться Consistency, так как гарантируется, что данные либо обновляются во всех копиях, либо ни в одной. И вот тут в игру вступает Availability. Чем больше у нас копий данных, чем больше серверов, тем дольше будет занимать каждая операция обновления (из-за издержек на проведение двухфазных транзакций).
Все становится совсем плохо, если вспомнить о Partition Tolerance. PT требует, чтобы система могла функционировать тогда, когда нарушается сетевое соединение между отдельными узлами (группами узлов) кластера, либо когда произвольные узлы уходят в даун.
Что, если у нас пропало соединение между двумя узлами, на которых лежат порции данных, причем пропало так, что мастер нод через который работает пользователь А может достучаться только до нода 1, а мастер нод через который работает пользователь Б, может достучаться только до нода 2? Например, пользователь А, нод 1, и один из мастер нодов в Украине, пользователь Б, нод 2 и соотв. мастер нод в Беларуси, и магистральное соединение нарушилось. Что произойдет, когда пользователи А и Б хотят прочитать статус С? Они могут это сделать, если пользователь А все еще имеет доступ к ноду 1, а пользователь Б - к ноду 2. Но вот обновить свой статус пользователь С теперь не сможет (если мы хотим обеспечить целостность данных). В самом деле, любое обновление повиснет на ожидании ответа от нода с протиположной стороны.
Каким образом мы можем позволить пользователю С обновлять свой статус в таких условиях? Очевидно, используя асинхронную репликацию, которая подразумевает т.н. Eventual Consistency. Т.е. когда пользователь С пытается изменить свой статус, мы меняем данные на ноде 1, а ноду 2, до которого достучаться не можем, посылаем каким-либо образом асинхронное сообщение о том, что статус надо бы изменить. До момента восстановления соединения, пользователь А будет видеть свежие изменение, а пользователь Б, которому, предположим, доступен только узел 2 - будет временно видеть старые данные, что, согласитесь, часто лучше чем ничего.
Когда соединение до нода 2 восстановится, нод 2 обновит свои данные с нода 1, и информация о статусе пользователя С снова будет консистентной во всей системе. Единственная проблема - что если в промежутке времени после потери соединения между нодами 1 и 2, на обоих были произведены некоторые изменения? После восстановления соединения нам надо будет решить, как нам смержить данные в единую версию, которая и будет считатьcя новой консистентной версией данных в системе. Как именно делать мерж - зависит от семантики приложения. В случае со статусом пользователя, можно тупо брать брать версию с самым поздним timestamp-ом. В каких-то случаях (для текстовых файлов в распределенных системах контоля версий или чем-то подобном) можно делать трехсторонний merge (три версии - предыдущая консистентная версия до потери соединения, сохраненная где-либо, версия на ноде 1, версия на ноде 2).
Итак, считая, что Partition Tolerance - требование для распределенной системы, работающей в несовершенном реальном мире, необходимое, у нас есть выбор между двумя типами систем - такими, в которых за основу взято Consistency (я думаю, в качестве примера можно привести банковские и разнообразные платежные системы, там, где нет сверхвысоких требований к latency), и системами, в которых во главе угла стоит Availability (некоторые интернет-мазазины, например, всемирно известный Amazon), социальные сети и пр.
Прежде всего, что такое CAP-теорема - читать тут. Если совсем вкратце, одним предложением - "В (идеальной) распределенной системе можно обеспечить одновременно выполнение только двух из следующих трех свойств - Consistency, Availability, и Partition tolerance", откуда, собственно, и название теоремы - CAP.
Под Consistency в этой теореме понимается целостность данных, так, как они видимы для пользователей системы. Т.е. не должно быть такого, что пользователь А логинится и видит одно, а пользователь Б, смотрящиq на те же (логически) данные в тот же момент времени - видит другое. Здесь важный момент - что такое логические данные. Допустим, оба пользователя смотрят на статус некоторого третьего пользователя С. Поскольку в распределенной системе для обеспечения сохранности данных при выходе из строя узлов кусочки данных (chunks) обычно хранятся в нескольких копиях на разных узлах системы, очевидно, что пользователь А может смотреть на одну копию данных, где хранится статус С, а пользователь Б может смотреть на другую копию (например, если это пользователи из разных регионов, и в системе присутствует load balancing по примерному географическому адресу, который установливается на основе IP адреса из http-запроса). Тогда, consistency требует, чтобы в любой момент времени, если пользователи А и Б смотрят на статус С, они видели один и тот же статус.
Возникает вопрос - а как обеспечить consistency? Очевидно, существует два базовых способа - это синхронная и асинхронная репликация данных при каждом их изменении.
Пример синхронной репликации - это двухфазный коммит транзакции в среде, которая поддерживает транзакционность в смысле ACID. Предположим, пользователь С обновляет свой статус. Запрос на обновление статуса приходит на некоторый промежуточный мастер-сервер. Мастер сервер находит два узла, на которых лежат две копии данных текущего статуса С.
Дальше первая фаза - мастер-нод отдает обоим нодам с данными команду применить обновление, но не коммитить транзакцию, и сообщить мастер-ноду, как только все будет готово и останется только сделать коммит (понятие "транзакция готова, останется только сделать коммит", само по себе может быть непростым.. Например, для СУБД Oracle транзакция считается закоммиченной, когда сгенерирован SCN (уникальный system change number, и соответствующий redo-log файл с информацией, необходимой для наката транзакции с нуля, сброшен на диск).
Вторая фаза - как только мастер-нод дождался подтверждения готовности обоих нодов закоммитить свою часть транзакции, мастер-нод отдает обоим нодам указание осуществить коммит и дожидается ответа, что коммит произведен на всех нодах. После этого транзакция считается законченной.
Итак, очевидно, синхронная репликация позволяет нам добиться Consistency, так как гарантируется, что данные либо обновляются во всех копиях, либо ни в одной. И вот тут в игру вступает Availability. Чем больше у нас копий данных, чем больше серверов, тем дольше будет занимать каждая операция обновления (из-за издержек на проведение двухфазных транзакций).
Все становится совсем плохо, если вспомнить о Partition Tolerance. PT требует, чтобы система могла функционировать тогда, когда нарушается сетевое соединение между отдельными узлами (группами узлов) кластера, либо когда произвольные узлы уходят в даун.
Что, если у нас пропало соединение между двумя узлами, на которых лежат порции данных, причем пропало так, что мастер нод через который работает пользователь А может достучаться только до нода 1, а мастер нод через который работает пользователь Б, может достучаться только до нода 2? Например, пользователь А, нод 1, и один из мастер нодов в Украине, пользователь Б, нод 2 и соотв. мастер нод в Беларуси, и магистральное соединение нарушилось. Что произойдет, когда пользователи А и Б хотят прочитать статус С? Они могут это сделать, если пользователь А все еще имеет доступ к ноду 1, а пользователь Б - к ноду 2. Но вот обновить свой статус пользователь С теперь не сможет (если мы хотим обеспечить целостность данных). В самом деле, любое обновление повиснет на ожидании ответа от нода с протиположной стороны.
Каким образом мы можем позволить пользователю С обновлять свой статус в таких условиях? Очевидно, используя асинхронную репликацию, которая подразумевает т.н. Eventual Consistency. Т.е. когда пользователь С пытается изменить свой статус, мы меняем данные на ноде 1, а ноду 2, до которого достучаться не можем, посылаем каким-либо образом асинхронное сообщение о том, что статус надо бы изменить. До момента восстановления соединения, пользователь А будет видеть свежие изменение, а пользователь Б, которому, предположим, доступен только узел 2 - будет временно видеть старые данные, что, согласитесь, часто лучше чем ничего.
Когда соединение до нода 2 восстановится, нод 2 обновит свои данные с нода 1, и информация о статусе пользователя С снова будет консистентной во всей системе. Единственная проблема - что если в промежутке времени после потери соединения между нодами 1 и 2, на обоих были произведены некоторые изменения? После восстановления соединения нам надо будет решить, как нам смержить данные в единую версию, которая и будет считатьcя новой консистентной версией данных в системе. Как именно делать мерж - зависит от семантики приложения. В случае со статусом пользователя, можно тупо брать брать версию с самым поздним timestamp-ом. В каких-то случаях (для текстовых файлов в распределенных системах контоля версий или чем-то подобном) можно делать трехсторонний merge (три версии - предыдущая консистентная версия до потери соединения, сохраненная где-либо, версия на ноде 1, версия на ноде 2).
Итак, считая, что Partition Tolerance - требование для распределенной системы, работающей в несовершенном реальном мире, необходимое, у нас есть выбор между двумя типами систем - такими, в которых за основу взято Consistency (я думаю, в качестве примера можно привести банковские и разнообразные платежные системы, там, где нет сверхвысоких требований к latency), и системами, в которых во главе угла стоит Availability (некоторые интернет-мазазины, например, всемирно известный Amazon), социальные сети и пр.
пятница, 6 января 2012 г.
Неявное приведение типов в Groovy
Не так давно я задавал вопрос в Groovy mail-list - есть ли какой-то устойчивый список вещей, которые надо избегать при написании высокогопроизводительного на Groovy. Среди прочих советов, один из главных разработчиков Groovy, Jochen "blackdrag" Theodorou указал, что в общем случае, зачастую использование конкретного типа при объявлении переменной (например, MyType var = ... вместо def var = ...) может ухудшить производительсть из-за накладных расходов на проверку типов и, если нужно, их приведение.
Небольшой эксперимент, который показывают эти накладные расходы даже на Groovy 1.8.3. Перед этим экспериментом будет полезно просмотреть следующую статью - http://groovy.codehaus.org/From+source+code+to+bytecode, где рассказывается про то, как по шагам исходный код на Groovy преобразуется в байткод JVM, а так же почитать какую-нибудь вводную статью в собственно байткод, например эту - http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/.
Итак, простейший код:
Небольшой эксперимент, который показывают эти накладные расходы даже на Groovy 1.8.3. Перед этим экспериментом будет полезно просмотреть следующую статью - http://groovy.codehaus.org/From+source+code+to+bytecode, где рассказывается про то, как по шагам исходный код на Groovy преобразуется в байткод JVM, а так же почитать какую-нибудь вводную статью в собственно байткод, например эту - http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/.
Итак, простейший код:
class NoStrictType {
void myMethod1() {
def a = 4.2
}
}
class StrictType {
void myMethod1() {
int a = 4.2
}
}
Единственное отличие - во втором классе тип локальной переменной определен явно. Скомпилируем оба класса и посмотрим на байткод этих методов.
Для первого случая (без конкретного типа):
Итак, код вполне очевиден. Опустим первую строчку, это получение массива кешированных CallSite-ов, они напрямую к операциям над типами не относятся. Далее мы создаем новый объект типа BigDecimal (все помнят, что по умолчанию в Groovy все нецелые числа представляются как BidDecimal?), дублируем текущее значение на вершине стека операндов, достаем значение 4.2 из пула констант, инициализируем объект BigDecimal, сохраняем ссылку на этот созданный объект во второй ячейке массива локальных переменных текущего фрейма, потом загружаем ее оттуда на вершину стека, и наконец, используем pop;return, чтобы вернуть эту ссылку из метода. Опять же, как все помнят, в Groovy даже без явного оператора return любой метод возвращает последнюю переменную, которая использовалась в вычислениях (точнее, последнюю сссылку, сохраненную на вершине стека операндов на момент окончания работы метода).
Теперь, байткод для второго класса, StrictType.
В чем разница по сравнению с первым случаем? Добавился вызов статического метода DefaultTypeTransformation.intUnbox(). Посмотрим в документацию к этому методу, что он делает.
http://groovy.codehaus.org/api/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.html
Видим, что этот метод просто конвертит объект-ссылку в в объект типа Number, и возвращает примитив.
public static int intUnbox(Object value) {
Number n = castToNumber(value, int.class);
return n.intValue();
}
Смотрим, как именно выполняется эта конвертация типов:
public static Number castToNumber(Object object, Class type) {
if (object instanceof Number)
return (Number) object;
if (object instanceof Character) {
return Integer.valueOf(((Character) object).charValue());
}
if (object instanceof GString) {
String c = ((GString) object).toString();
if (c.length() == 1) {
return Integer.valueOf(c.charAt(0));
} else {
throw new GroovyCastException(c, type);
}
}
if (object instanceof String) {
String c = (String) object;
if (c.length() == 1) {
return Integer.valueOf(c.charAt(0));
} else {
throw new GroovyCastException(c, type);
}
}
throw new GroovyCastException(object, type);
}
Несколько операторов instanceof, которые не самые дешевые, несколько явных приведений типа, условные операторы, работа с исключениями. Я не мерил, как это влияет на скорость работы в реальных приложениях, но судя по самому факту, сколько дополнительных байткодов надо выполнить в этом случае для приведения типов, впечатляет. Вспомните - сам оригинальный метод - всего 10 байткодов.
Подписаться на:
Сообщения (Atom)