Dubbo-go 源码笔记(一)Server 端开启服务过程

简介: 随着微服务架构的流行,许多高性能 rpc 框架应运而生,由阿里开源的 dubbo 框架 go 语言版本的 dubbo-go 也成为了众多开发者不错的选择。本文将介绍 dubbo-go 框架的基本使用方法,以及从 export 调用链的角度进行 server 端源码导读,希望能引导读者进一步认识这款框架。

1.png

作者 | 李志信

dubbo-go 源码:https://github.com/apache/dubbo-go

导读:随着微服务架构的流行,许多高性能 rpc 框架应运而生,由阿里开源的 dubbo 框架 go 语言版本的 dubbo-go 也成为了众多开发者不错的选择。本文将介绍 dubbo-go 框架的基本使用方法,以及从 export 调用链的角度进行 server 端源码导读,希望能引导读者进一步认识这款框架。下周将发表本文的姊妹篇:《从 client 端源码导读 dubbo-go 框架》。

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

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

运行官网提供的 helloworld-demo

官方 demo 相关链接:https://github.com/dubbogo/dubbo-samples/tree/master/golang/helloworld/dubbo

1. dubbo-go 2.7 版本 QuickStart

1)开启一个 go-server 服务

  • 将仓库 clone 到本地

$ git clone https://github.com/dubbogo/dubbo-samples.git

  • 进入 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 提供的目录如下:

2.png

  • 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   stringName stringAge  int32Time 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 官方文档提供的设计图:

3.png

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 地址:https://github.com/dubbogo/dubbo-samples/blob/master/golang/helloworld/dubbo/go-client/profiles/release/log.yml  

client 地址:https://github.com/dubbogo/dubbo-samples/blob/master/golang/helloworld/dubbo/go-client/profiles/release/client.yml

  • 在 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,warnconfig.Load()select {}
}

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

  • var consumerConfigStr = xxxxx// 配置文件内容,可以参考 log 和 clien。在这里你可以定义配置文件的获取方式,比如配置中心,本地文件读取。
  • 在 config.Load() 之前设置配置,例如:
func main() {p := config.ConsumerConfig{}yaml.Unmarshal([]byte(consumerConfigStr), &p)config.SetConsumerConfig(p)defaultClientConfig := dubbo.GetDefaultClientConfig()dubbo.SetClientConf(defaultClientConfig)logger.SetLoggerLevel("warn") // info,warnconfig.Load()user := &User{}err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)if err != nil {log.Print(err)return}log.Print(user)
}

Server 端

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

4.png

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中的interfaceinterface : "com.ikurento.user.UserProvider"loadbalance: "random"warmup: "100"cluster: "failover"methods:- name: "GetUser"retries: 1loadbalance: "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 routerinitRouter()// init the global event dispatcherextension.SetAndInitGlobalDispatcher(GetBaseConfig().EventDispatcherType)// start the metadata report if config setif err := startMetadataReport(GetApplicationConfig().MetadataType, GetBaseConfig().MetadataReportConfig); err != nil {logger.Errorf("Provider starts metadata report error, and the error is {%#v}", err)return}// reference configloadConsumerConfig()// service configloadProviderConfig()// init the shutdown callbackGracefulShutdownInit()
}

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

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

5.png

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

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

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

6.png

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

此处对应流程图的部分:

7.png

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

2. 原始 service 封装入 proxy_invoker

config/service_config.go :: Export()

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

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

1)首先通过配置生成对应 registryUrl 和 serviceUrl

8.png

registryUrl 是用来向中心注册组件发起注册请求的,对于 zookeeper 的话,会传入其 ip 和端口号,以及附加的用户名密码等信息。

这个 regUrl 目前只存有注册(zk)相关信息,后续会补写入 ServiceIvk,即服务调用相关信息,里面包含了方法名,参数等...

2)对于一个注册协议,将传入的 rpc-service 实例注册在 common.ServiceMap

9.png

这个 Register 函数将服务实例注册了两次,一次是以 Interface 为 key 写入接口服务组内,一次是以 interface 和 proto 为 key 写入特定的一个唯一的服务。

