Dubbo-go 源码笔记(一)Server 端开启服务过程(2),面试Golang

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注go)
img

正文

在运行 go run 即可开启服务。

2)运行 zookeeper

==============

安装 zookeeper,并运行 zkServer, 默认为 2181 端口。

3)运行 go-client 调用 server 服务

===========================

  • 进入 go-client 的源码目录

$ cd go-client/app

  • 同理,在 /app 下配置环境变量

$ export CONF_CONSUMER_FILE_PATH=“…/profiles/dev/client.yml”

$ export APP_LOG_CONF_FILE=“…/profiles/dev/log.yml”

配置 go 代理:

$ export GOPROXY=“http://goproxy.io”

  • 运行程序

$ go run .

即可在日志中找到打印出的请求结果:

response result: &{A001 Alex Stocks 18 2020-10-28 14:52:49.131 +0800 CST}

同样,在运行的 server 中,也可以在日志中找到打印出的请求:

req:[]interface {}{“A001”}

rsp:main.User{Id:“A001”, Name:“Alex Stocks”, Age:18, Time:time.Time{…}

恭喜!一次基于 dubbo-go 的 rpc 调用成功。

4)常见问题

======

  • 当日志开始部分出现 profiderInit 和 ConsumerInit 均失败的日志,检查环境变量中配置路径是否正确,配置文件是否正确。

  • 当日志中出现 register 失败的情况,一般为向注册中心注册失败,检查注册中心是否开启,检查配置文件中关于 register 的端口是否正确。

  • sample 的默认开启端口为 20000,确保启动前无占用。

2. 配置环境变量

==========

export APP_LOG_CONF_FILE=“…/profiles/dev/log.yml”

export CONF_CONSUMER_FILE_PATH=“…/profiles/dev/client.yml”

复制代码

3. 服务端源码

=========

1)目录结构

======

dubbo-go 框架的 example 提供的目录如下:

  • app/ 文件夹下存放源码,可以自己编写环境变量配置脚本 buliddev.sh

  • assembly/ 文件夹下存放不同平台的构建脚本

  • profiles/ 文件夹下存放不同环境的配置文件

  • target/ 文件夹下存放可执行文件

2)关键源码

======

源码放置在 app/ 文件夹下,主要包含 server.go 和 user.go 两个文件,顾名思义,server.go 用于使用框架开启服务以及注册传输协议;user.go 则定义了 rpc-service 结构体,以及传输协议的结构。

  • user.go

func init() {

config.SetProviderService(new(UserProvider))

// ------for hessian2------

hessian.RegisterPOJO(&User{})

}

type User struct {

Id string

Name string

Age int32

Time time.Time

}

type UserProvider struct {

}

func (u *UserProvider) GetUser(ctx context.Context, req []interface{}) (*User, error) {

复制代码

可以看到,user.go 中存在 init 函数,是服务端代码中最先被执行的部分。User 为用户自定义的传输结构体,UserProvider 为用户自定义的 rpc_service;包含一个 rpc 函数,GetUser。当然,用户可以自定义其他的 rpc 功能函数。

在 init 函数中,调用 config 的 SetProviderService 函数,将当前 rpc_service 注册在框架 config 上。

可以查看 dubbo 官方文档提供的设计图:

service 层下面就是 config 层,用户服务会逐层向下注册,最终实现服务端的暴露。

rpc-service 注册完毕之后,调用 hessian 接口注册传输结构体 User。

至此,init 函数执行完毕。

  • server.go

// they are necessary:

// export CONF_PROVIDER_FILE_PATH=“xxx”

// export APP_LOG_CONF_FILE=“xxx”

func main() {

hessian.RegisterPOJO(&User{})

config.Load()

initSignal()

}

func initSignal() {

signals := make(chan os.Signal, 1)

复制代码

之后执行 main 函数。

main 函数中只进行了两个操作,首先使用 hessian 注册组件将 User 结构体注册(与之前略有重复),从而可以在接下来使用 getty 打解包。

之后调用 config.Load 函数,该函数位于框架 config/config_loader.go 内,这个函数是整个框架服务的启动点,下面会详细讲这个函数内重要的配置处理过程。执行完 Load() 函数之后,配置文件会读入框架,之后根据配置文件的内容,将注册的 service 实现到配置结构里,再调用 Export 暴露给特定的 registry,进而开启特定的 service 进行对应端口的 tcp 监听,成功启动并且暴露服务。

最终开启信号监听 initSignal() 优雅地结束一个服务的启动过程。

4. 客户端源码

=========

客户端包含 client.go 和 user.go 两个文件,其中 user.go 与服务端完全一致,不再赘述。

  • client.go

// they are necessary:

// export CONF_CONSUMER_FILE_PATH=“xxx”

// export APP_LOG_CONF_FILE=“xxx”

func main() {

hessian.RegisterPOJO(&User{})

config.Load()

time.Sleep(3e9)

println(“\n\n\nstart to test dubbo”)

user := &User{}

err := userProvider.GetUser(context.TODO(), []interface{}{“A001”}, user)

if err != nil {

panic(err)

}

println(“response result: %v\n”, user)

initSignal()

}

复制代码

main 函数和服务端也类似,首先将传输结构注册到 hessian 上,再调用 config.Load() 函数。在下文会介绍,客户端和服务端会根据配置类型执行 config.Load() 中特定的函数 loadConsumerConfig() 和 loadProviderConfig(),从而达到“开启服务”、“调用服务”的目的。

加载完配置之后,还是通过实现服务、增加函数 proxy、申请 registry 和 reloadInvoker 指向服务端 ip 等操作,重写了客户端实例 userProvider 的对应函数,这时再通过调用 GetUser 函数,可以直接通过 invoker,调用到已经开启的服务端,实现 rpc 过程。

下面会从 server 端和 client 从两个角度,详细讲解服务启动、registry 注册和调用过程。

5. 自定义配置文件(非环境变量)方法

====================

1)服务端自定义配置文件

============

  • var providerConfigStr = xxxxx// 配置文件内容,可以参考 log 和 client。在这里你可以定义配置文件的获取方式,比如配置中心,本地文件读取。

log 地址:github.com/dubbogo/dub…

client 地址:github.com/dubbogo/dub…

  • 在 config.Load() 之前设置配置,例如:

func main() {

hessian.RegisterPOJO(&User{})

providerConfig := config.ProviderConfig{}

yaml.Unmarshal([]byte(providerConfigStr), &providerConfig)

config.SetProviderConfig(providerConfig)

defaultServerConfig := dubbo.GetDefaultServerConfig()

dubbo.SetServerConfig(defaultServerConfig)

logger.SetLoggerLevel(“warn”) // info,warn

config.Load()

select {

}

}

复制代码

2)客户端自定义配置文件

