服务的实例已在运行中_简述grpc在微服务架构中的运行流程

grpc在微服务中的运行流程图

3c8e009cd9e7c5487f8fc19ab0a222e1.png
  1. grpc server将自己注册到注册中心
  2. grpc client从注册中心获得注册信息从而知道有多少个服务可使用
  3. grpc client选择一个grpc server发起请求,完成调用

从以上流程图中可以看到,主要的工作其实是在client进行,server只需要将自身注册到注册中心,然后等待请求到来就可以了

简单grpc例子

```proto代码```
syntax="proto3";
package api;
option go_package="mytest/api;api";
service Api {
	rpc Ping(PingReq)returns(PingResp);
}
message PingReq{
	int64 id=1;
}
message PingResp{
	int64 status=2;
}

```go代码```
package main

import (
	"context"
	"flag"
	"fmt"
	"net"

	"mytest/api"

	"google.golang.org/grpc"
)

func main() {
	t := flag.String("t", "", "s to start servernc to start client")
	flag.Parse()
	if *t == "" || (*t != "s" && *t != "c") {
		panic("need flag")
	}
	if *t == "s" {
		server()
	} else {
		client()
	}
}
func client() {
	c, e := grpc.Dial("127.0.0.1:9234", grpc.WithInsecure())
	if e != nil {
		panic(e)
	}
	cc := api.NewApiClient(c)
	resp, e := cc.Ping(context.Background(), &api.PingReq{})
	if e != nil {
		panic(e)
	}
	fmt.Println(resp)
}
func server() {
	l, e := net.Listen("tcp", "127.0.0.1:9234")
	if e != nil {
		panic(e)
	}
	s := grpc.NewServer()
	api.RegisterApiService(s, &api.ApiService{
		Ping: Ping,
	})
	s.Serve(l)
}

func Ping(ctx context.Context, in *api.PingReq) (*api.PingResp, error) {
	fmt.Println(in)
	return &api.PingResp{}, nil
}

从Dial创建连接开始看Client是如何解析服务端地址的:Resolver

//文件位置 grpc/clientconn.go
//代码位置 DialContext函数,这里有一大堆代码,我们只看主要的代码
//在该函数中有以下代码,其中target就是我们传入的地址,目前为127.0.0.1:9234

//Part 1 获取resolver,创建resolver
cc.parsedTarget = grpcutil.ParseTarget(cc.target)//改函数展开在后面
resolverBuilder := cc.getResolver(cc.parsedTarget.Scheme)//改函数展开在后面
if resolverBuilder == nil {//使用默认的scheme
		cc.parsedTarget = resolver.Target{
			Scheme:   resolver.GetDefaultScheme(),
			Endpoint: target,
		}
		resolverBuilder = cc.getResolver(cc.parsedTarget.Scheme)
		if resolverBuilder == nil {
			return nil, fmt.Errorf("could not get resolver for default scheme: %q", cc.parsedTarget.Scheme)
		}
	}
rWrapper, err := newCCResolverWrapper(cc, resolverBuilder)//改函数展开在后面

//函数展开
func split2(s, sep string) (string, string, bool) {
	spl := strings.SplitN(s, sep, 2)
	if len(spl) < 2 {
		return "", "", false
	}
	return spl[0], spl[1], true
}
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
}
//resolver.Target为以下格式
type Target struct {
	Scheme    string
	Authority string
	Endpoint  string
}
unc (cc *ClientConn) getResolver(scheme string) resolver.Builder {
	for _, rb := range cc.dopts.resolvers {
		if scheme == rb.Scheme() {
			return rb
		}
	}
	return resolver.Get(scheme)
}
func newCCResolverWrapper(cc *ClientConn, rb resolver.Builder) (*ccResolverWrapper, error) {
	ccr := &ccResolverWrapper{
		cc:   cc,
		done: grpcsync.NewEvent(),
	}
        ......//最终调用的是resolver的Build函数
	ccr.resolver, err = rb.Build(cc.parsedTarget, ccr, rbo)
	if err != nil {
		return nil, err
	}
	return ccr, nil
}

