微服务是现在大多数新项目会采用的架构。服务端提供服务,客户端调用服务端的功能,就像调用一个函数一样。微服务的更新升级,只要接口没有变化,客户端完全可以无影响地升级到新功能。这就是微服务的第一个体验。
随之而来的,就是每个微服务都可以用任意计算机语言实现,方便了不同技术团队的协作。
而整个软件系统的构成,也不同于以往的实现方式了。这样的构成,更灵活和强大。当然随着服务数量的增加,也增加了服务的管理难度。
go语言的RPC服务实现起来非常简单。可以利用tcp或http协议来传递数据。
我们通过一个计算矩形面积和周长的微服务实例来学习rpc服务的编写方法。
首先,实现server端(server.go),定义一个矩形
// 声明矩形结构体
type Rect struct {
}
再定义矩形的参数
// 声明参数结构体
type Params struct {
// 宽,高
Width, Height int
}
因为计算面积和周长的RPC服务是远程调用,那么在服务端计算,通过获取和改变内存变量的值实现调用远程微服务像本地函数一样的效果。所以,传入的参数除了宽和高以外,还有一个供承载计算结果的整形指针。如果发生错误,就返回error,无错误的时候,return nil 就可以了。
// 计算矩形面积
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 计算周长
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
在主函数中,首先要注册服务
// 注册服务
rect := new(Rect)
rpc.Register(rect)
然后绑定服务到http协议
// 服务绑定http协议
rpc.HandleHTTP()
最后服务器开始监听服务,等待客户端来调用。这个服务我们使用8080端口。
// 监听服务,等待客户端调用(求面积和周长的方法)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
至此,微服务server端代码已经写完。
下面开始编写微服务client端代码(client.go)。
client端要和server端拥有相同的参数结构体。即
// 参数
type Params struct {
Width, Height int
}
client远程连接server,这里采用tcp协议,还需要提前知道server的地址和通讯端口。以下代码建立远程连接,并得到一个实例rp。
// 连接远程的RPC服务
rp, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
if err != nil {
log.Println(err)
}
为了获取计算结果和提供计算参数,声明2个变量。一个保存结果,一个保存参数。
// 结果
ret := 0
// 参数
p := Params{50 ,100}
调用远程服务(面积)
// 1. 求面积
err2 := rp.Call("Rect.Area", p, &ret)
if err2 != nil {
log.Println(err2)
}else{
fmt.Println("面积:", ret)
}
调用远程服务(周长)
// 2. 求周长
err3 := rp.Call("Rect.Perimeter", p, &ret)
if err3 != nil {
log.Println(err3)
}else {
fmt.Println("周长:", ret)
}
运行客户端client.go,由于此时server未启动,所以客户端会得到一个连接被拒绝的结果。
2021/03/21 20:23:48 dial tcp 127.0.0.1:8080: connectex: 由于目标计算机积极拒绝,无法连接。
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x10 pc=0x109e944]
...
启动server端,再次运行client端,得到运行结果
面积: 5000
周长: 300
server.go 完整代码
/**
* Package: rpcServer
* Description: This package is rpcServer example
* Author: Jian Junbo
* Email: junbojian@qq.com
* Date: 2021/3/21 17:31
* Copyright ©2021 Jian Junbo & Shanxi Xiyue Mancang Technology Co., Ltd. All rights reserved.
**/
package main
import (
"log"
"net/http"
"net/rpc"
)
// 声明矩形结构体
type Rect struct {
}
// 声明参数结构体
type Params struct {
// 宽,高
Width, Height int
}
// 计算矩形面积
func (r *Rect) Area(p Params, ret *int) error {
*ret = p.Width * p.Height
return nil
}
// 计算周长
func (r *Rect) Perimeter(p Params, ret *int) error {
*ret = (p.Width + p.Height) * 2
return nil
}
func main() {
// 注册服务
rect := new(Rect)
rpc.Register(rect)
// 服务绑定http协议
rpc.HandleHTTP()
// 监听服务,等待客户端调用(求面积和周长的方法)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}
client.go 完整代码
/**
* Package: rpcClient
* Description: This package is rpcClient example.
* Author: Jian Junbo
* Email: junbojian@qq.com
* Date: 2021/3/21 18:31
* Copyright ©2021 Jian Junbo & Shanxi Xiyue Mancang Technology Co., Ltd. All rights reserved.
**/
package main
import (
"fmt"
"log"
"net/rpc"
)
// 参数
type Params struct {
Width, Height int
}
// 主函数调用服务
func main() {
// 连接远程的RPC服务
rp, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
if err != nil {
log.Println(err)
}
// * 调用服务方法
// 结果
ret := 0
// 参数
p := Params{50 ,100}
// 1. 求面积
err2 := rp.Call("Rect.Area", p, &ret)
if err2 != nil {
log.Println(err2)
}else{
fmt.Println("面积:", ret)
}
// 2. 求周长
err3 := rp.Call("Rect.Perimeter", p, &ret)
if err3 != nil {
log.Println(err3)
}else {
fmt.Println("周长:", ret)
}
}