【go语言学习笔记】05 Go 语言实战

news2024/11/26 10:24:59

文章目录

    • 一、 RESTful API 服务
      • 1. RESTful API 定义
        • 1.1 HTTP Method
        • 1.2 RESTful API 规范
      • 2. RESTful API 风格示例
      • 3. RESTful JSON API
      • 4. Gin 框架
        • 4.1 导入 Gin 框架
        • 4.2 使用 Gin 框架
          • 4.2.1 获取特定的用户(GET)
          • 4.2.2 新增一个用户(POST)
          • 4.2.3 删除一个用户(DELETE)
          • 4.2.4 修改一个用户的名字(PATCH)
    • 二、 RPC 跨平台服务
      • 1. 定义
      • 2. Go语言中的RPC
        • 2.1 服务端
        • 2.2 客户端
      • 3. 基于 HTTP 的RPC
      • 3. JSON RPC 跨平台通信
        • 3.1 基于 TCP 的 JSON RPC
        • 3.2 基于 HTTP的JSON RPC
    • 三、Go 语言的发展前景

一、 RESTful API 服务

在做项目开发的时候,要善于借助已经有的轮子,让自己的开发更有效率,也更容易实现。

1. RESTful API 定义

RESTful API 是一套规范,它可以规范如何对服务器上的资源进行操作。和 RESTful API 和密不可分的是 HTTP Method。

1.1 HTTP Method

HTTP Method最常见的就是POST和GET,其实最早在 HTTP 0.9 版本中,只有一个GET方法,该方法是一个幂等方法,用于获取服务器上的资源。

在 HTTP 1.0 版本中又增加了HEAD和POST方法,其中常用的是 POST 方法,一般用于给服务端提交一个资源,导致服务器的资源发生变化。

随着网络越来越复杂,在 HTTP1.1 版本的时候,支持的方法增加到了 9 个,新增的方法有 HEAD、OPTIONS、PUT、DELETE、TRACE、PATCH 和 CONNECT。下面是它们各自的作用:

  1. GET 方法可请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。
  2. HEAD 方法用于请求一个与 GET 请求的响应相同的响应,但没有响应体。
  3. POST 方法用于将实体提交到指定的资源,通常导致服务器上的状态变化或副作用。
  4. PUT 方法用于请求有效载荷替换目标资源的所有当前表示。
  5. DELETE 方法用于删除指定的资源。
  6. CONNECT 方法用于建立一个到由目标资源标识的服务器的隧道。
  7. OPTIONS 方法用于描述目标资源的通信选项。
  8. TRACE 方法用于沿着到目标资源的路径执行一个消息环回测试。
  9. PATCH 方法用于对资源应用部分修改。

HTTP 规范针对每个方法都给出了明确的定义,所以使用的时候也要尽可能地遵循这些定义,这样在开发中才可以更好地协作。

1.2 RESTful API 规范

RESTful API 规范就是基于 HTTP Method 规范对服务器资源的操作,同时规范了 URL 的样式和 HTTP Status Code。

在 RESTful API 中,使用的主要是以下五种 HTTP 方法:

  1. GET,表示读取服务器上的资源;
  2. POST,表示在服务器上创建资源;
  3. PUT,表示更新或者替换服务器上的资源;
  4. DELETE,表示删除服务器上的资源;
  5. PATCH,表示更新 / 修改资源的一部分。

以上 HTTP 方法在 RESTful API 规范中是一个操作,操作的就是服务器的资源,服务器的资源通过特定的 URL 表示。

(1)GET 方法的示例

HTTP GET https://www.flysnow.org/users
HTTP GET https://www.flysnow.org/users/123

上例中

  • 第一个表示获取所有用户的信息;
  • 第二个表示获取 ID 为 123 用户的信息。

(2) POST 方法的示例

HTTP POST https://www.flysnow.org/users

该示例表示创建一个用户,通过 POST 方法给服务器提供创建这个用户所需的全部信息。

这里 users 是个复数。

(3)PUT 方法的示例

HTTP PUT https://www.flysnow.org/users/123

