深入理解etcd(十)--- Gateway & Proxy

362 阅读2分钟

本文主要介绍了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 |
    |         |  |         |  |         |
    +---------+  +---------+  +---------+