rpc wmi 服务不可用_golang 基础(5) RPC

![golang_real.jpg](https://upload-images.jianshu.io/upload_images/8207483-659e64f6f71b221d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

微服务已经 hot 了一段时间,自己作为 web 开发人员当然也不由自主想研究研究微服务,不过微服务的整个知识体系过于庞大,要掌握的概念和技术太多,一时有点吃不消。个人为了生计又没有大块时间去搞。不过还是难舍微服务,最近学习了 go 语言,想一块一块地吃掉微服务,先从 go 和容器入手。

我们知道微服务之间是需要相互调用和通讯的,一般会采用 RPC 来实现不同语言不用服务间的调用和通讯。那么我就先从 RPC 入手来学习微服务。

RPC 框架的特点

所谓的特点就是他能够满足那些需求,RPC 框架要实现以上目的需要满足以下需求

  • - 序列化(GOB)语言单一

  • - 上下文管理(超时控制)

  • - 拦截器(鉴权、统计和限流)

  • - 跨语言

  • - 服务注册

Call ID:由于客户端和服务端运行在不同进程,为了让客户端和服务端都了解调用了哪个函数,需要在两端维护一个函数到Call ID 的映射表,客户端根据表获取函数的 Call ID,发起请求,服务端根据 Call ID 来执行对应的函数,返回值给客户端。

序列化和反序列化:在本地调用时候函数是从栈中获取参数运行函数。而远程调用时候,如果需要在不同语言间相互调用函数,需要将参数进行序列化然后以字节流方式传递给服务端,服务端在反序列化来得到参数

网络传输:客户端和服务端间的调用往往是通过网络完成。只要能传递数据就行,与协议无关,可以使用 TCP 或 UDP。gRcp 使用的 HTTP2 。

什么是 RPC  

>  RPC是指**远程过程**调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

为什么需要 RPC 呢

因为 RPC 是分布式系统中不同节点间流行的通讯方式,在互联网时代,RPC 和 IPC 一样成为不可或缺的基础构建。在 Go 语言的标准库也提供了简单的 RPC 的实现。

RPC 在服务间调用流程

ddfaae033dda50668a5a681924babb76.png

我们通过上面图来看,这个流程比较清晰,也不难理解。

1. 调用客户端句柄,执行传送参数

2. 调用本地系统内核发送网络消息

3. 消息传送至远程机器

4. 服务器句柄得到消息并取得参数

5. 执行远程过程

6. 执行过程将结果返回给服务器句柄

7. 服务器句柄返回结果,调用远程系统内核

8. 消息传回本地主机

9. 客户句柄由内核接收消息

10. 客户接收句柄返回的数据

有了上面理论基础,我们基于理论来实现。

type RPCService struct{}

创建一个 RPCService 服务,随后将其进行注册

func (s *RPCService) Hello(request string, reply *string) error{  *reply = "Hello " + request  return nil}

- 函数必须是外部可以访问函数,函数名需要首字母大写

- 函数需要有两个参数

  1. 第一个参数接收的参数

  2. 第二个参数是返回给客户端的参数,而且需要是指针类型

- 函数还需要有一个 error 返回值  

rpc.RegisterName("RPCService",new(RPCService))

注册rpc服务

listener, err := net.Listen("tcp",":1234")

创建 tcp 服务端口号为 1234 用于 rpc 服务。

    conn, err := listener.Accept()    if err != nil{        log.Fatal("Accept error:", err)    }    rpc.ServeConn(conn)

服务端完整代码

package mainimport(  // "fmt"  "log"  "net"  "net/rpc")type RPCService struct{}func (s *RPCService) Hello(request string, reply *string) error{  *reply = "Hello " + request  return nil}func main() {  rpc.RegisterName("RPCService",new(RPCService))  listener, err := net.Listen("tcp",":1234")  if err != nil{    log.Fatal("ListenTCP error:",err)  }  conn, err := listener.Accept()  if err != nil{    log.Fatal("Accept error:", err)  }  rpc.ServeConn(conn)}

客户端代码

client, err := rpc.Dial("tcp","localhost:1234")

客户端调用 RPC 服务,然后通过client.Call调用具体的RPC方法。

err = client.Call("RPCService.Hello","World",&reply)

在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数。

package mainimport(  "fmt"  "log"  "net/rpc")func main() {  client, err := rpc.Dial("tcp","localhost:1234")  if err != nil{    log.Fatal("dialing:", err)  }  var reply string  err = client.Call("RPCService.Hello","World",&reply)  if err != nil{    log.Fatal("call Hello method of RPCService:",err)  }  fmt.Println(reply)}

我们先后启动服务端和客户端就可以看到下面效果

Hello World

下面更加贴近实际来写一个基于 HTTP 的 RPC 服务,服务提供两个数四则运算。

rpcService := new(RPCService)    rpc.Register(rpcService)    rpc.HandleHTTP()

这里的注册方式略有不同,但是大同小异相信大家一看就懂。

服务端完整代码

package mainimport(  "errors"  "fmt"  "net/http"  "net/rpc")type Args struct{  A, B int}type Quotient struct{  Quo, Rem int}type RPCService intfunc (t *RPCService) Add(args *Args, reply *int) error{  *reply = args.A - args.B  return nil}func (t *RPCService) Multiply(args *Args, reply *int) error{  *reply = args.A * args.B  return nil}func (t *RPCService) Divide(args *Args, quo *Quotient) error{  if args.B == 0{    return errors.New("divide by zero")  }  quo.Quo = args.A / args.B  quo.Rem = args.A % args.B  return nil}func main() {  rpcService := new(RPCService)  rpc.Register(rpcService)  rpc.HandleHTTP()    err := http.ListenAndServe(":1234",nil)  if err != nil{    fmt.Println(err.Error())  }}

客户端代码

```go

package mainimport(  "fmt"  "log"  "net/rpc"  "os")type Args struct{  A, B int}type Quotient struct{  Quo, Rem int}func main()  {  if len(os.Args) != 2{    fmt.Println("Usage: ", os.Args[0],"server")    os.Exit(1)  }  serverAddress := os.Args[1]  client, err := rpc.DialHTTP("tcp",serverAddress + ":1234")  if err != nil {    log.Fatal("dialing: ", err)  }  args := Args{17, 8}  var reply int  err = client.Call("RPCService.Add",args, &reply)  if err != nil{    log.Fatal("RPCService error: ", err)  }  fmt.Printf("RPCService: %d + %d = %d\n", args.A, args.B, &reply)  var quot Quotient  err = client.Call("RPCService.Divide",args, &quot)  if err != nil{    log.Fatal("RPCService error: ",err)  }  fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)}

```

```

RPCService: 17 + 8 = 824634312296RPCService: 17/8=2 remainder 1

```

其实在实际开发中我们还需要对其进行改造,例如让 rpc 请求可以获得一个 context 对象,其中包含用户信息等,然后可以对 rpc 进行超时处理。

#### JSONRPC

Go语言内置的 RPC 框架已经支持在 Http 协议上提供 RPC 服务。但是 Http 服务内置采用了 GOB 协议。编码不是 JSON 编码,不方便其他语言调用。不过 go 提供 JsonRPC 的 RPC 服务支持,我们来看一看怎么用代码实现。

服务端代码

```go

package mainimport(  "errors"  "fmt"  "net"  "net/rpc"  "net/rpc/jsonrpc"  // "os")type Args struct{  A, B int}type Quotient struct{  Quo, Rem int}type RPCService intfunc (t *RPCService) Multiply(args *Args, reply *int) error{  *reply = args.A * args.B  return nil}func (t *RPCService) Divide(args *Args, quo *Quotient) error{  if args.B == 0{    return errors.New("divide by zero")  }  quo.Quo = args.A / args.B  quo.Rem = args.A % args.B  return nil}func main() {  rpcService := new(RPCService)  rpc.Register(rpcService)  tcpAddr, err := net.ResolveTCPAddr("tcp",":1234")  checkError(err)    listener, err := net.ListenTCP("tcp",tcpAddr)  checkError(err)  for{    conn, err := listener.Accept()    if err != nil{      continue    }    jsonrpc.ServeConn(conn)  }}func checkError(err error){  if err != nil{    fmt.Println("Fatal error ", err.Error())  }}

客户端代码

package mainimport(  "fmt"  "log"  "net/rpc/jsonrpc"  "os")type Args struct{  A, B int}type Quotient struct{  Quo, Rem int}func main()  {  if len(os.Args) != 2{    fmt.Println("Usage: ", os.Args[0],"server")    log.Fatal(1)  }  serverAddress := os.Args[1]  client, err := jsonrpc.Dial("tcp",serverAddress + ":1234")  if err != nil {    log.Fatal("dialing: ", err)  }  args := Args{17, 8}  var quot Quotient  err = client.Call("RPCService.Divide",args, &quot)  if err != nil{    log.Fatal("RPCService error: ",err)  }  fmt.Printf("RPCService: %d/%d=%d remainder %d\n",args.A, args.B, quot.Quo,quot.Rem)}

对原有RPC进行改造,添加一些辅助的功能

上下文管理

通过net/rpcj进行改造,可以添加一个上下文对象

func (t *RPCService) Add(c contex.Context,args *Args, reply *int) error{  *reply = args.A - args.B  return nil}

我们看一看这里有一个 Context 接口有些方法需要在 RPC 中进行实现,这里我们用 TCP 需要记录时间,通过 Context 可以获得一些额外而必要信息。

type Context interface{    ctx.Context    Now() time.Time    Seq() uint64    ServiceMethod() string    User() string}type rpcCtx struct{}func NewContext(c ctx.Context, u, m string, s uint64) Context{    }

 超时控制

进行超时,通过超时控制避免过多请求阻塞,通过超时服务取消 rpc 请求。

func (t *RPCService) Timeout(c contex.Context, args *RPCTimeout, reply *struct{}) error{  log.Printf("Timeout: timeout=%s seq=%d\n",args.T, c.Seq())  time.Sleep(args.T)  return nil}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值