在调用第三方请求时,正确使用Client也不是一件非常容易的事。

下面是截取的一段描述,建议ClientTransport在整个服务期间最好是全局单例的,Transport本身会维护连接的状态,而且线程安全。强烈建议,不要使用系统提供的任何默认值。

The Client's Transport typically has internal state (cached TCP connections), so Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines.

Transport

如下是官方的简要描述。Transport字段在Client中被声明为接口类型,而实现这个接口的是Transport类型(略显绕)。在net包内部也提供了默认的实现变量:DefaultTransport

// Transport specifies the mechanism by which individual
// HTTP requests are made.
// If nil, DefaultTransport is used.
Transport RoundTripper
``` 看一下RoundTripper这个接口,官方描述:
RoundTripper is an interface representing the ability to execute a single HTTP transaction, obtaining the Response for a given Request.
既然是一个接口类型,我们就有理由自己去实现它,我们可以自定义自己的Transport。比如客户端发起一个请求,我们可以先去查询缓存中是否存在。如果存在,则将缓存中的数据写回response。如果不存在,请求远端服务获取数据,并缓存。 实现这样的功能,完全没有必要自定义一个Transport,我们也可以使用先请求缓存服务器,在请求远端服务器的方案来实现。但其实Transport就可以实现封装这些功能。

func cacheResponse(b []byte, r *http.Request) (*http.Response, error) {
//NewBuffer is intended to prepare a Buffer to read existing data.
buf := bytes.NewBuffer(b)
return http.ReadResponse(bufio.NewReader(buf), r)
}

官方提供了默认的Transport。如果不明确指定,那么底层就使用默认值。所以,可能连你也没有意识到,你在使用长链接。

另外:一定要记得当请求返回的error为空时,读取连接返回的数据,并明确调用Close关闭连接。否则连接会没法继续复用。


func (c *Client) transport() RoundTripper {
if c.Transport != nil {
return c.Transport
}
return DefaultTransport
}

缓存Idle连接

首先了解缓存长链接的Key是什么,即用来唯一确定连接的Key。我们选来看看它如何从缓存池获取的空闲连接:

//1. 获取的方法,截取其中一部分代码
func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn, idleSince time.Time) {
key := cm.key()
t.idleMu.Lock()
defer t.idleMu.Unlock()
for {
pconns, ok := t.idleConn[key]
if !ok {
return nil, time.Time{}
}
if len(pconns) == 1 {
pconn = pconns[0]
delete(t.idleConn, key)
} else {
// 2 or more cached connections; use the most
// recently used one at the end.
pconn = pconns[len(pconns)-1]
t.idleConn[key] = pconns[:len(pconns)-1]
}
t.idleLRU.remove(pconn)
//省略之后的代码......
``` 通过如下代码,可以确定net包通过当前请求的proxy URLSchemeAddr来缓存建立的连接。缓存的连接存储在一个MAP结构中: map[connectMethodKey][]*persistConnmap中的每一个Key对应了连接的slice数组,最新创建的连接会追加到slice的末尾。

func (cm *connectMethod) key() connectMethodKey {
proxyStr := ""
targetAddr := cm.targetAddr
if cm.proxyURL != nil {
proxyStr = cm.proxyURL.String()
if (cm.proxyURL.Scheme == "http" || cm.proxyURL.Scheme == "https") && cm.targetScheme == "http" {
targetAddr = ""
}
}
return connectMethodKey{
proxy: proxyStr,
scheme: cm.targetScheme,
addr: targetAddr,
}
}

因为Key中存在了Host地址,所以MaxIdleConnsPerHost这个值就显得格外重要。当准备缓存连接时,如果检测到当前的空闲连接数大于MaxIdleConnsPerHost,系统便会主动将这个连接关闭。这可能会是一个坑,特别要注意这一点。

如果不指定MaxIdleConnsPerHost,那么程序使用默认的值:DefaultMaxIdleConnsPerHost,这个默认值好比DefaultClient,都是问题所在。前者的默认值是2,可能直接导致在并发的时候,长链接的效率还不如短链接。后者的默认超时时间是0,这可能导致一个连接永远的挂在了那里。

使用net包提供的默认值,很多时候都不会是一件明智的事情。


