Однажды меня посетила мысль о том, что надо закодить что-нибудь на Java для RaspberryPI. Предыстория того, как я дошёл до жизни такой, сама по себе потянет на отдельный пост. Но вот сочные технические подробности, трудности и счастливый конец ниже под катом.
Постановка задачи
Немного разочаровавшись в движении проекта satnogs, я решил попробовать сам написать базовую станцию для приёма радио сигналов на raspberry pi. Проанализировав текущую функциональность satnogs и сложив с собственным заскорузлым enterprise пониманием того, что такое стабильная платформа, я придумал следующие требования:
- java вместо python. Конечно же.
- низкое потребление ресурсов. Embedded же.
- переиспользование уже существующих библиотек. Цель проекта не научиться декодировать самому, а максимально интегрировать уже существующие библиотеки
- стабильность. Коробочка должна работать сама по себе как можно дольше. В идеале её нужно настроить и забыть.
В результате в противоречие вступают только два требования: Java и низкое потребление ресурсов.
В этот момент я почему то вспомнил древний древний слоган «Java — write once, run everywhere» и присказку, что Java может запускаться на кофеварке. С этого момента началось погружение в Java Embedded.
Если вкратце, то в Java существуют две платформы для написания под маленькие устройства: Java ME и Java Embedded. Первая платформа предназначена для совсем маленьких (кофеварки) устройств, а вторая для тех, что чуть-чуть покрупнее. Я выбрал Java Embedded.
Сама Java Embedded в Java 8 претерпела изменения. Теперь её можно собрать с различными профайлами: compact1, compact2, compact3. По сути, это depedency management для бедных. Каждый профайл содержит какие-то части rt.jar, тем самым уменьшая минимальное потребление памяти JVM при загрузке. На моих как-бы тестах (колонка %RES в выводе команды top), я получил следующее потребление:
- compact1 — 10mb
- compact2 — 12mb
Для начала я выбрал самый хардкорный вариант: compact1. Нo если не получится найти под него библиотеки, то можно попробовать compact2.
После выбора версии Java нужно выбрать библиотеки. И вот тут дикий-дикий запад. Поскольку в Java мире всё течёт неспеша и с оглядкой на обратную совместимость, то никто из разработчиков библиотек не побежал оптимизировать свой код под новые профайлы. Тем более скоро выходит Java 9, где всё может ещё раз измениться.
Дальше я проанализировал, минимальный набор библиотек для создания не слишком нагруженного web приложения.
IoC фреймворк
- Dagger, Feather — нет @PreDestroy, @PostConstruct и принципиально не планируется. Про graceful shutdown разработчики видимо не слышали. Вручную контролировать последовательность вызова метода start, чтобы при остановке в обратном порядке вызвать stop, совсем не хочется делать.
- Guice — зависимость на guava, а значит ещё +2mb.
- picocontainer — не compact1
База данных
Какой же Java проект без базы данных. Но тут есть один подвох: в compact1 нет java.sql api. Поэтому я первым делом посмотрел на базы с native api без jdbc:
- berkleydb. NoSQL, но почему-то зависит от javax.transactional.
И с jdbc:
- sqlite — библиотека весит 5mb. Видимо содержит все нативные библиотеки для всех платформ.
- java db. Весит конечно много и разные версии отличаются существенно: 10.8 — 2.5mb, 10.13 — 3.1mb.
Есть ещё куча других мелких непонятных embedded баз данных, которые можно было бы попробовать. Но отлавливать их баги под raspberry pi у меня желания нет.
Зато есть пара других идей:
- А что, если обхитрить JVM: взять compact1 и вручную подложить java.sql api? Ответ: не получится. В Classloader есть вот такой замечательный код:
if ((name != null) && name.startsWith("java.")) { throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); }
Вообще непонятно почему существует такой maven артефакт, если его даже теоретически нельзя загрузить.
- А может без базы? Для моих целей вполне подходят обычные файлы. Sql join тоже вроде не имеет смысла делать.
В общем отказался совсем от базы. Посмотрим надолго ли.
Web container
- tomcat — Ха-ха-ха
- jetty — не compact1
- nanohttpd — не servlet, нет поддержки сессий. Но видимо такова судьба Embedded разработчика.
SSL temination
- nginx. 3mb master node + 3mb 1 client worker. = 6mb. Вроде неплохо.
Вэб клиент
- angular, reactjs — на ровном месте привносят десяток короткоживущих технологий.
- good-o-templates — наш выбор же.
Шаблонизаторы
- JSP — слишком тяжело и нужно много библиотек. Даже не стал копать.
- Freemarker — легко, но как оказалось не compact1.
- Кто-нибудь слышал про jtwig? Я тоже нет, но они умееют работать в compact1 и поддерживают базовые фичи.
Логирование
- logback — только compact3
- log4j — full JRE
- java.util.logging? — Хуже уже не будет.
Json
- gson. Зависимость на java.sql (!!!)
- jacksonxml. Зависимость на org.w3c.dom.Node
- очередной «нагуглил-ночью» код https://github.com/ralfstx/minimal-json. Посмотрел, вроде там нечему ломаться.
После нескольких запусков и сборке всего вместе выплыло несколько косяков, но их можно поправить конфигурацией. Например:
https://stackoverflow.com/questions/13825403/java-how-to-get-logger-to-work-in-shutdown-hook
Итого
- все библиотеки в сборе + прогретый кэш для шаблонизатора занимают в памяти ~23mb
- код открыт и доступен: https://github.com/dernasherbrezon/r2cloud (надеюсь пароли нигде там не закоммитил)
Автор: dernasherbrezon