莫想到有一天得重新写个 etcd client

其实8年前搞过一个,

不过经过8年时间,etcd 多了很多功能 ,原来的多半不行了

虽然暂时我也没啥需求,但是怕kv和watch有变化

而且其实通过 grpc api 访问 etcd 没啥技术难度,搞client 也没啥意思的 (只要有.proto文件,可以直接生成grpc client代码, 参见微软文档

现成的也很多

所以本来不想写, 直接从 nuget 上找了个下载量最高的 https://www.nuget.org/packages/dotnet-etcd

最开始使用也ok,能读能写

但是当我使用watch时, 居然没有任何变更触发

当然也有其他人发现这个bug 对应issue: https://github.com/shubhamranjan/dotnet-etcd/issues/238(不过我遇见问题时还没人提issue,我看了源码,找到问题原因之后,这位同志已经写了issue了,)

如下是 issue 内容

using dotnet_etcd;
using Etcdserverpb; EtcdClient etcdClient = new EtcdClient("http://localhost:2379");
etcdClient.Watch(
"test",
(WatchResponse response) =>
{
if (!response.Events.Any())
return;
var value = response.Events[0].Kv.Value.ToStringUtf8();
Console.WriteLine(value);
}
);

If you run this sample with 8.0.0, the watch callback never triggers if you modify the key test. If you run it with 6.0.1 it triggers correctly.

为什么 Watch 没用呢?

不是etcd版本问题, 不是网络访问有限制,不是你们第一直觉想到的问题

而是 作者没实现

https://github.com/shubhamranjan/dotnet-etcd/commit/4ba94d0a174fec42d190bffda733ded3a24055e0 这次代码提交中

watch 的 核心处理代码被删了, 但是package 依然水灵灵提交到了nuget上, 所以 当然不可能监听变更啦 囧 (issue 上我也加了描述,以免其他人遇见迷茫)

然后查看其原来实现, 还发现有个问题: 根本没有处理异常重连

更别提 etcd mvcc 避免变更丢失时要特别注意的 revsion

然后作者记录显示,已经近一个月没有更新

所以也不可能指望作者修正问题了

算了,还是自己来吧,

没想到 8年之后还得再次生成 etcd 的 grpc client

根据 最新的 etcd .proto文件 生成了最新的 grpc client

然后为 DependencyInjection 以及使用 简单封装了一下, 源码放在了 https://github.com/fs7744/etcdcsharp

如下是简单的使用文档

ETCD V3 Client

base code all Generate by grpc tools.

Quick start

Install package

  • Package Manager
Install-Package etcd.v3 -Version 0.0.2
Install-Package etcd.v3.Configuration -Version 0.0.2
  • .NET CLI
dotnet add package etcd.v3 --version 0.0.2
dotnet add package etcd.v3.Configuration --version 0.0.2

new client

new client with DI

 ServiceCollection services = new();
services.UseEtcdClient();
services.AddEtcdClient("test", new EtcdClientOptions() { Address = ["http://xxx:2379"] });
var p = services.BuildServiceProvider(); var client = p.GetRequiredKeyedService<IEtcdClient>("test"); // you also can create client by factory
var factory = p.GetRequiredService<IEtcdClientFactory>();
var client2 = factory.CreateClient(new EtcdClientOptions() { Address = ["http://xxx:2379"] }); // get all config
foreach (var i in await client.GetRangeValueUtf8Async("/ReverseProxy/"))
{
Console.WriteLine($"{i.Key} : {i.Value}");
} // OR get client in ctor
public class Testt
{
private readonly IEtcdClient client; public Testt([FromKeyedServices("test")] IEtcdClient client)
{
this.client = client;
}
}

new client without DI

 var factory = EtcdClientFactory.Create();
var client = factory.CreateClient(new EtcdClientOptions() { Address = ["http://xxx:2379"] }); // get all config
foreach (var i in await client.GetRangeValueUtf8Async("/ReverseProxy/"))
{
Console.WriteLine($"{i.Key} : {i.Value}");
}

use with Configuration

var b = new ConfigurationBuilder();
b.UseEtcd(new Etcd.Configuration.EtcdConfigurationOptions()
{
Prefix = "/ReverseProxy/",
RemovePrefix = true,
EtcdClientOptions = new EtcdClientOptions() { Address = ["http://xxx:2379"] }
});
var c = b.Build(); // test watch change
Test(c); private static void Test(IConfigurationRoot c)
{
foreach (var i in c.GetChildren())
{
Console.WriteLine($"{i.Key} : {i.Value}");
}
c.GetReloadToken().RegisterChangeCallback(i =>
{
Test(i as IConfigurationRoot);
}, c);
}

Address

Address just parse by GrpcChannel.ForAddress, so support

KV

get one by key

string v = await client.GetValueUtf8Async("/ReverseProxy/");
//or
string v = (await client.GetAsync("/ReverseProxy/")).Kvs?.First().Value.ToStrUtf8();
//or
string v = (await client.RangeAsync(new RangeRequest() { Key = ByteString.CopyFromUtf8("/ReverseProxy/") })).Kvs?.First().Value.ToStrUtf8();
get all IDictionary<string, string>
foreach (var i in await client.GetRangeValueUtf8Async("/ReverseProxy/"))
{
Console.WriteLine($"{i.Key} : {i.Value}");
}
//or
foreach (var i in (await client.GetRangeAsync("/ReverseProxy/")).Kvs)
{
Console.WriteLine($"{i.Key.ToStrUtf8()} : {i.Value.ToStrUtf8()}");
}
//or
foreach (var i in (await client.RangeAsync(new RangeRequest() { Key = ByteString.CopyFromUtf8("/ReverseProxy/"), RangeEnd = ByteString.CopyFromUtf8("/ReverseProxy/".GetRangeEnd()) })).Kvs)
{
Console.WriteLine($"{i.Key.ToStrUtf8()} : {i.Value.ToStrUtf8()}");
}

Put

await client.PutAsync("/ReverseProxy/test", "1");
//or
await client.PutAsync(new PutRequest() { Key = ByteString.CopyFromUtf8("/ReverseProxy/test"), Value = ByteString.CopyFromUtf8("1") });

Delete one

await client.DeleteAsync("/ReverseProxy/test");
//or
await client.DeleteRangeAsync(new DeleteRangeRequest() { Key = ByteString.CopyFromUtf8("/ReverseProxy/test") });

Delete all

await client.DeleteRangeAsync("/ReverseProxy/test");
//or
await client.DeleteRangeAsync(new DeleteRangeRequest() { Key = ByteString.CopyFromUtf8("/ReverseProxy/test"), RangeEnd = ByteString.CopyFromUtf8("/ReverseProxy/test".GetRangeEnd())) });