该示例表示要更新 / 替换 ID 为 123 的这个用户,在更新的时候,会通过 PUT 方法提供更新这个用户需要的全部用户信息。这里 PUT 方法和 POST 方法不太一样的是,从 URL 上看,PUT 方法操作的是单个资源,比如这里 ID 为 123 的用户。

如果要更新一个用户的部分信息,使用 PATCH 方法更恰当。

(4)DELETE 方法的示例

HTTP DELETE https://www.flysnow.org/users/123

DELETE 方法的使用和 PUT 方法一样,也是操作单个资源,这里是删除 ID 为 123 的这个用户。

2. RESTful API 风格示例

Go 语言的一个很大的优势,就是可以很容易地开发出网络后台服务,而且性能快、效率高。Golang 提供了内置的 net/http 包处理 HTTP 请求,让开发者可以比较方便地开发一个 HTTP 服务。

一个简单的 HTTP 服务的 Go 语言实现代码如下所示:

func main() {
   http.HandleFunc("/users",handleUsers)
   http.ListenAndServe(":8080", nil)
}
func handleUsers(w http.ResponseWriter, r *http.Request){
   fmt.Fprintln(w,"ID:1,Name:张三")
   fmt.Fprintln(w,"ID:2,Name:李四")
   fmt.Fprintln(w,"ID:3,Name:王五")
}

这个示例运行后,在浏览器中输入 http://localhost:8080/users, 就可以看到如下内容信息:

ID:1,Name:张三
ID:2,Name:李四
ID:3,Name:王五

这并不是一个 RESTful API,因为使用者不仅可以通过 HTTP GET 方法获得所有的用户信息,还可以通过 POST、DELETE、PUT 等 HTTP 方法获得所有的用户信息,这显然不符合 RESTful API 的规范。

对以上示例进行修改,使它符合 RESTful API 的规范,修改后的示例代码如下所示:

func handleUsers(w http.ResponseWriter, r *http.Request){
   switch r.Method {
   case "GET":
      w.WriteHeader(http.StatusOK)
      fmt.Fprintln(w,"ID:1,Name:张三")
      fmt.Fprintln(w,"ID:2,Name:李四")
      fmt.Fprintln(w,"ID:3,Name:王五")
   default:
      w.WriteHeader(http.StatusNotFound)
      fmt.Fprintln(w,"not found")
   }
}

该示例修改了 handleUsers 函数,在该函数中增加了只在使用 GET 方法时,才获得所有用户的信息的判断,其他情况返回 not found。

3. RESTful JSON API

在项目中最常见的是使用 JSON 格式传输信息,也就是提供的 RESTful API 要返回 JSON 内容给使用者。

用上面的示例改造成可以返回 JSON 内容的方式,示例代码如下所示:

//数据源,类似MySQL中的数据
var users = []User{
   {ID: 1,Name: "张三"},
   {ID: 2,Name: "李四"},
   {ID: 3,Name: "王五"},
}
func handleUsers(w http.ResponseWriter, r *http.Request){
   switch r.Method {
   case "GET":
      users,err:=json.Marshal(users)
      if err!=nil {
         w.WriteHeader(http.StatusInternalServerError)
         fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")
      }else {
         w.WriteHeader(http.StatusOK)
         w.Write(users)
      }
   default:
      w.WriteHeader(http.StatusNotFound)
      fmt.Fprint(w,"{\"message\": \"not found\"}")
   }
}
//用户
type User struct {
   ID int
   Name string
}

从以上代码可以看到,这次的改造主要是新建了一个 User 结构体,并且使用 users 这个切片存储所有的用户,然后在 handleUsers 函数中把它转化为一个 JSON 数组返回。这样,就实现了基于 JSON 数据格式的 RESTful API。

运行这个示例,在浏览器中输入 http://localhost:8080/users,可以看到如下信息:

[{"ID":1,"Name":"张三"},{"ID":2,"Name":"李四"},{"ID":3,"Name":"王五"}]

4. Gin 框架

虽然 Go 语言自带的 net/http 包,可以比较容易地创建 HTTP 服务,但是它也有很多不足:

  • 不能单独地对请求方法(POST、GET 等)注册特定的处理函数;
  • 不支持 Path 变量参数;
  • 不能自动对 Path 进行校准;
  • 性能一般;
  • 扩展性不足;
  • ……

