Etcd — це ще одне сховище ключ-значення. Однак, на відміну від Redis, ця база даних забезпечує сильну консистентність, що робить її кращим вибором для зберігання критичних даних у розподілених системах.
У цій статті ми розгорнемо і протестуємо базу даних, використовуючи C# та .NET. Без зайвих слів, почнемо!
Розгортання Etcd локально через Docker
Спершу нам потрібен екземпляр etcd
для підключення. На щастя, розгортання через Docker досить просте. Нам потрібно дозволити підключення до екземпляра без автентифікації, відобразити каталог зберігання на постійний том і відкрити стандартний порт 2379
. Ось як виглядає наш файл compose.yml
:
services:
etcd:
image: bitnami/etcd:latest
environment:
- ALLOW_NONE_AUTHENTICATION=yes
ports:
- "2379:2379"
volumes:
- etcd-data:/bitnami/etcd
volumes:
etcd-data:
Після цього розгортаємо контейнер командою:
docker compose up -d
Ми готові! Тепер переходимо до коду на C#.
Виконання базових операцій ключ-значення
Спершу, додамо пакет, що надає клієнт для бази даних. Ось команда, яка це зробить:
Припускаємо, що ви знаходитесь у папці з проєктом .NET. Найпростіший спосіб створити проєкт — виконати команду
dotnet new console
.
dotnet add package dotnet-etcd
Далі, підключаємось до бази даних. Також потрібно явно зазначити, що ми пропускаємо автентифікацію:
using dotnet_etcd;
using Grpc.Core;
// ...
EtcdClient etcdClient = new(
"http://localhost:2379",
configureChannelOptions: options =>
options.Credentials = ChannelCredentials.Insecure
);
Нарешті, записуємо та читаємо пару ключ-значення:
await etcdClient.PutAsync("name", "Egor");
var received = await etcdClient.GetValAsync("name");
Console.WriteLine($"Received. name = {received}");
Після запуску коду в консолі буде виведено:
Received. name = Egor
Це все, що потрібно знати для роботи з основами etcd. Тепер переходимо до чогось складнішого!
Слухання змін за допомогою Etcd WatchRequest
Однією з головних переваг etcd
є можливість стежити за змінами для конкретного ключа або набору ключів. Це можна зробити за допомогою методу WatchAsync
клієнта EtcdClient
.
Метод WatchAsync
повертає Task
, який працює безстроково (на час стеження). Тому ми не будемо чекати (await
), а просто відправимо його у змінну _
. При отриманні змін ми виведемо інформацію в консоль разом із деталями подій. Ось приклад:
using Etcdserverpb;
// ...
_ = etcdClient.WatchAsync(
"dog",
(WatchEvent[] response) =>
{
Console.WriteLine("received watch response");
foreach (var watchEvent in response)
{
Console.WriteLine($"Received event: {watchEvent.Key} -> {watchEvent.Value}. ({watchEvent.Type})");
}
}
);
await etcdClient.PutAsync("dog", "sits");
await etcdClient.PutAsync("dog", "runs");
await Task.Delay(100);
Результат:
received watch response
received watch response
Received event: dog -> sits. (Put)
received watch response
Received event: dog -> runs. (Put)
Як бачимо, підписка на події також викликає зворотний виклик із порожнім масивом WatchEvent
. Сам механізм слухання змін у etcd
дуже зручний, але це ще не все. Розглянемо ще одну цікаву функцію!
Атомарний запис за допомогою Etcd TxnRequest
Etcd
також дозволяє атомарно записувати кілька ключів, використовуючи механізм транзакцій. Цього разу нам знадобиться трохи складніший синтаксис, але він все ще легко зрозумілий.
Ось як можна записати два звуки тварин в одній транзакції:
using Google.Protobuf;
// ...
var transaction = new TxnRequest();
transaction.Success.AddRange(new []
{
new RequestOp {
RequestPut = new()
{
Key = ByteString.CopyFromUtf8("animals/cow"),
Value = ByteString.CopyFromUtf8("moo")
}
},
new RequestOp {
RequestPut = new()
{
Key = ByteString.CopyFromUtf8("animals/chicken"),
Value = ByteString.CopyFromUtf8("coo")
}
}
});
await etcdClient.TransactionAsync(transaction);
var cow = await etcdClient.GetValAsync("animals/cow");
var chicken = await etcdClient.GetValAsync("animals/chicken");
Console.WriteLine($"cow = {cow}");
Console.WriteLine($"chicken = {chicken}");
Цього разу, замість простого запуску коду, давайте зробимо щось цікавіше! Як щодо поєднання підходу з транзакціями та прослуховування змін?
Прослухаймо всі зміни у "папці" animals. etcd
обробляє ключі як послідовності байтів і порівнює їх лексикографічно. Це означає, що для отримання всіх ключів animals/
ми повинні вказати діапазон, починаючи з animals/
і закінчуючи після нього (animals0
, де 0
— наступний символ після /
). Ось код прослуховувача:
var request = new WatchRequest()
{
CreateRequest = new()
{
Key = ByteString.CopyFromUtf8("animals/"),
RangeEnd = ByteString.CopyFromUtf8("animals0")
}
};
_ = etcdClient.WatchAsync(
request,
(WatchEvent[] response) =>
{
Console.WriteLine("received watch response");
foreach (var watchEvent in response)
{
Console.WriteLine($"Received event: {watchEvent.Key} -> {watchEvent.Value}. ({watchEvent.Type})");
}
}
);
Поєднавши обидва уривки коду та запустивши їх разом, ми отримаємо такий результат:
received watch response
received watch response
Received event: dog -> sits. (Put)
received watch response
Received event: dog -> runs. (Put)
received watch response
received watch response
Received event: animals/cow -> moo. (Put)
Received event: animals/chicken -> coo. (Put)
cow = moo
chicken = coo
Як бачимо, транзакція була відправлена нашому прослуховувачу як єдина відповідь, що містить два події. І це найскладніше, що я хотів вам показати. Давайте підсумуємо і завершимо!
Підсумки
Ось що ми зробили у цій статті:
- Розгорнули локальний екземпляр
etcd
. - Виконали базові операції за допомогою коду на C#.
- Реалізували прослуховування змін через
WatchRequest
. - Виконали складні оновлення за допомогою
TxnRequest
.
Ви можете знайти вихідний код з цієї статті у цьому репозиторії GitHub. Не соромтеся поставити репозиторію зірочку ⭐! І не забудьте аплодувати цій статті 😉
Перекладено з: etcd with .NET