grpc进阶篇之resolver

当我们的服务刚刚成型时,可能一个服务只有一台实例,这时候client要建立grpc连接很简单,只需要指定server的ip就可以了。但是,当服务成熟了,业务量大了,这个时候,一个实例就就不够用了,我们需要部署一个服务集群。一个集群有很多实例,且可以随时的扩容,部分实例出现了故障也没关系,这样就提升了服务的处理能力和稳定性,但是也带来一个问题,grpc的client,如何和这个集群里的server建立连接?

这个问题可以一分为二,第一个问题:如何根据服务名称,返回实例的ip?这个问题有很多种解决方案,我们可以使用一些成熟的服务发现组件,例如consul或者zookeeper,也可以我们自己实现一个解析服务器;第二个问题,如何将我们选择的服务解析方式应用到grpc的连接建立中去?这个也不难,因为grpc的resolver,就是帮我们解决这个问题的,本篇,我们就来探讨一下,grpc的resolver是如何工作的,以及我们如何在项目中,使用resolver实现服务名称的解析。

resolver的工作原理

关于resolver,我们主要有两个问题:

1.程序启动时,客户端是如何从一个域名/服务名,获取到其对应的实例ip,然后与之建立连接的呢?

2.运行过程中,如果后端的实例挂了,grpc如何感知到,并重新建立连接呢?

接下来,我们就深入源码,搞清楚这两个问题。

启动时的解析过程

我们在使用grpc的时候,首先要做的就是调用Dial或DialContext函数来初始化一个clientConn对象,而resolver是这个连接对象的一个重要的成员,所以我们首先看一看clientConn对象创建过程中,resolver是怎么设置进去的。

客户端启动时,一定会调用grpc的Dial或DialContext函数来创建连接,而这两个函数都需要传入一个名为target的参数,target,就是连接的目标,也就是server了,接下来,我们就看一看,DialContext函数里是如何处理这个target的。

首先,创建了一个clientConn对象,并把target赋给了对象中的target:

	cc := &ClientConn{
		target:            target,
		csMgr:             &connectivityStateManager{},
		conns:             make(map[*addrConn]struct{}),
		dopts:             defaultDialOptions(),
		blockingpicker:    newPickerWrapper(),
		czData:            new(channelzData),
		firstResolveEvent: grpcsync.NewEvent(),
	}

接下来,对这个target进行解析

	cc.parsedTarget = grpcutil.ParseTarget(cc.target)

我们可以看看ParseTarget这个函数做了些什么:

// ParseTarget splits target into a resolver.Target struct containing scheme,
// authority and endpoint.
//
// If target is not a valid scheme://authority/endpoint, it returns {Endpoint:
// target}.
func ParseTarget(target string) (ret resolver.Target) {
	var ok bool
	ret.Scheme, ret.Endpoint, ok = split2(target, "://")
	if !ok {
		return resolver.Target{Endpoint: target}
	}
	ret.Authority, ret.Endpoint, ok = split2(ret.Endpoint, "/")
	if !ok {
		return resolver.Target{Endpoint: target}
	}
	return ret
}

可以看到,这个函数对target这个string进行了拆分,"://"前面的是scheme,也就是解析方案,后面的又可以分为authority和endpoint,endpoint比较好理解,就是对端,也就是server的一个标识,authority的话,我们的项目中并没有用,我也并不能完全理解,所以这里贴上官方文档给出的一行解释,大家自行体会去吧。。

authority indicates the DNS server to use, although this is only supported by some implementations. (In C-core, the default DNS resolver does not support this, but the c-ares based resolver supports specifying this in the form "IP:port".)

那么解析出来的scheme有什么用呢?不要急,我们回到DialContext函数,接着往下看:

解析完target之后执行的是下面这一句:

resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)

也就是在根据解析的结果,包括scheme和endpoint这两个参数,获取一个resolver的builder,我们来看看获取的逻辑是怎么样的:

func (cc *ClientConn) getResolver(scheme string) resolver.Builder {
	for _, rb := range cc.dopts.resolvers {
		if scheme == rb.Scheme() {
			return rb
		}
	}
	return resolver.Get(scheme)
}

这里呢,其实就是在根据scheme进行查找,如果resolver已经在调用DialContext的时候通过opts参数传了进来,那我们就直接用,否则调用resolver.Get(scheme)去找,我们项目中就是用的resolver.Get(scheme),所以我们再来看看这里是怎么做的:

// Get returns the resolver builder registered with the given scheme.
//
// If no builder is register with the scheme, nil will be returned.
func Get(scheme string) Builder {
	if b, ok := m[scheme]; ok {
		return b
	}
	return nil
}

这里面,Get函数是通过m这个map,去查找有没有scheme对应的re

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值