============

  • var consumerConfigStr = xxxxx// 配置文件内容,可以参考 log 和 clien。在这里你可以定义配置文件的获取方式,比如配置中心,本地文件读取。

  • 在 config.Load() 之前设置配置,例如:

func main() {

p := config.ConsumerConfig{}

yaml.Unmarshal([]byte(consumerConfigStr), &p)

config.SetConsumerConfig§

defaultClientConfig := dubbo.GetDefaultClientConfig()

dubbo.SetClientConf(defaultClientConfig)

logger.SetLoggerLevel(“warn”) // info,warn

config.Load()

user := &User{}

err := userProvider.GetUser(context.TODO(), []interface{}{“A001”}, user)

if err != nil {

log.Print(err)

return

}

log.Print(user)

}

复制代码

Server 端

========

服务暴露过程涉及到多次原始 rpcService 的封装、暴露,网上其他文章的图感觉太过笼统,在此,简要地绘制了一个用户定义服务的数据流图:

1. 加载配置

========

1)框架初始化

=======

在加载配置之前,框架提供了很多已定义好的协议、工厂等组件,都会在对应模块 init 函数内注册到 extension 模块上,以供接下来配置文件中进行选用。

其中重要的有:

  • 默认函数代理工厂:common/proxy/proxy_factory/default.go

func init() {

extension.SetProxyFactory(“default”, NewDefaultProxyFactory)

}

复制代码

它的作用是将原始 rpc-service 进行封装,形成 proxy_invoker,更易于实现远程 call 调用,详情可见其 invoke 函数。

  • 注册中心注册协议

registry/protocol/protocol.go

func init() {

extension.SetProtocol(“registry”, GetProtocol)

}

复制代码

它负责将 invoker 暴露给对应注册中心,比如 zk 注册中心。

  • zookeeper 注册协议:registry/zookeeper/zookeeper.go

func init() {

extension.SetRegistry(“zookeeper”, newZkRegistry)

}

复制代码

它合并了 base_resiger,负责在服务暴露过程中,将服务注册在 zookeeper 注册器上,从而为调用者提供调用方法。

  • dubbo 传输协议:protocol/dubbo/dubbo.go

func init() {

extension.SetProtocol(DUBBO, GetProtocol)

}

复制代码

它负责监听对应端口,将具体的服务暴露,并启动对应的事件 handler,将远程调用的 event 事件传递到 invoker 内部,调用本地 invoker 并获得执行结果返回。

  • filter 包装调用链协议:protocol/protocolwrapper/protocol_filter_wrapper.go

func init() {

extension.SetProtocol(FILTER, GetProtocol)

}

复制代码

