本文主要介绍了etcd的Gateway和Proxy
1. Gateway
etcd的Gateway是一个简单的tcp代理,只负责转发请求。
etcd的Gateway默认监听端口23790
$ etcd gateway start --help
start the gateway
Usage:
etcd gateway start [flags]
Flags:
--discovery-srv string DNS domain used to bootstrap initial cluster
--discovery-srv-name string service name to query when using DNS discovery
--endpoints strings comma separated etcd cluster endpoints (default [127.0.0.1:2379])
-h, --help help for start
--insecure-discovery accept insecure SRV records
--listen-addr string listen address (default "127.0.0.1:23790") 默认监听端口
--retry-delay duration duration of delay before retrying failed endpoints (default 1m0s) 重试连接到失败的端点延迟时间,默认为 1m0s
--trusted-ca-file string path to the client server TLS CA file for verifying the discovered endpoints when discovery-srv is provided.
在启动网关的时候,需要获取对应的ip地址(两种方式,一种是通过dns服务,一种是直接域名,具体看etcd 官方文档的etcd gateway)
$ etcd gateway start --endpoints=infra0.example.com:2379,infra1.example.com:2379,infra2.example.com:2379
底层的话,就是启动两个协程:一个定时监控etcd集群的服务器是否存活,一个负责处理请求
type TCPProxy struct {
Logger *zap.Logger
Listener net.Listener
Endpoints []*net.SRV // etcd集群的所有服务器
MonitorInterval time.Duration
...
}
func (tp *TCPProxy) Run() error {
...
// 监控任务
go tp.runMonitor()
for {
in, err := tp.Listener.Accept()
if err != nil {
return err
}
// 转发请求
go tp.serve(in)
}
}
func (tp *TCPProxy) serve(in net.Conn) {
var (
err error
out net.Conn
)
for {
tp.mu.Lock()
// 选择合适的连接,里面是简单的负载均衡
remote := tp.pick()
tp.mu.Unlock()
if remote == nil {
break
}
// TODO: add timeout
out, err = net.Dial("tcp", remote.addr)
if err == nil {
break
}
remote.inactivate()
if tp.Logger != nil {
tp.Logger.Warn("deactivated endpoint", zap.String("address", remote.addr), zap.Duration("interval", tp.MonitorInterval), zap.Error(err))
}
}
if out == nil {
in.Close()
return
}
// 转发请求
go func() {
io.Copy(in, out)
in.Close()
out.Close()
}()
io.Copy(out, in)
out.Close()
in.Close()
}
2. Proxy
gRPC Proxy 是一个在 gRPC 层(L7)操作的无状态 etcd 反向代理。
gRPC Proxy特点如下:
- 合并客户端的请求优化lease和watch操作
- 缓存对应的请求和响应
- 支持多个服务器端点和故障切换
- 支持命名空间,提供 --namespace 参数,所有进入该 Proxy 的客户端请求都将转化为在键上具有用户定义的前缀
grpc 的proxy 方式的启动如下:
$ etcd grpc-proxy start --endpoints=infra0.example.com,infra1.example.com,infra2.example.com --listen-addr=127.0.0.1:2379
这里简单讲一下原理,懒得去一点一点去拷贝源码:
- 命名空间的实现,就是在执行真正请求时,带上对应的前缀
- 缓存请求响应的实现,底层是通过lru缓存实现的,key为对应的请求,value为对应的响应,除此之外,还通过区间树,快速查找某个范围内的所有请求,用于快速失效缓存。注意只会缓存串行化读的请求
- watch,lease的优化是通过批处理思想实现的
下面解释如何优化lease的:
+-------------+
| etcd server |
+------+------+
^
| heartbeat L1, L2, L3
| (s-stream)
v
+------+-----+
| gRPC proxy +<-----------+
+---+------+--+ | heartbeat L3
^ ^ | (c-stream)
heartbeat L1 | | heartbeat L2 |
(c-stream) v v (c-stream) v
+------+-+ +-+------+ +-----+--+
| client | | client | | client |
+--------+ +--------+ +--------+
下面解释了如何优化watch:
+-------------+
| etcd server |
+------+------+
^ watch key A (s-watcher)
|
+------+-----+
| gRPC proxy | <-------+
| | |
++-----+------+ |watch key A (c-watcher)
watch key A ^ ^ watch key A |
(c-watcher) | | (c-watcher) |
+-------+-+ ++--------+ +----+----+
| client | | client | | client |
| | | | | |
+---------+ +---------+ +---------+