从以上代码可以看到,当我们指定ip创建一个简单的client的时候,ParseTarget无法解析到scheme,所以getResolver也无法获得,因此就会使用默认scheme进行解析地址,该scheme的名字叫做passthrough。

//文件地址 grpc/internal/resolver/passthrough/passthrough.go
package passthrough

import "google.golang.org/grpc/resolver"

const scheme = "passthrough"

type passthroughBuilder struct{}
//这里的Build函数就是Part 1中创建resolver中调用的
//这将最终创建passthrough这个resolver
func (*passthroughBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r := &passthroughResolver{
		target: target,
		cc:     cc,
	}
	r.start()
	return r, nil
}

func (*passthroughBuilder) Scheme() string {
	return scheme
}

type passthroughResolver struct {
	target resolver.Target
	cc     resolver.ClientConn
}

func (r *passthroughResolver) start() {
	r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint}}})
}

func (*passthroughResolver) ResolveNow(o resolver.ResolveNowOptions) {}

func (*passthroughResolver) Close() {}

func init() {
	resolver.Register(&passthroughBuilder{})
}
//这里调用的Register,文件位置 grpc/resolver/resolver.go
var m = make(map[string]Builder)
func Register(b Builder) {
	m[b.Scheme()] = b
}
//这里的Register正好呼应了Part 1中的getResolver

现在,我们已经创建了一个resolver了,我们也知道,resolver是用来解析服务端的地址的,接下来,我们就需要将resolver中解析到的地址告诉client,以便让client创建指向服务端的连接

//回到上面passthrough的代码中
func (*passthroughBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r := &passthroughResolver{
		target: target,
		cc:     cc,
	}
	r.start()//该函数开始解析地址
	return r, nil
}
func (r *passthroughResolver) start() {
        //该调用将解析到的地址往Client中抛
        //因为passthrough是一个默认的指向固定地址的resolver,因此直接调用
	r.cc.UpdateState(resolver.State{Addresses: []resolver.Address{{Addr: r.target.Endpoint}}})
}
//最终调用的函数 文件位置 grpc/resolver_conn_wrapper
func (ccr *ccResolverWrapper) UpdateState(s resolver.State) {
	......
	ccr.curState = s
        //最终调用Client的updateResoverState执行创建
	ccr.poll(ccr.cc.updateResolverState(ccr.curState, nil))
}
//文件位置 grpc/clientconn.go 该函数与Dail在一个文件,绕了一圈我们又回来了
//我们可以暂时认为连接在这里已经创建了,后续还会再进行展开
func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
}

至此,我们的resolver的工作就做完了,我们已经解析到了服务器的地址,并将地址通过UpdateState的方法传递给了Client进行创建到server的连接。我们现在可以和最开头的流程图结合,来思考一下,如何才能从discover server中动态的获得server的变动呢。

//回到passthrough的代码中
//我们知道,resolver的开始是通过Build
//Build中又调用了start
//那么我们可以改造一下start,然后给我们改造后的resolver重新起个名字,并且register自己的名字
//那么这个新的名字就变成了scheme,我们只需要在Dail的时候遵循ParseTarget的格式,就能使用我们自己的resolver了
package selfresolver

import "google.golang.org/grpc/resolver"
//改造成自己的scheme
const scheme = "selfresolver"

type selfbuilder struct{}

func (*selfbuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
	r := &passthroughResolver{
		target: target,
		cc:     cc,
	}
        //这里我们改造成使用go协程
	go r.start()
	return r, nil
}

func (*selfbuilder) Scheme() string {
	return scheme
}

type selfresolver struct {
	target resolver.Target
	cc     resolver.ClientConn
}

func (r *selfresolver) start() {
        for {
            //在这里写上从discover获取server更新的逻辑
            ......
            //更新完成后
            //state中更新serve的address
            state := resolver.State{Addresses: []resolver.Address{}}
            //使用updatestate去通知Client
            r.cc.UpdateState(state)
        }
}

