Go小课02:第一次Say Hello

2,395 阅读7分钟

头图

一、Say Hello请求

1、环境配置
  • 安装Go的包依赖管理命令行工具govendor
go get -u github.com/kardianos/govendor
  • 创建项目文件夹
mkdir -p $GOPATH/src/github.com/yourusername/project && cd "$_"
  • govendor初始化
govendor init
  • 获取go的web框架gin
govendor fetch github.com/gin-gonic/gin
  • 在项目根路径下,新建文件main.go文件
2、Get请求和Post请求代码
//main.go
package main

import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func main() {

	// gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数,gin把request和response都封装到gin.Context的上下文环境。
	// 最后启动路由的Run方法监听端口。
	router := gin.Default()

	// get请求
	router.GET("/user/welcome", func(c *gin.Context) {
		//获取url中的参数信息
		namestr := c.DefaultQuery("name", "Guest")
		agestr := c.DefaultQuery("age", "18")

		//各种处理逻辑

		ageValue, err := strconv.Atoi(agestr)
		role := "小伙子"
		if err != nil {
			role = "可疑的人"
		} else {
			if ageValue > 28 {
				role = "大哥"
			}
		}

		//返回
		c.String(http.StatusOK, "%s %s,你好呀", role, namestr)
	})

	//curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=18

	router.POST("/user/postdata", func(c *gin.Context) {

		message := c.PostForm("message")

		namestr := c.DefaultPostForm("name", "anonymous")
		agestr := c.DefaultPostForm("age", "18")

		//各种处理逻辑
		ageValue, err := strconv.Atoi(agestr)
		role := "小伙子"
		if err != nil {
			role = "可疑的人"
		} else {
			if ageValue > 28 {
				role = "大哥"
			}
		}

		message = role + namestr + " 你好呀"

		c.JSON(http.StatusOK, gin.H{
			"status": gin.H{
				"code":   http.StatusOK,
				"status": "ok",
			},
			"data": gin.H{
				"message": message,
			},
		})
	})

	//curl -X POST http://127.0.0.1:8000/user/postdata -H "Content-Type:application/x-www-form-urlencoded" -d "name=jack&age=28" | python -m json.tool
	router.Run(":8000")
}

说明:启动服务后,可以用curl命令工具来发请求,也可以用Postman来发请求,借此来测试接口是否可以访问。

二、接口联调

1、GET请求联调
  • 接口名:/user/welcome

  • 端口:80000

curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=38
大哥 jack,你好呀**%**                                                              
curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=18
小伙子 jack,你好呀**%**                                                            
curl http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=hhah
可疑的人 jack,你好呀**%** 

说明:可以使用本机的ip地址。

2、POST请求联调
  • 接口名:/user/postdata

  • 端口:80000

curl -X POST http://127.0.0.1:8000/user/postdata -H "Content-Type:application/x-www-form-urlencoded" -d "name=jack&age=28" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   123  100   107  100    16   104k  16000 --:--:-- --:--:-- --:--:--  120k
{
    "data": {
        "message": "54GP5b+O57Su54Cb5oGTYWNrIOa1o+eKsuOCvemNm+KCrA=="
    },
    "status": {
        "code": 200,
        "status": "ok"
    }
}

三、接口性能测试

1、接口性能指标

衡量接口性能可以从下面几个指标来看:

  • QPS(TPS):每秒钟 Request/事务 数量,在互联网领域,指每秒响应请求数(指http请求)

  • 事务: 用户某一步或几步操作的集合,我们要保证它有一个完整意义。比如用户对某一个页面的一次请求,用户对某系统的一次登录,淘宝用户对商品的一次确认支付过程。这些我们都可以看作一个事务

  • 响应时间:系统对一个请求做出响应的平均时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间(我认为这里应该仅包含处理时间,网络传输时间忽略)

  • 并发数:系统同时处理的request / 事务数

  • 吞吐量:单位时间内处理的请求数量(通常由QPS与并发数决定);

参考:Web开发中,什么级别才算是高并发

2、性能测试工具安装

​ 我们将利用wrk工具简单测试下接口的QPS和响应时间,wrk是轻量级的 HTTP 性能测试工具,在这里我们使用wrk简单测试下我们刚写接口的QPS和响应时间

#安装
brew install wrk
3、接口性能测试
#以get请求为例
wrk -t4 -c1000 -d30s -T30s --latency http://127.0.0.1:8000/user/welcome\?name\=jack\&age\=38
Running 30s test @ http://127.0.0.1:8000/user/welcome?name=jack&age=38
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.48ms    1.15ms  36.87ms   80.11%
    Req/Sec    24.44k     2.48k   33.65k    72.67%
  Latency Distribution
     50%    2.33ms
     75%    2.81ms
     90%    3.71ms
     99%    6.53ms
  2919566 requests in 30.03s, 389.80MB read
  Socket errors: connect 751, read 89, write 0, timeout 0