后续会从 common.Map 里面取出来这个实例。

3)获取默认代理工厂,将实例封装入代理 invoker

// 拿到一个proxyInvoker,这个invoker的url是传入的regUrl,这个地方将上面注册的service实例封装成了invoker
// 这个GetProxyFactory返回的默认是common/proxy/proxy_factory/default.go
// 这个默认工厂调用GetInvoker获得默认的proxyInvoker,保存了当前注册url
invoker := extension.GetProxyFactory(providerConfig.ProxyFactory).GetInvoker(*regUrl)
// 暴露出来 生成exporter,开启tcp监听
// 这里就该跳到registry/protocol/protocol.go registryProtocol 调用的Export,将当前proxyInvoker导出
exporter = c.cacheProtocol.Export(invoker)

这一步的 GetProxyFactory("default") 方法获取默认代理工厂,通过传入上述构造的 regUrl,将 url 封装入代理 invoker。

可以进入 common/proxy/proxy_factory/default.go::ProxyInvoker.Invoke() 函数里,看到对于 common.Map 取用为 svc 的部分,以及关于 svc 对应 Method 的实际调用 Call 的函数如下:

10.png

到这里,上面 GetInvoker(*regUrl) 返回的 invoker 即为 proxy_invoker,它封装好了用户定义的 rpc_service,并将具体的调用逻辑封装入了 Invoke 函数内。

为什么使用 Proxy_invoker 来调用?

通过这个 proxy_invoke 调用用户的功能函数,调用方式将更加抽象化,可以在代码中看到,通过 ins 和 outs 来定义入参和出参,将整个调用逻辑抽象化为 invocation 结构体,而将具体的函数名的选择、参数向下传递和 reflect 反射过程封装在 invoke 函数内,这样的设计更有利于之后远程调用。个人认为这是 dubbo Invoke 调用链的设计思想。

至此,实现了图中对应的部分:

11.png

3. registry 协议在 zkRegistry 上暴露上面的 proxy_invoker

上面,我们执行到了 exporter = c.cacheProtocol.Export(invoker)。

这里的 cacheProtocol 为一层缓存设计,对应到原始的 demo 上,这里是默认实现好的 registryProtocol。

registry/protocol/protocol.go:: Export()

这个函数内构造了多个 EventListener,非常有 java 的设计感。

我们只关心服务暴露的过程,先忽略这些监听器。

1)获取注册 url 和服务 url

12.png

2)获取注册中心实例 zkRegistry

13.png

一层缓存操作,如果 cache 没有需要从 common 里面重新拿 zkRegistry。

3)zkRegistry 调用 Registry 方法,在 zookeeper 上注册 dubboPath

上述拿到了具体的 zkRegistry 实例,该实例的定义在:registry/zookeeper/registry.go。

14.png

该结构体组合了 registry.BaseRegistry 结构,base 结构定义了注册器基础的功能函数,比如 Registry、Subscribe 等,但在这些默认定义的函数内部,还是会调用 facade 层(zkRegistry 层)的具体实现函数,这一设计模型能在保证已有功能函数不需要重复定义的同时,引入外层函数的实现,类似于结构体继承却又复用了代码。这一设计模式值得学习。

我们查看上述 registry/protocol/protocol.go:: Export() 函数,直接调用了:

// 1. 通过zk注册器,调用Register()函数,将已有@root@rawurl注册到zk上err := reg.Register(*registeredProviderUrl)

将已有 RegistryUrl 注册到了 zkRegistry 上。

这一步调用了 baseRegistry 的 Register 函数,进而调用 zkRegister 的 DoRegister 函数,进而调用:

15.png

在这个函数里,将对应 root 创造一个新的节点。

16.png

并且写入具体 node 信息,node 为 url 经过 encode 的结果,包含了服务端的调用方式。

这部分的代码较为复杂,具体可以看 baseRegistry 的 processURL() 函数:http://t.tb.cn/6Xje4bijnsIDNaSmyPc4Ot。