func (*selfresolver) ResolveNow(o resolver.ResolveNowOptions) {}

func (*selfresolver) Close() {}

func init() {
	resolver.Register(&selfbuilder{})
}
//这里调用的Register,文件位置 grpc/resolver/resolver.go
var (
        m = make(map[string]Builder)
)
func Register(b Builder) {
	m[b.Scheme()] = b
}
//这时候我们Dail的时候,就可以修改我们的target为 selfresolver://auth/endpoint
//其中endpoint可以是discovery的地址,然后在上面start函数中使用endpoint

至此,我们对服务端地址的解析以及动态变更已经完成

负载均衡:Balancer

在分布式程序中,最重要的一环应该就是负载均衡,它能使得请求流量均匀地分散到所有的服务器上,这样配合我们上面讲到的resolver,就能实现在高负载下的动态扩容,对server随时进行增加,只要server注册到discover上,那么client就能通过resolver获得新增加server,进而通过Balancer将流量分到新服务器中。

//balancer也是和resolver一样Register在一个地方的
//文件位置 grpc/balancer/balancer.go
var m = make(map[string]Builder)
func Register(b Builder) {
	m[strings.ToLower(b.Name())] = b
}
func Get(name string) Builder {
	if b, ok := m[strings.ToLower(name)]; ok {
		return b
	}
	return nil
}

//我们先来看几个Dial时与balancer有关的选项
grpc.WithBalancerName()//该方法已经被标注为弃用
//该方法在Dail的时候就指定了一个需要使用的balancer
func WithBalancerName(balancerName string) DialOption {
	builder := balancer.Get(balancerName)
	if builder == nil {
		panic(fmt.Sprintf("grpc.WithBalancerName: no balancer is registered for name %v", balancerName))
	}
	return newFuncDialOption(func(o *dialOptions) {
		o.balancerBuilder = builder
	})
}
grpc.WithDefaultServiceConfig()//该方法暂时被标注为试验,非稳定
//该方法在Dail的时候指定了一个配置文件,其中包括了balancer信息,名字,配置等
func WithDefaultServiceConfig(s string) DialOption {
	return newFuncDialOption(func(o *dialOptions) {
		o.defaultServiceConfigRawJSON = &s
	})
}
grpc.WithDisableServiceConfig()//该方法禁用resolver的ServiceConfig
//resolver在调用UpdateState的时候
//state参数中不仅可以传入servers的address
//还可以传入一个ServiceConfig,其中包括了balancer信息,名字,配置等
//该方法将禁用resolver的config,强制使用defaultserviceconfig,如果defaultservice不存在就用空的
func WithDisableServiceConfig() DialOption {
	return newFuncDialOption(func(o *dialOptions) {
		o.disableServiceConfig = true
	})
}

从上面选项中,废弃,以及实验等信息来看,使用resolver中的serviceconfig应该是一个比较合适的做法,这样也无须在Dail中增加额外的with选项,因此,在resolver中的UpdateState中,传入的state需要带上ServiceConfig,我们来看下state和ServiceConfig的结构

//这里只留下了暂时需要关心的部分字段,与流程无关的一些字段删除了
type State struct {
	Addresses []Address
	ServiceConfig *serviceconfig.ParseResult
}
type ServiceConfig struct {
	LB *string
	lbConfig *lbConfig
}

现在我们来看下,balancer到底是怎么运行的

