Go 开发者的涨薪通道:自主开发 PaaS 平台核心功能【高清】

发布于:2022-10-21 ⋅ 阅读:(484) ⋅ 点赞:(0)

Google推出Go是源于几个大佬的灵光一现,当时一推出就受到极大的关注,虽中间也经历了几年的低谷期,但十几年过去了,Go也能和其他语言分庭抗礼,甚至还收获了一大批忠实拥护者。目前国内很多公司都在很多业务中使用golang,特别是一些国内头部互联网企业,比如:字节、腾讯、滴滴等,所以学习golang是一个不错的方向。

Go 开发者的涨薪通道:自主开发 PaaS 平台核心功能KE

下栽课呈:lexuecode.com/5927.html

3ad9fb937262ed449e246840dfc4b815.jpeg


Go 开发者的涨薪通道:自主开发 PaaS 平台核心功能

4.4 gRPC入门
gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。本节将讲述gRPC的简单用法。



4.4.1 gRPC技术栈
Go语言的gRPC技术栈如图4-1所示:







图4-1 gRPC技术栈



最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现,然后在HTTP/2协议之上又构建了针对Go语言的gRPC核心库。应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信。



4.4.2 gRPC入门
如果从Protobuf的角度看,gRPC只不过是一个针对service接口生成代码的生成器。我们在本章的第二节中手工实现了一个简单的Protobuf代码生成器插件,只不过当时生成的代码是适配标准库的RPC框架的。现在我们将学习gRPC的用法。



创建hello.proto文件,定义HelloService接口:

syntax = "proto3";

package main;

message String {
string value = 1;
}

service HelloService {
rpc Hello (String) returns (String);
}
使用protoc-gen-go内置的gRPC插件生成gRPC代码:

$ protoc --go_out=plugins=grpc:. hello.proto
gRPC插件会为服务端和客户端生成不同的接口:

type HelloServiceServer interface {
Hello(context.Context, *String) (*String, error)
}

type HelloServiceClient interface {
Hello(context.Context, *String, ...grpc.CallOption) (*String, error)
}
gRPC通过context.Context参数,为每个方法调用提供了上下文支持。客户端在调用方法的时候,可以通过可选的grpc.CallOption类型的参数提供额外的上下文信息。



基于服务端的HelloServiceServer接口可以重新实现HelloService服务:

type HelloServiceImpl struct{}

func (p *HelloServiceImpl) Hello(
ctx context.Context, args *String,
) (*String, error) {
reply := &String{Value: "hello:" + args.GetValue()}
return reply, nil
}
gRPC服务的启动流程和标准库的RPC服务启动流程类似:

func main() {
grpcServer := grpc.NewServer()
RegisterHelloServiceServer(grpcServer, new(HelloServiceImpl))

lis, err := net.Listen("tcp", ":1234")
if err != nil {
log.Fatal(err)
}
grpcServer.Serve(lis)
}
首先是通过grpc.NewServer()构造一个gRPC服务对象,然后通过gRPC插件生成的RegisterHelloServiceServer函数注册我们实现的HelloServiceImpl服务。然后通过grpcServer.Serve(lis)在一个监听端口上提供gRPC服务。



然后就可以通过客户端链接gRPC服务了:

func main() {
conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
if err != nil {
log.Fatal(err)
}
defer conn.Close()

client := NewHelloServiceClient(conn)
reply, err := client.Hello(context.Background(), &String{Value: "hello"})
if err != nil {
log.Fatal(err)
}
fmt.Println(reply.GetValue())
}
其中grpc.Dial负责和gRPC服务建立链接,然后NewHelloServiceClient函数基于已经建立的链接构造HelloServiceClient对象。返回的client其实是一个HelloServiceClient接口对象,通过接口定义的方法就可以调用服务端对应的gRPC服务提供的方法。



gRPC和标准库的RPC框架有一个区别,gRPC生成的接口并不支持异步调用。不过我们可以在多个Goroutine之间安全地共享gRPC底层的HTTP/2链接,因此可以通过在另一个Goroutine阻塞调用的方式模拟异步调用。


Go 开发者的涨薪通道:自主开发 PaaS 平台核心功能


作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算,参见第 15 章)和程序的并发。程序可以在不同的处理器和计算机上同时执行不同的代码段。Go 语言为构建并发程序的基本代码块是 协程 (goroutine) 与通道 (channel)。他们需要语言,编译器,和runtime的支持。Go 语言提供的垃圾回收器对并发编程至关重要。

不要通过共享内存来通信,而通过通信来共享内存。

通信强制协作。

14.2.2 通信操作符 <-
这个操作符直观的标示了数据的传输:信息按照箭头的方向流动。

流向通道(发送)

ch <- int1 表示:用通道 ch 发送变量 int1(双目运算符,中缀 = 发送)

从通道流出(接收),三种方式:

int2 = <- ch 表示:变量 int2 从通道 ch(一元运算的前缀操作符,前缀 = 接收)接收数据(获取新值);假设 int2 已经声明过了,如果没有的话可以写成:int2 := <- ch。

<- ch 可以单独调用获取通道的(下一个)值,当前值会被丢弃,但是可以用来验证,所以以下代码是合法的:

if <- ch != 1000{
...
}
操作符 <- 也被用来发送和接收,Go 尽管不必要,为了可读性,通道的命名通常以 ch 开头或者包含 chan。通道的发送和接收操作都是自动的:它们通常一气呵成。下面的示例展示了通信操作。

示例 14.2-goroutine2.go

package main

import (
"fmt"
"time"
)

func main() {
ch := make(chan string)

go sendData(ch)
go getData(ch)

time.Sleep(1e9)
}

func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
}

func getData(ch chan string) {
var input string
// time.Sleep(1e9)
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
输出:

Washington Tripoli London Beijing Tokio
main() 函数中启动了两个协程:sendData() 通过通道 ch 发送了 5 个字符串,getData() 按顺序接收它们并打印出来。

如果 2 个协程需要通信,你必须给他们同一个通道作为参数才行。

尝试一下如果注释掉 time.Sleep(1e9) 会如何。

我们发现协程之间的同步非常重要:

main() 等待了 1 秒让两个协程完成,如果不这样,sendData() 就没有机会输出。
getData() 使用了无限循环:它随着 sendData() 的发送完成和 ch 变空也结束了。
如果我们移除一个或所有 go 关键字,程序无法运行,Go 运行时会抛出 panic:
---- Error run E:/Go/Goboek/code examples/chapter 14/goroutine2.exe with code Crashed ---- Program exited with code -2147483645: panic: all goroutines are asleep-deadlock!
为什么会这样?运行时会检查所有的协程(也许只有一个是这种情况)是否在等待(可以读取或者写入某个通道),意味着程序无法处理。这是死锁(deadlock)形式,运行时可以检测到这种情况。

注意:不要使用打印状态来表明通道的发送和接收顺序:由于打印状态和通道实际发生读写的时间延迟会导致和真实发生的顺序不同。

练习 14.4:解释一下为什么如果在函数 getData() 的一开始插入 time.Sleep(1e9),不会出现错误但也没有输出呢。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到