Requests/sec:  97228.78
Transfer/sec:     12.98MB

解释1:用4个线程来模拟1000个并发连接,整个测试持续30秒,连接超时30秒,打印出请求的延迟统计信息。 wrk 使用异步非阻塞的 io,并不是用线程去模拟并发连接,因此不需要设置很多的线程,一般根据 CPU 的核心数量设置即可。(网络通信不会阻塞线程执行,用很少的线程模拟大量网路连接)

解释2:

- Socket errors socket 错误的数量
- Requests/sec 每秒请求数量,也就是并发能力
- Latency 响应时间
Avg:平均、Max:最大、Stdev:标准差、+/- Stdev: 正负一个标准差占比
4、性能说明
  • 使用Go写一个简单接口,在我们的开发机器上的QPS大约是9w7左右,哈哈,我们也算写了一个"高性能,低延迟"接口,当然这归功于优秀的go语言的并发处理能力和web框架gin

  • 实际的后台接口的延迟不可能这么快,它涉及到业务处理,服务间调用,复杂的网络环境,真实的QPS远远没有这么夸张。

#测试百度首页的QPS和Latency
wrk -t4 -c1000 -d30s -T30s https://www.baidu.com
Running 30s test @ https://www.baidu.com
  4 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   196.47ms   44.94ms   1.18s    88.71%
    Req/Sec   310.42     59.95   494.00     69.06%
  37095 requests in 30.09s, 548.20MB read
  Socket errors: connect 754, read 111, write 0, timeout 0
Requests/sec:   1232.74
Transfer/sec:     18.22MB

四、小课堂

1、govendor
  • 基于 vendor 机制实现的 Go 包依赖管理命令行工具。与原生 vendor 无侵入性融合,也支持从其他依赖管理工具迁移,可以很方便的实现同一个包在不同项目中不同版本、以及无相互侵入的开发和管理。

  • 最开始的时候,Go 并没有提供较为妥当的包管理工具。从 1.5 版本开始提供了 vendor 特性,但需要手动设置环境变量 GO15VENDOREXPERIMENT=1

  • 在执行 go buildgo run 命令时,会按照以下顺序去查找包:

    • 当前包下的 vendor 目录
    • 向上级目录查找,直到找到 src 下的 vendor 目录
    • 在 GOROOT 目录下查找
    • 在 GOPATH 下面查找依赖包
  • 在发布 1.6 版本时,该环境变量的值已经默认设置为 1 了,该值可以使用 go env 命令查看;在发布 1.7 版本时,已去掉该环境变量,默认开启 vendor 特性。

  • govendor一些常用命令如下:

//初始化
govendor init

//将已被引用且在 $GOPATH 下的所有包复制到 vendor 目录
govendor add +external

//仅从 $GOPATH 中复制指定包
govendor add gopkg.in/yaml.v2

//列出代码中所有被引用到的包及其状态
govendor list

//列出一个包被哪些包引用
govendor list -v fmt

//从远程仓库添加或更新某个包(不会在 $GOPATH 也存一份)
govendor fetch golang.org/x/net/context

//安装指定版本的包,eg:
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55
govendor fetch golang.org/x/net/context@v1   # Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@=v1  # Get the tag or branch named "v1".

参考Go 包依赖管理工具 —— govendor

2、C10k问题
  • C10K 是 Client 10000 问题,即「在同时连接到服务器的客户端数量超过 10000 个的环境中,即便硬件性能足够, 依然无法正常提供服务」,简而言之,就是单机1万个并发连接问题。

  • 计算机程序可依据其瓶颈分为磁盘IO瓶颈型,CPU计算瓶颈型,网络带宽瓶颈型,分布式场景下有时候也会外部系统而导致自身瓶颈。

  • Web系统打交道最多的是网络,无论是接收,解析用户请求,访问存储,还是把响应数据返回给用户,都是要走网络的。在没有epoll/kqueue之类的系统提供的IO多路复用接口之前,多个核心的现代计算机最头痛的是C10k问题,C10k问题会导致计算机没有办法充分利用CPU来处理更多的用户连接,进而没有办法通过优化程序提升CPU利用率来处理更多的请求。

  • 自从Linux实现了epoll,FreeBSD实现了kqueue,这个问题基本解决了,我们可以借助内核提供的API轻松解决当年的C10k问题,也就是说如今如果你的程序主要是和网络打交道,那么瓶颈一定在用户程序而不在操作系统内核。

  • 随着时代的发展,编程语言对这些系统调用又进一步进行了封装,如今做应用层开发,几乎不会在程序中看到epoll之类的字眼,大多数时候我们就只要聚焦在业务逻辑上就好。Go 的 net 库针对不同平台封装了不同的syscall API,http库又是构建在net库之上,所以在Go语言中我们可以借助标准库,很轻松地写出高性能的http服务

参考程序员怎么会不知道 C10K 问题呢?