mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
web -> Web
This commit is contained in:
parent
5d91a934bd
commit
d5a3c92c9b
16
SUMMARY.md
16
SUMMARY.md
@ -44,14 +44,14 @@
|
|||||||
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
|
* [4.9 补充说明](ch4-rpc/ch4-09-ext.md)
|
||||||
* [第5章 Go和Web](ch5-web/readme.md)
|
* [第5章 Go和Web](ch5-web/readme.md)
|
||||||
* [5.1 Web开发简介](ch5-web/ch5-01-introduction.md)
|
* [5.1 Web开发简介](ch5-web/ch5-01-introduction.md)
|
||||||
* [5.2 Router请求路由](ch5-web/ch5-02-router.md)
|
* [5.2 请求路由](ch5-web/ch5-02-router.md)
|
||||||
* [5.3 Middleware中间件](ch5-web/ch5-03-middleware.md)
|
* [5.3 中间件](ch5-web/ch5-03-middleware.md)
|
||||||
* [5.4 Validator请求校验](ch5-web/ch5-04-validator.md)
|
* [5.4 请求校验](ch5-web/ch5-04-validator.md)
|
||||||
* [5.5 Database和数据库打交道](ch5-web/ch5-05-database.md)
|
* [5.5 和数据库打交道](ch5-web/ch5-05-database.md)
|
||||||
* [5.6 Ratelimit 服务流量限制](ch5-web/ch5-06-ratelimit.md)
|
* [5.6 服务流量限制](ch5-web/ch5-06-ratelimit.md)
|
||||||
* [5.7 Layout大型web项目分层](ch5-web/ch5-07-layout-of-web-project.md)
|
* [5.7 大型Web项目分层](ch5-web/ch5-07-layout-of-web-project.md)
|
||||||
* [5.8 interface 和 table-driven 开发](ch5-web/ch5-08-interface-and-web.md)
|
* [5.8 接口和表驱动开发](ch5-web/ch5-08-interface-and-web.md)
|
||||||
* [5.9 灰度发布和 A/B test](ch5-web/ch5-09-gated-launch.md)
|
* [5.9 灰度发布和A/B测试](ch5-web/ch5-09-gated-launch.md)
|
||||||
* [5.10 补充说明](ch5-web/ch5-10-ext.md)
|
* [5.10 补充说明](ch5-web/ch5-10-ext.md)
|
||||||
* [第6章 分布式系统](ch6-cloud/readme.md)
|
* [第6章 分布式系统](ch6-cloud/readme.md)
|
||||||
* [6.1 分布式 id 生成器](ch6-cloud/ch6-01-dist-id.md)
|
* [6.1 分布式 id 生成器](ch6-cloud/ch6-01-dist-id.md)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 5.1 web 开发简介
|
# 5.1 Web 开发简介
|
||||||
|
|
||||||
因为Go的 `net/http`包提供了基础的路由函数组合与丰富的功能函数。所以在社区里流行一种用Go编写api不需要框架的观点;在我们看来,如果你的项目的路由在个位数、URI 固定且不通过 URI 来传递参数,那么确实使用官方库也就足够。但在复杂场景下,官方的 http 库还是有些力有不逮。例如下面这样的路由:
|
因为Go的`net/http`包提供了基础的路由函数组合与丰富的功能函数。所以在社区里流行一种用Go编写api不需要框架的观点;在我们看来,如果你的项目的路由在个位数、URI固定且不通过URI来传递参数,那么确实使用官方库也就足够。但在复杂场景下,官方的http库还是有些力有不逮。例如下面这样的路由:
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /card/:id
|
GET /card/:id
|
||||||
@ -18,9 +18,9 @@ Go的Web框架大致可以分为这么两类:
|
|||||||
1. Router框架
|
1. Router框架
|
||||||
2. MVC类框架
|
2. MVC类框架
|
||||||
|
|
||||||
在框架的选择上,大多数情况下都是依照个人的喜好和公司的技术栈。例如公司有很多技术人员是PHP出身,那么他们一定会非常喜欢像beego 这样的框架,但如果公司有很多 C 程序员,那么他们的想法可能是越简单越好。比如很多大厂的 C 程序员甚至可能都会去用 C 去写很小的 CGI 程序,他们可能本身并没有什么意愿去学习MVC或者更复杂的 web 框架,他们需要的只是一个非常简单的路由(甚至连路由都不需要,只需要一个基础的HTTP协议处理库来帮他省掉没什么意思的体力劳动)。
|
在框架的选择上,大多数情况下都是依照个人的喜好和公司的技术栈。例如公司有很多技术人员是PHP出身,那么他们一定会非常喜欢像beego这样的框架,但如果公司有很多C程序员,那么他们的想法可能是越简单越好。比如很多大厂的C程序员甚至可能都会去用 C 去写很小的CGI程序,他们可能本身并没有什么意愿去学习MVC或者更复杂的Web框架,他们需要的只是一个非常简单的路由(甚至连路由都不需要,只需要一个基础的HTTP协议处理库来帮他省掉没什么意思的体力劳动)。
|
||||||
|
|
||||||
Go的 net/http 包提供的就是这样的基础功能,写一个 http echo server 只需要30s。
|
Go的`net/http`包提供的就是这样的基础功能,写一个简单的`http echo server`只需要30s。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
//brief_intro/echo.go
|
//brief_intro/echo.go
|
||||||
@ -50,7 +50,7 @@ func main() {
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你过了30s还没有完成这个程序,请检查一下你自己的打字速度是不是慢了(开个玩笑 :D)。这个例子是为了说明在Go中写一个HTTP协议的小程序有多么简单。如果你面临的情况比较复杂,例如几十个接口的企业级应用,直接用 net/http 库就显得不太合适了。
|
如果你过了30s还没有完成这个程序,请检查一下你自己的打字速度是不是慢了(开个玩笑 :D)。这个例子是为了说明在Go中写一个HTTP协议的小程序有多么简单。如果你面临的情况比较复杂,例如几十个接口的企业级应用,直接用`net/http`库就显得不太合适了。
|
||||||
|
|
||||||
我们来看看开源社区中一个 kafka 监控项目中的做法:
|
我们来看看开源社区中一个 kafka 监控项目中的做法:
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ func NewHttpServer(app *ApplicationContext) (*HttpServer, error) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
上面这段代码来自大名鼎鼎的 linkedin 公司的 kafka 监控项目 Burrow,没有使用任何 router 框架,只使用了 net/http。只看上面这段代码似乎非常优雅,我们的项目里大概只有这五个简单的 URI,所以我们提供的服务就是下面这个样子:
|
上面这段代码来自大名鼎鼎的linkedin公司的kafka监控项目 Burrow,没有使用任何router框架,只使用了`net/http`。只看上面这段代码似乎非常优雅,我们的项目里大概只有这五个简单的 URI,所以我们提供的服务就是下面这个样子:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
/
|
/
|
||||||
@ -79,7 +79,7 @@ func NewHttpServer(app *ApplicationContext) (*HttpServer, error) {
|
|||||||
/v2/zookeeper
|
/v2/zookeeper
|
||||||
```
|
```
|
||||||
|
|
||||||
如果你确实这么想的话就被骗了。我们再进 handleKafka 这个函数一探究竟:
|
如果你确实这么想的话就被骗了。我们再进`handleKafka()`这个函数一探究竟:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func handleKafka(app *ApplicationContext, w http.ResponseWriter, r *http.Request) (int, string) {
|
func handleKafka(app *ApplicationContext, w http.ResponseWriter, r *http.Request) (int, string) {
|
||||||
@ -146,11 +146,11 @@ func handleKafka(app *ApplicationContext, w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
因为默认的net/http包中的 mux 不支持带参数的路由,所以Burrow 这个项目使用了非常蹩脚的字符串 Split 和乱七八糟的 switch case 来达到自己的目的,但实际上却让本来应该很集中的路由管理逻辑变得复杂,散落在系统的各处,难以维护和管理。如果读者细心地看过这些代码之后,可能会发现其它的几个 handler 函数逻辑上较简单,最复杂的也就是这个 handleKafka。但实际上我们的系统总是从这样微不足道的混乱开始积少成多,最终变得难以收拾。
|
因为默认的`net/http`包中的mux不支持带参数的路由,所以Burrow这个项目使用了非常蹩脚的字符串 Split 和乱七八糟的 `switch case`来达到自己的目的,但实际上却让本来应该很集中的路由管理逻辑变得复杂,散落在系统的各处,难以维护和管理。如果读者细心地看过这些代码之后,可能会发现其它的几个handler函数逻辑上较简单,最复杂的也就是这个handleKafka。但实际上我们的系统总是从这样微不足道的混乱开始积少成多,最终变得难以收拾。
|
||||||
|
|
||||||
根据我们的经验,简单地来说,只要你的路由带有参数,并且这个项目的 api 数目超过了 10,就尽量不要使用 net/http 中默认的路由。在Go开源界应用最广泛的 router 是 httpRouter,很多开源的 router 框架都是基于 httpRouter 进行一定程度的改造的成果。关于 httpRouter 路由的原理,会在本章节的 router 一节中进行详细的阐释。
|
根据我们的经验,简单地来说,只要你的路由带有参数,并且这个项目的api数目超过了10,就尽量不要使用`net/http`中默认的路由。在Go开源界应用最广泛的router是httpRouter,很多开源的router框架都是基于httpRouter进行一定程度的改造的成果。关于httpRouter路由的原理,会在本章节的router一节中进行详细的阐释。
|
||||||
|
|
||||||
再来回顾一下文章开头说的,开源界有这么几种框架,第一种是对 httpRouter 进行简单的封装,然后提供定制的 middleware 和一些简单的小工具集成比如 gin,主打轻量,易学,高性能。第二种是借鉴其它语言的编程风格的一些 MVC 类框架,例如 beego,方便从其它语言迁移过来的程序员快速上手,快速开发。还有一些框架功能更为强大,除了 db 设计,大部分代码直接生成,例如 goa。不管哪种框架,适合开发者背景的就是最好的。
|
再来回顾一下文章开头说的,开源界有这么几种框架,第一种是对httpRouter进行简单的封装,然后提供定制的middleware和一些简单的小工具集成比如gin,主打轻量,易学,高性能。第二种是借鉴其它语言的编程风格的一些MVC类框架,例如beego,方便从其它语言迁移过来的程序员快速上手,快速开发。还有一些框架功能更为强大,除了数据库schema设计,大部分代码直接生成,例如goa。不管哪种框架,适合开发者背景的就是最好的。
|
||||||
|
|
||||||
本章的内容除了会展开讲解 router 和 middleware 的原理外,还会以现在工程界面临的问题结合 Go 来进行一些实践性的说明。希望能够对没有接触过相关内容的读者有所帮助。
|
本章的内容除了会展开讲解router和middleware的原理外,还会以现在工程界面临的问题结合Go来进行一些实践性的说明。希望能够对没有接触过相关内容的读者有所帮助。
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 5.2 router 请求路由
|
# 5.2 router 请求路由
|
||||||
|
|
||||||
在常见的 web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。
|
在常见的 Web 框架中,router 是必备的组件。golang 圈子里 router 也时常被称为 http 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道如何用 http 标准库中内置的 mux 来完成简单的路由功能了。如果开发 Web 系统对路径中带参数没什么兴趣的话,用 http 标准库中的 mux 就可以。
|
||||||
|
|
||||||
restful 是几年前刮起的 API 设计风潮,在 restful 中除了 GET 和 POST 之外,还使用了 http 协议定义的几种其它的标准化语义。具体包括:
|
restful 是几年前刮起的 API 设计风潮,在 restful 中除了 GET 和 POST 之外,还使用了 http 协议定义的几种其它的标准化语义。具体包括:
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ DELETE /user/starred/:owner/:repo
|
|||||||
|
|
||||||
## 5.2.1 httprouter
|
## 5.2.1 httprouter
|
||||||
|
|
||||||
较流行的开源 golang web 框架大多使用 httprouter,或是基于 httprouter 的变种对路由进行支持。前面提到的 github 的参数式路由在 httprouter 中都是可以支持的。
|
较流行的开源 golang Web 框架大多使用 httprouter,或是基于 httprouter 的变种对路由进行支持。前面提到的 github 的参数式路由在 httprouter 中都是可以支持的。
|
||||||
|
|
||||||
因为 httprouter 中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:
|
因为 httprouter 中使用的是显式匹配,所以在设计路由的时候需要规避一些会导致路由冲突的情况,例如:
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ r.PanicHandler = func(w http.ResponseWriter, r *http.Request, c interface{}) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
目前开源界最为流行(star 数最多)的 web 框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。
|
目前开源界最为流行(star 数最多)的Web框架 [gin](https://github.com/gin-gonic/gin) 使用的就是 httprouter 的变种。
|
||||||
|
|
||||||
## 5.2.2 原理
|
## 5.2.2 原理
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 5.3 middleware 中间件
|
# 5.3 middleware 中间件
|
||||||
|
|
||||||
本章将对现在流行的 web 框架中的中间件技术原理进行分析,并介绍如何使用中间件技术将业务和非业务代码功能进行解耦。
|
本章将对现在流行的Web框架中的中间件技术原理进行分析,并介绍如何使用中间件技术将业务和非业务代码功能进行解耦。
|
||||||
|
|
||||||
## 5.3.1 代码泥潭
|
## 5.3.1 代码泥潭
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
这是一个典型的 web 服务,挂载了一个简单的路由。我们的线上服务一般也是从这样简单的服务开始逐渐拓展开去的。
|
这是一个典型的 Web 服务,挂载了一个简单的路由。我们的线上服务一般也是从这样简单的服务开始逐渐拓展开去的。
|
||||||
|
|
||||||
现在突然来了一个新的需求,我们想要统计之前写的 hello 服务的处理耗时,需求很简单,我们对上面的程序进行少量修改:
|
现在突然来了一个新的需求,我们想要统计之前写的 hello 服务的处理耗时,需求很简单,我们对上面的程序进行少量修改:
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ func helloHandler(wr http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
修改到这里,本能地发现我们的开发工作开始陷入了泥潭。无论未来对我们的这个 web 系统有任何其它的非功能或统计需求,我们的修改必然牵一发而动全身。只要增加一个非常简单的非业务统计,我们就需要去几十个 handler 里增加这些业务无关的代码。虽然一开始我们似乎并没有做错,但是显然随着业务的发展,我们的行事方式让我们陷入了代码的泥潭。
|
修改到这里,本能地发现我们的开发工作开始陷入了泥潭。无论未来对我们的这个 Web 系统有任何其它的非功能或统计需求,我们的修改必然牵一发而动全身。只要增加一个非常简单的非业务统计,我们就需要去几十个 handler 里增加这些业务无关的代码。虽然一开始我们似乎并没有做错,但是显然随着业务的发展,我们的行事方式让我们陷入了代码的泥潭。
|
||||||
|
|
||||||
## 5.3.2 使用 middleware 剥离非业务逻辑
|
## 5.3.2 使用 middleware 剥离非业务逻辑
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ throttler.go
|
|||||||
=> 通过定长大小的 channel 存储 token,并通过这些 token 对接口进行限流
|
=> 通过定长大小的 channel 存储 token,并通过这些 token 对接口进行限流
|
||||||
```
|
```
|
||||||
|
|
||||||
每一个 web 框架都会有对应的 middleware 组件,如果你有兴趣,也可以向这些项目贡献有用的 middleware,只要合理一般项目的维护人也愿意合并你的 pull request。
|
每一个 Web 框架都会有对应的 middleware 组件,如果你有兴趣,也可以向这些项目贡献有用的 middleware,只要合理一般项目的维护人也愿意合并你的 pull request。
|
||||||
|
|
||||||
比如开源界很火的 gin 这个框架,就专门为用户贡献的 middleware 开了一个仓库:
|
比如开源界很火的 gin 这个框架,就专门为用户贡献的 middleware 开了一个仓库:
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
实际上这是一个语言无关的场景,需要进行字段校验的情况有很多,web 系统的 Form/json 提交只是一个典型的例子。我们用 go 来写一个类似上图的校验 demo。然后研究怎么一步步对其进行改进。
|
实际上这是一个语言无关的场景,需要进行字段校验的情况有很多,Web系统的form/json提交只是一个典型的例子。我们用go来写一个类似上图的校验demo。然后研究怎么一步步对其进行改进。
|
||||||
|
|
||||||
## 5.4.1 重构请求校验函数
|
## 5.4.1 重构请求校验函数
|
||||||
|
|
||||||
@ -233,6 +233,6 @@ func main() {
|
|||||||
|
|
||||||
这里我们简单地对 eq=x 和 email 这两个 tag 进行了支持,读者可以对这个程序进行简单的修改以查看具体的 validate 效果。为了演示精简掉了错误处理和复杂 case 的处理,例如 reflect.Int8/16/32/64,reflect.Ptr 等类型的处理,如果给生产环境编写 validate 库的话,请务必做好功能的完善和容错。
|
这里我们简单地对 eq=x 和 email 这两个 tag 进行了支持,读者可以对这个程序进行简单的修改以查看具体的 validate 效果。为了演示精简掉了错误处理和复杂 case 的处理,例如 reflect.Int8/16/32/64,reflect.Ptr 等类型的处理,如果给生产环境编写 validate 库的话,请务必做好功能的完善和容错。
|
||||||
|
|
||||||
在前一小节中介绍的 validator 组件在功能上要远比我们这里的 demo 复杂的多。但原理很简单,就是用 reflect 对 struct 进行树形遍历。有心的读者这时候可能会产生一个问题,我们对 struct 进行 validate 时大量使用了 reflect,而 go 的 reflect 在性能上不太出众,有时甚至会影响到我们程序的性能。这样的考虑确实有一些道理,但需要对 struct 进行大量校验的场景往往出现在 web 服务,这里并不一定是程序的性能瓶颈所在,实际的效果还是要从 pprof 中做更精确的判断。
|
在前一小节中介绍的 validator 组件在功能上要远比我们这里的 demo 复杂的多。但原理很简单,就是用 reflect 对 struct 进行树形遍历。有心的读者这时候可能会产生一个问题,我们对 struct 进行 validate 时大量使用了 reflect,而 go 的 reflect 在性能上不太出众,有时甚至会影响到我们程序的性能。这样的考虑确实有一些道理,但需要对 struct 进行大量校验的场景往往出现在 Web 服务,这里并不一定是程序的性能瓶颈所在,实际的效果还是要从 pprof 中做更精确的判断。
|
||||||
|
|
||||||
如果基于反射的 validator 真的成为了你服务的性能瓶颈怎么办?现在也有一种思路可以避免反射:使用 golang 内置的 parser 对源代码进行扫描,然后根据 struct 的定义生成校验代码。我们可以将所有需要校验的结构体放在单独的 package 内。这就交给读者自己去探索了。
|
如果基于反射的 validator 真的成为了你服务的性能瓶颈怎么办?现在也有一种思路可以避免反射:使用 golang 内置的 parser 对源代码进行扫描,然后根据 struct 的定义生成校验代码。我们可以将所有需要校验的结构体放在单独的 package 内。这就交给读者自己去探索了。
|
||||||
|
@ -108,7 +108,7 @@ func main() {
|
|||||||
|
|
||||||
## 5.5.2 提高生产效率的 ORM 和 SQL Builder
|
## 5.5.2 提高生产效率的 ORM 和 SQL Builder
|
||||||
|
|
||||||
在 web 开发领域常常提到的 ORM 是什么?我们先看看万能的维基百科:
|
在Web开发领域常常提到的ORM是什么?我们先看看万能的维基百科:
|
||||||
|
|
||||||
```
|
```
|
||||||
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),
|
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
计算机程序可依据其瓶颈分为 Disk IO-bound,CPU-bound,Network-bound,分布式场景下有时候也会外部系统而导致自身瓶颈。
|
计算机程序可依据其瓶颈分为 Disk IO-bound,CPU-bound,Network-bound,分布式场景下有时候也会外部系统而导致自身瓶颈。
|
||||||
|
|
||||||
web 系统打交道最多的是网络,无论是接收,解析用户请求,访问存储,还是把响应数据返回给用户,都是要走网络的。在没有 epoll/kqueue 之类的系统提供的 IO 多路复用接口之前,多个核心的现代计算机最头痛的是 C10k 问题,C10k 问题会导致计算机没有办法充分利用 CPU 来处理更多的用户连接,进而没有办法通过优化程序提升 CPU 利用率来处理更多的请求。
|
Web 系统打交道最多的是网络,无论是接收,解析用户请求,访问存储,还是把响应数据返回给用户,都是要走网络的。在没有 epoll/kqueue 之类的系统提供的 IO 多路复用接口之前,多个核心的现代计算机最头痛的是 C10k 问题,C10k 问题会导致计算机没有办法充分利用 CPU 来处理更多的用户连接,进而没有办法通过优化程序提升 CPU 利用率来处理更多的请求。
|
||||||
|
|
||||||
自从 linux 实现了 epoll,freebsd 实现了 kqueue,这个问题基本解决了,我们可以借助内核提供的 API 轻松解决当年的 C10k 问题,也就是说如今如果你的程序主要是和网络打交道,那么瓶颈一定在用户程序而不在操作系统内核。
|
自从 Linux 实现了 epoll,FreeBSD 实现了 kqueue,这个问题基本解决了,我们可以借助内核提供的 API 轻松解决当年的 C10k 问题,也就是说如今如果你的程序主要是和网络打交道,那么瓶颈一定在用户程序而不在操作系统内核。
|
||||||
|
|
||||||
随着时代的发展,编程语言对这些系统调用又进一步进行了封装,如今做应用层开发,几乎不会在程序中看到 epoll 之类的字眼,大多数时候我们就只要聚焦在业务逻辑上就好。Go 的 net 库针对不同平台封装了不同的 syscall API,http 库又是构建在 net 库之上,所以在 Go 我们可以借助标准库,很轻松地写出高性能的 http 服务,下面是一个简单的 `hello world` 服务的代码:
|
随着时代的发展,编程语言对这些系统调用又进一步进行了封装,如今做应用层开发,几乎不会在程序中看到 epoll 之类的字眼,大多数时候我们就只要聚焦在业务逻辑上就好。Go 的 net 库针对不同平台封装了不同的 syscall API,http 库又是构建在 net 库之上,所以在 Go 我们可以借助标准库,很轻松地写出高性能的 http 服务,下面是一个简单的 `hello world` 服务的代码:
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
我们需要衡量一下这个 web 服务的吞吐量,再具体一些,实际上就是接口的 QPS。借助 wrk,在家用电脑 Macbook Pro 上对这个 `hello world` 服务进行基准测试,Mac 的硬件情况如下:
|
我们需要衡量一下这个Web服务的吞吐量,再具体一些,实际上就是接口的QPS。借助wrk,在家用电脑 Macbook Pro上对这个 `hello world` 服务进行基准测试,Mac的硬件情况如下:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
CPU: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
|
CPU: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz
|
||||||
@ -87,7 +87,7 @@ Requests/sec: 45118.57
|
|||||||
Transfer/sec: 5.51MB
|
Transfer/sec: 5.51MB
|
||||||
```
|
```
|
||||||
|
|
||||||
多次测试的结果在 4w 左右的 QPS 浮动,响应时间最多也就是 40ms 左右,对于一个 web 程序来说,这已经是很不错的成绩了,我们只是照抄了别人的示例代码,就完成了一个高性能的 `hello world` 服务器,是不是很有成就感?
|
多次测试的结果在 4w 左右的 QPS 浮动,响应时间最多也就是 40ms 左右,对于一个Web程序来说,这已经是很不错的成绩了,我们只是照抄了别人的示例代码,就完成了一个高性能的 `hello world` 服务器,是不是很有成就感?
|
||||||
|
|
||||||
这还只是家用 PC,线上服务器大多都是 24 核心起,32G 内存+,CPU 基本都是 Intel i7。所以同样的程序在服务器上运行会得到更好的结果。
|
这还只是家用 PC,线上服务器大多都是 24 核心起,32G 内存+,CPU 基本都是 Intel i7。所以同样的程序在服务器上运行会得到更好的结果。
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ Transfer/sec: 5.51MB
|
|||||||
|
|
||||||
对于 IO/Network bound 类的程序,其表现是网卡/磁盘 IO 会先于 CPU 打满,这种情况即使优化 CPU 的使用也不能提高整个系统的吞吐量,只能提高磁盘的读写速度,增加内存大小,提升网卡的带宽来提升整体性能。而 CPU bound 类的程序,则是在存储和网卡未打满之前 CPU 占用率提前到达 100%,CPU 忙于各种计算任务,IO 设备相对则较闲。
|
对于 IO/Network bound 类的程序,其表现是网卡/磁盘 IO 会先于 CPU 打满,这种情况即使优化 CPU 的使用也不能提高整个系统的吞吐量,只能提高磁盘的读写速度,增加内存大小,提升网卡的带宽来提升整体性能。而 CPU bound 类的程序,则是在存储和网卡未打满之前 CPU 占用率提前到达 100%,CPU 忙于各种计算任务,IO 设备相对则较闲。
|
||||||
|
|
||||||
无论哪种类型的服务,在资源使用到极限的时候都会导致请求堆积,超时,系统 hang 死,最终伤害到终端用户。对于分布式的 web 服务来说,瓶颈还不一定总在系统内部,也有可能在外部。非计算密集型的系统往往会在关系型数据库环节失守,而这时候 web 模块本身还远远未达到瓶颈。
|
无论哪种类型的服务,在资源使用到极限的时候都会导致请求堆积,超时,系统 hang 死,最终伤害到终端用户。对于分布式的 Web 服务来说,瓶颈还不一定总在系统内部,也有可能在外部。非计算密集型的系统往往会在关系型数据库环节失守,而这时候 Web 模块本身还远远未达到瓶颈。
|
||||||
|
|
||||||
不管我们的服务瓶颈在哪里,最终要做的事情都是一样的,那就是流量限制。
|
不管我们的服务瓶颈在哪里,最终要做的事情都是一样的,那就是流量限制。
|
||||||
|
|
||||||
@ -261,6 +261,6 @@ cur = cur > cap ? cap : cur
|
|||||||
|
|
||||||
前面我们说了很多 CPU-bound、IO-bound 之类的概念,这种性能瓶颈从大多数公司都有的监控系统中可以比较快速地定位出来,如果一个系统遇到了性能问题,那监控图的反应一般都是最快的。
|
前面我们说了很多 CPU-bound、IO-bound 之类的概念,这种性能瓶颈从大多数公司都有的监控系统中可以比较快速地定位出来,如果一个系统遇到了性能问题,那监控图的反应一般都是最快的。
|
||||||
|
|
||||||
虽然性能指标很重要,但对用户提供服务时还应考虑服务整体的 QoS。QoS 全称是 Quality of Service,顾名思义是服务质量。QoS 包含有可用性、吞吐量、时延、时延变化和丢失等指标。一般来讲我们可以通过优化系统,来提高 web 服务的 CPU 利用率,从而提高整个系统的吞吐量。但吞吐量提高的同时,用户体验是有可能变差的。用户角度比较敏感的除了可用性之外,还有时延。虽然你的系统吞吐量高,但半天刷不开页面,想必会造成大量的用户流失。所以在大公司的 web 服务性能指标中,除了平均响应时延之外,还会把响应时间的 95 分位,99 分位也拿出来作为性能标准。平均响应在提高 CPU 利用率没受到太大影响时,可能 95 分位、 99 分位的响应时间大幅度攀升了,那么这时候就要考虑提高这些 CPU 利用率所付出的代价是否值得了。
|
虽然性能指标很重要,但对用户提供服务时还应考虑服务整体的 QoS。QoS 全称是 Quality of Service,顾名思义是服务质量。QoS 包含有可用性、吞吐量、时延、时延变化和丢失等指标。一般来讲我们可以通过优化系统,来提高 Web 服务的 CPU 利用率,从而提高整个系统的吞吐量。但吞吐量提高的同时,用户体验是有可能变差的。用户角度比较敏感的除了可用性之外,还有时延。虽然你的系统吞吐量高,但半天刷不开页面,想必会造成大量的用户流失。所以在大公司的 Web 服务性能指标中,除了平均响应时延之外,还会把响应时间的 95 分位,99 分位也拿出来作为性能标准。平均响应在提高 CPU 利用率没受到太大影响时,可能 95 分位、 99 分位的响应时间大幅度攀升了,那么这时候就要考虑提高这些 CPU 利用率所付出的代价是否值得了。
|
||||||
|
|
||||||
在线系统的机器一般都会保持 CPU 有一定的余裕。
|
在线系统的机器一般都会保持 CPU 有一定的余裕。
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 5.7 layout 常见大型 web 项目分层
|
# 5.7 layout 常见大型 Web 项目分层
|
||||||
|
|
||||||
流行的 web 框架大多数是 MVC 框架,MVC 这个概念最早由 Trygve Reenskaug 在 1978 年提出,为了能够对 GUI 类型的应用进行方便扩展,将程序划分为:
|
流行的 Web 框架大多数是 MVC 框架,MVC 这个概念最早由 Trygve Reenskaug 在 1978 年提出,为了能够对 GUI 类型的应用进行方便扩展,将程序划分为:
|
||||||
|
|
||||||
1. 控制器(Controller)- 负责转发请求,对请求进行处理。
|
1. 控制器(Controller)- 负责转发请求,对请求进行处理。
|
||||||
2. 视图(View) - 界面设计人员进行图形界面设计。
|
2. 视图(View) - 界面设计人员进行图形界面设计。
|
||||||
@ -126,7 +126,7 @@ type FeatureSetParams struct {
|
|||||||
|
|
||||||
既然工作流已经成型,我们可以琢磨一下怎么让整个流程对用户更加友好。
|
既然工作流已经成型,我们可以琢磨一下怎么让整个流程对用户更加友好。
|
||||||
|
|
||||||
比如在前面的生成环境引入 GUI 或者 web 页面,只要让用户点点鼠标就能生成 SDK,这些就靠读者自己去探索了。
|
比如在前面的生成环境引入 GUI 或者 Web 页面,只要让用户点点鼠标就能生成 SDK,这些就靠读者自己去探索了。
|
||||||
|
|
||||||
虽然我们成功地使自己的项目在入口支持了多种交互协议,但是还有一些问题没有解决。本节中所叙述的分层没有将 middleware 作为项目的分层考虑进去。如果我们考虑 middleware 的话,请求的流程是什么样的?
|
虽然我们成功地使自己的项目在入口支持了多种交互协议,但是还有一些问题没有解决。本节中所叙述的分层没有将 middleware 作为项目的分层考虑进去。如果我们考虑 middleware 的话,请求的流程是什么样的?
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 5.8 接口和表驱动开发
|
# 5.8 接口和表驱动开发
|
||||||
|
|
||||||
在 web 项目中经常会遇到外部依赖环境的变化,比如:
|
在 Web 项目中经常会遇到外部依赖环境的变化,比如:
|
||||||
|
|
||||||
1. 公司的老存储系统年久失修,现在已经没有人维护了,新的系统上线也没有考虑平滑迁移,但最后通牒已下,要求 N 天之内迁移完毕。
|
1. 公司的老存储系统年久失修,现在已经没有人维护了,新的系统上线也没有考虑平滑迁移,但最后通牒已下,要求 N 天之内迁移完毕。
|
||||||
2. 平台部门的老用户系统年久失修(怎么都是年久失修,摔!),现在已经没有人维护了,真是悲伤的故事。新系统上线没有考虑兼容老接口,但最后通牒已下,要求 N 个月之内迁移完毕。
|
2. 平台部门的老用户系统年久失修(怎么都是年久失修,摔!),现在已经没有人维护了,真是悲伤的故事。新系统上线没有考虑兼容老接口,但最后通牒已下,要求 N 个月之内迁移完毕。
|
||||||
|
@ -50,7 +50,7 @@ func passed() bool {
|
|||||||
3. 按百分比发布
|
3. 按百分比发布
|
||||||
4. 按白名单发布
|
4. 按白名单发布
|
||||||
5. 按业务线发布
|
5. 按业务线发布
|
||||||
6. 按 UA 发布(app、web、pc)
|
6. 按 UA 发布(APP、Web、PC)
|
||||||
7. 按分发渠道发布
|
7. 按分发渠道发布
|
||||||
|
|
||||||
因为和公司的业务相关,所以城市、业务线、UA、分发渠道这些都可能会被直接编码在系统里,不过功能其实大同小异。
|
因为和公司的业务相关,所以城市、业务线、UA、分发渠道这些都可能会被直接编码在系统里,不过功能其实大同小异。
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# 5.10 补充说明
|
# 5.10 补充说明
|
||||||
|
|
||||||
现代的软件工程是离不开 web 的,广义地来讲,web 甚至可以不用非得基于 http 协议。只要是 CS 或者 BS 架构,都可以认为是 web 系统。
|
现代的软件工程是离不开 Web 的,广义地来讲,Web 甚至可以不用非得基于 http 协议。只要是 CS 或者 BS 架构,都可以认为是 Web 系统。
|
||||||
|
|
||||||
即使是在看起来非常封闭的游戏系统里,因为玩家们与日俱增的联机需求,也同样会涉及到远程通信,这里面也会涉及到很多 web 方面的技术。
|
即使是在看起来非常封闭的游戏系统里,因为玩家们与日俱增的联机需求,也同样会涉及到远程通信,这里面也会涉及到很多 Web 方面的技术。
|
||||||
|
|
||||||
所以这个时代,web 编程是一个程序员所必须接触的知识领域。无论你的目标是成为架构师,是去创业,或是去当技术顾问。web 方面的知识都会成为你的硬通货。
|
所以这个时代,Web 编程是一个程序员所必须接触的知识领域。无论你的目标是成为架构师,是去创业,或是去当技术顾问。Web 方面的知识都会成为你的硬通货。
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 第5章 go 和 web
|
# 第5章 go 和 Web
|
||||||
|
|
||||||
本章将会阐述 go 在 web 开发方面的现状,并以几个典型的开源 web 框架为例,带大家深入 web 框架本身的执行流程。
|
本章将会阐述 go 在 Web 开发方面的现状,并以几个典型的开源 Web 框架为例,带大家深入 Web 框架本身的执行流程。
|
||||||
|
|
||||||
同时会介绍现代企业级 web 开发面临的一些问题,以及在 go 中如何面对,并解决这些问题。
|
同时会介绍现代企业级 Web 开发面临的一些问题,以及在 go 中如何面对,并解决这些问题。
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# 6.4 分布式搜索引擎
|
# 6.4 分布式搜索引擎
|
||||||
|
|
||||||
在 web 一章中,我们提到 MySQL 很脆弱。数据库系统本身要保证实时和强一致性,所以其功能设计上都是为了满足这种一致性需求。比如 write ahead log 的设计,基于 B+ 树实现的索引和数据组织,以及基于 MVCC 实现的事务等等。
|
在Web一章中,我们提到 MySQL 很脆弱。数据库系统本身要保证实时和强一致性,所以其功能设计上都是为了满足这种一致性需求。比如 write ahead log 的设计,基于 B+ 树实现的索引和数据组织,以及基于 MVCC 实现的事务等等。
|
||||||
|
|
||||||
关系型数据库一般被用于实现 OLTP 系统,所谓 OLTP,援引 wikipedia:
|
关系型数据库一般被用于实现 OLTP 系统,所谓 OLTP,援引 wikipedia:
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
除业务线管理之外,很多互联网公司会按照城市来铺展自己的业务。在某个城市未开城之前,理论上所有模块都应该认为带有该城市 id 的数据是脏数据并自动过滤掉。而如果业务开城,在系统中就应该自己把这个新的城市 id 自动加入到白名单中。这样业务流程便可以自动运转。
|
除业务线管理之外,很多互联网公司会按照城市来铺展自己的业务。在某个城市未开城之前,理论上所有模块都应该认为带有该城市 id 的数据是脏数据并自动过滤掉。而如果业务开城,在系统中就应该自己把这个新的城市 id 自动加入到白名单中。这样业务流程便可以自动运转。
|
||||||
|
|
||||||
再举个例子,互联网公司的运营系统中会有各种类型的运营活动,有些运营活动推出后可能出现了超出预期的事件(比如公关危机),需要紧急将系统下线。这时候会用到一些开关来快速关闭相应的功能。或者快速将想要剔除的活动 id 从白名单中剔除。在 web 章节中的 ab test 一节中,我们也提到,有时需要有这样的系统来告诉我们当前需要放多少流量到相应的功能代码上。我们可以像那一节中,使用远程 rpc 来获知这些信息,但同时,也可以结合分布式配置系统,主动地拉取到这些信息。
|
再举个例子,互联网公司的运营系统中会有各种类型的运营活动,有些运营活动推出后可能出现了超出预期的事件(比如公关危机),需要紧急将系统下线。这时候会用到一些开关来快速关闭相应的功能。或者快速将想要剔除的活动 id 从白名单中剔除。在 Web 章节中的 ab test 一节中,我们也提到,有时需要有这样的系统来告诉我们当前需要放多少流量到相应的功能代码上。我们可以像那一节中,使用远程 rpc 来获知这些信息,但同时,也可以结合分布式配置系统,主动地拉取到这些信息。
|
||||||
|
|
||||||
## 6.6.2 使用 etcd 实现配置更新
|
## 6.6.2 使用 etcd 实现配置更新
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user