Go语言学习 - RPC篇:理解标准库HTTP的hander实现逻辑

简介: 在Go语言中,常见的RPC包括HTTP/gRPC/Thrift等,但绝大多数的开发场景仍是基于HTTP。本文对RPC的讨论,主要是基于HTTP的场景。

RPC框架

作为一名开发者,我们最常见的日常工作就是web类编程:即对于CRUD请求,开发相关的业务代码。

在Go语言中,常见的RPC包括HTTP/gRPC/Thrift等,但绝大多数的开发场景仍是基于HTTP。本文对RPC的讨论,主要是基于HTTP的场景。

如果我们能熟练地掌握一套主流RPC框架,至少能提升20%的开发效率,而优秀的框架能带来更大的帮助。提效是为了有更多时间提升个人能力,我们今天就先对RPC框架有一个概览。

RPC的处理过程

对一个web程序来说,它的核心功能就是处理一个请求。一个RPC的处理流程可以简单划分为3块:

  1. 解析请求数据
  2. 业务逻辑处理
  3. 返回结果

这个看似简单的流程,在实际开发过程中会遇到很多问题。抛开业务逻辑,我们重点看一下1、3两步:

解析请求数据:

  1. 要理解HTTP协议的标准,了解URL/Header/Body里的数据信息,如Content-Type
  2. 定义这个请求的数据结构,将数据 反序列化 到程序中的结构体

返回结果:

  1. 定义请求的返回数据结构,将程序中的结构体 序列化
  2. 异常情况下,如业务处理错误、程序崩溃等,如何保证返回的数据结构一致

简单来说,这两步的功能可以概括为:如何将数据按定义的标准,进行序列化与反序列化

常见的序列化工具如json/xml/protobuf等,新手主要了解 json 即可。

接下来,我们来看看标准库对请求的处理。

Go标准库的示例代码

我们先来看标准HTTP库,它的实现是我们学习RPC的基础:

http.HandleFunc("/router", func(writer http.ResponseWriter, request *http.Request) {
   
  type MyRequest struct {
   
    Name string `json:"name"`
  }
  type MyResponse struct {
   
    Errno int `json:"errno"`
  }

  var resp = new(MyResponse)

  // 1. 解析参数
  var req MyRequest
  b, err := ioutil.ReadAll(request.Body)
  if err != nil {
   
    resp.Errno = 1
    b, _ = json.Marshal(resp)
    writer.Write(b)
    return
  }
  json.Unmarshal(b, &req)

  // 2. 业务逻辑处理

  // 3. 返回结果
  b, _ = json.Marshal(resp)
  writer.Write(b)
  return
})

两个关键参数

响应 http.ResponseWriter 与 请求*http.Request。这两个参数里面包含了许多信息,我这里列举最常用的几个:

  1. 结构体Request
    1. Method - http方法
    2. URL - http URL
    3. Header - http头
    4. Body - http消息体
  2. 接口ResponseWriter
    1. Write - 写Body
    2. WriteHeader - 写状态码

开发思路

我们梳理一下,一个新的HTTP接口的开发是什么样的逻辑:

1.如何匹配到handler

示例就是/router这个路由匹配,但实际情况中会更复杂:

  1. 如前缀匹配、模糊匹配等
  2. 按照RESTful协议,不同Method的处理逻辑不一样

对于第二点,我们自然也可以通过在handler函数中增加if-else的逻辑来覆盖,但这么写下来,显然会增加handler函数的复杂程度。

从RPC的编程术语来说,我们称这个匹配逻辑为mux,即多路复用。于是,我们就发现了http标准库中的2大优化点:

  1. 更灵活的URL匹配逻辑
  2. mux支持HTTP Method的区分

2.解析参数

解析参数可以分解为3个问题:

  1. 参数来源于哪?如URL/Header/Body
  2. 参数要怎么解析?如json/form/xml
  3. 参数要解析到那个Go结构体?

有经验的朋友能深刻体会其中的繁琐(这部分工作不难,但很费开发与排查问题的时间)。比如说,在写业务层代码时,发现某个参数没有解析到,我们要分析的点非常多,包括协议问题、字段名称、字段类型、解析的工具库等等。

对于程序员来说,当然是希望尽可能地将这部分高度重复的工作进行简化,提升效率。

