Фото-заметки за апрель и май
Давно уже не выкладывал свои пленочные фото, пришло время исправляться.
Нотатки про програмування, музику, подорожі та плівку
Про мене • Список нотаток • Плівка
Давно уже не выкладывал свои пленочные фото, пришло время исправляться.
При работе с большими системами, которые состоят из множества компонентов, возникает вопрос: «Как интегрировать несколько приложений для работы друг с другом?». Для этого нужен надёжный, асинхронный и быстрый способ передачи данных, который также позволит передавать информацию в разных форматах.
Все эти свойства есть в системе обмена сообщениями. Они полезны когда:
Обмен сообщениями неновое изобретение. Он активно используется внутри операционной системы для обмена информацией между несколькими процессами (inter-process communication) и для обмена между несколькими потоками внутри процесса (inter-thread communication).
Самый простой способ представить очередь сообщений как длинную трубу в которую можно помещать шарики. Мы можем написать сообщение на шаре и закинуть его в трубу и кто-то получит его на другой стороне. Из такой концепции можно понять несколько вещей.
Все эти свойства позволяют разделить две системы с точки зрения ответственности, времени, пропускной способности, внутреннего устройства, нагрузки и географии. Само по себе разделение является очень важной частью большой распределенной системы. Чем больше независимых компонентов, тем проще их разрабатывать, тестировать и запускать независимо от других.
Также очереди работают по модели «издатель-подписчик». В ней издатель оправляет сообщение в очередь, а подписчик смотрит в очередь и извлекает интересные ему сообщения, после чего занимается их обработкой. Благодаря этой концепции, осуществляется асинхронная коммуникация.
Избыточность и поддержка транзакций
Перед тем как сообщение будет удалено может потребоваться подтверждение, что приложение, которое прочитало сообщение, успешно его обработало. В случае возникновения проблем, сообщение не потеряется, что позволит повторно его обработать.
Масштабирование
Очереди распределяют процессы обработки информации. Это дает возможность гибко реагировать на нагрузку и добавлять или убирать дополнительные обработчики.
Эластичность
Очереди сообщений позволяют выровнять нагрузку на приложение за счет буферизации данных. В случае большой нагрузки на систему очередь будет накапливать сообщения, а сами обработчики будут работать в нормальном режиме самым облегчая обработку данных и не допуская отказа системы.
Отказоустойчивость
В случае отказа обработчиков, очередь продолжит сохранять все сообщения, что позволит обработать их когда система поднимется, тем самым не потеряв никаких данных.
Decoupling and coupling
С помощью очередей можно достичь двух противоположных целей.
Понимание потоков данных
По сути, очереди накладывают ограничения на передачу данных между компонентами, что позволяет очень легко мониторить как обрабатываются данные. Благодаря этой информации можно понять как работает система и ее компоненты. Например, некоторые очереди могут простаивать, а некоторые всегда забиты, тем самым показывая где у нас есть проблемы с обработкой.
Гарантированная доставка является одной из ключевых характеристик системы обмена сообщений. Всего есть три типа доставки:
At least once
Самый простой способ доставки при котором обработчик получает одно и то же сообщение до тех пор, пока не удалит его из очереди или не подтвердит получение. Это значит что возможны ситуации, когда приложение обрабатывает одно сообщение несколько раз.
Такая особенность подразумевает, что обработчик будет корректно работать в случае дублирования сообщений. А такое может случаться часто. Например, упала сеть в момент, когда приложение подтверждало получение — оно получит его ещё раз.
Если в сообщениях хранятся результаты каких-то измерений, у которых есть временная метка, то ничего страшного не произойдёт, если мы получим хоть миллион дубликатов. Но если в сообщениях храниться информация о финансовых транзакциях то будет совсем не круто обработать его дважды. Но такую ситуацию можно решить используя уникальные идентификаторы для сообщений и хранить список сообщений, которые мы уже обработали.
С другой стороны, такой подход гарантирует 100% получение сообщения. Даже если получатель сообщения упадёт, до того как подтвердит обработку, то он просто ещё раз его обработает после перезапуска.
At most once
Очень редкий сценарий, к нему прибегают когда двойная обработка сообщения может привести к серьезным проблемам. В таких очередях мы предпочтем потерять сообщение чем обработать его дважды.
Если приложение упадет во время обработки сообщения, то мы его повторно не получим и оно будет считаться утерянным.
Exactly Once
Идеальный сценарий работы очереди, но проблема в том что его нереально воплотить в жизнь из-за множества проблема, которые сами по себе решить сложно, не говоря уже об их совокупности.
Все они происходят из двух утверждений:
Что порождает такие проблемы как:
Именно поэтому очень сложно гарантировать одноразовую доставку сообщения. Намного проще сделать систему устойчивой к дубликатам сообщений и использовать подход at-least-once.
Система обмена сообщениями состоит из нескольких компонентов, которые позволяют ей нормально функционировать и выполнять свои задачи.
Сообщения — любые данные, которые нужно передать через очередь конвертируются в сообщение, которые состоят из двух частей:
Каналы (channels) — логические соединения между приложениями и системой очередей, которые предоставляют изолированную коммуникацию. Каналы позволяют передавать сообщение в одном из двух режимов:
Маршрутизатор (router) — помещает сообщения из каналов по разным очередям используя ключ маршрутизации из заголовков сообщения.
Очередь (queue) — хранилище для наших сообщений, которое может находиться как в оперативной памяти так и на диске.
Очереди сообщений можно разделить на несколько видов. Обычно несколько видом могут быть реализованы в одном продукте.
broadcast (fanout) exchange
В таком случае отправляется копия сообщения во все доступные очереди, ключ маршрутизации игнорируется.
direct exchange
Все сообщения имеют свой ключ маршрутизации, который определяет в какую очередь нужно положить сообщение. В дальнейшем сообщения будут переданы по принципу Round Robin подписанным обработчикам. Это значит что только один обработчик получит сообщение.
topic exchange (multicast)
Такие очереди подписаны на получение сообщений чей ключ подпадает под определенный паттерн. Если ключ маршрутизации подходит для нескольких очередей, то каждая получит по своей копии.
Обычно ключи маршрутизации стараются делать в иерархическом виде. Это достигается за счет разделения логических частей (слов) точками. Например вот так:
[region].[availability-zone].[service].[instance]
eu-east.az1.computer.homepcСами же паттерны создаются с использованием специальных символов:
Что позволяет сделать такие шаблоны:
Исторически сложилось так, что почти все существующие протоколы для работы с очередями были проприетарными. Это накладывало массу ограничений, потому что не все языки поддерживали тот или иной протокол.
Спустя какое-то время появились три открытых стандарта, которые сейчас повсеместно используются:
В C# для хранения набора однотипных данных можно использовать массивы. Но с ними не всегда удобно работать потому, что они имеют фиксированный размер и часто бывает сложно угадать, какого размера нужен массив.
Для решения этих задач в C# есть коллекции. Они позволяют динамически изменять свой размер. Также они удобны тем что некоторые из них представляют из себя готовые реализации стандартных структур данных, таких как список, хеш таблица, стек, очередь.
Все коллекции лежат в нескольких пространствах имен:
Все коллекции, так или иначе, реализую интерфейс ICollection, некоторые реализуют интерфейсы IList и IDictionary (которые внутри наследуют ICollection). Этот интерфейс предоставляет минимальный набор методов, которые позволяют реализовать коллекцию.
В свою очередь, ICollection расширяет интерфейс IEnumerable. Он предоставляет нумератор, который позволяет обходить коллекции элемент за элементом. Именно этот интерфейс позволяет использовать коллекции в цикле foreach.
Одна из ключевых особенностей коллекций это изменяемый размер. Когда создается экземпляр коллекции она внутри себя инициализирует какую-то структуру данных, зачастую это массив. По умолчанию этот массив имеет определённую вместительность, которую можно просмотреть с помощью свойства Capacity.
После активного наполнения коллекции наступает момент, когда внутренний массив заполнен и мы не можем добавить новый элемент. В таком случае коллекция создаёт новый массив с большей вместительностью (обычно в два раза больше) и копирует туда данные со старого массива.
Поэтому коллекции, которые основаны на массивах имеют сложность вставки:
Чтобы избежать уменьшения производительности нужно при создании коллекции указать необходимую нам вместительности. Это позволит уменьшить количество копирований.
Сравнение
Такие методы как Contains, IndexOf, LastIndexOf, and Remove используют сравнение элементов для свое работы. Если коллекция является обобщенной, то используются два механизма сравнения:
Некоторые коллекции имеют конструктор, который принимает имплементацию IEqualityComparer
Сортировка
Сортировка работает похожим образом, как и сравнение, но делиться на два вида: явную сортировку и сортировка по умолчанию.
Сортировка по умолчанию подразумевает, что типы хранящиеся в коллекции реализуют интерфейс IComparable, чьи методы под капотом используют коллекции для сравнения.
Явная сортировка подразумевает, что наши элементы не реализуют интерфейс IComparable, поэтому в качестве параметра метода сортировки нужно передать объект, который реализует интерфейс IComparer.
Если тип не реализует интерфейс IComparable и мы не передали явно тип, который реализует IComparer, то при вызове метода сортировки вылетит исключение.
System.InvalidOperationException: Failed to compare two elements in the array.
System.ArgumentException: At least one object must implement IComparable.Класс List
var linkedList = new List<string>();
linkedList.Add("A");
linkedList.Add("B");
linkedList.Add("C");Для своей работы списки используют обычные массивы. Это значит что могут быть проблемы с производительностью из-за частого создания нового массива. Также у списков есть еще два нюанса:
Если эти проблемы существенны для вас, то стоит присмотреться к LinkedList
Класс LinkedList
Каждый элемент списка оборачивается в специальный класс LinkedListNode
var linkedList = new LinkedList<string>();
linkedList.AddFirst("A");
linkedList.AddLast("B");
linkedList.AddLast("C");
Console.WriteLine(linkedList.First.Previous == null); // True
Console.WriteLine(linkedList.Last.Next == null); // TrueСвязанный список позволяет вставлять и удалять элементы со сложностью O (1). Также мы можем удалить элемент и заново вставить в тот же или другой список без дополнительного выделения памяти.
Словари хранят данные в виде ключ-значение. Каждый элемент словаря представляет из себя объект структуры KeyValuePair
В качестве ключа можно использовать любой объект. Ключи должны быть уникальными в рамках коллекции.
var linkedList = new Dictionary<string, string>();
linkedList.Add("key1", "A");
linkedList.Add("key2", "B");
linkedList.Add("key3", "C");Внутри словари построены на базе хеш-таблицы, что позволяет очень быстро вставлять элементы и получать по ключу (сложность O (1)). Сами же хеш-таблицы, в свою очередь, реализованы с помощью массивов.
Для адресации значений внутри коллекции используются хеш коды ключей. Это значит что объект ключа не должен изменяться, потому что это приведет к изменению хеш кода, что в свою очередь приведет к потере данных.
Очереди и стеки полезны, когда нужно временно хранить какие-то элементы, то есть удалять элемент после его извлечения. Также они позволяют определить строгую очередность записи и извлечения элементов.
Стеки Stack
var stack = new Stack<int>();
stack.Push(1); // stack = [1]
stack.Push(2); // stack = [1,2]
var item = stack.Pop(); // stack = [1], item = 2Очереди Queue
var queue = new Queue<int>();
queue.Enqueue(1); // queue = [1]
queue.Enqueue(2); // queue = [1,2]
item = queue.Dequeue(); // queue = [2], item = 1Внутри они реализованы с помощью обычных массивов.
Эти классы реализуют математические множества. По своей природе множество — набор уникальных элементов с общими характеристиками, в нашем случае одного типа.
Также множества отличаются от обычных списков тем что они предоставляют набор методов, которые реализуют операции с теории множеств.
Внутренняя реализация этих классов отличается:
ISet<int> set = new HashSet<int> { 1, 2, 3, 4, 5 };
set.UnionWith(new[] { 5, 6 }); // set = { 1, 2, 3, 4, 5, 6 }
set.IntersectWith(new[] { 3, 4, 5, 6, 7 }); // set = { 3, 4, 5, 6 }
set.ExceptWith(new[] { 6, 7 }); // set = { 3, 4, 5 }
set.SymmetricExceptWith(new[] { 4, 5, 6 }); // set = { 3, 6 }Можно заметить что LINQ предоставляет несколько похожих операций (Distinct, Union, Intersect, Except), которые можно выполнить с любой коллекцией. Но HastSet предоставляет намного больший набор операций с множествами.
Основная разница в том что методы множеств изменяют текущую коллекцию, в то время как LINQ методы всегда создают новый экземпляр коллекции.
KeyedCollection это абстрактный класс, который позволяет построить собственную коллекцию.
Эта коллекция является гибридом между списками (IList
В отличие от словарей, элемент KeyedCollection коллекции не является парой ключ-значение, вместо этого весь элемент является значением, а в качестве ключа используется его поле, свойство или любое другое значение. Для получения ключа используется абстрактный метод, который является обязательным для реализации. GetKeyForItem
var keyedCollection = new UserCollection();
keyedCollection.Add(new User {
Id = 1,
Name = "A"
});
keyedCollection.Add(new User {
Id = 2,
Name = "B"
});
Console.WriteLine(keyedCollection[2].Name); // B
public class UserCollection: KeyedCollection<int, User>
{
protected override int GetKeyForItem(User user) => user.Id;
}
public class User
{
public int Id {get;set;}
public string Name {get;set;}
}Внутри KeyedCollection построен на базе двух структур данных. Для быстрого получения данных по ключу используется словарь, а для получение элемента по индексу используется массив.
Представляет из себя коллекцию, которая похожа на словарь но в качестве ключей и значений используются строки. Элементы можно получить как по индексу так и по ключу.
Особенной эту коллекцию делает то что однин ключ может содержать несколько эллементов.
var namedCollection = new NameValueCollection();
namedCollection.Add("key1", "value1");
namedCollection.Add("key2", "value2");
namedCollection.Add("key1", "value3");
Console.WriteLine(namedCollection.Count); // 2
Console.WriteLine(namedCollection["key1"]); // value1,value3Иммутабельные коллекции не входят в стандартную библиотеку классов (BCL). Для их использования нужно установить System.Collections.Immutable NuGet пакет.
Они позволяют безопасно работать в многопоточной среде. Вместо того чтобы использовать блокировки синхронизации, как это делают многопоточные коллекции, неизменяемые коллекции не могут быть изменены после создания. Это автоматически делает их безопасными для использования в многопоточных сценариях, так как нет возможности другому потоку изменить коллекцию.
Сами же коллекции можно поделить на несколько видов:
Детальнее можно ознакомиться в статье: Read only, frozen, and immutable collections.
Ничем не отличаются от обычных стеков и очередей, кроме того, что являются не изменяемыми.
Для реализации иммутабельных стеков/очередей массивы не подойдут. Причина заключается в том, что на каждое изменение придётся делать полную копию массива что очень неэффективно.
Поэтому для работы иммутабельных стеков/очередей используется связанный список. При изменении коллекции нужно всего лишь создать новый элемент, который ссылается на предыдущее значение. В итоге не происходит никакого копирования и экономится память.
Под капотом используют сбалансированное бинарное дерево вместо массива или связанного списка.
Массивы не подходят из-за накладных расходов на их использование. Связанный список тоже не подойдет, потому что ImmutableList поддерживает обращение по индексу из-за чего нужно долго перебирать связанные элементы. Поэтому для нормальной работы иммутабельных списков используют сбалансированное бинарное дерево.
По сути, это небольшая прослойка над обычными массивами и все. Любые мутабельные операции приводят к копированию массива. Из-за этого скорость добавления элементов равна O (n), но в то же время получение элемента по индексу занимает O (1).
Итерация по массиву работает в несколько раз быстрее чем у других неизменяемых коллекциях.
Неизменяемые словари внутри работают на базе сбалансированного дерева, но с одной особенностью. Каждый элемент внутри коллекции представлен в виде отдельного дерева (ImmutableList
Из-за своей особенности иммутабельные словари потребляют очень много памяти и долго работают. Поэтому стоит аккуратно их использовать.
Как было выше сказано все операции изменения коллекций приводят к созданию новой коллекции. Нужно помнить, что необходимо использовать новый экземпляр вместо старого.
var immutableList = new[] { 1, 2, 3 }.ToImmutableList();
immutableList = immutableList.Add(4);По идее чтобы было проще, мы можем объединить все изменения в цепочку:
immutableList = immutableList
.Add(5)
.Add(6)
.Add(7);Однако такого способа стоит избегать, потому что каждый вызов метода создает новый экземпляр коллекции. Хотя неизменяемые коллекции реализованы таким образом, чтобы повторно использовать как можно большую часть оригинальной коллекции при создании нового экземпляра, некоторые выделения памяти все же необходимы. Это означает больше работы для сборщика мусора.
Чтобы минимизировать проблему стоит использовать методы, которые могут выполнить нужные изменения за один вызов. Например:
immutableList = immutableList.AddRange(new[] { 5, 6, 7 });Но такие методы позволяют сделать только один вид изменения. Например у нас нет единого метода, который позволяет добавить и удалить одну запись:
immutableList = immutableList
.Add(6)
.Remove(2);Для решения этой проблемы иммутабельные коллекции предоставляют билдеры (builders).
var builder = immutableList.ToBuilder();
builder.Add(6);
builder.Remove(2);
immutableList = builder.ToImmutable();Внутри себя билдеры используют соответствующую мутабельную коллекцию, что позволяет выполнить все операции над одним экземпляром коллекции. Только после вызова метода ToImmutable экземпляр снова будет неизменяемым. Таким образом можно сократить объем работы уборщика мусора.
На днях пополнил коллекцию сертификатов еще одним — AWS Cloud Practitioner.
К AWS присматривался давно, на текущий момент это самое популярное облако. C Нового года я работаю на новом проекте, который активно использует AWS. Это стало дополнительным мотиватором, чтобы разобраться с тем как всё устроено.
В качестве своего рода чекпоинта решил получить сертификат.
Также ко мне присоединился мой друг вместе, с которым начали активно готовиться. Сразу хочу отметить, что вдвоём готовиться намного эффективнее. В какой-то момент у нас проснулась небольшая гонка и не хотелось отставать друг от друга. Ещё мы пару раз собирались и активно задавали друг другу вопросы по AWS, что как мне кажется, было самым эффективным при подготовке.
Источники для подготовки:
Сам экзамен проходили в офлайне, что, как по мне, намного удобнее, чем в онлайне.
Рано или поздно наступает момент, когда приложение начинает не справляться с нагрузкой и мы решаем горизонтально масштабировать нашу систему. Тогда возникает вопрос: как распределить запросы между несколькими машинами?
Для решения этой проблемы используют балансировщики нагрузки. С помощью набора алгоритмов они позволяют равномерно распределить нагрузку на сервера. Зачастую представлены в виде софтверного решения, реже как отдельные устройства.
Помимо распределения нагрузки, балансировщики также выполняют дополнительные задачи, такие как обнаружение служб и проверка работоспособности. Иногда они работают в качестве API Gateway для маршрутизации трафика или его троттлинга. Например, отправлять запросы по специфичному URL на какой-то конкретный сервис и не давать пользователям делать больше указанного порога запросов в секунду.
Это процесс, который позволяет определить набор сервером, на которые можно отправлять запросы. Для реализации этого подхода можно использовать несколько способов:
Проверки работоспособности позволяют понять, какие сервера сейчас работают и могут обрабатывать запросы. Сами проверки деляться на два типа: активные и пассивные.
В пассивном режиме балансировщик наблюдает за основным потоком запросов и за тем как бэкенд на них отвечает. Например, он может решить что сервер нерабочий, если он несколько раз подряд отвечает 503 статус кодом.
В активном режиме балансировщик периодически делает запросы на специальный эндпоинт, который проверяет состояние приложения. Их также можно разделить на несколько типов: liveness и readiness
Все современные балансировщики имеют поддержку множества алгоритмов, которые позволяют оптимально распределить запросы. Самих алгоритмов есть огромное множество, но основных всего несколько:
DNS
Самый простой способ распределить запросы это использовать DNS, он позволяет работать клиентам с несколькими серверами и повысить их доступность. Для этого достаточно зарегистрировать несколько серверов на одно доменное имя. Когда клиент запрашивает IP адрес, DNS возвращает список адресов серверов, который каждый раз начинается с другого адреса. Такой подход похож на работу алгоритма Round Robin.
Проблема заключается в том, что DNS запросы обычно кэшируются в браузере пользователя или на уровне операционной системы. Из-за такого поведения пользователь может обращяться к неработающему серверу. Даже если оперативно удалить адрес упавшего сервера из списка, может пройти время пока DNS записи реплицируются на другие сервера и пока кэш пользователя инвалидируется.
Round Robin
Самый простой алгоритм. Балансировщик держит обычную очередь из серверов. Первый сервер в очереди обрабатывает запрос и помещается в конец очереди и так по кругу. Таким образом сервера равномерно нагружены.
Алгоритм отлично походит когда сервера в пуле имеют одинаковую мощность и время обработки запросов.
Weighted Round Robin
Тот же round robin, но имеет дополнительное свойство — вес сервера. С его помощью мы можем указать балансировщику сколько трафика отправлять на тот или иной сервер. Так сервера помощнее будут иметь больший вес и соответственно обрабатывать больше запросов чем другие сервера.
Least Connections
В основе алгоритма лежит очередь с приоритетом, которая отсортирована по количеству активных пользователей, где первый сервер имеет наименьшее количество соединений. Такой способ отлично подходит для систем где много активных соединений, например стриминг сервис или онлайн чат.
Алгоритм можно улучшить и учитывать не только количество соединений, но и среднее время. Тогда первым в списке будет сервер с наименьшим количеством подключений и наименьшим временем ответа. Такой алгоритм называется Least Response Time. Такой способ позволяет выровнять нагрузку если сервера отвечают с разной скоростью.
Hash
Такой способ использует в своей основе механизм хеширования. Он позволяет распределить запросы на основе хеша, для которого обычно используется IP адрес или URL. В таком случае запросы от одного и того же IP будут отправлены на один и тот же сервер. Тоже самое касается URL. Такой алгоритм обычно используют, когда сервер хранит какие-то локальные данные, которые нужны для ответа.
Время от времени поднимаю виртуальные машины на убунту для своих проектов или экспериментов. Первое что я настраиваю — безопасность. Раньше у меня не было единого чек-листа и приходилось каждый раз гуглить. Поэтому решил сделать такой список и оставить у себя в блоге, чтобы обращаться к нему по мере необходимости.
Чеклист
Terminal
# Создаем нового пользователя и добавляем в группу sudo.
adduser bstefaniuk
usermod -aG sudo bstefaniuk
# Копируем свой публичный ключ для нового пользователя.
ssh-copy-id bstefaniuk@vps-ip
# Отключаем доступ по паролю и root для ssh.
nano /etc/ssh/sshd_config
## Установить значения:
PasswordAuthentication no
PermitRootLogin no
## Перезапустить службу
systemctl restart ssh
# Настройка ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80
ufw allow 433
ufw enable
## Проверяем что все ОК
ufw statusВ будущем хочу написать ansible-playbook скрипт, который будет все настраивать сам.
Хороший мануал по ufw
Генератор конфигураций nginx
Deployment: настраиваем пользователей
После полутора месяцев подготовки я получил сертификат Google Cloud — Associate Cloud Engineer.
Экзамен состоит из 50 вопросов и на их решение даётся два часа. Вопросы можно помечать и потом возвращаться к ним. Более того, разрешено возвращаться к любым вопросам в любой момент и что-то поменять. На ответы у меня ушёл 1 час + 20 минут на просмотр отмеченных вопросов.
Результат отображается сразу после отправки ответов. Всё что можно узнать это сдали вы или нет, нельзя посмотреть процент верных ответов или правильные ответы. Официальное письмо с сертификатов прийдут в течении 7-10 дней.
На этом я не закончу написания статей по GCP. Планирую рассказать про:
Google Cloud Platform предоставляет большой выбор разных способов хранить данные. Некоторые из них построены на базе существующих продуктов, другие — собственная разработка гугла.
Для начала нужно понять, что такое managed databases. Это услуга по настройке и администрированию баз данных. Облачный провайдер сам отвечает за работу сервера, установку патчей безопасности, доступность сервиса. Для того чтобы достичь такого же результата с помощью self hosted, нужно иметь в штате специалиста, который умеет администрировать сервера, закупить железо и подготовить инфраструктуру. В случае с managed databases платишь только за то количество ресурсов, которое используешь.
Cloud SQL это классический managed database сервис. Он позволяет развернуть 3 самые популярные базы данных. Такие как:
Также гугл гарантирует доступность базы данных на уровне 99,95%. Дополнительно получаем автоматическую репликацию и бекапы.
Ограничения
Spanner — реляционная база данных, разработка Google. Spanner позиционирует себя как горизонтально масштабируемая база данных, способна хранить петабайты информации, гарантирует строгую согласованность данных. А также доступность 99.999%.
По своей природе Cloud Spanner это распределённая база данных с автоматическим шардированием и репликацией, которые скрыты под капотом. Чтобы создать БД, нужно выбрать локацию (region или multi-region) и количество нод. Количество нод влияет на размер данных, которые кластер способен хранить и его доступность. Каждая нода может обслуживать до 2 Тб данных.
Пример кластера, который состоит из 4 нод. Каждая зона содержит полную копию базы данных и 4 процесса, которые обслуживают эти данные.
Гугл советует иметь минимум 3 ноды для прода. Но есть один нюанс — цена. Cloud Spanner очень дорогое решение, созданное для работы с огромным количеством данных. За 1 петабайт данных прийдется отдать ......... 1 645 568 $ ......... в месяц.
Столбцовая NoSQL база данных, которая масштабируется до миллиарда строк и тысяч колонок. Способна хранить петабайты информации.
Основная фича — наличие интерфейса HBase и нативная поддержка Hadoop. Это позволяет перенести данные с собственного кластера в Big Table без каких либо изменений. Big Table идеально подойдёт для очень быстрой записи и чтения, а также хранения данных типа ключ/значения, размер которых не превышает 10 Мб.
Данные внутри базы данных лежат в огромных таблицах. Грубо говоря, таблица в HBase представлена в виде огромного словаря словарей. Таблица состоит из строк, каждая из которых обычно описывает одну сущность, и столбцов, которые содержат отдельные значения для каждой строки. Каждая строка индексируется одним ключом, а столбцы, которые связаны друг с другом, обычно группируются в семейство столбцов.
Для более глубокого ознакомления советую прочитать главу «HBase» из книги «7 баз данных за 7 недель». Также советую ознакомится с официальной документацией.
Cloud Firestore — это полностью управляемая,документоориентированная serverless база данных, предназначена для разработки serverless приложений. Структура данных сильно напоминает такую в MongoDB.
Firestore поддерживает офлайн режим и живую синхронизацию. С помощью этих фич удобно строить приложения, которые предназначены для совместного использования, например, Google Docs или другие похожие варианты.
А также она пришла на замену предыдущего сервиса — Cloud Datastore. В 2021 году гугл обещает автоматически всех мигрировать с Datastore на Firestore. Это возможно благодаря обратной совместировать с Datastore API.
Firestore имеет два режива работы:
Детальнее с режимами можно ознакомится в официальной документации.
Управляемый in-memory сервис, построенный на базе Redis и memcached.
Cloud Storage — сервис хранения неструктурированных данных. Внутри можно хранить все что захотим, но в основном используется как файловое хранилище. Каждый файл представлен в виде объекта, сами же объекты лежал в бакетах.
В Cloud Storage нету ограничений на количество и размер файлов. Мы платим только за то что используем.
Бакеты
Бакет это коллекция объектов, все файлы должны лежать внутри. Также бакет является центральным местом управления жизненным циклом объектов и доступа к ним. Каждый бакет имеет свое глобально уникальное имя, регион и класс хранения данных. Для работы с бакетом используют конструкцию:
gs://название-бакетаОбъекты
Технически Cloud Storage не является файловым хранилищем или файловой системой, это объектное хранилище. Поэтому каждый файл представляет из себя объект, который состоит из двух частей: сам файл и метаданные. Метаданные описывают характеристики объекта в виде ключ-значение.
Иммутабельность
Все объекты иммутабельные, их можно только перезаписать или удалить. При перезаписи предыдущая версия файла будет доступна пока новая успешно не запишется на диск.
Классы хранения отвечают за доступность и цену хранения объектов.
Standard Storage
Лучший выбор для данных, которые активно используются (горячие данные) и хранятся непродолжительное время.
В свою очередь standard класс можно поделить еще на 3 подкласса: multi-regional, dual-region, single region. Они влияют на доступность файлов и скорость доступа к ним, особенно если пользователи разбросаны по миру. При использовании multi-regional класса автоматически выберется регион, который ближе к пользователю.
Nearline
Для этого и всех что ниже классов появляется стоимость за извлечение данных. Но хранение файлов дешевле.
Такое хранилище отлично подходит для данных, которые редко используются, обычно раз в месяц. Например, бэкапы или архивные данные. Если файлы нужны реже, чем один раз в квартал, лучше использовать Coldline или Archive классы.
Coldline
Используется для данных, которые нужны раз в квартал.
Archive
Самый дорогой в плане доступа к данным, но самый дешёвый для хранения. Файлы в нём должны храниться минимум 365 дней. Подходит для бэкапов и для аварийного восстановления. Но этот класс не имеет SLA для доступности.
Для Cloud Storage есть своя консольная утилита. Большенство команд похожи на те что есть в Linux для работы с файловой системой.
С помощью gsutil можно:
Пример команды, которая отобразит список объектов в бакете.
gsutil ls gs://some-bucketКопирование файлов
gsutil cp -r <source> <target>Cloud Storage поддерживает два механизма обеспечения безопасности: IAM и ACL.
IAM
Про IAM я рассказывал в предыдущей статье. По умолчанию есть 3 типа ролей, они распространяются на проект или бакет. Для тонкой настройки лучше использовать ACL.
ACLs
ACL приходят на помощь, когда нам нужно организовать атомарный доступ к отдельным объектам. Каждый элемент ACL состоит из разрешения и пользователя, кому эти разрешения выданы.
Что выбрать?
Первое что нужно понимать — IAM и ACL независимы и не влияют друг на друга. Если использовать оба механизма, можно выстрелить себе в колено. Убрав права на уровне IAM не забудь убрать из ACL.
Гугл советует использовать uniform подход, в котором используется только IAM. При таком подходе мы создаем бакет для конкретной группы пользователей.
Также у IAM есть система аудита, которая фиксирует все действия. Их можно проанализировать с помощью Big Query.
Signed URL
Иногда нужно дать доступ к файлу для пользователя, у которого нет гугл аккаунта. Тут на помощь приходит Signed URL. Он работает также как и ссылки в гугл документах. Единственное отличие в том что все ссылки имеют срок жизни, который мы указываем при создании.
С помощью этого механизма можем дать доступ на чтение, загрузку и удаление. Поэтому следите за тем кому даёте ссылку и с какими правами.
Google Storage всегда шифрует данные на сервере, перед тем как они будут записаны на диск. По умолчанию гугл использует собственные ключи шифрования. Но можно использовать собственные ключи или сгенерировать их с помощью Cloud Key Management Service.
Cloud Storage поддерживает версионирование объектов. Каждое изменение или удаление на самом деле не удаляет файл, а архивирует его. По умолчанию версионирование отключено, но его можно включить с помощью gsutil
$ gsutil versioning set on gs://bucket-nameОбъекты для которых есть несколько версий имеют одно имя, но разные идентификаторы.
$ gsutil ls -a gs://my-files
gs://my-files/sample.txt#1602324272958747
gs://my-files/sample.txt#1602324615090803Количество версий не ограничено из-за чего данные могут разрастись и сжигать больше денег. Для решения этой проблемы существует механизм управления жизненным циклом.
Также нужно помнить что архивные файлы имеют свои ACL, которые могут отличатся от актуальных!
Жизненный цикл помогает поддерживать порядок в бакетах. С его помощью можно настроить количество версий, которые надо хранить, понижать класс хранения, удалять объекты после какой-то даты.
Общий вид правил:
В качестве условий можно использовать:
Действия:
А вот так правила выглядят в жизни: