1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/apache-tephra

Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.rst

(Tephra)

Примечание: Tephra переместился в Apache Incubator

Для последних обновлений Apache Tephra посетите его новый сайт по адресу http://tephra.incubator.apache.org.


Транзакции для Apache HBase™: Cask Tephra обеспечивает глобально согласованные транзакции на основе Apache HBase. В то время как HBase обеспечивает сильную согласованность на уровне строк или областей с операциями ACID, он жертвует согласованностью между областями и таблицами в пользу масштабируемости. Это компромисс требует от разработчиков приложений управлять сложностью обеспечения согласованности, когда их изменения пересекают границы областей. Предоставляя поддержку для глобальных транзакций, которые охватывают области, таблицы или несколько RPC, Tephra упрощает разработку приложений на основе HBase, не оказывая значительного влияния на производительность или масштабируемость для многих рабочих нагрузок.

Как это работает

Tephra использует нативное версионирование данных HBase для предоставления многоверсионного управления конкурентными операциями (MVCC) для транзакционных чтений и записей. С использованием возможностей MVCC каждая транзакция видит свою собственную согласованную "снимок" данных, обеспечивая изоляцию снимка конкурентных транзакций.Tephra состоит из трех основных компонентов:

  • Сервер транзакций - поддерживает глобальное представление состояния транзакций, назначает новые идентификаторы транзакций и выполняет обнаружение конфликтов;
  • Клиент транзакций - координирует начало, завершение и откат транзакций;
  • Копроцессор TransactionProcessor - применяет фильтрацию к данным, считанным (на основе состояния данной транзакции), и удаляет данные из старых (уже невидимых) транзакций.

Сервер транзакций

Центральный управляющий транзакциями генерирует глобально уникальный, временем-зависимый идентификатор транзакции для каждой начатой транзакции и поддерживает состояние всех текущих и недавно завершенных транзакций для обнаружения конфликтов. Хотя можно запустить несколько экземпляров сервера транзакций одновременно для автоматического переключения, только один экземпляр сервера активно обслуживает запросы в любое время. Это координируется путем проведения выборов лидера среди запущенных экземпляров через ZooKeeper. Активный экземпляр сервера транзакций также регистрируется с помощью интерфейса обнаружения служб в ZooKeeper, что позволяет клиентам обнаруживать текущий активный экземпляр сервера без дополнительной конфигурации.

Клиент транзакций ..................Клиент отправляет запрос к активному серверу транзакций для запуска новой транзакции. Это возвращает новый экземпляр транзакции клиенту, с уникальным идентификатором транзакции (используемым для идентификации записей для транзакции), а также список идентификаторов транзакций для исключения при чтении (из текущих или отмененных транзакций). При выполнении записей клиент переопределяет время создания для всех измененных ячеек HBase с идентификатором транзакции. При чтении данных из HBase клиент пропускает ячейки, связанные с любыми из исключенных идентификаторов транзакций. Исключения при чтении применяются через фильтр стороннего сервера, внедренный копроцессором TransactionProcessor.

Копроцессор TransactionProcessor .................................Копроцессор TransactionProcessor загружается на всех таблицах HBase, где выполняются транзакционные чтения и записи. Когда клиенты читают данные, он координирует фильтрацию стороннего сервера, выполняемую на основе снимка транзакции клиента. Ячейки данных из любых транзакций, которые в настоящее время выполняются или которые завершились неудачей и не могут быть отменены ("отменённые" транзакции), будут пропущены при этих чтениях. Кроме того, TransactionProcessor удаляет любые версии данных, которые больше не видны ни одной выполняющейся транзакции, либо потому, что транзакция, связанная с ячейкой, завершилась неудачей, либо запись из более поздней транзакции успешно была подтверждена в той же колонке.Более подробная информация о том, как работают транзакции Tephra и взаимодействие между этими компонентами, может быть найдена в нашей презентации Транзакции над HBase.

В разработке?

Статус непрерывной интеграции на Travis CI: (BuildStatus)

Требования

Java Runtime

