searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Go:当net/http包出现性能瓶颈,可以试试使用fasthttp包

2023-06-12 07:32:49
110
0

一、  基本介绍

当使用go进行http或者https请求时,我们立刻可以想到的就是官方提供的net/http包。而valyala公司编写推出了一种新的http包——fasthttp,并且其宣称比net/http要快十倍。正如其名,快是valyala公司非常看重的点,经过几次的更新迭代,该包也越来越多地被使用。

二、  相互对比

net/http:

说明:在net/http包中,如果服务器收到一个请求,就会accept一个conn,然后创建一个协程(worker)来处理该请求,期间worker会一直保持,请求处理完后worker会被删除,即连接关闭,请求完成。

fasthttp:

说明:在fasthttp包中,如果一个服务器监听到一个请求,那么Workerpool会从ready状态的channel中取出一个。被取出之后,这个请求conn将会被accept到该channel中。worker goroutine检测到channel有conn之后,会接收并处理该请求。请求处理完成之后,worker goroutine并不会退出,而是将该channel重新放回到Workerpool中,等待下一次的复用。

实际上,fasthttp的实现使用到了池的概念,就像线程池、连接池一样,只不过其是协程池的实现。当有请求来到时,会从池子中取出空闲的协程来处理,处理完成后不会立刻退出协程,而是交还给pool来管理,这样就实现了复用协程,这也是速度比net/http快的一个重要原因。

三、  安装

go get -u github.com/valyala/fasthttp

四、  使用例子

简单的get请求

url := "https://www.ctyun.cn/developer"
code, body, err := fasthttp.Get(nil, url)
if err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}
if code != fasthttp.StatusOK {
	fmt.Println("请求错误: ", code)
	return err
}
fmt.Println(body)

简单的post请求

url := "https://www.ctyun.cn/developer"
//定义请求参数
args := fasthttp.Args{}
args.Set("name", "zhans")
args.Set("age", "25")
args.Set("type", "code")
	
code, body, err := fasthttp.Post(nil, url, &args)
if err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}
if code != fasthttp.StatusOK {
	fmt.Println("请求错误: ", code)
		return err
}
fmt.Println(body)

设置请求头/请求体

url := "https://www.ctyun.cn/developer"
//定义请求
req := fasthttp.Request{}
//设置请求url
req.SetRequestURI(url)
//设置请求体
body := []byte(`{"name": "ctyun"}`)
req.SetBody(body)

//设置请求头
req.Header.SetMethod("POST")
//默认是application/x-www-form-urlencoded
req.Header.SetContentType("application/json")

resp := fasthttp.Response{}
client := fasthttp.Client{}
if err := client.Do(&req, &resp); err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}

fmt.Println(resp.Body())

从协程池获取request和response

设置client对象

func getHttpClient() *fasthttp.Client {
	return &fasthttp.Client{
		// 读超时时间,不设置read超时,可能会造成连接复用失效
		ReadTimeout: time.Second * 5,
		// 写超时时间
		WriteTimeout: time.Second * 5,
		// 5秒后,关闭空闲的活动连接
		MaxIdleConnDuration: time.Second * 5,
		// 当true时,从请求中去掉User-Agent标头
		NoDefaultUserAgentHeader: true,
		// 当true时,header中的key按照原样传输,默认会根据标准化转化
		DisableHeaderNamesNormalizing: true,
		//当true时,路径按原样传输,默认会根据标准化转化
		DisablePathNormalizing: true,
		Dial: (&fasthttp.TCPDialer{
			// 最大并发数,0表示无限制
			Concurrency: 4096,
			// 将 DNS 缓存时间从默认分钟增加到一小时
			DNSCacheDuration: time.Hour,
		}).Dial,
	}
}

get请求

func HttpGet(uri string, arg *fasthttp.Args) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()
   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()
	req.Header.SetMethod(fasthttp.MethodGet)
	req.SetRequestURI(url)

   // 设置请求参数
	if arg != nil {
		req.URI().SetQueryString(arg.String())
	}
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

post请求(application/x-www-form-urlencoded)

func HttpPostData(uri string, args *fasthttp.Args) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()

   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()

	req.Header.SetMethod(fasthttp.MethodPost)
	req.SetRequestURI(url)
	req.Header.SetContentType("application/x-www-form-urlencoded")

   // 获取请求体输出流,并将请求参数写入
	if _, err := args.WriteTo(req.BodyWriter()); err != nil {
		return "", err
	}

   // 发起请求
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

post请求(application/json)

func HttpPostJson(uri string, data []byte) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()

   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()

	req.Header.SetMethod(fasthttp.MethodPost)
	req.SetRequestURI(url)
	req.Header.SetContentType("application/json")
	req.SetBody(data)

   // 发起请求
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

五、  总结

fasthttp通过复用来提高了处理的性能和效率,尤其是对服务协程以及内存对象的复用,节省了大量的资源分配成本,同时也大量使用了sync.Pool,资源得到了合理的运用。所以,如果您在使用net/http包的过程中出现了性能瓶颈突破不了的话,不妨考虑一下fasthttp

 

0条评论
0 / 1000
g****n
2文章数
0粉丝数
g****n
2 文章 | 0 粉丝
g****n
2文章数
0粉丝数
g****n
2 文章 | 0 粉丝
原创

Go:当net/http包出现性能瓶颈,可以试试使用fasthttp包

2023-06-12 07:32:49
110
0

一、  基本介绍