至此,将服务端调用 url 注册到了 zookeeper 上,而客户端如果想获取到这个 url,只需要传入特定的 dubboPath,向 zk 请求即可。目前 client 是可以获取到访问方式了,但服务端的特定服务还没有启动,还没有开启特定协议端口的监听,这也是 registry/protocol/protocol.go:: Export() 函数接下来要做的事情。

4)proxy_invoker 封装入 wrapped_invoker,得到 filter 调用链

    // invoker封装入warppedInvokerwrappedInvoker := newWrappedInvoker(invoker, providerUrl)// 经过为invoker增加filter调用链,再使用dubbo协议Export,开启service并且返回了Exporter 。// export_1cachedExporter = extension.GetProtocol(protocolwrapper.FILTER).Export(wrappedInvoker)

新建一个 WrappedInvoker,用于之后链式调用。

拿到提前实现并注册好的 ProtocolFilterWrapper,调用 Export 方法,进一步暴露。

protocol/protocolwrapped/protocol_filter_wrapper.go:Export()

17.png

protocol/protocolwrapped/protocol_filter_wrapper.go:buildInvokerChain

18.png

可见,根据配置的内容,通过链式调用的构造,将 proxy_invoker 层层包裹在调用链的最底部,最终返回一个调用链 invoker。

对应图中部分:

19.png

至此,我们已经拿到 filter 调用链,期待将这个 chain 暴露到特定端口,用于相应请求事件。

5)通过 dubbo 协议暴露 wrapped_invoker

protocol/protocolwrapped/protocol_filter_wrapper.go:Export()

// 通过dubbo协议Export  dubbo_protocol调用的 export_2return pfw.protocol.Export(invoker)

回到上述 Export 函数的最后一行,调用了 dubboProtocol 的 Export 方法,将上述 chain 真正暴露。

该 Export 方法的具体实现在:protocol/dubbo/dubbo_protocol.go: Export()。

20.png

这一函数做了两个事情:构造触发器、启动服务。

  • 将传入的 Invoker 调用 chain 进一步封装,封装成一个 exporter,再将这个 export 放入 map 保存。注意!这里把 exporter 放入了 SetExporterMap中,在下面服务启动的时候,会以注册事件监听器的形式将这个 exporter 取出!
  • 调用 dubboProtocol 的 openServer 方法,开启一个针对特定端口的监听。

21.png

如上图所示,一个 Session 被传入,开启对应端口的事件监听。

至此构造出了 exporter,完成图中部分:

22.png

4. 注册触发动作

上述只是启动了服务,但还没有看到触发事件的细节,点进上面的 s.newSession 可以看到,dubbo 协议为一个 getty 的 session 默认使用了如下配置:

23.png

其中很重要的一个配置是 EventListener,传入的是 dubboServer 的默认 rpcHandler。

protocol/dubbo/listener.go:OnMessage()

rpcHandler 有一个实现好的 OnMessage 函数,根据 getty 的 API,当 client 调用该端口时,会触发 OnMessage。