func (t *Transport) tryPutIdleConn(pconn *persistConn) error {
//省略之前的代码......
if t.idleConn == nil {
t.idleConn = make(map[connectMethodKey][]*persistConn)
}
idles := t.idleConn[key]
if len(idles) >= t.maxIdleConnsPerHost() {
return errTooManyIdleHost
}
//省略之后的代码...... //主动关闭连接的代码
func (t *Transport) putOrCloseIdleConn(pconn *persistConn) {
if err := t.tryPutIdleConn(pconn); err != nil {
pconn.close(err)
}
}

客户端对每个主机最多可以保持Transport.MaxIdleConnsPerHost个长链接。对于长链接而言,一般是由服务端主动关闭的,而连接维持的时间也由服务端来决定。如果对于请求的域名,对应的Host足够多,在服务端关闭这些连接之前,可能会存在大量的空闲连接,造成资源浪费。

Test Case

下面是测试使用的例子,但很多细节都跟猜想不一致,也不敢妄下断言。后续确定了再来完善

func PrintLocalDial(ctx context.Context, network, addr string) (net.Conn, error) {
dial := net.Dialer{
Timeout: 30 * time.Second,
//指定的这个时间并没有生效,即使在请求完成后Sleep 30s连接仍然有效
KeepAlive: 5 * time.Second,
}
conn, err := dial.Dial(network, addr)
if err != nil {
return conn, err
}
fmt.Println("connect done, use ", conn.LocalAddr().String(), conn.RemoteAddr().String())
return conn, err

}

var client = &http.Client{

Transport: &http.Transport{

DialContext: PrintLocalDial,

},

}

func TestRequestBaiDu(t *testing.T) {

for i := 0; i < 30; i ++ {

go func() {

resp, err := client.Get("http://baidu.com")

if err != nil {

fmt.Println(err)

return

}

        _, err = ioutil.ReadAll(resp.Body)
if err := resp.Body.Close(); err != nil {
fmt.Println(err)
}
}()
}

}


<p>参考文章:</p>
<ol>
<li><a href="https://serholiu.com/go-http-client-keepalive" rel="nofollow noreferrer"><code>Go HTTP Client 持久连接</code></a></li>
<li><a href="https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779" rel="nofollow noreferrer"><code>Don’t use Go’s default HTTP client (in production)</code></a></li>
</ol>

Net Transport的更多相关文章

  1. iOS App 不支持http协议 App Transport Security has blocked a cleartext HTTP (http://)

    目前iOS已经不支持http协议了,不过可以通过info.plist设置允许 App Transport Security has blocked a cleartext HTTP (http://) ...

  2. WCF wsHttpBinding之Transport security Mode, clientCredentialType=”Basic”

    原创地址:http://www.cnblogs.com/jfzhu/p/4071342.html 转载请注明出处 如何在WCF中使用Transport Security Mode,以及如何创建证书,请 ...

  3. WCF basicHttpBinding之Transport Security Mode, clientCredentialType="None"

    原创地址:http://www.cnblogs.com/jfzhu/p/4071342.html 转载请注明出处 前面文章介绍了<WCF basicHttpBinding之Message Sec ...

  4. App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file

    ios进行http请求,会出现这个问题: App Transport Security has blocked a cleartext HTTP (http://) resource load sin ...

  5. 解决echsop兼容jquery(transport.js的冲突)的问题

    方案一: 本人亲测过,可以用.有的人说需要删除js目录下的gobal.js文件,否则依然会冲突.我没删除也解决了冲突. 1.加入JSON2.js文件 原因很简单,transport修改Object是为 ...

  6. 网络请求出错:The resource could not be loaded because the App Transport

    Google后查证,iOS9引入了新特性App Transport Security (ATS).详情:App Transport Security (ATS) 新特性要求App内访问的网络必须使用H ...

  7. Transport Block Size, Throughput and Code rate-----http://www.simpletechpost.com/2012/12/transport-block-size-code-rate-protocol.html

    Transport Block Size, Throughput and Code rate   Since the size of transport block is not fixed, oft ...

  8. App Transport Security has blocked a cleartext HTTP (http://)

    使用SDWebImage加载“http://”开头的图片报错,错误如下: App Transport Security has blocked a cleartext HTTP (http://) r ...

  9. iOS9中的App Transport Security

    问题:webView加载网页加载不出来 原因:苹果在iOS9 sdk中加入了App Transport Security限制(iOS9以前的iOS sdk默认关闭ATS),默认强制使用https,并且 ...

  10. TDI - Transport Driver Interface

    [TDI - Transport Driver Interface] The Transport Driver Interface or TDI is the protocol understood ...

随机推荐

  1. python运~算~~符!!!!!!!!!!!

    目录: 算术运算, 用于加减乘除等数学运算 赋值运算,用于接收运算符或方法调用返回的结果 比较运算, 用于做大小或等值比较运算 逻辑运算,用于做 与.或.非运算 位运算, 用于二进制运算 每种运算中所 ...

  2. 使用C#反射实现用户控件调用父页面方法

    using System.Reflection; MethodInfo mi = this.Page.GetType().GetMethod("GetUserName"); //该 ...

  3. GUID 使用方法

    GUID(全局统一标识符)是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的.通常平台会提供生成GUID的API.生成算法很有意思,用到了以太网卡地址.纳秒级时间.芯片ID码和许多可 ...

  4. StopWatch 监控Java代码运行时间和分析性能

    背景 有时我们在做开发的时候需要记录每个任务执行时间,或者记录一段代码执行时间,最简单的方法就是打印当前时间与执行完时间的差值,然后这样如果执行大量测试的话就很麻烦,并且不直观,如果想对执行的时间做进 ...

  5. CF538G (套路题)

    CF538G 题目大意 你有一个长度为\(l\)的指令序列,每个指令为向上,向下,向左,向右中的一种. 机器人会循环执行该序列,即,执行完第\(l\)个指令后,就会重新开始执行第一个指令. 现在,给你 ...

  6. mac下更改MySQL的默认编码

    mysql默认的编码是latin1,它不支持中文,所以我们一般需要修改他的默认编码格式. 打开终端1. 进入root权限sudo -i 2. cp /usr/local/mysql/support-f ...

  7. .NET2.0引用.NET3.5的System.Core.dll&Dapper在.NET2.0下的配置

    微软MSDN对.NET2.0,3.0,3.5的描述: .NET Framework 版本 2.0.3.0 和 3.5 是使用同一 CLR 版本 (CLR 2.0) 生成的. 这些版本表示单个安装的连续 ...

  8. 前端(jQuery)(6)-- jQuery的扩展与noConflict

    1.jQuery的扩展 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...

  9. [转]js设计模式—发布订阅模式

    发布—订阅模式又叫观察者模式,它定义对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.在javascript开发中,一般用事件模型来替代传统的发布—订阅模式.本文将 ...

  10. java并发系列(四)-----源码角度彻底理解ReentrantLock(重入锁)

    1.前言 ReentrantLock可以有公平锁和非公平锁的不同实现,只要在构造它的时候传入不同的布尔值,继续跟进下源码我们就能发现,关键在于实例化内部变量sync的方式不同,如下所示: /** * ...