当前位置: 首页 > 科技观察

微服务服务注册和服务发现_0

时间:2023-03-16 21:34:30 科技观察

有了服务注册和发现机制,消费者不需要知道具体调用的服务提供者的真实物理地址,也不需要知道有多少服务提供者可用;服务商只需要在注册中心注册就可以对外提供服务,你对外提供服务时不需要知道是哪些服务调用了你。RPC配置etcd:Hosts:-127.0.0.1:2379Key:user.rpc这里分析一下go-zero的etcd部分源码,源码参考https://github.com/zeromicro/go-zero-demo/tree/master/mall调用方服务注册mall/user/rpc/user.go源码如下/config""go-zero-demo-rpc/mall/user/rpc/internal/server""go-zero-demo-rpc/mall/user/rpc/internal/svc""go-zero-demo-rpc/商城/用户/rpc/类型/用户”“github.com/zeromicro/go-zero/core/conf”“github.com/zeromicro/go-zero/core/service”“github.com/zeromicro/go-zero/zrpc""google.golang.org/grpc""google.golang.org/grpc/reflection")varconfigFile=flag.String("f","etc/user.yaml","配置文件")funcmain(){flag.Parse()varcconfig.Configconf.MustLoad(*configFile,&c)ctx:=svc.NewServiceContext(c)svr:=server.NewUserServer(ctx)s:=zrpc.MustNewServer(c.RpcServerConf,func(grpcServer*grpc.Server){user.RegisterUserServer(grpcServer,svr)如果c.Mode==service.DevMode||c.Mode==service.TestMode{reflection.Register(grpcServer)}})defers.Stop()fmt.Printf("Startingrpcserverat%s...\n",c.ListenOn)s.Start()}MustNewServer内部实现调用NewServer方法,这里我们要注意,NewServer是通过internal.NewRpcPubServer方法实例化的=nil{returnnil,err}}internal.NewRpcPubServerregisterEtcd将调用Publisher.KeepAlive方法//KeepAlivekeepskey:valuealive.func(p*Publisher)KeepAlive()error{//在这里获取etcd连接cli,err:=internal.GetRegistry().GetConn(p.endpoints)iferr!=nil{returnerr}p.lease,err=p.register(cli)iferr!=nil{returnerr}proc.AddWrapUpListener(func(){p.Stop()})returnp.keepAliveAsync(cli)}p.register在这里,自己在服务中注册func(p*Publisher)register(clientinternal.EtcdClient)(clientv3.LeaseID,error){//创建一个newonehereAleaseresp,err:=client.Grant(client.Ctx(),TimeToLive)iferr!=nil{returnclientv3.NoLease,err}//获取租约的IDlease:=resp.ID//拼接实际存储的密钥ifp.id>0{p.fullKey=makeEtcdKey(p.key,p.id)}else{p.fullKey=makeEtcdKey(p.key,int64(lease))}//p.value是前面figureOutListenOn方法获取到自己的address_,err=client.Put(client.Ctx(),p.fullKey,p.value,clientv3.WithLease(lease))returnlease,err}注册后keepAliveAsync开启一个Coroutinekeep-aliveservice当该服务意外宕机时,将不再对etcd保活,etcd会删除key-registered服务如图1.pngcaller-servicediscoveryorder/api/order.go源码如下packagemainimport("flag""fmt""go-zero-demo-rpc/order/api/internal/config”“go-zero-demo-rpc/order/api/internal/handler”“go-zero-demo-rpc/order/api/internal/svc”“github.com/zeromicro/go-zero/core/conf""github.com/zeromicro/go-zero/rest")varconfigFile=flag.String("f","etc/order.yaml","配置文件")funcmain(){flag.Parse()varcconfig.Configconf.MustLoad(*configFile,&c)server:=rest.MustNewServer(c.RestConf)延迟server.Stop()ctx:=svc.NewServiceContext(c)handler.RegisterHandlers(server,ctx)fmt.Printf("Startingserverat%s:%d...\n",c.Host,c.Port)server.Start()}调用zrpc.MustNewClient,zrpc在svc.NewServiceContext方法里面。MustNewClient是主要实现在zrpc.NewClientfuncNewServiceContext(cconfig.Config)*ServiceContext{return&ServiceContext{Config:c,UserRpc:user.NewUser(zrpc.MustNewClient(c.UserRpc)),}}最后实际上调用了internal.NewClient来实例化rpcclientfuncNewClient(cRpcClientConf,options...ClientOption)(Client,error){varopts[]ClientOptionifc.HasCredential(){opts=append(opts,WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{App:c.App,Token:c.Token,})))}ifc.NonBlock{opts=append(opts,WithNonBlock())}ifc.Timeout>0{opts=append(opts,WithTimeout(time.Duration(c.Timeout)*time.Millisecond))}opts=append(opts,options...)target,err:=c.BuildTarget()iferr!=nil{returnnil,err}客户端,err:=internal.NewClient(target,opts...)iferr!=nil{returnnil,err}return&RpcClient{client:client,},nil}在zrpc/internal/client.go文件中,包含一个init方法,也就是真正发现服务的地方,服务发现者funcinit(){resolver.Register()}resolver.Registermethodimplementspackageresolverimport("github.com/zeromicro/go-zero/zrpc/resolver/internal")//Register寄存器方案定义zrpc。//将其保存在单独的包中让第三方手动注册。ectResolverBuilder)resolver.Register(&discovResolverBuiltder)resolver.Register(&etcdResolverBuilder)resolver.Register(&k8sResolverBuilder)}etcdBuilder内嵌discovBuilder结构,build方法调用过程:实例化server:internal.NewClient->client.dial->grpc.DialContext由于etcd是resolver生成的taget。BuildDiscovTarget类似这样:discov://127.0.0.1:2379/user.rpc解析服务发现:ClientConn.parseTargetAndFindResolver->grpc.parseTarget->ClientConn.getResolver然后在grpc.newCCResolverWrapper.Builder.Build方法中调用resolver发现服务我们关注discovBuilder.Build方法typeetcdBuilderstruct{discovBuilder}typediscovBuilderstruct{}func(b*discovBuilder)Build(targetresolver.Target,ccresolver.ClientConn,_resolver.BuildOptions)(resolver.Resolver,error){hosts:=strings.FieldsFunc(targets.GetAuthority(target),func(rrune)bool{returnr==EndpointSepChar})sub,err:=discov.NewSubscriber(hosts,targets.GetEndpoints(target))如果错误!=nil{returnnil,err}update:=func(){varaddrs[]resolver.Addressfor_,val:=rangesubset(sub.Values(),subsetSize){addrs=append(addrs,resolver.Address{Addr:val,})}iferr:=cc.UpdateState(resolver.State{Addresses:addrs,});呃!=nil{logx.Error(err)}}sub.AddListener(update)update()return&nopResolver{cc:cc},nil}func(b*discovBuilder)Scheme()string{returnDiscovScheme}discov.NewSubscriber方法调用内部.GetRegistry().Monitor最后调用Registry.monitor方法监控cluster.getClient获取etcd连接到cluster.load作为第一次加载数据到cluster.watch观察etcd前缀keyfunc(c*cluster)monitor(keystring,lUpdateListener)error{c.lock.Lock()c.listeners[key]=append(c.listeners[key],l)c.lock.Unlock()cli,错误:=c.getClient()iferr!=nil{returnerr}c.load(cli,key)c.watchGroup.Run(func(){c.watch(cli,key)})returnnil}下图是cluster.load的实现,就是根据前缀,获取user.prc服务注册的所有地址2.pngQ为什么不使用Redis作为注册中心呢(反正就是存储调用方的地址,以及Redis也可以胜任pires),找了半天,从下面简单的找到了这个说法先说说Redis在微服务中为什么不能替代etcd:1.Redis不版本的概念。历史版本数据在大规模微服务中是非常必要的。对于状态回滚和故障排除,甚至是去补锅都非常重要。2、redis的注册和发现目前只能通过pub和sub实现。这个命令根本不能满足生产环境的要求。具体原因可以使用gg或者查看源码了解。如果再需要,可以想redis的pub和sub能不能满足这种低延迟的要求。4、宿主机看到的微服务架构,应该直接将etcd暴露给客户端和服务端。etcd的性能放在那里,顶得住。有多少个c/s是直连的?更好的方法应该是为etcd提供一层保护。当然,这种做法会失去一些功能。5、redis和etcd的集群实现方案不一致。etcd使用raft协议。一主多从,只能写master,底层使用boltdb作为k/v存储,直接放到磁盘6上。redis的持久化方案有aof和rdb。缺少引用自https://www.v2ex.com/t/520367的数据