基于以上这些不足,出现了很多 Golang Web 框架,如 Mux,Gin、Fiber 等,其中使用最多是 Gin 框架。

4.1 导入 Gin 框架

Gin 框架是一个在 Github 上开源的 Web 框架,封装了很多 Web 开发需要的通用功能,并且性能也非常高,可以很容易地写出 RESTful API。

Gin 框架其实是一个模块,也就是 Go Mod,所以采用 Go Mod 的方法引入即可。

安装代码如下:

$ go get -u github.com/gin-gonic/gin

导入代码如下:

import "github.com/gin-gonic/gin"

4.2 使用 Gin 框架

用 Gin 框架重写上面的示例,修改的代码如下所示:

func main() {
   r:=gin.Default()
   r.GET("/users", listUser)
   r.Run(":8080")
}
func listUser(c *gin.Context)  {
   c.JSON(200,users)
}

相比 net/http 包,Gin 框架的代码非常简单,通过它的 GET 方法就可以创建一个只处理 HTTP GET 方法的服务,而且输出 JSON 格式的数据也非常简单,使用 c.JSON 方法即可。

最后通过 Run 方法启动 HTTP 服务,监听在 8080 端口。运行这个示例,在浏览器中输入 http://localhost:8080/users,看到的信息和通过 net/http 包实现的效果是一样的。

4.2.1 获取特定的用户(GET)

如果要获得特定用户的信息,需要使用的是 GET 方法,并且 URL 格式如下所示:

http://localhost:8080/users/2

以上示例中的 2 是用户的 ID,也就是通过 ID 来获取特定的用户。

通过 Gin 框架 Path 路径参数可以实现这个功能,示例代码如下:

func main() {
   //省略没有改动的代码
   r.GET("/users/:id", getUser)
}
func getUser(c *gin.Context) {
   id := c.Param("id")
   var user User
   found := false
   //类似于数据库的SQL查询
   for _, u := range users {
      if strings.EqualFold(id, strconv.Itoa(u.ID)) {
         user = u
         found = true
         break
      }
   }
   if found {
      c.JSON(200, user)
   } else {
      c.JSON(404, gin.H{
         "message": "用户不存在",
      })
   }
}

在 Gin 框架中,路径中使用冒号表示 Path 路径参数,比如示例中的 :id,然后在 getUser 函数中可以通过 c.Param(“id”) 获取需要查询用户的 ID 值。

Param 方法的参数要和 Path 路径参数中的一致,比如示例中都是 ID。

运行这个示例,通过浏览器访问 http://localhost:8080/users/2,就可以获得 ID 为 2 的用户,输出信息如下所示:

{"ID":2,"Name":"李四"}

假如我们访问一个不存在的 ID,得到的结果如下所示:

curl http://localhost:8080/users/99
{"message":"用户不存在"}%
4.2.2 新增一个用户(POST)

根据 RESTful API 规范,实现新增使用的是 POST 方法,并且 URL 的格式为 http://localhost:8080/users ,向这个 URL 发送数据,就可以新增一个用户,然后返回创建的用户信息。

使用 Gin 框架实现新增一个用户,示例代码如下:

func main() {
   //省略没有改动的代码
   r.POST("/users", createUser)
}
func createUser(c *gin.Context) {
   name := c.DefaultPostForm("name", "")
   if name != "" {
      u := User{ID: len(users) + 1, Name: name}
      users = append(users, u)
      c.JSON(http.StatusCreated,u)
   } else {
      c.JSON(http.StatusOK, gin.H{
         "message": "请输入用户名称",
      })
   }
}

以上新增用户的主要逻辑是获取客户端上传的 name 值,然后生成一个 User 用户,最后把它存储到 users 集合中,达到新增用户的目的。

在这个示例中,使用 POST 方法来新增用户,所以只能通过 POST 方法才能新增用户成功。

运行这个示例,通过如下命令发送一个新增用户的请求,查看结果:

curl -X POST -d 'name=小明' http://localhost:8080/users
{"ID":4,"Name":"小明"}
4.2.3 删除一个用户(DELETE)

删除一个用户比较简单,它的 API 格式和获取一个用户一样,但是 HTTP 方法换成了DELETE。示例代码如下所示:

func main() {
   //省略没有修改的代码
   r.DELETE("/users/:id", deleteUser)
}
func deleteUser(c *gin.Context) {
   id := c.Param("id")
   i := -1
   //类似于数据库的SQL查询
   for index, u := range users {
      if strings.EqualFold(id, strconv.Itoa(u.ID)) {
         i = index
         break
      }
   }
   if i >= 0 {
      users = append(users[:i], users[i+1:]...)
      c.JSON(http.StatusNoContent, "")
   } else {
      c.JSON(http.StatusNotFound, gin.H{
         "message": "用户不存在",
      })
   }
}

这个示例的逻辑就是注册 DELETE 方法,达到删除用户的目的。删除用户的逻辑是通过ID 查询:

  • 如果可以找到要删除的用户,记录索引并跳出循环,然后根据索引删除该用户;
  • 如果找不到要删除的用户,则返回 404。
4.2.4 修改一个用户的名字(PATCH)

修改和删除一个用户非常像,实现代码如下所示:

func main() {
   //省略没有修改的代码
   r.PATCH("/users/:id",updateUserName)
}
func updateUserName(c *gin.Context) {
   id := c.Param("id")
   i := -1
   //类似于数据库的SQL查询
   for index, u := range users {
      if strings.EqualFold(id, strconv.Itoa(u.ID)) {
         i = index
         break
      }
   }
   if i >= 0 {
      users[i].Name = c.DefaultPostForm("name",users[i].Name)
      c.JSON(http.StatusOK, users[i])
   } else {
      c.JSON(http.StatusNotFound, gin.H{
         "message": "用户不存在",
      })
   }
}

逻辑和删除的差不多的,只不过这里使用的是 PATCH方法。

二、 RPC 跨平台服务

1. 定义

RPC,也就是远程过程调用,是分布式系统中不同节点调用的方式(进程间通信),属于 C/S 模式。RPC 由客户端发起,调用服务端的方法进行通信,然后服务端把结果返回给客户端。

RPC的核心有两个:通信协议序列化。在 HTTP 2 之前,一般采用自定义 TCP 协议的方式进行通信,HTTP 2 出来后,也有采用该协议的,比如流行的gRPC。

序列化反序列化是一种把传输内容编码和解码的方式,常见的编解码方式有 JSON、Protobuf 等。

在大多数 RPC的架构设计中,都有ClientClient StubServerServer Stub这四个组件,Client 和 Server 之间通过 Socket 进行通信。RPC 架构如下图所示:
在这里插入图片描述
RPC 调用的流程:

  1. 客户端(Client)调用客户端存根(Client Stub),同时把参数传给客户端存根;
  2. 客户端存根将参数打包编码,并通过系统调用发送到服务端;
  3. 客户端本地系统发送信息到服务器;
  4. 服务器系统将信息发送到服务端存根(Server Stub);
  5. 服务端存根解析信息,也就是解码;
  6. 服务端存根调用真正的服务端程序(Sever);
  7. 服务端(Server)处理后,通过同样的方式,把结果再返回给客户端(Client)。

RPC 调用常用于大型项目,也就是常说的微服务,而且还会包含服务注册、治理、监控等功能,是一套完整的体系。

2. Go语言中的RPC

在 Go SDK 中,已经内置了 net/rpc 包来帮助开发者实现 RPC。简单来说,net/rpc 包提供了通过网络访问服务端对象方法的能力。

在实际的项目开发中,使用Go 语言自带的 RPC 框架并不多,比较常用的是Google的gRPC 框架,它是通过Protobuf 序列化的,是基于 HTTP/2 协议的二进制传输,并且支持很多编程语言,效率也比较高。

2.1 服务端

一个 RPC 示例的服务端代码如下所示:

package server
type MathService struct {
}
type Args struct {
   A, B int
}
func (m *MathService) Add(args Args, reply *int) error {
   *reply = args.A + args.B
   return nil
}

在以上代码中:

  • 定义了MathService,用于表示一个远程服务对象;
  • Args 结构体用于表示参数;
  • Add 这个方法实现了加法的功能,加法的结果通过 replay这个指针变量返回。

