Kanashi — это распределенная NoSQL база данных с памятью, основанная на Kotlin и поддерживающая транзакции. В точности говоря, она предназначена для углубленного изучения автором распределённых систем и не является конкурентом существующих зрелых баз данных. Kanashi использует Netty для связи и реализацию выборки через Raft, что можно рассматривать как встроенное грубое решение NameServer. Функции синхронизации журнала были адаптированы от Kafka. Кроме того, Kanashi поддерживает транзакции, но пока только уровень изоляции Repeatable Read.
В нестрогих тестах производительности было выявлено следующее:
Проект Kanashi был создан на основе предыдущего проекта автора — фреймворка Hanabi, который был значительно улучшен.
Сейчас весь проект достаточно развит, так что его можно быстро преобразовать в другой тип фреймворков, таких как RPC фреймворк или распределённый планировщик задач.
Настройте сервер, конфигурация которого находится в модуле kanashi-server в файле kanashi.properties:
#
# Установите имя сервера, будьте осторожны: имя сервера должно быть указано в client.addr
#
server.name=kanashi.1
#
# Конфигурация адреса состоит из client.addr.{serverName}={host}:{port}
#
client.addr.kanashi.1=127.0.0.1:11001
client.addr.kanashi.2=127.0.0.1:11002
client.addr.kanashi.3=127.0.0.1:11003
Вы можете использовать client.addr.{serverName}={host}:{port}
для настройки информации о кластере, однако динамическое масштабирование узлов пока не поддерживается. server.name
объявляет текущий узел как конкретный узел в сети. Однако это не обязательно должно быть указано в конфигурационном файле; его можно указать при запуске с помощью параметра server_name:kanashi.1
.
Клиентская часть пока очень проста и может быть запущена через импорт kanashi-client
или непосредственно внутри kanashi-client
. Для клиентской части требуется конфигурация в файле application.properties
, расположенном в модуле kanashi-client
. Конфигурация должна выглядеть следующим образом:
kanashi.host=127.0.0.1
kanashi.port=11001
kanashi.resend.backoff.ms=30
Используйте KanashiStrTemplate
для отправки команд на сервер:
@Autowired
private KanashiStrTemplate kanashiStrTemplate;
@Test
public void test() {
kanashiStrTemplate.delete("Anur");
assert (kanashiStrTemplate.get("Anur") == null);
kanashiStrTemplate.set("Anur", "1");
assert (kanashiStrTemplate.get("Anur").equals("1"));
kanashiStrTemplate.setIf("Anur", "2", "2");
assert (kanashiStrTemplate.get("Anur").equals("1"));
}
Дополнительные примеры тестирования можно найти в файлах TestWithNonTrx
, TestWithTrx
, TestWritingAmountOfMsg
.
Проект Kanashi состоит из шести основных модулей: модуль выбора лидерства, модуль журнала, модуль IOC, модуль данных, бизнес-модуль и конфигурационный модуль.
Модуль журнала был адаптирован от Kafka и использует последовательную запись на диск для записи журналов. Одним журналом управляет объект LogItem
. При записи на диск используется информация о смещении плюс объект LogItem
. Набор журналов определённого размера (который можно указать в конфигурации) записывается в файл .log
. Имя файла формируется на основе смещения первой записи в журнале.
Каждому файлу .log
соответствует файл .index
— индексный файл. Индексный файл использует разреженный индекс, который регистрирует относительное положение каждой записи в файле после записи определённого объёма данных. Когда кластер принимает решение о подтверждении определённого прогресса, все логи, старше этого прогресса, будут записаны на диск. Услуги, которые ещё не получили этот прогресс, могут синхронизироваться через запрос Fetch
. Сообщение синхронизации будет отправлено в сеть непосредственно из логов на диске с использованием технологии нулевой копии.
Проект реализует простое внедрение:
/**
* Created by Anur IjuoKaruKas on July 8, 2019
*
* Метаданные, связанные с выборами, хранятся здесь
*/
@NigateBean
public class ElectionMetaService {
@NigateInject
private final InetSocketAddressConfiguration inetSocketAddressConfiguration;
@NigateInject
private final LogService logService;
}
``````markdown
@NigateInject
private final NigateListenerService kanashiListenerService;
private static final Logger logger = LoggerFactory.getLogger(ElectionMetaService.class);
/**
* После запуска загружаем сохраненный ранее прогресс GAO обратно в память
*/
@NigatePostConstruct(dependsOn = "logService")
public void init() {
InitialGAO initialGAO = logService.getInitialGAO();
generation = initialGAO.generation;
offset = initialGAO.offset;
}
}
С помощью аннотации @NigateBean
мы объявляем экземпляр как bean
, который можно внедрять в других beans
с помощью аннотации @NigateInject
. Также поддерживаются такие возможности, как PostConstruct
, Listener
и другие.
Хотя проект и небольшой, но он имеет все необходимые компоненты. Это основное отличие от проекта Hanabi
. Хотя язык Kotlin позволяет легко реализовать паттерн Singleton, чтобы реализовать дополнительные функции, такие как слушатель событий или управление жизненным циклом, потребуется много усилий по поддержанию чистого кода.
Модуль данных был разработан недавно, его архитектурный дизайн удовлетворяет требования, модуль хорошо упакован и практически полностью декупирован от остальных модулей.
Модуль данных можно рассматривать как потребитель объектов журнала LogItem
. В нем используются паттерны цепочки ответственности для выполнения операций чтения и записи. Для обеспечения изоляции данные разделены на три части: «неотправленные», «отправленные» и «часть LSM».
```Изначально было планирование полной реализации данных на уровне диска и построения дерева LSM
. Однако эта задача требует значительных трудозатрат, поэтому работа над ней была приостановлена. Также требуется учет таких факторов, как распределение области, страницы и блока на диске. Кроме того, следует учитывать производительность, например, использование булевых фильтров для предотвращения бесполезных запросов. Сторона транзакций использует битовые карты для общего представления состояния транзакций, что также полезно для создания "снимка" всех транзакций базы данных.
Бизнес-модуль в основном представляет собой простое сочетание и вызов модулей движка данных:
StrApiTypeEnum.SELECT -> {
engineDataQueryer.doQuery(engineExecutor, {
if (it != null && !it.isDelete()) {
engineExecutor.getEngineResult().setKanashiEntry(it);
}
engineExecutor.shotSuccess();
}, false);
}
Интересной особенностью является управление блокировками транзакций:
fun acquire(trxId: Long, key: String, whatEverDo: () -> Unit) {
// Создаем или получаем ранее зарегистрированную транзакцию и регистрируем владение этим ключом
val trxHolder = trxHolderMap.compute(trxId) { _, th -> th ?: TrxHolder(trxId) }!!.also { it.holdKeys.add(key) };
// Сначала встаем в очередь
val waitQueue = lockKeeper.compute(key) { _, ll ->
(ll ?: LinkedList()).also { if (!it.contains(trxId)) it.add(trxId) };
}!!
``` когда (очередьОжидания.first()) {
// Это означает, что ключ не заблокирован
trxId -> {
чтоКонечноДелаешь.invoke()
logger.trace("Транзакция $trxId успешно приобрела или заново вошла в блокировку ключа $key и успешно выполнила операцию")
}
}```markdown
## Блокировка ключа
```kotlin
// This means that the key is locked
else -> {
trxHolder.undoEvent.compute(key) { _, undoList ->
(undoList ?: mutableListOf()).also { it.add(whatEverDo) }
}
logger.debug("Transaction $trxId cannot obtain a lock on key $key, will wait for notification from previous transaction and postpone operation execution")
}
Код написан в функциональном стиле, где выполняемые бизнес-действия выделены в отдельную функцию. Если блокировка была получена, то достаточно просто вызвать эту функцию; если нет, то она помещается в отображении для "ожидания уведомления", что позволяет избежать реального блокирования и снизить затраты на переключение потока из-за блокировки ключа.
### 1.5 Конфигурационный модуль
Код конфигурационного модуля был написан примерно год назад. В настоящее время многие настраиваемые места являются "магическими числами", записанными напрямую, реализация довольно простая, а приоритет совершенства невелик.
```### 3. Требования по исправлению или решению проблем — Кластерный режим менее стабилен в модуле данных по сравнению с одиночной конфигурацией, недостаточно протестирован
- Недостаток тестирования кластерного режима несколькими клиентами
- Отсутствие тестирования одиночного режима нескольким клиентам
- Жесткий дизайн соединения клиента с кластером и получения информации о нем
- Иногда возникают проблемы с чтением лог-файлов при неправильно настроенной грациозной остановке
- Необходим механизм принудительного завершения транзакций в случае превышения времени ожидания
- Возможна проблема конкурентного доступа к объекту `TrxHolder`, который хранит снимок транзакции; вероятность этого крайне мала и трудна для воспроизведения
### 4. ДругоеЕсли вас интересует проект или вы хотите вместе поработать над этими странными вещами, присоединяйтесь к группе, там в данный момент практически нет людей: [1035435027](https://vk.com/public1035435027)

Для обсуждения Java-технологий можно присоединиться к другой группе: [767271344](https://vk.com/public767271344)

Кроме того, ищу работу = =, опыт работы 3 года — Java-backend, мое резюме:
[http://anur.ink/upload/2020/4/JAVA%E5%90%8E%E7%AB%AF-3%E5%B9%B4-%E7%BD%91%E7%BB%9F%E7%89%88-4ea872fb596f4553aa9f04cefcb65a84.pdf](http://anur.ink/upload/2020/4/JAVA%E5%90%8E%E7%AB%AF-3%E5%B9%B4-%E7%BD%91%E7%BB%9F%E7%89%88-4ea872fb596f4553aa9f04cefcb65a84.pdf)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )