ETCD源码分析Client端启动流程分析

ETCD源码基于v3.5,在分析之前,需要搭建好源码分析的环境。首先,从GitHub的仓库中克隆下ETCD的源码,再利用docker搭建我们的ETCD测试集群,命令如下:

REGISTRY=quay.io/coreos/etcd
NAME_1=etcd-node-0
NAME_2=etcd-node-1
NAME_3=etcd-node-2
# IP在不同机器上不同,请查看docker的子网网段
HOST_1=172.20.0.2  
HOST_2=172.20.0.3
HOST_3=172.20.0.4
PORT_1=2379
PORT_2=12379
PORT_3=22379
PORT_C_1=2380
PORT_C_2=12380
PORT_C_3=22380
CLUSTER=${NAME_1}=http://${HOST_1}:${PORT_C_1},${NAME_2}=http://${HOST_2}:${PORT_C_2},${NAME_3}=http://${HOST_3}:${PORT_C_3}
# 需要保证目录存在并可写
DATA_DIR=/var/folders/

# 需要创建docker网络,用于模拟集群网络分区的情况。
docker network create etcd_cluster

docker run \
  -p $PORT_1:$PORT_1 \
  -p $PORT_C_1:$PORT_C_1 \
  --volume "${DATA_DIR}${NAME_1}:/etcd-data" \
  --name ${NAME_1} \
  --network etcd_cluster \
  ${REGISTRY}:v3.5.0 \
  /usr/local/bin/etcd \
  --name ${NAME_1} \
  --data-dir /etcd-data \
  --listen-client-urls http://0.0.0.0:$PORT_1 \
  --advertise-client-urls http://$HOST_1:$PORT_1 \
  --listen-peer-urls http://0.0.0.0:$PORT_C_1 \
  --initial-advertise-peer-urls http://$HOST_1:$PORT_C_1 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-token tkn \
  --initial-cluster-state new \
  --log-level info \
  --logger zap \
  --log-outputs stderr

docker run \
  -p $PORT_2:$PORT_2 \
  -p $PORT_C_2:$PORT_C_2 \
  --volume=${DATA_DIR}${NAME_2}:/etcd-data \
  --name ${NAME_2} \
  --network etcd_cluster \
  ${REGISTRY}:v3.5.0 \
  /usr/local/bin/etcd \
  --name ${NAME_2} \
  --data-dir /etcd-data \
  --listen-client-urls http://0.0.0.0:$PORT_2 \
  --advertise-client-urls http://$HOST_2:$PORT_2 \
  --listen-peer-urls http://0.0.0.0:$PORT_C_2 \
  --initial-advertise-peer-urls http://$HOST_2:$PORT_C_2 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-token tkn \
  --initial-cluster-state new \
  --log-level info \
  --logger zap \
  --log-outputs stderr

docker run \
  -p $PORT_3:$PORT_3 \
  -p $PORT_C_3:$PORT_C_3 \
  --volume=${DATA_DIR}${NAME_3}:/etcd-data \
  --name ${NAME_3} \
  --network etcd_cluster \
  ${REGISTRY}:v3.5.0 \
  /usr/local/bin/etcd \
  --name ${NAME_3} \
  --data-dir /etcd-data \
  --listen-client-urls http://0.0.0.0:$PORT_3 \
  --advertise-client-urls http://$HOST_3:$PORT_3 \
  --listen-peer-urls http://0.0.0.0:$PORT_C_3 \
  --initial-advertise-peer-urls http://$HOST_3:$PORT_C_3 \
  --initial-cluster ${CLUSTER} \
  --initial-cluster-token tkn \
  --initial-cluster-state new \
  --log-level info \
  --logger zap \
  --log-outputs stderr

如上,我们创建了三个ETCD节点,组成了一个集群。接下来我们正式进入源码分析流程。

ETCD Client启动流程分析

我们先看一段启动代码样例:

        cli, err := clientv3.New(clientv3.Config{
			Endpoints:   exampleEndpoints(),
			DialTimeout: dialTimeout,
		})
		if err != nil {
			log.Fatal(err)
		}
		defer cli.Close()

		ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
		_, err = cli.Put(ctx, "sample_key", "sample_value")
		cancel()
		if err != nil {
			log.Fatal(err)
		}

一个最简单的程序只需要提供集群的所有节点的ip和端口就能访问,这里需要注意的是,一定要填写ETCD集群的所有节点,这样才能有故障转移、负载均衡的特性。或者运行一个ETCD的代理节点(ETCD网关)负责转发请求,这样只填写代理节点ip即可,当然性能上会有所损失。

一、ETCD的Client启动流程分析

接下来我们看看Client是如何被创建出来的:


func newClient(cfg *Config) (*Client, error) {
    // -----A-----
	ctx, cancel := context.WithCancel(baseCtx)
	client := &Client{
		conn:     nil,
		cfg:      *cfg,
		creds:    creds,
		ctx:      ctx,
		cancel:   cancel,
		mu:       new(sync.RWMutex),
		callOpts: defaultCallOpts,
		lgMu:     new(sync.RWMutex),
	}
	 // -----A-----

    // -----B-----
	client.resolver = resolver.New(cfg.Endpoints...)

	conn, err := client.dialWithBalancer()
	if err != nil {
		client.cancel()
		client.resolver.Close()
		return nil, err
	}
	client.conn = conn
	 // -----B-----

    // -----C-----
	client.Cluster = NewCluster(client)
	client.KV = NewKV(client)
	client.Lease = NewLease(client)
	client.Watcher = NewWatcher(client)
	client.Auth = NewAuth(client)
	client.Maintenance = NewMaintenance(client)
	
	...
    // -----C-----
    
	return client, nil
}

A段代码分析

首先来看第A段代码,其主要是初始化了一个client的实例,并把Config结构体传递给它,那么Config中包含了什么配置项呢?

type Config struct {
	// ETCD服务器地址,注意需要提供ETCD集群所有节点的ip
	Endpoints []string `json:"endpoints"`

	// 设置了此间隔时间,每 AutoSyncInterval 时间ETCD客户端都会
	// 自动向ETCD服务端请求最新的ETCD集群的所有节点列表
	// 
	// 默认为0,即不请求
	AutoSyncInterval time.Duration `json:"auto-sync-interval"`

	// 建立底层的GRPC连接的超时时间
	DialTimeout time.Duration `json:"dial-timeout"`

	// 这个配置和下面的 DialKeepAliveTimeoutt
	// 都是用来打开GRPC提供的 KeepAlive
	// 功能,作用主要是保持底层TCP连接的有效性,
	// 及时发现连接断开的异常。
	// 
	// 默认不打开 keepalive
	DialKeepAliveTime time.Duration `json:"dial-keep-alive-time"`

	// 客户端发送 keepalive 的 ping 后,等待服务端的 ping ack 包的时长
	// 超过此时长会报 `translation is closed`
	DialKeepAliveTimeout time.Duration `json:"dial-keep-alive-timeout"`
		
	// 也是 keepalive 中的设置,
	// true则表示无论有没有活跃的GRPC连接,都执行ping
	// false的话,没有活跃的连接也就不会发送ping。
	PermitWithoutStream bool `json:"permit-without-stream"`

	// 最大可发送字节数,默认为2MB
	// 也就是说,我们ETCD的一条KV记录最大不能超过2MB,
	// 如果要设置超过2MB的KV值,
	// 只修改这个配置也是无效的,因为ETCD服务端那边的限制也是2MB。
    // 需要先修改ETCD服务端启动参数:`--max-request-bytes`,再修改此值。
	MaxCallSendMsgSize int

    // 最大可接收的字节数,默认为`Int.MaxInt32`
    // 一般不需要改动
	MaxCallRecvMsgSize int

	// HTTPS证书配置
	TLS *tls.Config
	
	// 上下文,一般用于取消操作
	ctx.Context

	// 设置此值,会拒绝连接到低版本的ETCD
	// 什么是低版本呢?
	// 写死了,小于v3.2的版本都是低版本。
	RejectOldCluster bool `json:"reject-old-cluster"`

	// GRPC 的连接配置,具体可参考GRPC文档
	DialOptions []grpc.DialOption

	// zap包的Logger配置 
	// ETCD用的日志包就是zap
	Logger *zap.Logger
	LogConfig *zap.Config
	
	...
}

还有一些常用配置项,比较简单,这里就不再列出了。

B段代码分析

本段是整个代码的核心部分,主要做了两件事:

  1. 创建了 resolver 用于解析ETCD服务的地址
    resolver(解析器)其实是grpc中的概念,比如:DNS解析器,域名转化为真实的ip;服务注册中心,也是一种把服务名转化为真实ip的解析服务。

    具体的概念就不展开了,如果对grpc这方面比较感兴趣,文末会推荐一个讲的很好的grpc源码分析博客。

    总之,etcd自己写了一个解析器,就在resolver包里,这个解析器提供了以下几个功能:

    1. 把Endpoints里的ETCD服务器地址传给grpc框架,这里,因为ETCD自己实现的解析器不支持DNS解析,所以Endpoints只能是ip地址或者unix套接字。
    2. 告诉grpc,如果Endpoints有多个,负载均衡的策略是轮询,这点很重要。
  2. dialWithBalancer() 建立了到ETCD的服务端链接

func (c *Client) dialWithBalancer(dopts ...grpc.DialOption) (*grpc.ClientConn, error) {
	creds := c.credentialsForEndpoint(c.Endpoints()[0])
	opts := append(dopts, grpc.WithResolvers(c.resolver))
	return c.dial(creds, opts...)
}

这个用于建立到ETCD服务端的连接的方法名很有意思,虽然叫dialWithBalancer但内部代码很简单,可以看到里面并无Balancer(负载均衡器)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值