// OnMessage notified when RPC server session got any message in connection
func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) {

这一函数实现了在 getty session 接收到 rpc 调用后的一系列处理:

  • 传入包的解析

24.png

  • 根据请求包构造请求 url

25.png

  • 拿到对应请求 key,找到要被调用的 exporter

26.png

  • 拿到对应的 Invoker

27.png

  • 构造 invocation

28.png

  • 调用

29.png

  • 返回

30.png

整个被调过程一气呵成。实现了从 getty.Session 的调用事件,到经过层层封装的 invoker 的调用。

至此,一次 rpc 调用得以正确返回。

小结

  • 关于 Invoker 的层层封装

能把一次调用抽象成一次 invoke;能把一个协议抽象成针对 invoke 的封装;能把针对一次 invoke 所做出的特定改变封装到 invoke 函数内部,可以降低模块之间的耦合性。层层封装逻辑更加清晰。

  • 关于 URL 的抽象

关于 dubbo 的统一化请求对象 URL 的极度抽象是之前没有见过的... 个人认为这样封装能保证请求参数列表的简化和一致。但在开发的过程中,滥用极度抽象的接口可能造成... debug 的困难?以及不知道哪些字段是当前已经封装好的,哪些字段是无用的。

  • 关于协议的理解

之前理解的协议还是太过具体化了,而关于 dubbo-go 对于 dubboProtocol 的协议,我认为是基于 getty 的进一步封装,它定义了客户端和服务端,对于 getty 的 session 应该有哪些特定的操作,从而保证主调和被调的协议一致性,而这种保证也是一种协议的体现,是由 dubbo 协议来规范的。

如果你有任何疑问,欢迎钉钉扫码加入交流群:钉钉群号 23331795!

作者简介

李志信 (GitHubID LaurenceLiZhixin),中山大学软件工程专业在校学生,擅长使用 Java/Go 语言,专注于云原生和微服务等技术方向。

 

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/514782.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql怎么多重查询_mysql基于值的多重查询

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里云数据库专家保驾护航,为用户…

华为在中国建立其全球最大的网络安全透明中心

2021年6月9日,华为最大的网络安全透明中心今天在中国东莞正式启用,来自GSMA、阿联酋、印尼的监管机构及英国标准协会、SUSE等机构代表出席并在活动上发言。借此机会,华为发布了《华为产品安全基线》白皮书,首次将产品安全需求基线…

浅析云控平台画面传输的视频流方案

简介: 本文将小结本次云控平台画面传输的视频流方案。 背景 ARC(高德车机云控平台)是一个基于车载设备业务深度定制的云控平台,通过该平台我们能够实现远程使用不同类型的车载设备。为了让远程使用者像在本地一样使用车载设备&am…

解读云原生基础设施

简介: 云原生是云计算领域的热点之一。就像 “一千个人眼里有一千个哈姆雷特”,大家对"云原生"的定义也见仁见智。本文将介绍云原生应用架构和生命周期管理的进化方向。 作者 | 易立 阿里云资深技术专家 导读:云原生是云计算领域的…

mysql al32utf8_Oracle 11g更改字符集AL32UTF8为ZHS16GBK

Oracle 9i更改字符集AL32UTF8为ZHS16GBKSQLgt; conn /as sysdba SQLgt; shutdown immediate; SQLgt; startup mount SQLgt; A首页 → 数据库技术背景:阅读新闻Oracle 11g更改字符集AL32UTF8为ZHS16GBK[日期:2011-04-26]来源:Linux社区作者&am…

共筑全场景智慧生态,华为HMS全球应用创新大赛火热开启

6月10日,2021华为HMS全球应用创新大赛(Apps UP)正式启动。此次大赛以“HMS Innovate For All”为主题,激励全球开发者集成华为HMS Core开放能力开发创新应用,打造全场景数字创新体验,为全球消费者带来全场景…

2020-11-06

一、背景介绍 (一)流平台通用框架 目前流平台通用的架构一般来说包括消息队列、计算引擎和存储三部分,通用架构如下图所示。客户端或者 web 的 log 日志会被采集到消息队列;计算引擎实时计算消息队列的数据;实时计算…

移动端堆栈关键行定位的新思路

简介: 崩溃堆栈是我们日常应用问题排查中的重要辅助手段,在移动开发上也不例外,为了支持用户在堆栈上的快速定位,我们面临一个看似比较简单问题:高亮崩溃中的关键行, 辅助用户快速定位问题。 阿里云 云原生应用研发平台…

mysql exists in join_子查询、left join、 exists 替代 not in

如果要实现一张表有而另外一张表没有的数据时,我们通常会这么写:SELECT id FROM user WHERE id NOT IN (SELECT id FROM student)not in不会走索引, 可以用exists替代SELECT id FROM user WHERE NOT exists (SELECT id FROM student WHERE user.id stud…

华云数据升级发布“信创云基座“ 用“全芯全栈”支持“信创强国”

2021年6月10日,北京——2021年是我国“十四五”规划的开局之年,也是我国“加快数字发展 建设数字中国”的关键之年。值此历史交汇的关键点,云计算、大数据、人工智能、物联网、工业互联网、区块链等重点产业将对国家数字经济发展起到巨大推动…

最IN的云原生架构,阿里云 Serverless 事件总线 EventBridge 重磅发布

简介: Serverless 是云计算下一个10年的主要形态,通过大量端到端的整合和云服务的集成,能极大地提高研发效率。了解阿里云 Serverless 产品家族的最新进展,包括函数计算FC、Serverless应用引擎SAE和 Serverless事件总线EventBridg…

智能技术改变淘宝,阿里巴巴首次详解核心商业AI体系

简介: 双11背后的万亿人次商品需求:淘宝创造新一代智能科技,淘宝成为超大规模智能APP,前沿科技重塑双11人货场。 图:淘宝APP已成为超大规模智能APP “淘宝APP已成为超大规模智能APP。”阿里巴巴集团资深副总裁周靖人11…

wow mysql dbc_DBC中悲观锁介绍附案例详解

DBC中悲观锁介绍附案例详解了解下DBC中悲观锁:代码如下:BDUtils 工具类:package JDBC;import java.sql.*;public class BDUtils {private BDUtils() {}static {try {Class.forName("com.mysql.jdbc.Driver");} catch (ClassNotFoun…

融云任杰:强互动,RTC下一个“爆点”场景|拟合

从无序中寻找踪迹,从眼前事探索未来。 2021 年正值黄金十年新开端,CSDN 以中立技术社区专业、客观的角度,深度探讨中国前沿 IT 技术演进,推出年度重磅企划栏目——「拟合」,通过对话企业技术高管大咖,跟踪报…

直面最大挑战双11 阿里数据中台为商家带来确定性保障

2020双11将成为史上最具科技含量的一届双11。 11月3日,在阿里巴巴双11技术沟通会上,阿里巴巴集团首席技术官程立公布了大规模运用于2020双11的十大前沿技术,既有基于数字技术的原生商业创新,也有引领时代的基础技术突破。 阿里巴巴…

mysql rpm包安装指定路径_安装rpm包时指定路径

1、安装rpm包可以指定路径,但是安装包时它可能执行一些内置的命令。如果手动指定路径,可能造成部分功能失效比如下面安装jdk的rpm包。默认安装后它会创建个软链接。下面就提示创建软链接失败了。但是不影响使用[rootdawn-cobbler-1-1 /]# mkdir /tools[r…

玩转ECS第6讲 | 弹性计算Region化部署和跨可用区容灾介绍

弹性计算Region化部署和跨可用区容灾本身是非常复杂的课题,本次分享由阿里云弹性计算架构负责人李钟(谢顿)为大家介绍如何选择Region,同时结合阿里云在Region化部署和跨可用区容灾的实践经验,分享多region部署场景中如…

为产业数字化赋能!施耐德电气数字产业示范园落户北京

无人车在工厂里按部就班输送货物,机械臂快速装备各零件,庞大的车间内,智能生产线只有寥寥的工人……小时候畅想过的智能工厂,在北京亦庄新成立的施耐德电气数字产业示范园里实现了。 新成立的示范园包括施耐德电气(中…

在大规模 Kubernetes 集群上实现高 SLO 的方法

简介: 随着 Kubernetes 集群规模和复杂性的增加,集群越来越难以保证高效率、低延迟的交付 pod。本文将分享蚂蚁金服在设计 SLO 架构和实现高 SLO 的方法和经验。 作者 | 蚂蚁金服技术专家 姚菁华;蚂蚁金服高级开发工程师 范康 导读&#xf…

TI Inside,情报协同的最佳实践

6月18日,以“新IN力 御万象”为主题的 TI Inside 威胁情报应用生态协同峰会在北京隆重召开。此次峰会吸引了数百位网络安全产业专家、龙头企业代表、威胁情报生态合作机构、分析机构以及权威媒体齐聚一堂,共同交流威胁情报的最佳应用实践,探讨…