Go最全Dubbo-go 源码笔记(一)Server 端开启服务过程(2),2024年最新2024Golang开发面试题及答案

img
img
img

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

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

如果你需要这些资料,可以戳这里获取

当拿到一款框架之后,一种不错的源码阅读方式大致如下:从运行最基础的 helloworld demo 源码开始 —> 再查看配置文件 —> 开启各种依赖服务(比如zk、consul) —> 开启服务端 —> 再到通过 client 调用服务端 —> 打印完整请求日志和回报。调用成功之后,再根据框架的设计模型,从配置文件解析开始,自顶向下递阅读整个框架的调用栈。

对于 C/S 模式的 rpc 请求来说,整个调用栈被拆成了 client 和 server 两部分,所以可以分别从 server 端的配置文件解析阅读到 server 端的监听启动,从 client 端的配置文件解析阅读到一次 invoker Call 调用。这样一次完整请求就明晰了起来。

运行官网提供的 helloworld-demo

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

官方 demo 相关链接:github.com/dubbogo/dub…

1. dubbo-go 2.7 版本 QuickStart

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

1)开启一个 go-server 服务

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

  • 将仓库 clone 到本地

$ git clone github.com/dubbogo/dub…

  • 进入 dubbo 目录

$ cd dubbo-samples/golang/helloworld/dubbo

进入目录后可看到四个文件夹,分别支持 go 和 java 的 client 以及 server,我们尝试运行一个 go 的 server。进入 app 的文件夹内,可以看到里面保存了 go 文件。

$ cd go-server/app

  • sample 文件结构

可以在 go-server 里面看到三个文件夹:app、assembly、profiles。

其中 app 文件夹下保存 go 源码,assembly 文件夹下保存可选的针对特定环境的 build 脚本,profiles 下保存配置文件。对于 dubbo-go 框架,配置文件非常重要,没有文件将导致服务无法启动。

  • 设置指向配置文件的环境变量

由于 dubbo-go 框架依赖配置文件启动,让框架定位到配置文件的方式就是通过环境变量来找。对于 server 端需要两个必须配置的环境变量:CONF_PROVIDER_FILE_PATH、APP_LOG_CONF_FILE,分别应该指向服务端配置文件、日志配置文件。

在 sample 里面,我们可以使用 dev 环境,即 profiles/dev/log.yml 和 profiles/dev/server.yml 两个文件。在 app/ 下,通过命令行中指定好这两个文件:

$ export CONF_PROVIDER_FILE_PATH=“…/profiles/dev/server.yml”

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

  • 设置 go 代理并运行服务

$ go run .

如果提示 timeout,则需要设置 goproxy 代理。

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

在运行 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() 函数的直接调用,该函数执行细节如下:

img
img

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

“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() 函数的直接调用,该函数执行细节如下:

[外链图片转存中…(img-gYgqgUYc-1715502805034)]
[外链图片转存中…(img-uQFFaVUB-1715502805034)]

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值