etcd з .NET

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

Leave a Reply

Your email address will not be published. Required fields are marked *