etcd3

etcd3 client/client.go设计分析

Posted by Liangjf on July 20, 2020

etcd3 client/client.go设计分析

pkg/mod/github.com/coreos/etcd@v3.3.17+incompatible/client/client.go:176

  • Client接口
    • etcd3的client接口
  • newHTTPClientFactory对象
    • Client接口的工厂方法
    • 返回httpClient接口Do
  • httpClient接口
    • client http请求的实现接口
    • Do(context.Context, httpAction) (*http.Response, []byte, error)
  • httpAction接口
    • 封装了http request结构,结构返回http.Request
    • HTTPRequest(url.URL) *http.Request

//Client etcd client通信的接口

type Client interface {
	Sync(context.Context) error
	AutoSync(context.Context, time.Duration) error
	Endpoints() []string
	SetEndpoints(eps []string) error
	GetVersion(ctx context.Context) (*version.Versions, error)

	//接口类型,
	httpClient
}

//New 面向接口编程,返回接口,对外暴露接口即可

func New(cfg Config) (Client, error) {
	//httpClusterClient 实现Client接口,对外调用
	c := &httpClusterClient{
		//工厂方法设计模式
		clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
		rand:          rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
		selectionMode: cfg.SelectionMode,
	}
	if cfg.Username != "" {
		c.credentials = &credentials{
			username: cfg.Username,
			password: cfg.Password,
		}
	}
	if err := c.SetEndpoints(cfg.Endpoints); err != nil {
		return nil, err
	}
	return c, nil
}

//httpClient client真正触发http请求,参数是httpAction,只要实现了httpAction的对象都可以作为参数,也就是说用户外部自定义http请求过程传入即可

type httpClient interface {
	Do(context.Context, httpAction) (*http.Response, []byte, error)
}

//newHTTPClientFactory http client的工厂方法模式,可以随时替换,这里返回接口httpClientFactory,httpClientFactory又是函数返回接口httpClient,httpClient会触发http请求Do,实现了链式调用的技巧

func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
	return func(ep url.URL) httpClient {
		return &redirectFollowingHTTPClient{
			checkRedirect: cr,
			client: &simpleHTTPClient{
				transport:     tr,
				endpoint:      ep,
				headerTimeout: headerTimeout,
			},
		}
	}
}

type httpClientFactory func(url.URL) httpClient

//httpAction http请求接口,只要实现此接口的都可以http请求

type httpAction interface {
	HTTPRequest(url.URL) *http.Request
}

//httpClusterClient 实现Client接口,作为client对象

type httpClusterClient struct {
	clientFactory httpClientFactory
	...
}

httpClusterClient实现接口

func (c *httpClusterClient) getLeaderEndpoint(ctx context.Context, eps []url.URL) (string, error) {
	...
}

func (c *httpClusterClient) parseEndpoints(eps []string) ([]url.URL, error) {
	...l
}

func (c *httpClusterClient) SetEndpoints(eps []string) error {
	...
}

func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
	...
}

func (c *httpClusterClient) Endpoints() []string {
	...
}

func (c *httpClusterClient) Sync(ctx context.Context) error {
	...
}

func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
	...
}

func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
	...
}

//simpleHTTPClient 实现httpClient接口,用于触发http请求

type simpleHTTPClient struct {
	transport     CancelableTransport
	endpoint      url.URL
	headerTimeout time.Duration
}
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
	req := act.HTTPRequest(c.endpoint)
}

这样的设计真的是妙极了!!!

调用逻辑:

client接口–>httpClient接口–>httpAction接口

  • client接口:对外暴露Client接口
  • httpClient接口:实现真正的http请求接口Do
  • httpAction接口:实现构造http request

从上而下调用,层层封装,实现从对外接口到底层http请求,request封装的解耦

1.实例化client (httpClientFactory工厂方法), 先得到实现Client接口的httpClusterClient,进而得到实现Do接口的simpleHTTPClient

func New(cfg Config) (Client, error) {
	c := &httpClusterClient{
		clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
		rand:          rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
		selectionMode: cfg.SelectionMode,
	}
	...
}
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
	return func(ep url.URL) httpClient {
		return &redirectFollowingHTTPClient{
			checkRedirect: cr,
			client: &simpleHTTPClient{
				transport:     tr,
				endpoint:      ep,
				headerTimeout: headerTimeout,
			},
		}
	}
}

2.实现httpAction,构造http request请求

type authAPIAction struct {
	verb string
}
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
	u := v2AuthURL(ep, "enable", "")
	req, _ := http.NewRequest(l.verb, u.String(), nil)
	return req
}

3.调用Client接口(由于Client内嵌HTTPClient接口, 所以即调用simpleHTTPClient的Do)

func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
	req := act.HTTPRequest(c.endpoint)
	...
}