它负责在服务暴露过程中,将代理 invoker 打包,通过配置好的 filter 形成调用链,并交付给 dubbo 协议进行暴露。

上述提前注册好的框架已实现的组件,在整个服务暴露调用链中都会用到,会根据配置取其所需。

2)配置文件

======

服务端需要的重要配置有三个字段:services、protocols、registries。

profiles/dev/server.yml:

registries :

“demoZk”:

protocol: “zookeeper”

timeout : “3s”

address: “127.0.0.1:2181”

services:

“UserProvider”:

可以指定多个registry,使用逗号隔开;不指定默认向所有注册中心注册

registry: “demoZk”

protocol : “dubbo”

相当于dubbo.xml中的interface

interface : “com.ikurento.user.UserProvider”

loadbalance: “random”

warmup: “100”

cluster: “failover”

methods:

  • name: “GetUser”

retries: 1

loadbalance: “random”

protocols:

“dubbo”:

name: “dubbo”

port: 20000

复制代码

其中 service 指定了要暴露的 rpc-service 名("UserProvider)、暴露的协议名(“dubbo”)、注册的协议名(“demoZk”)、暴露的服务所处的 interface、负载均衡策略、集群失败策略及调用的方法等等。

其中,中间服务的协议名需要和 registries 下的 mapkey 对应,暴露的协议名需要和 protocols 下的 mapkey 对应。

可以看到上述例子中,使用了 dubbo 作为暴露协议,使用了 zookeeper 作为中间注册协议,并且给定了端口。如果 zk 需要设置用户名和密码,也可以在配置中写好。

3)配置文件的读入和检查

============

config/config_loader.go:: Load()

在上述 example 的 main 函数中,有 config.Load() 函数的直接调用,该函数执行细节如下:

// Load Dubbo Init

func Load() {

// init router

initRouter()

// init the global event dispatcher

extension.SetAndInitGlobalDispatcher(GetBaseConfig().EventDispatcherType)

// start the metadata report if config set

if err := startMetadataReport(GetApplicationConfig().MetadataType, GetBaseConfig().MetadataReportConfig); err != nil {

logger.Errorf(“Provider starts metadata report error, and the error is {%#v}”, err)

return

}

// reference config

loadConsumerConfig()

// service config

loadProviderConfig()

// init the shutdown callback

GracefulShutdownInit()

}

复制代码

在本文中,我们重点关心 loadConsumerConfig() 和 loadProviderConfig() 两个函数。

对于 provider 端,可以看到 loadProviderConfig() 函数代码如下:

前半部分是配置的读入和检查,进入 for 循环后,是单个 service 的暴露起始点。

前面提到,在配置文件中已经写好了要暴露的 service 的种种信息,比如服务名、interface 名、method 名等等。在图中 for 循环内,会将所有 service 的服务依次实现。

for 循环的第一行,根据 key 调用 GetProviderService 函数,拿到注册的 rpcService 实例,这里对应上述提到的 init 函数中,用户手动注册的自己实现的 rpc-service 实例:

这个对象也就成为了 for 循环中的 rpcService 变量,将这个对象注册通过 Implement 函数写到 sys(ServiceConfig 类型)上,设置好 sys 的 key 和协议组,最终调用了 sys 的 Export 方法。

此处对应流程图的部分:

至此,框架配置结构体已经拿到了所有 service 有关的配置,以及用户定义好的 rpc-service 实例,它触发了 Export 方法,旨在将自己的实力暴露出去。这是 Export 调用链的起始点。

2. 原始 service 封装入 proxy_invoker

=================================

config/service_config.go :: Export()

接下来进入 ServiceConfig.Export() 函数.

这个函数进行了一些细碎的操作,比如为不同的协议分配随机端口,如果指定了多个中心注册协议,则会将服务通过多个中心注册协议的 registryProtocol 暴露出去,我们只关心对于一个注册协议是如何操作的。还有一些操作比如生成调用 url 和注册 url,用于为暴露做准备。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
sys 的 key 和协议组,最终调用了 sys 的 Export 方法。

此处对应流程图的部分:

至此,框架配置结构体已经拿到了所有 service 有关的配置,以及用户定义好的 rpc-service 实例,它触发了 Export 方法,旨在将自己的实力暴露出去。这是 Export 调用链的起始点。

2. 原始 service 封装入 proxy_invoker

=================================

config/service_config.go :: Export()

接下来进入 ServiceConfig.Export() 函数.

这个函数进行了一些细碎的操作,比如为不同的协议分配随机端口,如果指定了多个中心注册协议,则会将服务通过多个中心注册协议的 registryProtocol 暴露出去,我们只关心对于一个注册协议是如何操作的。还有一些操作比如生成调用 url 和注册 url,用于为暴露做准备。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-1reaOYSt-1713711950210)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值