Последняя версия JDK или JRE 1.7.xx или 1.8.xx для Linux, Windows или Mac OS X должна быть установлена в вашей среде; мы рекомендуем использовать Oracle JDK.

Чтобы проверить установленную версию Java, выполните команду:

$ java -version

Tephra тестировался с Oracle JDK; он может работать с другими JDK, такими как Open JDK, но он не тестировался с ними. После установки JDK вам потребуется настроить переменную окружения JAVA_HOME.

Окружение Hadoop/HBase

Tephra требует рабочего окружения HBase и HDFS для своей работы. Tephra поддерживает следующие версии компонентов:+---------------+-------------------+---------------------------------------------------------+
Компонент | Источник | Поддерживаемые версии |

+===============+===================+=========================================================+ | HDFS | Apache Hadoop | 2.0.2-alpha до 2.6.0 | + +-------------------+---------------------------------------------------------+ | | CDH или HDP | (CDH) 5.0.0 до 5.7.0 или (HDP) 2.0, 2.1, 2.2 или 2.3 | + +-------------------+---------------------------------------------------------+ | | MapR | 4.1 (с MapR-FS) | +---------------+-------------------+---------------------------------------------------------+ | HBase | Apache | 0.96.x, 0.98.x, 1.0.x и 1.1.x | + +-------------------+---------------------------------------------------------+ | | CDH или HDP | (CDH) 5.0.0 до 5.7.0 или (HDP) 2.0, 2.1, 2.2 или 2.3 | + +-------------------+---------------------------------------------------------+ | | MapR | 4.1 (с Apache HBase) | +---------------+-------------------+---------------------------------------------------------+ | Zookeeper | Apache | Версии 3.4.3 до 3.4.5 | + +-------------------+---------------------------------------------------------+ | | CDH или HDP | (CDH) 5.0.0 до 5.7.0 или (HDP) 2.0, 2.1, 2.2 или 2.3 | + +-------------------+---------------------------------------------------------+ | | MapR | 4.1 | +---------------+-------------------+---------------------------------------------------------+**Примечание:** В таблице показаны версии компонентов, которые мы протестировали и в отношении которых уверены в их пригодности и совместимости.Поздние версии компонентов могут работать, но не обязательно были протестированы или подтверждены как совместимые.Начало работы


Вы можете начать работу с Tephra, построив напрямую из последнего исходного кода:

git clone https://github.com/caskdata/tephra.git
cd tephra
mvn clean package

После завершения сборки, у вас будет полная бинарная дистрибутив Tephra в директории tephra-distribution/target/. Возьмите файл tephra-<version>.tar.gz и установите его на вашей системе.

Для любых клиентских приложений, добавьте следующие зависимости в любые файлы POM Apache Maven (или эквивалентную конфигурацию вашей системы сборки), чтобы использовать классы Tephra:

<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-api</artifactId>
  <version>0.7.1</version>
</dependency>
<dependency>
  <groupId>co.cask.tephra</groupId>
 <artifactId>tephra-core</artifactId>
  <version>0.7.1</version>
</dependency>

Так как API HBase изменились между версиями, вам потребуется выбрать соответствующую библиотеку совместимости HBase.

Для HBase 0.96.x:

<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-hbase-compat-0.96</artifactId>
  <version>0.7.1</version>
</dependency>

Для HBase 0.98.x:

<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-hbase-compat-0.98</artifactId>
  <version>0.7.1</version>
</dependency>

Для HBase 1.0.x:

<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-hbase-compat-1.0</artifactId>
  <version>0.7.1</version>
</dependency>

Если вы используете версию HBase 1.0.x CDH 5.4, 5.5 или 5.6 (эта версия содержит несовместимости API с Apache HBase 1.0.x):

<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-hbase-compat-1.0-cdh</artifactId>
  <version>0.7.1</version>
</dependency>Для HBase 1.1.x или версии CDH 5.7 HBase 1.2.x:
<dependency>
  <groupId>co.cask.tephra</groupId>
  <artifactId>tephra-hbase-compat-1.1</artifactId>
  <version>0.7.1</version>
</dependency>

Развертывание и конфигурация