当使用go进行http或者https请求时,我们立刻可以想到的就是官方提供的net/http包。而valyala公司编写推出了一种新的http包——fasthttp,并且其宣称比net/http要快十倍。正如其名,快是valyala公司非常看重的点,经过几次的更新迭代,该包也越来越多地被使用。

二、  相互对比

net/http:

说明:在net/http包中,如果服务器收到一个请求,就会accept一个conn,然后创建一个协程(worker)来处理该请求,期间worker会一直保持,请求处理完后worker会被删除,即连接关闭,请求完成。

fasthttp:

说明:在fasthttp包中,如果一个服务器监听到一个请求,那么Workerpool会从ready状态的channel中取出一个。被取出之后,这个请求conn将会被accept到该channel中。worker goroutine检测到channel有conn之后,会接收并处理该请求。请求处理完成之后,worker goroutine并不会退出,而是将该channel重新放回到Workerpool中,等待下一次的复用。

实际上,fasthttp的实现使用到了池的概念,就像线程池、连接池一样,只不过其是协程池的实现。当有请求来到时,会从池子中取出空闲的协程来处理,处理完成后不会立刻退出协程,而是交还给pool来管理,这样就实现了复用协程,这也是速度比net/http快的一个重要原因。

三、  安装

go get -u github.com/valyala/fasthttp

四、  使用例子

简单的get请求

url := "https://www.ctyun.cn/developer"
code, body, err := fasthttp.Get(nil, url)
if err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}
if code != fasthttp.StatusOK {
	fmt.Println("请求错误: ", code)
	return err
}
fmt.Println(body)

简单的post请求

url := "https://www.ctyun.cn/developer"
//定义请求参数
args := fasthttp.Args{}
args.Set("name", "zhans")
args.Set("age", "25")
args.Set("type", "code")
	
code, body, err := fasthttp.Post(nil, url, &args)
if err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}
if code != fasthttp.StatusOK {
	fmt.Println("请求错误: ", code)
		return err
}
fmt.Println(body)

设置请求头/请求体

url := "https://www.ctyun.cn/developer"
//定义请求
req := fasthttp.Request{}
//设置请求url
req.SetRequestURI(url)
//设置请求体
body := []byte(`{"name": "ctyun"}`)
req.SetBody(body)

//设置请求头
req.Header.SetMethod("POST")
//默认是application/x-www-form-urlencoded
req.Header.SetContentType("application/json")

resp := fasthttp.Response{}
client := fasthttp.Client{}
if err := client.Do(&req, &resp); err != nil {
	fmt.Println("请求异常: ", err.Error())
	return err
}

fmt.Println(resp.Body())

从协程池获取request和response

设置client对象

func getHttpClient() *fasthttp.Client {
	return &fasthttp.Client{
		// 读超时时间,不设置read超时,可能会造成连接复用失效
		ReadTimeout: time.Second * 5,
		// 写超时时间
		WriteTimeout: time.Second * 5,
		// 5秒后,关闭空闲的活动连接
		MaxIdleConnDuration: time.Second * 5,
		// 当true时,从请求中去掉User-Agent标头
		NoDefaultUserAgentHeader: true,
		// 当true时,header中的key按照原样传输,默认会根据标准化转化
		DisableHeaderNamesNormalizing: true,
		//当true时,路径按原样传输,默认会根据标准化转化
		DisablePathNormalizing: true,
		Dial: (&fasthttp.TCPDialer{
			// 最大并发数,0表示无限制
			Concurrency: 4096,
			// 将 DNS 缓存时间从默认分钟增加到一小时
			DNSCacheDuration: time.Hour,
		}).Dial,
	}
}

get请求

func HttpGet(uri string, arg *fasthttp.Args) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()
   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()
	req.Header.SetMethod(fasthttp.MethodGet)
	req.SetRequestURI(url)

   // 设置请求参数
	if arg != nil {
		req.URI().SetQueryString(arg.String())
	}
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

post请求(application/x-www-form-urlencoded)

func HttpPostData(uri string, args *fasthttp.Args) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()

   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()

	req.Header.SetMethod(fasthttp.MethodPost)
	req.SetRequestURI(url)
	req.Header.SetContentType("application/x-www-form-urlencoded")

   // 获取请求体输出流,并将请求参数写入
	if _, err := args.WriteTo(req.BodyWriter()); err != nil {
		return "", err
	}

   // 发起请求
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

post请求(application/json)

func HttpPostJson(uri string, data []byte) (string, error) {
	url := "https://www.ctyun.cn/developer"
	req, resp := fasthttp.AcquireRequest(), fasthttp.AcquireResponse()

   // 最后需要归还req、resp到池中
	defer func() {
		fasthttp.ReleaseRequest(req)
		fasthttp.ReleaseResponse(resp)
	}()

	req.Header.SetMethod(fasthttp.MethodPost)
	req.SetRequestURI(url)
	req.Header.SetContentType("application/json")
	req.SetBody(data)

   // 发起请求
	if err := getHttpClient().Do(req, resp); err != nil {
		return "", err
	}

	return string(resp.Body()), nil
}

五、  总结

fasthttp通过复用来提高了处理的性能和效率,尤其是对服务协程以及内存对象的复用,节省了大量的资源分配成本,同时也大量使用了sync.Pool,资源得到了合理的运用。所以,如果您在使用net/http包的过程中出现了性能瓶颈突破不了的话,不妨考虑一下fasthttp

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
1
0