定义好服务对象就可以把它注册到暴露的服务列表中,以供其他客户端使用了。在Go 语言中,要注册一个RPC 服务对象可以通过 RegisterName 方法,示例代码如下所示:

package main
import (
   "server"
   "log"
   "net"
   "net/rpc"
)
func main()  {
   rpc.RegisterName("MathService",new(server.MathService))
   l, e := net.Listen("tcp", ":1234")
   if e != nil {
      log.Fatal("listen error:", e)
   }
   rpc.Accept(l)
}

以上示例代码中,通过 RegisterName 函数注册了一个服务对象,该函数接收两个参数:

  1. 服务名称(MathService);
  2. 具体的服务对象,也就是刚刚定义好的MathService 这个结构体。

然后通过 net.Listen 函数建立一个TCP 链接,在 1234 端口进行监听,最后通过 rpc.Accept 函数在该 TCP 链接上提供 MathService 这个 RPC 服务。现在客户端就可以看到MathService这个服务以及它的Add 方法了。

在 net/rpc 这个RPC框架时,要想把一个对象注册为 RPC 服务,可以让客户端远程访问,那么该对象(类型)的方法必须满足如下条件:

  • 方法的类型是可导出的(公开的);
  • 方法本身也是可导出的;
  • 方法必须有 2 个参数,并且参数类型是可导出或者内建的;
  • 方法必须返回一个 error 类型。

总结来说该方法的格式如下所示:

func (t *T) MethodName(argType T1, replyType *T2) error

这里面的 T1、T2都是可以被 encoding/gob 序列化的。

  • 第一个参数 argType 是调用者(客户端)提供的;
  • 第二个参数 replyType是返回给调用者结果,必须是指针类型。

2.2 客户端

代码如下所示:

package main
import (
   "fmt"
   "server"
   "log"
   "net/rpc"
)
func main()  {
   client, err := rpc.Dial("tcp",  "localhost:1234")
   if err != nil {
      log.Fatal("dialing:", err)
   }
   args := server.Args{A:7,B:8}
   var reply int
   err = client.Call("MathService.Add", args, &reply)
   if err != nil {
      log.Fatal("MathService.Add error:", err)
   }
   fmt.Printf("MathService.Add: %d+%d=%d", args.A, args.B, reply)
}

在以上实例代码中,首先通过 rpc.Dial 函数建立 TCP 链接。TCP 链接建立成功后,就需要准备远程方法需要的参数,也就是示例中的args 和 reply。参数准备好之后,就可以通过 Call 方法调用远程的RPC 服务了。Call 方法有 3 个参数,它们的作用分别如下所示:

  1. 调用的远程方法的名字,这里是MathService.Add,点前面的部分是注册的服务的名称,点后面的部分是该服务的方法;
  2. 客户端为了调用远程方法提供的参数,示例中是args;
  3. 为了接收远程方法返回的结果,必须是一个指针,也就是示例中的 &reply,这样客户端就可以获得服务端返回的结果了。

3. 基于 HTTP 的RPC

RPC 除了可以通过 TCP 协议调用之外,还可以通过HTTP 协议进行调用,而且内置的net/rpc 包已经支持,修改以上示例代码支持 HTTP 协议的调用,服务端代码如下所示:

func main() {
   rpc.RegisterName("MathService", new(server.MathService))
   rpc.HandleHTTP()//新增的
   l, e := net.Listen("tcp", ":1234")
   if e != nil {
      log.Fatal("listen error:", e)
   }
   http.Serve(l, nil)//换成http的服务
}

客户端修改的代码如下所示:

func main()  {
   client, err := rpc.DialHTTP("tcp",  "localhost:1234")
   //省略了其他没有修改的代码
}

可以看到,只需要把建立链接的方法从 Dial 换成 DialHTTP 即可。

Go 语言 net/rpc 包提供的 HTTP 协议的 RPC 还有一个调试的 URL,运行服务端代码后,在浏览器中输入 http://localhost:1234/debug/rpc 回车,即可看到服务端注册的RPC 服务,以及每个服务的方法,如下图所示:
在这里插入图片描述
如上图所示,注册的 RPC 服务、方法的签名、已经被调用的次数都可以看到。

3. JSON RPC 跨平台通信