Tephra использует центральный сервер транзакций для назначения уникальных идентификаторов транзакций для изменений данных и для выполнения обнаружения конфликтов. Однако одновременно может активно обрабатывать запросы клиентов только один сервер транзакций. Тем не менее, дополнительные экземпляры сервера транзакций могут быть запущены одновременно, обеспечивая автоматическое переключение, если активный сервер становится недоступным.

Конфигурация сервера транзакций

Сервер транзакций Tephra может быть развернут на тех же узлах кластера, где выполняется процесс HMaster HBase. Сервер транзакций требует, чтобы библиотеки HBase были доступны в переменной окружения Java CLASSPATH.Сервер транзакций поддерживает следующие параметры конфигурации. Все параметры конфигурации могут быть добавлены в файл hbase-site.xml на CLASSPATH сервера:

data.tx.long.timeout | 86400 | Время ожидания завершения долгих транзакций (в секундах) |
data.tx.snapshot.dir | | Директория HDFS для хранения снимков состояния транзакций |

Чтобы запустить сервер транзакций, выполните следующую команду в вашей установке Tephra:: ./bin/tephra start

Любые специфические для окружения настройки можно сделать, отредактировав скрипт bin/tephra-env.sh.

Конфигурация клиента

Так как клиенты Tephra будут взаимодействовать с HBase, клиентские библиотеки HBase и конфигурация кластера HBase должны быть доступны в переменной окружения Java CLASSPATH клиента.

Использование клиентских API описано в разделе `Клиентские API`_.Клиент службы транзакций поддерживает следующие конфигурационные свойства. Все конфигурационные свойства могут быть добавлены в файл hbase-site.xml на CLASSPATH клиента:

```

Конфигурация HBase Coprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .В дополнение к серверу транзакций, Tephra требует установки HBase coprocessor на всех таблицах, где будут выполняться транзакционные чтения и записи.

Чтобы настроить coprocessor на всех таблицах HBase, добавьте следующее в файл hbase-site.xml.

Для HBase 0.96.x:

<property>
  <name>hbase.coprocessor.region.classes</name>
  <value>co.cask.tephra.hbase96.coprocessor.TransactionProcessor</value>
</property>

Для HBase 0.98.x:

<property>
  <name>hbase.coprocessor.region.classes</name>
  <value>co.cask.tephra.hbase98.coprocessor.TransactionProcessor</value>
</property>

Для HBase 1.0.x:

<property>
  <name>hbase.coprocessor.region.classes</name>
  <value>co.cask.tephra.hbase10.coprocessor.TransactionProcessor</value>
</property>

Для версий HBase 1.0.x в CDH 5.4, cq 5.5 или 5.6:

<property>
  <name>hbase.coprocessor.region.classes</name>
  <value>co.cask.tephra.hbase10cdh.coprocessor.TransactionProcessor</value>
</property>

Для HBase 1.1.x или версий HBase 1.2.x в CDH 5.7:

<property>
  <name>hbase.coprocessor.region.classes</name>
  <value>co.cask.tephra.hbase11.coprocessor.TransactionProcessor</value>
</property>

Вы можете настроить TransactionProcessor для загрузки только на тех таблицах HBase, которые вы будете использовать для транзакционных чтений и записей. Однако, вы должны убедиться, что coprocessor доступен на всех затронутых таблицах для корректной работы Tephra.

Использование существующих таблиц HBase в транзакционном режиме ................................................................Tephra заменяет метки времени ячеек HBase транзакционными ID и использует эти ID для фильтрации ячеек старше TTL (Time-To-Live). Транзакционные ID находятся на более высоком масштабе, чем метки времени ячеек. При конвертации обычной таблицы HBase с существующими данными в транзакционную таблицу, существующие данные могут быть исключены из чтения. Чтобы позволить чтение существующих данных из транзакционной таблицы, вам потребуется установить свойство data.tx.read.pre.existing как true в описании таблицы.Обратите внимание, что даже без установки свойства data.tx.read.pre.existing как true, существующие данные не будут удалены во время компактации. Существующие данные просто не будут видны при чтении.

Отчетность по метрикам

Tephra поставляется с встроенной поддержкой для отчетности по метрикам через JMX и файл журнала, используя Dropwizard Metrics библиотеку.

Чтобы включить отчетность по метрикам через JMX, вам нужно включить JMX в аргументах запуска Java. Редактируйте скрипт bin/tephra-env.sh и раскомментируйте следующие строки, внося необходимые изменения в конфигурацию для используемого порта, SSL и аутентификации JMX:

# export JMX_OPTS="-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=13001"
# export OPTS="$OPTS $JMX_OPTS"

Чтобы включить файловую отчетность по метрикам, редактируйте файл conf/logback.xml и раскомментируйте следующий раздел, заменив местоимение FILE-PATH на действительный каталог на локальной файловой системе:

<appender name="METRICS" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>/FILE-PATH/metrics.log</file>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>metrics.log.%d{yyyy-MM-dd}</fileNamePattern>
    <maxHistory>30</maxHistory>
  </rollingPolicy>
  <encoder>
    <pattern>%d{ISO8601} %msg%n</pattern>
  </encoder>
</appender>
<logger name="tephra-metrics" level="TRACE" additivity="false">
  <appender-ref ref="METRICS" />
</logger>

Частота отчетности по метрикам может быть настроена путем установки свойства конфигурации data.tx.metrics.period на частоту отчетности в секундах.Клиентские API

Класс TransactionAwareHTable реализует интерфейс HTableInterface HBase, предоставляя те же API, что и стандартный экземпляр HBase HTable. Только определенные операции поддерживаются транзакционно. Это:

Другие операции не поддерживаются транзакционно и будут выбрасывать исключение UnsupportedOperationException, если будут вызваны.

Чтобы разрешить использование этих нотранзакционных операций, вызовите setAllowNonTransactional(true). Это позволяет вызывать следующие методы нотранзакционно:

Использование

Чтобы использовать TransactionalAwareHTable, вам нужен экземпляр TransactionContext. TransactionContext предоставляет базовый контракт для использования транзакций клиентом. На каждом этапе жизненного цикла транзакции он обеспечивает необходимые взаимодействия с сервером транзакций Tephra для начала, завершения и отката транзакций. Базовое использование TransactionContext обрабатывается с помощью следующего шаблона:

TransactionContext context = new TransactionContext(client, transactionAwareHTable);
try {
  context.start();
  transactionAwareHTable.put(new Put(Bytes.toBytes("row")));
  // ...
  context.finish();
} catch (TransactionFailureException e) {
  context.abort();
}
  1. Сначала начинается новая транзакция с помощью метода TransactionContext.start().
  2. Затем выполняются любые операции с данными в контексте транзакции.
  3. После завершения операций с данными вызывается метод TransactionContext.finish() для завершения транзакции.
  4. Если возникает исключение, метод TransactionContext.abort() можно вызвать для отката транзакции. TransactionAwareHTable обрабатывает детали выполнения операций с данными транзакционно и реализует необходимые механизмы для выполнения и отката данных (см. TransactionAware).

Пример

Для демонстрации того, как использовать TransactionAwareHTable, ниже приведён базовый пример реализации класса SecondaryIndexTable. Этот класс упаковывает использование TransactionContext и предоставляет пользователю простой интерфейс:.. code:: java

/**
  • Транзакционный SecondaryIndexTable.

*/

public class SecondaryIndexTable {

private byte[] secondaryIndex; private TransactionAwareHTable transactionAwareHTable; private TransactionAwareHTable secondaryIndexTable; private TransactionContext transactionContext; private final TableName secondaryIndexTableName; private static final byte[] secondaryIndexFamily =

Bytes.toBytes("secondaryIndexFamily");

private static final byte[] secondaryIndexQualifier = Bytes.toBytes('r'); private static final byte[] DELIMITER = new byte[] {0};

public SecondaryIndexTable(TransactionServiceClient transactionServiceClient,
HTable hTable, byte[] secondaryIndex) {
secondaryIndexTableName =
TableName.valueOf(hTable.getName().getNameAsString() + ".idx");

HTable secondaryIndexHTable = null; HBaseAdmin hBaseAdmin = null; try {

hBaseAdmin = new HBaseAdmin(hTable.getConfiguration()); if (!hBaseAdmin.tableExists(secondaryIndexTableName)) {

hBaseAdmin.createTable(new HTableDescriptor(secondaryIndexTableName));

} secondaryIndexHTable = new HTable(hTable.getConfiguration(),

secondaryIndexTableName);
} catch (Exception e) {
Throwables.propagate(e);
} finally {
try {
hBaseAdmin.close();
} catch (Exception e) {
Throwables.propagate(e);

}

}

this.secondaryIndex = secondaryIndex; this.transactionAwareHTable = new TransactionAwareHTable(hTable); this.secondaryIndexTable = new TransactionAwareHTable(secondaryIndexHTable); this.transactionContext = new TransactionContext(transactionServiceClient,

transactionAwareHTable, secondaryIndexTable);

}

}

public Result get(Get get) throws IOException {
    return get(Collections.singletonList(get))[0];
}        public Result[] get(List<Get> gets) throws IOException {
        try {
            transactionContext.start();
            Result[] result = transactionAwareHTable.get(gets);
            transactionContext.finish();
            return result;
        } catch (Exception e) {
            try {
                transactionContext.abort();
            } catch (TransactionFailureException e1) {
                throw new IOException("Не удалось откатить транзакцию", e1);
            }
        }
        return null;
    }```rst