//文件位置 grpc/clientconn.go
//代码位置 updateResolverState函数
//这个方法是不是感觉很眼熟,这不就是之前说resolver调用UpdateState之后创建连接的地方吗
//其实,连接不是在这里创建的,之前只是说暂定认为是在这里创建
//我们来想一下,负载均衡是干什么的,不就是分散流量吗
//那么流量往哪走是负载均衡说了算,连接的管理自然肯定也是在负载均衡里面
//因此,resolver的UpdateState只是触发了balancer的初始化/更新
func (cc *ClientConn) updateResolverState(s resolver.State, err error) error {
        .......        
        var ret error
        //这里就是上面说过的,grpc.WithDisableServiceConfig()生效的地方
        //禁用了resolver来的ServiceConfig
	if cc.dopts.disableServiceConfig || s.ServiceConfig == nil {
		//cc.maybeApplyDefaultServiceConfig(s.Addresses)//将此方法展开
                if cc.sc != nil {
                    cc.applyServiceConfigAndBalancer(cc.sc, addrs)
		    return
	        }
	        if cc.dopts.defaultServiceConfig != nil {
                    //使用grpc.WithDefaultServiceConfig()设置的
		    cc.applyServiceConfigAndBalancer(cc.dopts.defaultServiceConfig, addrs)
	        } else {
                    //使用空的
		    cc.applyServiceConfigAndBalancer(emptyServiceConfig, addrs)
	        }
	} else {
		if sc, ok := s.ServiceConfig.Config.(*ServiceConfig); s.ServiceConfig.Err == nil && ok {
			//使用从resolver来的
                        cc.applyServiceConfigAndBalancer(sc, s.Addresses)
		} else {
                        //出错,只有在首次才出错,因为首次需要初始化balancer
			......
		}
	}
        ......//最后执行更新操作
	bw.updateClientConnState(&balancer.ClientConnState{ResolverState: s, BalancerConfig: balCfg})
	......
}
//调用的主要方法,用于创建balancer
func (cc *ClientConn) applyServiceConfigAndBalancer(sc *ServiceConfig, addrs []resolver.Address) {
	cc.sc = sc
        ......
        //grpc.WithBalancerName()会设置该值
        //没有使用grpc.WithBalancerName()时该值为nil
        //会从ServiceConfig中获取balancer名字
        //最后通过switchBalancer获取到balancer builder
	if cc.dopts.balancerBuilder == nil {
		var newBalancerName string
                ......
		newBalancerName = cc.sc.lbConfig.name
                ......
		cc.switchBalancer(newBalancerName)
	} else if cc.balancerWrapper == nil {
		cc.curBalancerName = cc.dopts.balancerBuilder.Name()
		cc.balancerWrapper = newCCBalancerWrapper(cc, cc.dopts.balancerBuilder, cc.balancerBuildOpts)
	}
}
func (cc *ClientConn) switchBalancer(name string) {
        ......
	builder := balancer.Get(name)
	cc.curBalancerName = builder.Name()
	cc.balancerWrapper = newCCBalancerWrapper(cc, builder, cc.balancerBuildOpts)
}
func newCCBalancerWrapper(cc *ClientConn, b balancer.Builder, bopts balancer.BuildOptions) *ccBalancerWrapper {
	ccb := &ccBalancerWrapper{
		cc:       cc,
		scBuffer: buffer.NewUnbounded(),
		done:     grpcsync.NewEvent(),
		subConns: make(map[*acBalancerWrapper]struct{}),
	}
        //watcher用于监听连接的状态
	go ccb.watcher()
        //最终调用了balancer中的Build方法进行构造balancer,代码转到balancer中的实现
	ccb.balancer = b.Build(ccb, bopts)
	return ccb
}
func (ccb *ccBalancerWrapper) watcher() {
	for {
		select {
		case t := <-ccb.scBuffer.Get():
                        //最终调用balancer中的UpdateSubConnState方法通知balancer更新连接状态
			ccb.balancer.UpdateSubConnState(su.sc, balancer.SubConnState{ConnectivityState: su.state, ConnectionError: su.err})
		case <-ccb.done.Done():
		}
	}
}
//调用的主要方法,用于更新balancer
unc (ccb *ccBalancerWrapper) updateClientConnState(ccs *balancer.ClientConnState) error {
	//最终调用了balancer中的UpdateClientConnState的方法,代码转到balancer中的实现
        return ccb.balancer.UpdateClientConnState(*ccs)
}