以上实现的RPC 服务是基于 gob 编码的,这种编码在跨语言调用的时候比较困难,当前在微服务架构中,RPC 服务的实现者和调用者都可能是不同的编程语言,因此实现的 RPC 服务要支持多语言的调用。

3.1 基于 TCP 的 JSON RPC

实现跨语言 RPC 服务的核心在于选择一个通用的编码,这样大多数语言都支持,比如常用的JSON。在 Go 语言中,实现一个 JSON RPC 服务非常简单,只需要使用 net/rpc/jsonrpc 包即可。

以上面的示例为例,改造成支持 JSON的RPC 服务,服务端代码如下所示:

func main() {
   rpc.RegisterName("MathService", new(server.MathService))
   l, e := net.Listen("tcp", ":1234")
   if e != nil {
      log.Fatal("listen error:", e)
   }
   for {
      conn, err := l.Accept()
      if err != nil {
         log.Println("jsonrpc.Serve: accept:", err.Error())
         return
      }
      //json rpc
      go jsonrpc.ServeConn(conn)
   }
}

从以上代码可以看到,相比 gob 编码的RPC 服务,JSON 的 RPC 服务是把链接交给了jsonrpc.ServeConn这个函数处理,达到了基于 JSON 进行 RPC 调用的目的。

JSON RPC 的客户端代码修改的部分如下所示:

func main()  {
   client, err := jsonrpc.Dial("tcp",  "localhost:1234")
   //省略了其他没有修改的代码
}

从以上代码可以看到,只需要把建立链接的 Dial方法换成 jsonrpc 包中的即可。

3.2 基于 HTTP的JSON RPC

Go 语言内置的jsonrpc 并没有实现基于 HTTP的传输,需要自己实现,这里参考 gob 编码的HTTP RPC 实现方式,来实现基于 HTTP的JSON RPC 服务。

RPC 服务端代码如下所示:

func main() {
   rpc.RegisterName("MathService", new(server.MathService))
   //注册一个path,用于提供基于http的json rpc服务
   http.HandleFunc(rpc.DefaultRPCPath, func(rw http.ResponseWriter, r *http.Request) {
      conn, _, err := rw.(http.Hijacker).Hijack()
      if err != nil {
         log.Print("rpc hijacking ", r.RemoteAddr, ": ", err.Error())
         return
      }
      var connected = "200 Connected to JSON RPC"
      io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
      jsonrpc.ServeConn(conn)
   })
   l, e := net.Listen("tcp", ":1234")
   if e != nil {
      log.Fatal("listen error:", e)
   }
   http.Serve(l, nil)//换成http的服务
}

以上代码的实现基于 HTTP 协议的核心,即使用 http.HandleFunc 注册了一个 path,对外提供基于 HTTP 的 JSON RPC 服务。在这个 HTTP 服务的实现中,通过Hijack方法劫持链接,然后转交给 jsonrpc 处理,这样就实现了基于 HTTP 协议的 JSON RPC 服务。

客户端调用的代码如下所示:

func main()  {
     client, err := DialHTTP("tcp",  "localhost:1234")
     if err != nil {
        log.Fatal("dialing:", err)
     }
     args := server.Args{A:7,B:8}
     var reply int
     err = client.Call("MathService.Add", args, &reply)
     if err != nil {
        log.Fatal("MathService.Add error:", err)
     }
     fmt.Printf("MathService.Add: %d+%d=%d", args.A, args.B, reply)
  }
  // DialHTTP connects to an HTTP RPC server at the specified network address
  // listening on the default HTTP RPC path.
  func DialHTTP(network, address string) (*rpc.Client, error) {
     return DialHTTPPath(network, address, rpc.DefaultRPCPath)
  }
  // DialHTTPPath connects to an HTTP RPC server
  // at the specified network address and path.
  func DialHTTPPath(network, address, path string) (*rpc.Client, error) {
     var err error
     conn, err := net.Dial(network, address)
     if err != nil {
        return nil, err
     }
     io.WriteString(conn, "GET "+path+" HTTP/1.0\n\n")
     // Require successful HTTP response
     // before switching to RPC protocol.
     resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "GET"})
     connected := "200 Connected to JSON RPC"
     if err == nil && resp.Status == connected {
        return jsonrpc.NewClient(conn), nil
     }
     if err == nil {
        err = errors.New("unexpected HTTP response: " + resp.Status)
     }
     conn.Close()
     return nil, &net.OpError{
        Op:   "dial-http",
        Net:  network + " " + address,
        Addr: nil,
        Err:  err,
     }
  }