public Result[] getByIndex(byte[] value) throws IOException {
    try {
        transactionContext.start();
        Scan scan = new Scan(value, Bytes.add(value, new byte[0]));
        scan.addColumn(secondaryIndexFamily, secondaryIndexQualifier);
        ResultScanner indexScanner = secondaryIndexTable.getScanner(scan);

        ArrayList<Get> gets = new ArrayList<Get>();
        for (Result result : indexScanner) {
            for (Cell cell : result.listCells()) {
                gets.add(new Get(cell.getValue()));
            }
        }
        Result[] results = transactionAwareHTable.get(gets);
        transactionContext.finish();
        return results;
    } catch (Exception e) {
        try {
            transactionContext.abort();
        } catch (TransactionFailureException e1) {
            throw new IOException("Не удалось откатить транзакцию", e1);
        }
    }
    return null;
}

public void put(Put put) throws IOException {
    put(Collections.singletonList(put));
}
``` public void put(List<Put> puts) throws IOException {
try {

transactionContext.start(); ArrayList<Put> secondaryIndexPuts = new ArrayList<Put>(); for (Put put : puts) {

List<Put> indexPuts = new ArrayList<Put>(); Set<Map.Entry<byte[], List<KeyValue>>> familyMap = put.getFamilyMap().entrySet(); for (Map.Entry<byte [], List<KeyValue>> family : familyMap) {

for (KeyValue value : family.getValue()) {
if (value.getQualifier().equals(secondaryIndex)) {
byte[] secondaryRow = Bytes.add(value.getQualifier(),
DELIMITER, Bytes.add(value.getValue(), DELIMITER, value.getRow()));

Put indexPut = new Put(secondaryRow); indexPut.add(secondaryIndexFamily, secondaryIndexQualifier, put.getRow()); indexPuts.add(indexPut);

}

}

} secondaryIndexPuts.addAll(indexPuts);

} transactionAwareHTable.put(puts); secondaryIndexTable.put(secondaryIndexPuts); transactionContext.finish();

} catch (Exception e) {
try {
transactionContext.abort();
} catch (TransactionFailureException e1) {
throw new IOException("Failed to rollback transaction", e1);

}

}

}

}

`Известные проблемы и ограничения ---------------------------------- В настоящее время операции семейства столбцов ``Delete реализуются путем записи ячейки с пустым