我们找一个最简单的balancer来分析下代码

//文件位置 grpc/balancer/base/balancer.go
package base

type baseBuilder struct {
	name          string
	pickerBuilder PickerBuilder
	config        Config
}
//用于构建balancer
func (bb *baseBuilder) Build(cc balancer.ClientConn, opt balancer.BuildOptions) balancer.Balancer {
	bal := &baseBalancer{
		cc:            cc,
		pickerBuilder: bb.pickerBuilder,

		subConns: make(map[resolver.Address]balancer.SubConn),
		scStates: make(map[balancer.SubConn]connectivity.State),
		csEvltr:  &balancer.ConnectivityStateEvaluator{},
		config:   bb.config,
	}
	bal.picker = NewErrPicker(balancer.ErrNoSubConnAvailable)
	return bal
}
//用于register
func (bb *baseBuilder) Name() string {
	return bb.name
}

type baseBalancer struct {
	cc            balancer.ClientConn
	pickerBuilder PickerBuilder

	csEvltr *balancer.ConnectivityStateEvaluator
	state   connectivity.State

	subConns map[resolver.Address]balancer.SubConn
	scStates map[balancer.SubConn]connectivity.State
	picker   balancer.Picker
	config   Config

	resolverErr error
	connErr     error 
}

//从updateResolverState调用该函数
func (b *baseBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
	b.resolverErr = nil
        //解析resolver中的server地址
	addrsSet := make(map[resolver.Address]struct{})
	for _, a := range s.ResolverState.Addresses {
		addrsSet[a] = struct{}{}
		if _, ok := b.subConns[a]; !ok {
                        //通知Client创建新连接,有新的server上线了
                        //最终Client创建完成后,通过balancerwrapper的watcher调用UpdateSubConnState
			sc, err := b.cc.NewSubConn([]resolver.Address{a}, balancer.NewSubConnOptions{HealthCheckEnabled: b.config.HealthCheck})
		        ......
                }
	}
        //删除已经下线的server的连接
	for a, sc := range b.subConns {
		if _, ok := addrsSet[a]; !ok {
			b.cc.RemoveSubConn(sc)
			delete(b.subConns, a)
		}
	}
        ......
}
//刷新选择器,当一起请求发起时,用于选择向哪个server发起
func (b *baseBalancer) regeneratePicker() {
	if b.state == connectivity.TransientFailure {
		b.picker = NewErrPicker(b.mergeErrors())
		return
	}
	readySCs := make(map[balancer.SubConn]SubConnInfo)

	// Filter out all ready SCs from full subConn map.
	for addr, sc := range b.subConns {
		if st, ok := b.scStates[sc]; ok && st == connectivity.Ready {
			readySCs[sc] = SubConnInfo{Address: addr}
		}
	}
	b.picker = b.pickerBuilder.Build(PickerBuildInfo{ReadySCs: readySCs})
}

//在balancerwrapper的watcher方法中调用该函数,用于更新连接状体啊
func (b *baseBalancer) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
	......
	b.scStates[sc] = s
	switch s {
	case connectivity.Idle:
		sc.Connect()
	case connectivity.Shutdown:
		delete(b.scStates, sc)
	case connectivity.TransientFailure:
		b.connErr = state.ConnectionError
	}
        if (s == connectivity.Ready) != (oldS == connectivity.Ready) ||
		b.state == connectivity.TransientFailure {
		b.regeneratePicker()
	}
	b.cc.UpdateState(balancer.State{ConnectivityState: b.state, Picker: b.picker})
}

//其中使用的主要方法
//文件位置 grpc/balancer_conn_wrappers.go
func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer.NewSubConnOptions) (balancer.SubConn, error) {
	//调用Client的newconn方法
        ac, err := ccb.cc.newAddrConn(addrs, opts)
}

至于还剩下的,在发起请求时,如何选择server,就相对简单了,各位自己看一下吧,入口在grpc/call.go的Invoke函数,有空再补全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值