以上这段代码的核心在于通过建立好的TCP 链接,发送 HTTP 请求调用远程的HTTP JSON RPC 服务,这里使用的是 HTTP GET 方法。

三、Go 语言的发展前景

Go 语言就是为云而生的编程语言,所以在云原生的时代,它就具备了天生的优势:易于学习、天然的并发、高效的网络支持、跨平台的二进制文件编译等。

CNCF(云原生计算基金会)对云原生的定义是:

  • 应用容器化;
  • 面向微服务架构;
  • 应用支持容器的编排调度。

对于这三点有代表性的 Docker、K8s 以及 istio 都是采用 Go 语言编写的,所以 Go 语言在云原生中发挥了极大的优势。

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

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

相关文章

一位年薪50W的测试被开除,回怼的一番话,令人沉思

一位年薪35W测试工程师被开除回怼道:“反正我有技术,在哪不一样” 一技傍身,万事不愁,当我们掌握了一技之长后,在职场上说话就硬气了许多,不用担心被炒,反过来还可以炒了老板,这一点…

前后端分离------后端创建笔记(05)用户列表查询接口(上)

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论,如有侵权请联系 源码:https://gitee.com/green_vegetables/x-admin-project.git 素材:https://pan.baidu.com/s/…

推荐5款能帮你解决各种问题的神器

​ 今天我要向大家推荐5款超级好用的效率软件,无论是在学习还是办公中都能够极大地提高效率。这些软件可以帮助你解决许多问题,而且每个都是真正的神器。 网速和硬件监控——TrafficMonitor ​ TrafficMonitor 是一款可以在任务栏或桌面悬浮窗显示系统…

配置service管理nginx

一.以源码形式安装的nginx,没有nginx.service 二.切换到service配置目录 三.编辑nginx.service文件 四.启动测试 1.我的开始报了这个问题,说没有这个/var/cache/nginx/client_temp目录,直接创建一个就好了 2.开启/关闭 一.以源码形式安装…

参加NPCon2023 AI模型技术与应用峰会(北京站):作为北京社区主理人的参会感受

参加NPCon2023 AI模型技术与应用峰会:作为北京社区主理人的参会感受 🏉🏉前言🏉🏉活动周边琳琅满目🏉🏉主题演讲🏉🏉亮点🏉🏉总结与展望 博主 默语…

项目管理的艺术:掌握成本效益分析

引言 在项目管理中,我们经常面临着如何有效地使用有限的资源来实现项目目标的挑战。为了解决这个问题,我们需要使用一种强大的工具——成本效益分析。通过成本效益分析,我们可以评估和比较不同的项目选项,选择最具成本效益的项目…

备战2024秋招面试题-最左匹配原则、索引失效情况、算法(最长回文子串)

前言: \textcolor{Green}{前言:} 前言: 💞快秋招了,那么这个专栏就专门来记录一下,同时呢整理一下常见面试题 💞部分题目来自自己的面试题,部分题目来自网络整理 给我冲 学习目标&am…

Python爬虫——scrapy_基本使用

安装scrapy pip install scrapy创建scrapy项目,需要在终端里创建 注意:项目的名字开头不能是数字,也不能包含中文 scrapy startproject 项目名称 示例: scrapy startproject scra_baidu_36创建好后的文件 3. 创建爬虫文件&…

【构造】CF1761 E

Problem - 1761E - Codeforces 题意&#xff1a; 思路&#xff1a; Code&#xff1a; #include <bits/stdc.h>using i64 long long;void solve() {int n;std::cin >> n;std::vector g(n, std::vector<int>(n));std::vector<int> deg(n);for (int i …

电脑xinput1_3.dll丢失的解决方法?哪个解决方法更简单