квалификатором (пустым byte[]) и пустым значением (пустым byte[]). Это делается вместо встроенных операций Delete HBase для возможности отката маркера удаления в случае сбоя транзакции — стандартные операции Delete HBase не могут быть отменены. Однако это означает, что приложения, хранящие данные в столбце с пустым квалификатором, не смогут хранить пустые значения и не смогут транзакционно удалять этот столбец.
  • Операции Delete столбцов реализуются путем записи пустого значения (пустого byte[]) в столбец. Это означает, что приложения не смогут хранить пустые значения в столбцах.
  • Недействительные транзакции автоматически не удаляются из списка исключений. Когда транзакция становится недействительной, будь то по истечению времени или по причине недействительности клиентом из-за невозможности отката изменений, ее идентификатор транзакции добавляется в список исключенных транзакций. Данные от недействительных транзакций будут удаляться компонентом TransactionProcessor при операциях сброса региона HBase и компактации. Однако в настоящее время идентификаторы транзакций могут быть удалены из списка исключенных идентификаторов транзакций только вручную с помощью инструмента co.cask.tephra.TransactionAdmin.Как внести вклад

Заинтересованы в улучшении Tephra? Мы приветствуем все вклады, будь то в виде детальных отчетов о багах, запросов на вытягивание кода или помощи на списке рассылки.

Отчеты о багах и запросы на функции

Баги и задачи отслеживаются в публичном JIRA issue tracker.

Группы пользователей Tephra и списки рассылки

  • Группа пользователей Tephra: tephra-user@googlegroups.com

    Список рассылки tephra-user предназначен в основном для пользователей, использующих продукт для разработки приложений. Вы можете ожидать вопросы от пользователей, объявления о выпусках и любые другие обсуждения, которые мы считаем полезными для пользователей.

  • Группа разработчиков Tephra и обсуждения разработки: tephra-dev@googlegroups.com

    Список рассылки tephra-dev предназначен для разработчиков, активно работающих над продуктом, и должен использоваться для всех наших обсуждений по дизайну, архитектуре и техническим вопросам в будущем. Этот список рассылки также будет получать все уведомления из JIRA и GitHub.

IRC

Есть вопросы о том, как работает Tephra, или вам нужна помощь в использовании его? Присоединяйтесь к чат-комнате #tephra на irc.freenode.net.

Запросы на слияние ..................У нас есть простая модель разработки на основе запросов на слияние с фазой построения консенсуса, аналогичная процедуре голосования Apache. Если вы хотите помочь улучшить Tephra, добавив новые функции, улучшив существующие функции или исправив ошибки, вот как это сделать:

  1. Если вы планируете крупные изменения или вклад, обсудите ваши планы на списке рассылки tephra-dev. Это поможет нам понять ваши потребности и лучше направить ваше решение в соответствии с проектом.
  2. Создайте форк Tephra в свой собственный репозиторий GitHub.
  3. Создайте тематическую ветку с подходящим названием.
  4. Работайте над кодом по своему усмотрению.
  5. Как только вы будете удовлетворены результатом, создайте запрос на слияние из вашего репозитория GitHub (полезно, если вы заполните все поля описания).
  6. После того, как мы проверим и примем ваш запрос, мы добавим ваш код в репозиторий caskdata/tephra.

Спасибо за помощь в улучшении Tephra!

Лицензия и товарные знаки

Разрешено к использованию на условиях лицензии Apache, версия 2.0 («Лицензия»); вы не можете использовать этот продукт, если не будете соблюдать условия Лицензии. Вы можете получить копию Лицензии по адресу

http://www.apache.org/licenses/LICENSE-2.0В случае, если это требует применимого закона или было согласовано в письменной форме, программное обеспечение, распространяемое по Лицензии, распространяется на условиях «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, явных или подразумеваемых. См. Лицензию для точного определения условий и ограничений, применяемых к Лицензии.Cask, Cask Tephra и Tephra являются товарными знаками компании Cask Data, Inc. Все права защищены.

Apache, Apache HBase и HBase являются товарными знаками Фонда Apache. Используется с разрешения. Не подразумевается одобрение со стороны Фонда Apache при использовании этих знаков.

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Транзакции для Apache HBase. Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/apache-tephra.git
git@api.gitlife.ru:oschina-mirror/apache-tephra.git
oschina-mirror
apache-tephra
apache-tephra
develop