go跨语言调用 go跨平台编译

微服务跨语言调用(摘?。?/h2>微服务架构已成为目前互联网架构的趋势 , 关于微服务的讨论,几乎占据了各种技术大会的绝大多数版面 。国内使用最多的服务治理框架非阿里开源的 dubbo 莫属 , 千米网也选择了 dubbo 作为微服务治理框架 。另一方面 , 和大多数互联网公司一样,千米的开发语言是多样的,大多数后端业务由 java 支撑,而每个业务线有各自开发语言的选择权,便出现了 nodejs , python,go 多语言调用的问题 。
跨语言调用是一个很大的话题,也是一个很有挑战的技术活,目前业界经常被提及的解决方案有如下几种 , 不妨拿出来老生常谈一番:
当我们再聊跨语言调用时我们在聊什么?纵观上述几个较为通用,成熟的解决方案 , 可以得出结论:解决跨语言调用的思路无非是两种:
如果一个新型的团队面临技术选型,我认为上述的方案都可以纳入参考,可考虑到遗留系统的兼容性问题
旧系统的迁移成本
这也关键的选型因素 。我们做出的第一个尝试,便是在 RPC 协议上下功夫 。
通用协议的跨语言支持
springmvc的美好时代
springmvc
springmvc
在没有实现真正的跨语言调用之前,想要实现“跨语言”大多数方案是使用 http 协议做一层转换,最常见的手段莫过于借助 springmvc 提供的 controller/restController,间接调用 dubbo provider 。这种方案的优势和劣势显而易见
通用协议的支持
事实上,大多数服务治理框架都支持多种协议 , dubbo 框架除默认的 dubbo 协议之外,还有当当网扩展的 rest协议和千米网扩展的 json-rpc 协议可供选择 。这两者都是通用的跨语言协议 。
rest 协议为满足 JAX-RS 2.0 标准规范,在开发过程中引入了 @Path,@POST,@GET 等注解,习惯于编写传统 rpc 接口的人可能不太习惯 rest 风格的 rpc 接口 。一方面这样会影响开发体验,另一方面 , 独树一帜的接口风格使得它与其他协议不太兼容,旧接口的共生和迁移都无法实现 。如果没有遗留系统 , rest 协议无疑是跨语言方案最简易的实现 , 绝大多数语言支持 rest 协议 。
和 rest 协议类似,json-rpc 的实现也是文本序列化http 协议 。dubbox 在 restful 接口上已经做出了尝试 , 但是 rest 架构和 dubbo 原有的 rpc 架构是有区别的,rest 架构需要对资源(Resources)进行定义,需要用到 http 协议的基本操作 GET、POST、PUT、DELETE 。在我们看来 , restful 更合适互联网系统之间的调用,而 rpc 更适合一个系统内的调用 。使用 json-rpc 协议使得旧接口得以兼顾,开发习惯仍旧保留,同时获得了跨语言的能力 。
千米网在早期实践中采用了 json-rpc 作为 dubbo 的跨语言协议实现,并开源了基于 json-rpc 协议下的 python 客户端 dubbo-client-py 和 node 客户端 dubbo-node-client , 使用 python 和 nodejs 的小伙伴可以借助于它们直接调用 dubbo-provider-java 提供的 rpc 服务 。系统中大多数 java 服务之间的互相调用还是以 dubbo 协议为主,考虑到新旧协议的适配,在不影响原有服务的基础上 , 我们配置了双协议 。
dubbo 协议主要支持 java 间的相互调用,适配老接口;json-rpc 协议主要支持异构语言的调用 。
定制协议的跨语言支持
微服务框架所谓的协议(protocol)可以简单理解为:报文格式和序列化方案 。服务治理框架一般都提供了众多的协议配置项供使用者选择,除去上述两种通用协议,还存在一些定制化的协议 , 如 dubbo 框架的默认协议:dubbo 协议以及 motan 框架提供的跨语言协议:motan2 。
motan2协议的跨语言支持
motan2
motan2
motan2 协议被设计用来满足跨语言的需求主要体现在两个细节中—MetaData 和 motan-go 。在最初的 motan 协议中,协议报文仅由 Header Body 组成,这样导致 path , param , group 等存储在 Body 中的数据需要反序列得到,这对异构语言来说是很不友好的,所以在 motan2 中修改了协议的组成;weibo 开源了 motan-go , motan-php ,motan-openresty ,并借助于 motan-go 充当了 agent 这一翻译官的角色,使用 simple 序列化方案来序列化协议报文的 Body 部分(simple 序列化是一种较弱的序列化方案) 。
agent
agent
仔细揣摩下可以发现这么做和双协议的配置区别并不是大,只不过这里的 agent 是隐式存在的 , 与主服务共生 。明显的区别在于 agent 方案中异构语言并不直接交互 。
dubbo协议的跨语言支持
dubbo 协议设计之初只考虑到了常规的 rpc 调用场景,它并不是为跨语言而设计,但跨语言支持从来不是只有支持、不支持两种选择,而是要按难易程度来划分 。是的,dubbo 协议的跨语言调用可能并不好做 , 但并非无法实现 。千米网便实现了这一点 , nodejs 构建的前端业务是异构语言的主战?。?最终实现了 dubbo2.js,打通了 nodejs 和原生 dubbo 协议 。作为本文第二部分的核心内容,重点介绍下我们使用 dubbo2.js 干了什么事 。
Dubbo协议报文格式
dubbo协议
dubbo协议
dubbo协议报文消息头详解:
magic:类似java字节码文件里的魔数 , 用来判断是不是 dubbo 协议的数据包 。魔数是常量 0xdabb
flag:标志位, 一共8个地址位 。低四位用来表示消息体数据用的序列化工具的类型(默认 hessian) , 高四位中,第一位为 1 表示是 request 请求,第二位为 1 表示双向传输(即有返回 response),第三位为 1 表示是心跳 ping 事件 。
status:状态位, 设置请求响应状态 , dubbo 定义了一些响应的类型 。具体类型见com.alibaba.dubbo.remoting.exchange.Response
invoke id:消息 id, long 类型 。每一个请求的唯一识别 id(由于采用异步通讯的方式,用来把请求 request 和返回的 response 对应上)
body length:消息体 body 长度, int 类型,即记录 Body Content 有多少个字节
body content:请求参数,响应参数的抽象序列化之后存储于此 。
协议报文最终都会变成字节 , 使用 tcp 传输 , 任何语言只要支持网络模块 , 有类似 Socket 之类的封装,那么通信就不成问题 。那,跨语言难在哪儿?以其他语言调用 java 来说,主要有两个难点:
ps:dubbo 协议通讯demo()
Go语言的跨平台能力到底有多强?看完你就知道了对比于其go跨语言调用他语言的程序go跨语言调用,Go语言的跨平台能力是真的强,拿.Net和JAVA来说吧,.Net在.Net core出现之前是不能跨平台的,只能在windows上编译运行,即使是.net core出现以后,跨平台的程序也是相当的麻烦 。而java虽然一直都可以跨平台,但是运行JAVA程序的机器上也必须要有JAVA程序运行环境JRE 。而相对于Go程序,跨平台就简单的多了,只需要在编译指定目标程序运行的架构和环境即可编译出指定操作系统和架构的程序 。
以上是指定了go的环境变量后执行的go build命令进行目标程序的构建 , 这种方式会一直生效的,如果不让go跨语言调用他一直生效,可以在构建的时候临时指定环境变量,下面以window的环境为例,来介绍临时指定环境变量的方式构建可以在Linux环境下运行的可执行程序:
可以根据不同的架构和操作系统将其编写为不同的.bat的可执行文件放置在程序的根目录,Linux的和MAC的也一样编写成脚本文件放置在程序的根目录,这样在构建的时候就不用再敲命令了,直接运行脚本就可以了 。
Java程序编译打包后为war包或者是java包,必须执行java -jar 命令或者将其放置到tomcat的指定目录下,运行tomcat程序 。而Go语言编写的程序最终为可执行的文件(window下编译出的是.exe的可执行文件) , 只需要将其赋予可执行的权限就可以直接运行了 。
构建JAVA程序的镜像需要指定java的基础镜像,否则就需要在镜像中安装java的运行环境了 , 下面展示的是构建的一个JAVA程序的镜像 , 构建出来镜像的体积相对比较大
而Go程序制作出的镜像就不需要安装任何的依赖环境,因为他在打包的时候就已经将依赖的包一块打包到一起了
拿着这个镜像就可以到处运行了 。
通过对比go跨语言调用我们可以发现 , 如果没有之前的技术和业务的积累,重新开发一个新的项目,使用go去开发无疑是最容易上手的,所以现在很多公司都使用go进行开发,也逐渐将其他语言的项目逐步的用go语言进行改造 。其实用什么语言不重要,合适的才重要,开发项目在选择语言的时候也会综合多方面来考虑选择合适的语言和架构,毕竟很多公司都不是搞研究的,都需要项目来赚钱,所以开发的速度、客户的满意度、项目交付的时间才是驱动公司技术的主要因素 。
我们个人也应该不断完善自己的技术栈,不应该太依靠某种语言 , 最重要的还是自己的架构思想和底层架构知识,只有掌握了这些才能够不被 社会 和公司“优化” 。
一学就会,手把手教你用Go语言调用智能合约智能合约调用是实现一个 DApp 的关键,一个完整的 DApp 包括前端、后端、智能合约及区块 链系统 , 智能合约的调用是连接区块链与前后端的关键 。
go跨语言调用我们先来go跨语言调用了解一下智能合约调用的基础原理 。智能合约运行在以太坊节点的 EVM 中 。因此要 想调用合约必须要访问某个节点 。
以后端程序为例,后端服务若想连接节点有两种可能 , 一种是双 方在同一主机 , 此时后端连接节点可以采用 本地 IPC(Inter-Process Communication , 进 程间通信)机制,也可以采用 RPC(Remote Procedure Call,远程过程调用)机制;另 一种情况是双方不在同一台主机,此时只能采用 RPC 机制进行通信 。
提到 RPC, 读者应该对 Geth 启动参数有点印象,Geth 启动时可以选择开启 RPC 服务,对应的 默认服务端口是 8545 。。
接着,我们来了解一下智能合约运行的过程 。
智能合约的运行过程是后端服务连接某节点,将 智能合约的调用(交易)发送给节点,节点在验证了交易的合法性后进行全网广播,被矿工打包到 区块中代表此交易得到确认,至此交易才算完成 。
就像数据库一样,每个区块链平台都会提供主流 开发语言的 SDK(Software Development Kit,软件开发工具包),由于 Geth 本身就是用 Go 语言 编写的,因此若想使用 Go 语言连接节点、发交易,直接在工程内导入 go-ethereum(Geth 源码) 包就可以了,剩下的问题就是流程和 API 的事情了 。
总结一下,智能合约被调用的两个关键点是节点和 SDK 。
由于 IPC 要求后端与节点必须在同一主机,所以很多时候开发者都会采用 RPC 模式 。除了 RPC,以太坊也为开发者提供了 json- rpc 接口,本文就不展开讨论了 。
接下来介绍如何使用 Go 语言,借助 go-ethereum 源码库来实现智能合约的调用 。这是有固定 步骤的,我们先来说一下总体步骤,以下面的合约为例 。
步骤 01:编译合约,获取合约 ABI(Application Binary Interface,应用二进制接口) 。单击【ABI】按钮拷贝合约 ABI 信息,将其粘贴到文件 calldemo.abi 中(可使用 Go 语言IDE 创建该文件,文件名可自定义,后缀最好使用 abi) 。
最好能将 calldemo.abi 单独保存在一个目录下 , 输入“ls”命令只能看到 calldemo.abi 文件,参 考效果如下:
步骤 02:获得合约地址 。注意要将合约部署到 Geth 节点 。因此 Environment 选择为 Web3 Provider 。
在【Environment】选项框中选择“Web3 Provider”,然后单击【Deploy】按钮 。
部署后,获得合约地址为:0xa09209c28AEf59a4653b905792a9a910E78E7407 。
步骤 03:利用 abigen 工具(Geth 工具包内的可执行程序)编译智能合约为 Go 代码 。abigen 工具的作用是将 abi 文件转换为 Go 代码,命令如下:
其中各参数的含义如下 。(1)abi:是指定传入的 abi 文件 。(2)type:是指定输出文件中的基本结构类型 。(3)pkg:指定输出文件 package 名称 。(4)out:指定输出文件名 。执行后,将在代码目录下看到 funcdemo.go 文件,读者可以打开该文件欣赏一下,注意不要修改它 。
步骤 04:创建 main.go,填入如下代码 。注意代码中 HexToAddress 函数内要传入该合约部署后的地址,此地址在步骤 01 中获得 。
步骤 04:设置 go mod,以便工程自动识别 。
前面有所提及,若要使用 Go 语言调用智能合约,需要下载 go-ethereum 工程,可以使用下面 的指令:
该指令会自动将 go-ethereum 下载到“$GOPATH/src/github.com/ethereum/go-ethereum”,这样还算 不错 。不过,Go 语言自 1.11 版本后,增加了 module 管理工程的模式 。只要设置好了 go mod,下载 依赖工程的事情就不必关心了 。
接下来设置 module 生效和 GOPROXY,命令如下:
在项目工程内,执行初始化,calldemo 可以自定义名称 。
步骤 05:运行代码 。执行代码,将看到下面的效果,以及最终输出的 2020 。
上述输出信息中,可以看到 Go 语言会自动下载依赖文件,这就是 go mod 的神奇之处 。看到 2020,相信读者也知道运行结果是正确的了 。
怎么样使用Go语言中函数的参数传递与调用按值传递函数参数,是拷贝参数的实际值到函数的形式参数的方法调用 。在这种情况下,参数在函数内变化对参数不会有影响 。
默认情况下,Go编程语言使用调用通过值的方法来传递参数 。在一般情况下,这意味着,在函数内码不能改变用来调用所述函数的参数 。考虑函数swap()的定义如下 。
代码如下:
/* function definition to swap the values */
func swap(int x, int y) int {
var temp int
temp = x /* save the value of x */
x = y/* put y into x */
y = temp /* put temp into y */
return temp;
}
现在,让我们通过使实际值作为在以下示例调用函数swap():
代码如下:
package main
import "fmt"
func main() {
/* local variable definition */
var a int = 100
var b int = 200
fmt.Printf("Before swap, value of a : %d\n", a )
fmt.Printf("Before swap, value of b : %d\n", b )
/* calling a function to swap the values */
swap(a, b)
fmt.Printf("After swap, value of a : %d\n", a )
fmt.Printf("After swap, value of b : %d\n", b )
}
func swap(x, y int) int {
var temp int
temp = x /* save the value of x */
x = y/* put y into x */
y = temp /* put temp into y */
return temp;
}
让我们把上面的代码放在一个C文件 , 编译并执行它,它会产生以下结果:
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :100
After swap, value of b :200
这表明,参数值没有被改变,虽然它们已经在函数内部改变 。
通过传递函数参数,即是拷贝参数的地址到形式参数的参考方法调用 。在函数内部,地址是访问调用中使用的实际参数 。这意味着 , 对参数的更改会影响传递的参数 。
要通过引用传递的值,参数的指针被传递给函数就像任何其他的值 。所以,相应的,需要声明函数的参数为指针类型如下面的函数swap(),它的交换两个整型变量的值指向它的参数 。
代码如下:
/* function definition to swap the values */
func swap(x *int, y *int) {
var temp int
temp = *x/* save the value at address x */
*x = *y/* put y into x */
*y = temp/* put temp into y */
}
现在,让我们调用函数swap()通过引用作为在下面的示例中传递数值:
代码如下:
package main
import "fmt"
func main() {
/* local variable definition */
var a int = 100
var b int= 200
fmt.Printf("Before swap, value of a : %d\n", a )
fmt.Printf("Before swap, value of b : %d\n", b )
/* calling a function to swap the values.
* a indicates pointer to a ie. address of variable a and
* b indicates pointer to b ie. address of variable b.
*/
swap(a, b)
fmt.Printf("After swap, value of a : %d\n", a )
fmt.Printf("After swap, value of b : %d\n", b )
}
func swap(x *int, y *int) {
var temp int
temp = *x/* save the value at address x */
*x = *y/* put y into x */
*y = temp/* put temp into y */
}
让我们把上面的代码放在一个C文件,编译并执行它 , 它会产生以下结果:
Before swap, value of a :100
Before swap, value of b :200
After swap, value of a :200
After swap, value of b :100
这表明变化的功能以及不同于通过值调用的外部体现的改变不能反映函数之外 。
go语言如何调用c函数直接嵌入c源代码到go代码里面
package main
/*
#include stdio.h
void myhello(int i) {
printf("Hello C: %d\n", i);
}
*/
import "C"
import "fmt"
func main() {
C.myhello(C.int(12))
fmt.Println("Hello Go");
}
需要注意的是C代码必须放在注释里面
import "C"语句和前面的C代码之间不能有空行
运行结果
$ go build main.go./main
Hello C: 12
Hello Go
分开c代码到单独文件
嵌在一起代码结构不是很好看 , 很多人包括我,还是喜欢把两个分开,放在不同的文件里面,显得干净,go源文件里面是go的源代码,c源文件里面是c的源代码 。
$ ls
hello.chello.hmain.go
$ cat hello.h
void hello(int);
$ cat hello.c
#include stdio.h
void hello(int i) {
printf("Hello C: %d\n", i);
}
$ cat main.go
package main
// #include "hello.h"
import "C"
import "fmt"
func main() {
C.hello(C.int(12))
fmt.Println("Hello Go");
}
编译运行
$ go build./main
Hello C: 12
Hello Go
编译成库文件
如果c文件比较多,最好还是能够编译成一个独立的库文件,然后go来调用库 。
$ find mylib main
mylib
mylib/hello.h
mylib/hello.c
main
main/main.go
编译库文件
$ cd mylib
# gcc -fPIC -shared -o libhello.so hello.c
编译go程序
$ cd main
$ cat main.go
package main
// #cgo CFLAGS: -I../mylib
【go跨语言调用 go跨平台编译】// #cgo LDFLAGS: -L../mylib -lhello
// #include "hello.h"
import "C"
import "fmt"
func main() {
C.hello(C.int(12))
fmt.Println("Hello Go");
}
$ go build main.go
运行
$ export LD_LIBRARY_PATH=../mylib
$ ./main
Hello C: 12
Hello Go
在我们的例子中,库文件是编译成动态库的,main程序链接的时候也是采用的动态库
$ ldd main
linux-vdso.so.1 =(0x00007fffc7968000)
libhello.so = ../mylib/libhello.so (0x00007f513684c000)
libpthread.so.0 = /lib64/libpthread.so.0 (0x00007f5136614000)
libc.so.6 = /lib64/libc.so.6 (0x00007f5136253000)
/lib64/ld-linux-x86-64.so.2 (0x000055d819227000)
理论上讲也是可以编译成整个一静态链接的可执行程序 , 由于我的机器上缺少静态链接的系统库,比如libc.a,所以只能编译成动态链接 。
go跨语言调用的介绍就聊到这里吧 , 感谢你花时间阅读本站内容,更多关于go跨平台编译、go跨语言调用的信息别忘了在本站进行查找喔 。

    推荐阅读