最近在打开软件或者游戏的时候&#xff0c;电脑提示xinput1_3.dll文件丢失的错误。这个问题导致我无法运行某些游戏和应用程序。通过一番尝试和研究&#xff0c;我找到了一些修复xinput1_3.dll文件丢失的方法&#xff0c;并在此分享给大家。 首先&#xff0c;我了解到xinput1_3…

让音频翻译软件成为你的翻译利器吧

音频翻译功能是现代技术的一大进步&#xff0c;它通过语音识别技术和机器翻译技术&#xff0c;可以将音频文件自动翻译成其他语言的文字&#xff0c;让人们跨越语言障碍进行交流。现在市面上有很多种音频文件翻译软件&#xff0c;每个软件都有其独特的功能和特点。那么&#xf…

【状态模式】拯救if-else堆出来的屎山代码

前言 我想大家平时都在开发重都遇见过屎山代码&#xff0c;这些屎山代码一般都是由于复杂且庞大的if-else造成的&#xff0c;状态模式&#xff0c;是一种很好的优化屎山代码的设计模式&#xff0c;本文将采用两个业务场景的示例来讲解如何使用状态模式拯救屎山代码。 目录 前…

什么是管理的本质?

管理不是一门硬科学。与&#xff08;通常&#xff09;存在正确答案的化学或代数不同&#xff0c;管理是流动的&#xff0c;主观的&#xff0c;并且对于如何运用其原理存在不同的观点。但究竟什么是管理&#xff1f;大多数学者都有相同定义的变体&#xff0c;包括利用资源来实现…

Ubuntu系统搭建socks5代理 Danted SOCKS Proxy

本教程介绍如何在Ubuntu系统中搭建Dated Socks5 代理服务器 更新系统 apt update安装 danted软件 apt install dante-server修改该danted软件的配置文件 vi /etc/danted.conf修改后的内容为下&#xff1a; logoutput: syslog user.privileged: root user.unprivileged: no…

沁恒ch32V208处理器开发(五)复用与重映射

目录 概述实现方法GPIO 及其复用功能AFIO 的寄存器实现 重映射的功能限制 概述 在ch32V2xx系列处理器中&#xff0c;芯片引脚数从28到68不等&#xff0c;分为几个系列&#xff0c;不同型号之间外设资源有差异&#xff0c;所以&#xff0c;引脚复用和重映射功能 也不一样&#…

jmeter获取mysql数据

JDBC Connection Configuration Database URL: jdbc:mysql:// 数据库地址 /库名 JDBC Driver class&#xff1a;com.mysql.jdbc.Driver Username&#xff1a;账号 Password&#xff1a;密码 JDBC Request 字段含义 字段含义 Variable Name Bound to Pool 数据库连接池配置…

分类预测 | MATLAB实现GWO-BiGRU-Attention多输入分类预测

分类预测 | MATLAB实现GWO-BiGRU-Attention多输入分类预测 目录 分类预测 | MATLAB实现GWO-BiGRU-Attention多输入分类预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.GWO-BiGRU-Attention 数据分类预测程序 2.代码说明&#xff1a;基于灰狼优化算法&#xff08;GW…

这四种订货系统不能选(二):不能独立部署

订货系统在传统批发贸易企业数字化转型中扮演着重要的角色。然而&#xff0c;有一种类型的订货系统并不适合选择&#xff0c;那就是无法独立部署的系统。 无法独立部署的订货系统意味着数据必须存放在软件厂商的服务器上。当我们选择这样的系统时&#xff0c;需要确保系统具备强…

C++11并发与多线程笔记 (1)

C11并发与多线程笔记&#xff08;1&#xff09; 1、并发、进程、线程的基本概念和综述1.1 并发1.2 可执行程序1.3 进程1.4 线程1.5 学习心得 2、并发的实现方法2.1 多进程并发2.2 多线程并发 3、C11新标准线程库 1、并发、进程、线程的基本概念和综述 1.1 并发 指在一个时间段…

Kotlin入门:面向对象编程——04

一、类和对象&#xff1a; 在Kotlin中&#xff0c;使用class关键字定义类&#xff0c;使用object关键字定义对象。类是对象的蓝图&#xff0c;用于描述对象的属性和行为&#xff0c;而对象则是类的一个实例。类可以包含属性&#xff08;成员变量&#xff09;和方法&#xff08…