3.返回结果

返回数据的代码看过去很简单,就是将数据序列化后返回。

但是,难点在于异常情况下的处理:例如,当handler中某个逻辑出错时,我们要怎么返回数据呢?最常见的方案,就是增加一个特殊的字段进行标记,如错误码errno,不为0时表示错误,为0时才表示正确、再去解析数据结构。

核心问题

上述3点没有什么技术上的难度,但在稍微复杂点的web程序时,会遇到什么问题呢?我们再次一起看看handler这个函数签名:

handler func(http.ResponseWriter, *http.Request)

如果你随意编写一个handler,也可以轻松编译通过,例如:

func(writer http.ResponseWriter, request *http.Request) {
   
  return
}

因此,最主要的问题是在于:没办法对开发者在编写HTTP接口时,提供一定的强制规范

质量低 - 容易失误

由于handler这层的无法强制性地标准化,容易出现下限很低的失误,例如:

  1. 用错了请求的数据结构,尤其是ctrl+c/ctrl+v
  2. 返回的数据结构没有强限制,完全可以自定义

效率低 - 重复编码

对于解析参数和返回数据,往往需要大量的重复编码。这部分虽然可以通过封装一些库来缓解,但每个handler都至少仍有2个调用:

  • 解析数据的函数,如Bind
  • 返回数据的函数,如WriteResponse

而对于有异常情况的,如发生error,WriteResponse的调用量相应增加

标准化低 - 内部实现各异

由于handler内的 解析请求返回响应 没有任何代码限制,所以可以采用任意开源或自研的组件。

这些组件的实现各异,一旦扩散后很难收敛,很容易遇上不兼容的问题:

  • 如果只是程序内实现的不兼容,还可以通过修改完成兼容
  • 但如果多个调用方发生了不兼容,那就很难控制了

测试难 - 单测难覆盖

整个handler的可测试性是很低的,构造一个单测堪比写一大串业务代码,调试时很复杂。

所以,开发者往往更愿意靠 启动go程序+postman发请求 这样相对重量级的接口测试。

更大规模下的问题

实现与接口文档的不一致

随着平台的迭代,我们经常会去修改一些接口。

但在Go语言中,它无法直接生成接口文档(如swagger文档)。普遍的方案会利用注释,但注释依旧无法和代码里的实现保证强一致性(如接口文档为OrderV1,但实际已经升级到了OrderV2)。

调用方的开发工作

对于接口调用方,有4个工作是必须做的:

  1. 定义URL/方法等
  2. 定义请求的数据结构
  3. 定义返回的数据结构
  4. 拼接处一个HTTP请求

每个服务调用方,都需要重复地做这部分的工作。

这个问题可以通过统一建设公共库(SDK)来减轻,但SDK库如何与服务端的实现保证一致,是比较复杂的问题:例如新增了一个url+handler的处理逻辑,如何保证SDK会自动更新?

业务逻辑的兼容性问题

业务逻辑往往是复杂的,我们更多的时间是投入在业务逻辑处理上,但传统的方式容易出现各种兼容性问题,比如:

开发者可能只是发现某个内部bug,改了某个字段的数据结构,但却导致所有调用方整个解析失败(如json.Unmarshal)。

总结

也许,有的朋友看了上述问题,会觉得不以为然:如果能搞好工具库和标准,以上问题都能解决

没错,上述问题都不致命,否则业界也早就出现明确的标准了。但是我们要考虑到两点:

  1. 人员的不确定因素:不同的能力阶段、人员流动性
  2. 效率与质量:将开发时间更多地投入到业务逻辑上,提升质量

就像是你要从上海到北京出差,你当然可以自驾、歪歪扭扭地沿着高速公路到达目的地,有很高的选择自由度;但有了更快的高铁路线,何乐而不为呢?毕竟,从出差这件事来看,最重要的是保证准时地到达目的地,

那么RPC的“高铁方案”是怎么样的呢?下一节我们继续展开。