Watch

 await client.WatchRangeBackendAsync("/ReverseProxy/", i =>
{
if (i.Events.Count > 0)
{
foreach (var item in i.Events)
{
Console.WriteLine($"{item.Type} {item.Kv.Key.ToStrUtf8()}");
}
}
return Task.CompletedTask;
}, startRevision: 6, reWatchWhenException: true); // or
await Task.Factory.StartNew(async () =>
{
long startRevision = 6;
while (true)
{
try
{
using var watcher = await client.WatchRangeAsync("/ReverseProxy/", startRevision: startRevision);
await watcher.ForAllAsync(i =>
{
startRevision = i.FindRevision(startRevision);
foreach (var item in i.Events)
{
Console.WriteLine($"{item.Type} {item.Kv.Key.ToStrUtf8()}");
}
return Task.CompletedTask;
});
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
}
});

all grpc client

if IEtcdClient Missing some grpc method , you can just use grpc client to do

public partial interface IEtcdClient
{
public AuthClient AuthClient { get; }
public Cluster.ClusterClient ClusterClient { get; }
public ElectionClient ElectionClient { get; }
public KV.KVClient KVClient { get; }
public LeaseClient LeaseClient { get; }
public LockClient LockClient { get; }
public MaintenanceClient MaintenanceClient { get; }
public Watch.WatchClient WatchClient { get; }
}

api doc

Main api doc please see

https://fs7744.github.io/etcdcsharp/api/Etcd.html

https://fs7744.github.io/etcdcsharp/api/Microsoft.Extensions.Configuration.EtcdConfigurationExtensions.html

All api doc ( include code generate by grpc tool ) please see

https://fs7744.github.io/etcdcsharp/api/index.html

莫想到有一天得重新写个 etcd client的更多相关文章

  1. 从头写个http client(java)

    不熟悉java,但我熟悉http,然后从头打造个简单的httpclient,支持get/post,支持gzip,支持重定向,支持encoding,支持transfer-encoding,支持ssl,支 ...

  2. 一个手写的 http client

    public class HTTPClient { public static final String GET = "GET"; public static final Stri ...

  3. (填坑系列) 用aio写server与client进行通信的坑

    最近闲来无事,就估摸着自己写个“服务注册中心”来玩,当然因为是个人写的,所以一般都是简洁版本. 代码地址在:https://gitee.com/zhxs_code/my-service-registe ...

  4. netty写Echo Server & Client完整步骤教程(图文)

    1.创建Maven工程 1.1 父节点的pom.xml代码(root pom文件) 1 <?xml version="1.0" encoding="UTF-8&qu ...

  5. 一个轻client,多语言支持,去中心化,自己主动负载,可扩展的实时数据写服务的实现方案讨论

    背景 背景是设计一个实时数据接入的模块,负责接收client的实时数据写入(如日志流,点击流),数据支持直接下沉到HBase上(兴许提供HBase上的查询),或先持久化到Kafka里.方便兴许进行一些 ...

  6. 使用client对象模型回写SharePoint列表

    使用client对象模型回写SharePoint列表 client对象模型是一个有效的方式回写SharePoint列表. 1. 管理员身份打开VS,新建WPF应用程序SPWriteListApp,确保 ...

  7. boost--asio--读写大总结

    NO.1 ASIO 读操作大总结: A. Boos::asio::read 同步读方式 void client::read_data(char   * sourse  , int num ) { bo ...

  8. HBase写请求分析

    HBase作为分布式NoSQL数据库系统,不单支持宽列表.而且对于随机读写来说也具有较高的性能.在高性能的随机读写事务的同一时候.HBase也能保持事务的一致性. 眼下HBase仅仅支持行级别的事务一 ...

  9. [转帖]我花了10个小时,写出了这篇K8S架构解析

    我花了10个小时,写出了这篇K8S架构解析 https://www.toutiao.com/i6759071724785893891/   每个微服务通过 Docker 进行发布,随着业务的发展,系统 ...

  10. zookeeper源码 — 五、处理写请求过程

    目录 处理写请求总体过程 客户端发起写请求 follower和leader交互过程 follower发送请求给客户端 处理写请求总体过程 zk为了保证分布式数据一致性,使用ZAB协议,在客户端发起一次 ...

随机推荐

  1. 2025牛客寒假算法基础集训营1 (E)

    [!note] 比赛链接 https://ac.nowcoder.com/acm/contest/953231 A.茕茕孑立之影 题目标签 构造 数论 题目大意 找到一个数x,x和长度为n的数组中的数 ...

  2. 朋友说喊搞个简单的微信对接的封装搞外包,不要那么多的方法拿来就用的的那种,来看看Simple.Wechat吧

    不知道大家有没有和我朋友一样,很多时候做外包总免不了去对接微信,最简单的微信用户信息获取.微信支付.微信模板消息发送,要是不熟悉总是要去找这个那个的包,但是人家的包封装的又丰富,又不想去看,本文将给大 ...

  3. C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)

    前言 自从 DeepSeek 大模型火了以来,网络上出现了许多关于本地部署的教程和方法.然而,要真正深入了解其功能和应用,还是需要自己动手进行一次本地部署. DeepSeek 作为一个高效的自然语言处 ...

  4. MySQL Q&A - [02] windows上MySQL的安装路径变更之后无法启动MySQL服务

    参考:https://blog.csdn.net/weixin_45271005/article/details/130091868 Step1:首先,变更之前,需要保证MySQL服务是停止运行的 S ...

  5. 机器学习 | 强化学习(7) | 融合学习与规划(Integrating Learning and Planning)

    7-融合学习与规划(Integrating Learning and Planning) 1.导论 基于模型的强化学习(Model-Based Reinforcement Learning) 在上一个 ...

  6. 「二」nginx下载与安装

    1.下载地址(开源版):https://nginx.org/en/download.html wget https://nginx.org/download/nginx-1.14.2.tar.gz 2 ...

  7. 寒武纪平台上传 Docker 镜像

    前言 学校的算力平台更换为了寒武纪平台,相较于以前简单的通过 Linux 用户隔离,使用门槛有所提升.但从整体来看,这样拥有更好的隔离性,在 docker 中即便搞崩了也可以重新来过,可以避免因他人的 ...

  8. Windows Api如何创建一个快捷方式并且在开始菜单搜索到自己的应用

    原文链接:http://cshelloworld.com/home/detail/1804473083243925504 当我们点击win10系统搜索框的时候,输入名称 ,win10会帮助我们匹配到对 ...

  9. 五大股票金融数据API接口推荐:从实时行情到历史数据全覆盖

    摘要:本文将介绍五大主流的股票金融数据API接口,涵盖实时行情.历史数据.技术指标等功能,帮助开发者快速构建金融数据应用.(本文由deepseek生成) 一.StockTV API 1. 核心优势 全 ...

  10. 解决Mac M芯片 Wireshark 运行rvictl -s 后,出现Starting device failed

    前言 mac os big sur 之后,苹果系统的安全性能提升,导致 rvictl -s 创建虚拟网卡失败. $ rvictl -s 000348120-001621w21184C01E boots ...