目录
相关文章
|
16小时前
|
JSON 安全 Java
2024年的选择:为什么Go可能是理想的后端语言
【4月更文挑战第27天】Go语言在2024年成为后端开发的热门选择,其简洁设计、内置并发原语和强大工具链备受青睐。文章探讨了Go的设计哲学,如静态类型、垃圾回收和CSP并发模型,并介绍了使用Gin和Echo框架构建Web服务。Go的并发通过goroutines和channels实现,静态类型确保代码稳定性和安全性,快速编译速度利于迭代。Go广泛应用在云计算、微服务等领域,拥有丰富的生态系统和活跃社区,适合作为应对未来技术趋势的语言。
8 0
|
19小时前
|
安全 测试技术 Go
Golang深入浅出之-Go语言单元测试与基准测试:testing包详解
【4月更文挑战第27天】Go语言的`testing`包是单元测试和基准测试的核心,简化了测试流程并鼓励编写高质量测试代码。本文介绍了测试文件命名规范、常用断言方法,以及如何进行基准测试。同时,讨论了测试中常见的问题,如状态干扰、并发同步、依赖外部服务和测试覆盖率低,并提出了相应的避免策略,包括使用`t.Cleanup`、`t.Parallel()`、模拟对象和检查覆盖率。良好的测试实践能提升代码质量和项目稳定性。
6 1
|
19小时前
|
运维 监控 Go
Golang深入浅出之-Go语言中的日志记录:log与logrus库
【4月更文挑战第27天】本文比较了Go语言中标准库`log`与第三方库`logrus`的日志功能。`log`简单但不支持日志级别配置和多样化格式,而`logrus`提供更丰富的功能,如日志级别控制、自定义格式和钩子。文章指出了使用`logrus`时可能遇到的问题,如全局logger滥用、日志级别设置不当和过度依赖字段,并给出了避免错误的建议,强调理解日志级别、合理利用结构化日志、模块化日志管理和定期审查日志配置的重要性。通过这些实践,开发者能提高应用监控和故障排查能力。
8 1
|
19小时前
|
安全 Go
Golang深入浅出之-Go语言标准库中的文件读写:io/ioutil包
【4月更文挑战第27天】Go语言的`io/ioutil`包提供简单文件读写,适合小文件操作。本文聚焦`ReadFile`和`WriteFile`函数,讨论错误处理、文件权限、大文件处理和编码问题。避免错误的关键在于检查错误、设置合适权限、采用流式读写及处理编码。遵循这些最佳实践能提升代码稳定性。
5 0
|
20小时前
|
Go C++
go 语言回调函数和闭包
go 语言回调函数和闭包
|
1天前
|
存储 负载均衡 监控
【Go 语言专栏】构建高可靠性的 Go 语言服务架构
【4月更文挑战第30天】本文探讨了如何利用Go语言构建高可靠性的服务架构。Go语言凭借其高效、简洁和并发性能,在构建服务架构中备受青睐。关键要素包括负载均衡、容错机制、监控预警、数据存储和服务治理。文章详细阐述了实现这些要素的具体步骤,通过实际案例分析和应对挑战的策略,强调了Go语言在构建稳定服务中的作用,旨在为开发者提供指导。
|
1天前
|
测试技术 Go 开发工具
【Go语言专栏】Go语言中的代码审查与最佳实践
【4月更文挑战第30天】Go语言因其简洁、高性能及并发能力,在云计算等领域广泛应用。代码审查对提升Go代码质量、遵循规范及团队协作至关重要。审查流程包括提交、审查、反馈、修改和合并代码。工具如GoLand、Git、ReviewBoard和GitHub提供支持。最佳实践包括遵循命名规范、添加注释、保持代码结构清晰、复用代码和确保测试覆盖。积极参与代码审查是提高质量的关键。
|
Web App开发 前端开发
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
总结和计划总是让人喜悦或镇痛,一方面以前一段时间没有荒废,能给现在的行动以信心,另一方面看到一年的时间并不能完成很多事情,需要抓紧时间。
583 0
|
Java Apache
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
hbase从集群中有8台regionserver服务器,已稳定运行了5个多月,8月15号,发现集群中4个datanode进程死了,经查原因是内存 outofMemory了(因为这几台机器上部署了spark,给spark开的...
785 0
|
Web App开发 前端开发
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html><head><meta http-equiv="Cont
PipeMapRed.waitOutputThreads(): subprocess failed with code X ,这里code X对应的信息如下:error code 1: Operation not perm...
912 0