`
jinnianshilongnian
  • 浏览: 21513638 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2420447
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3010220
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5640568
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:260217
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1597925
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:250368
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5860558
Group-logo
跟我学Nginx+Lua开...
浏览量:702867
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:785734
社区版块
存档分类
最新评论

使用httpclient必须知道的参数设置及代码写法、存在的风险

 
阅读更多
结论:
如果使用httpclient 3.1并发量比较大的项目,最好升级到httpclient4.2.3上,保证并发量大时能抗住。httpclient 4.3.3,目前还有一些bug;还是用4.2.x稳定版本吧。
 
以库存项目为例:

httpclient一天并发量在1500w左右,峰值一秒7万。

 

在之前使用过程中,一直存在大量的

 

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClientConnectionManager.java:232)
at org.apache.http.impl.conn.PoolingClientConnectionManager$1.getConnection(PoolingClientConnectionManager.java:199)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:456)
另外通过jstack查看线程,会发现:
"pool-21-thread-3" prio=10 tid=0x00007f6b7c002800 nid=0x40ff waiting on condition [0x00007f6b37020000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000f97918b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkUntil(LockSupport.java:239)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitUntil(AbstractQueuedSynchronizer.java:2072)
at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:129)
at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:281)
at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:62)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:176)
at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:172)
at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:100)
at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClientConnectionManager.java:212)
 
 
问题:
因为使用了连接池,但连接不够用,造成大量的等待;而且这种等待都有滚雪球的效应(和交易组最近使用的apache common dbcp存在的风险是类似的)。
 
 
解决方案
最终我们定了一些合理的参数值,目前来看还没有遇到问题。
 
 
思考
其实出问题的原因是我们对一些参数不了解,随意设置其值,不出现问题则好,出现问题很难排查到原因,因此我把使用httpclient必须设置的参数及代码写法及排查方法总结一下,供参考。
 
参数设置
1、httpclient 4.2.3
HttpParams params = new BasicHttpParams();
//设置连接超时时间
Integer CONNECTION_TIMEOUT = 2 * 1000; //设置请求超时2秒钟 根据业务调整
Integer SO_TIMEOUT = 2 * 1000; //设置等待数据超时时间2秒钟 根据业务调整
//定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间
//这个参数期望得到一个java.lang.Long类型的值。如果这个参数没有被设置,默认等于CONNECTION_TIMEOUT,因此一定要设置
Long CONN_MANAGER_TIMEOUT = 500L; //该值就是连接不够用的时候等待超时时间,一定要设置,而且不能太大 ()
 
params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONNECTION_TIMEOUT);
params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, SO_TIMEOUT);
params.setLongParameter(ClientPNames.CONN_MANAGER_TIMEOUT, CONN_MANAGER_TIMEOUT);
//在提交请求之前 测试连接是否可用
params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, true);
 
PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();
conMgr.setMaxTotal(200); //设置整个连接池最大连接数 根据自己的场景决定
//是路由的默认最大连接(该值默认为2),限制数量实际使用DefaultMaxPerRoute并非MaxTotal。
//设置过小无法支持大并发(ConnectionPoolTimeoutException: Timeout waiting for connection from pool),路由是对maxTotal的细分。
conMgr.setDefaultMaxPerRoute(conMgr.getMaxTotal());//(目前只有一个路由,因此让他等于最大值)
 
//另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(0, false));
此处解释下MaxtTotal和DefaultMaxPerRoute的区别:
1、MaxtTotal是整个池子的大小;
2、DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;比如:
MaxtTotal=400 DefaultMaxPerRoute=200
而我只连接到http://sishuok.com时,到这个主机的并发最多只有200;而不是400;
而我连接到http://sishuok.com 和 http://qq.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400);所以起作用的设置是DefaultMaxPerRoute。
 
 
2、httpclient 3.1
HttpConnectionManagerParams params = new HttpConnectionManagerParams();
params.setConnectionTimeout(2000);
params.setSoTimeout(2000);
// 最大连接数
params.setMaxTotalConnections(500);
params.setDefaultMaxConnectionsPerHost(500);
params.setStaleCheckingEnabled(true);
connectionManager.setParams(params);
 
HttpClientParams httpClientParams = new HttpClientParams();
// 设置httpClient的连接超时,对连接管理器设置的连接超时是无用的
httpClientParams.setConnectionManagerTimeout(5000); //等价于4.2.3中的CONN_MANAGER_TIMEOUT
httpClient = new HttpClient(connectionManager);
httpClient.setParams(httpClientParams);
 
//另外设置http client的重试次数,默认是3次;当前是禁用掉(如果项目量不到,这个默认即可)
httpClientParams.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false));
 
参数类似 就不多解释了;
 
代码写法
1、httpclient 4.2.3
HttpResponse response = null;
HttpEntity entity = null;
try {
  HttpGet get = new HttpGet();
  String url = "http://hc.apache.org/";
  get.setURI(new URI(url));
  response = getHttpClient().execute(get);
/  /处理响应
} catch (Exception e) {
  //处理异常
} finally {
  if(response != null) { 
    EntityUtils.consume(response.getEntity()); //会自动释放连接
  }
  //如下方法也是可以的,但是存在一些风险;不要用
  //InputStream is = response.getEntity().getContent();
  //is.close();
}
 
2、httpclient 3.1
PostMethod postMethod = new PostMethod(yxUrl);
try { 
  httpClient.executeMethod(postMethod);
} catch (Exception e) {
  //处理异常
} finally {
  if(postMethod != null) { //不要忘记释放,尽量通过该方法实现,
    postMethod.releaseConnection();
    //存在风险,不要用
    //postMethod.setParameter("Connection", "close");
    //InputStream is = postMethod.getResponseBodyAsStream();
    //is.clsoe();也会关闭并释放连接的
  }
}
 
存在的风险
1、httpclient 4.2.3 在释放连接时
if (managedConn.isOpen() && !managedConn.isMarkedReusable()) { //如果连接打开的且不可重用(not keepalive) close socket
  try {
    managedConn.shutdown();
  } catch (IOException iox) {
    if (this.log.isDebugEnabled()) {
      this.log.debug("I/O exception shutting down released connection", iox);
    }
  }
}
// Only reusable connections can be kept alive
if (managedConn.isMarkedReusable()) {
  entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
  if (this.log.isDebugEnabled()) {
    String s;
    if (keepalive > 0) {
      s = "for " + keepalive + " " + tunit;
    } else { 
      s = "indefinitely";
    }
    this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
  }
}
无风险
 
2、httpclient 3.1
1、如果走http1.1协议:如果proxy-connection/connection请求头设置为close;那么会关闭socket; 或者这两个头不等于close 也会自动关;
2、如果是keep-alive ,不会关闭;
3、如果协议小于等于http1.0协议没有问题;调用releaseConnection时会close socket;
4、其他情况不会close;
 
也就是说如果走http1.1且没有设置相关参数;那么socket其实是没有关闭的;可能造成很多TIME_WAIT;因此如果是走短连接建议设置postMethod.setParameter("Connection", "close")。
 
其他注意事项:
1、使用keep-alive一定要设置Content-Length头(否则也不是长连接)。
 
2、在使用httpclient3.1时(4.2.3没问题);尽量不要调用 byte[] getResponseBody() :因为如果Content-Length没设置或者传输的数据大于1M,会有大量如下日志
LOG.warn("Going to buffer response body of large or unknown size. "
+"Using getResponseBodyAsStream instead is recommended.");
 
如果大于1M可以设置该参数;但是-1的话就没办法了,就不要调用 byte[] getResponseBody()
httpClientParams.setLongParameter(HttpMethodParams.BUFFER_WARN_TRIGGER_LIMIT, 2L * 1024 * 1024);
 
 
3、锁
httpclient 3.1 使用synchronized+wait+notifyAll,存在两个问题,量大synchronized慢和notifyAll可能造成线程饥饿;httpclient 4.2.3 使用 ReentrantLock(默认非公平) + Condition(每个线程一个)。
 
这里有个测试:http://java.dzone.com/articles/synchronized-vs-lock ,在我本机(jdk1.6.0_43 )测试结果明细锁的优势比较大
1x synchronized {} with 32 threads took 2.621 seconds
1x Lock.lock()/unlock() with 32 threads took 1.951 seconds
1x AtomicInteger with 32 threads took 4.113 seconds
1x synchronized {} with 64 threads took 2.621 seconds
1x Lock.lock()/unlock() with 64 threads took 1.983 seconds
 
这也是为什么在库存项目中使用httpclient 3.1 依然有大量的wait,而httpclient4.2.3 一个没有的问题所在。
 
如有问题,请赐教。
30
2
分享到:
评论
24 楼 k10509806 2018-01-15  
还有一个风险博主可能没提到,在不共享httpclient的情况下,就是EntityUtils.consume(response.getEntity())只是把链接返回给链接池,链接池还没有关闭,经典写法是这样:
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpResponse response = null;
HttpEntity entity = null;
try {
  HttpGet get = new HttpGet();
  String url = "http://hc.apache.org/";
  get.setURI(new URI(url));
  response = getHttpClient().execute(get);
/  /处理响应
} catch (Exception e) {
  //处理异常
} finally {
  if(response != null) { 
    EntityUtils.consume(response.getEntity()); //会自动释放连接
  }
  
  httpClient.close();
}
23 楼 lcclostheart 2017-06-12  
   你好能共享下你的 getHttpClient的这个方法么,最近用到httpclient,问题这个连接池一直报错
22 楼 hqf2009 2017-06-08  
博主分析到位,实战经验丰富
21 楼 石開猶 2016-09-19  
malie0 写道
好像很久没看到LZ了,本来在问答区还是很活跃的

20 楼 yudilan6 2016-02-15  

请问下文中所说的路由的默认最大连接是2,这个值是在源码的哪个位置设置的呢?

PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();
conMgr.setMaxTotal(200); //设置整个连接池最大连接数 根据自己的场景决定
//是路由的默认最大连接(该值默认为2),限制数量实际使用DefaultMaxPerRoute并非MaxTotal。
19 楼 dirklei 2015-11-20  
yjp322 写道
讨厌淡紫色 写道
请问下大神,我用工具类A
static{
cm=new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
   /* HttpHost localhost = new HttpHost("http://api.map.baidu.com", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);*/
}我是直接静态块构造的,然后
/**
* 获取连接
* */
public static CloseableHttpClient getConnection(){
CloseableHttpClient httpClient=HttpClients.custom().setConnectionManager(cm).build();
return httpClient;
}这个静态方法获取
外部调用类B
CloseableHttpClient  httpClient=A.getConnection();
  方法最后httpClient.close(),为什么这个操作会关闭连接池

解决了吗?同样的疑问,感觉是static 惹得祸




因为你的client对象默认是是IntenalHttpClient实例,
看这个类中close方法的定义:
public void close() {
        this.connManager.shutdown();
        if (this.closeables != null) {
            for (final Closeable closeable: this.closeables) {
                try {
                    closeable.close();
                } catch (final IOException ex) {
                    this.log.error(ex.getMessage(), ex);
                }
            }
        }
    }
18 楼 yjp322 2015-11-10  
讨厌淡紫色 写道
请问下大神,我用工具类A
static{
cm=new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
   /* HttpHost localhost = new HttpHost("http://api.map.baidu.com", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);*/
}我是直接静态块构造的,然后
/**
* 获取连接
* */
public static CloseableHttpClient getConnection(){
CloseableHttpClient httpClient=HttpClients.custom().setConnectionManager(cm).build();
return httpClient;
}这个静态方法获取
外部调用类B
CloseableHttpClient  httpClient=A.getConnection();
  方法最后httpClient.close(),为什么这个操作会关闭连接池

解决了吗?同样的疑问,感觉是static 惹得祸
17 楼 讨厌淡紫色 2015-10-28  
请问下大神,我用工具类A
static{
cm=new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
//将目标主机的最大连接数增加到50
   /* HttpHost localhost = new HttpHost("http://api.map.baidu.com", 80);
    cm.setMaxPerRoute(new HttpRoute(localhost), 50);*/
}我是直接静态块构造的,然后
/**
* 获取连接
* */
public static CloseableHttpClient getConnection(){
CloseableHttpClient httpClient=HttpClients.custom().setConnectionManager(cm).build();
return httpClient;
}这个静态方法获取
外部调用类B
CloseableHttpClient  httpClient=A.getConnection();
  方法最后httpClient.close(),为什么这个操作会关闭连接池
16 楼 yanyinxi 2015-08-20  
能否共享一下你的httpClient工具类
15 楼 豆腐脑o 2015-06-09  
    
14 楼 tobe 2014-07-10  
13 楼 jinnianshilongnian 2014-07-10  
lz958942 写道
喜欢这种文章

12 楼 jinnianshilongnian 2014-07-10  
宋建勇 写道
又见到楼主了,very nice!

11 楼 宋建勇 2014-07-10  
又见到楼主了,very nice!
10 楼 lz958942 2014-07-10  
喜欢这种文章
9 楼 jinnianshilongnian 2014-07-09  
malie0 写道
好像很久没看到LZ了,本来在问答区还是很活跃的

谢谢,上班忙啊
8 楼 jinnianshilongnian 2014-07-09  
yixiandave 写道
卧槽。。。今天看了你的文章才发现commons-httpclient原来是07年的老版本。。。我还一直在用
话说4.0接口变化真大,之前一个项目改了我一上午

最后问下4.3.4版本有什么已知bug吗

https://issues.apache.org/jira/browse/HTTPCLIENT-1478 现在说是修复了,没看最新的源代码。 用之前可以再看下源代码或者跑下量测试下。
7 楼 yixiandave 2014-07-09  
卧槽。。。今天看了你的文章才发现commons-httpclient原来是07年的老版本。。。我还一直在用
话说4.0接口变化真大,之前一个项目改了我一上午

最后问下4.3.4版本有什么已知bug吗
6 楼 malie0 2014-07-09  
好像很久没看到LZ了,本来在问答区还是很活跃的
5 楼 jinnianshilongnian 2014-07-09  
kidding87 写道


so_timeout这个必须设置,否则pool就可能呆死在这里了


这个是控制并发量的,两个都要设置
cm.setDefaultMaxPerRoute(100);
cm.setMaxTotal(200);

不知道楼主那么大的并发不是针对相同的机器么,相同的话,keepalive能提升不少性能,减少了连接次数
time_wait 需要设置reuseaddr

下面是官方默认给的
httpClient.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler());
httpClient.setReuseStrategy(new DefaultConnectionReuseStrategy());
httpClient.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());

官方的例子还给了一个定期清理pool中connection的例子

  class IdleConnectionMonitorThread extends Thread {
		private final ClientConnectionManager connMgr;
		private volatile boolean shutdown;

		public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
			super();
			this.setName("idle-connection-monitor");
			this.setDaemon(true);
			this.connMgr = connMgr;
			this.start();
		}

		@Override
		public void run() {
			try {
				while (!shutdown) {
					synchronized (this) {
						wait(5000);
						// Close expired connections
						connMgr.closeExpiredConnections();
						// Optionally, close connections
						// that have been idle longer than 30 sec
						connMgr.closeIdleConnections(60, TimeUnit.SECONDS);
					}
				}
			} catch (InterruptedException ex) {
				// terminate
			}
		}

		public void shutdown() {
			synchronized (this) {
				shutdown = true;
				notifyAll();
			}
		}
	}

恩,是相同机器 keepalive方式 我没有试,我是帮同事调试的,只是给他建议。

相关推荐

    c++ HttpClient 最新代码

    c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient 最新代码c++ HttpClient ...

    使用HttpClient必须的jar包

    在压缩包中的"使用HttpClient发送json必须的jar包"可能包含了处理JSON数据所必需的库,如`gson-x.x.x.jar`或`jackson-core-x.x.x.jar`、`jackson-databind-x.x.x.jar`等。 在"使用说明.txt"中,通常会详细阐述如何...

    使用HttpClient获取网页html源代码.zip

    设置完请求后,我们通过HttpClient执行请求并获取HttpResponse: ```java HttpResponse response = httpClient.execute(httpGet); ``` 在响应对象中,我们可以找到HttpEntity,它包含了服务器返回的数据。使用...

    JAVA发送HttpClient请求及接收请求完整代码实例

    在本实例中,我们将深入探讨如何使用HttpClient来发送请求以及处理返回的数据。 首先,让我们了解HttpClient的基本用法。Apache HttpClient库提供了一个`CloseableHttpClient`接口,它是执行HTTP请求的主要入口点。...

    java代码-使用java解决HttpClientUtil的源代码

    java代码-使用java解决HttpClientUtil的源代码 ——学习参考资料:仅用于个人学习使用!

    Java 使用HttpClient保持SESSION状态

    下面我们将详细探讨如何在Java中使用HttpClient来实现这一目标。 首先,我们需要导入必要的Apache HttpClient库,通常包含以下依赖: ```xml &lt;groupId&gt;org.apache.httpcomponents &lt;artifactId&gt;httpclient ...

    httpclient 3.1 jar包 手册 源代码

    本手册将详细探讨HttpClient 3.1版本,包括其源代码分析和使用指南,帮助开发者充分利用这个强大的工具。 一、HttpClient 3.1简介 HttpClient 3.1是HttpClient系列的一个稳定版本,它提供了丰富的功能,如支持...

    httpClient调用webservice接口

    本文将详细介绍如何使用HttpClient来调用WebService接口,并提供一个具体的示例代码来进行解析。 #### 二、HttpClient简介 HttpClient是Apache的一个开源项目,它提供了一套丰富的API用于发送HTTP请求和接收HTTP...

    使用HttpClient下载图片

    HttpClient是个很不错的开源框架(org.appache.http),封装了访问http的请求头,参数,内容体,响应等等,使用起来更方面更强大。 HttpURLConnection是java的标准类,可以实现简单的基于URL请求、响应功能,什么都...

    httpclient 4.0.3 源代码

    《HttpClient 4.0.3源代码解析》 HttpClient是一个由Apache基金会开发的开源HTTP客户端API,广泛应用于Java编程环境中,用于实现与HTTP服务器的通信。版本4.0.3是HttpClient的一个稳定版本,提供了丰富的功能和改进...

    一个使用HttpClient调用天气预报接口的例程

    在Android中,HttpClient被广泛用于网络通信,尤其在Android 4.2及以下版本。 2. **GET请求**:在HTTP协议中,GET是最基本的请求方法,用于从服务器获取资源。在这个例程中,我们使用GET方法调用天气预报接口,将...

    commons-httpclient-3.0.jar JAVA中使用HttpClient可以用到

    《JAVA中使用HttpClient:commons-httpclient-3.0.jar详解》 在JAVA开发中,进行HTTP请求时,Apache的HttpClient库是一个不可或缺的工具。本文将深入解析`commons-httpclient-3.0.jar`,它是HttpClient的一个重要...

    httpclient4.3 设置代理

    javase http通讯技术 apache httpclient4.3 设置代理详解

    HttpClient模拟get,post请求并发送请求参数(json等)

    接下来,我们讨论如何使用HttpClient进行GET请求。GET请求通常用于获取资源,不涉及请求体: ```java CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(...

    使用HttpClient异步请求数据

    这个教程可能涵盖了设置请求参数、处理响应、异常处理以及如何在`AsyncTask`中集成`HttpClient`的细节。 总之,`HttpClient`和`AsyncTask`的结合使用,能够让我们在Android应用中高效、非阻塞地获取服务器数据。...

    java使用HttpClient通过url下载文件到本地

    - **创建HttpClient实例**:首先,我们需要创建一个HttpClient对象,例如使用`HttpClientBuilder`或`HttpClients.createDefault()`方法。 - **构建HttpGet请求**:使用`HttpGet`类创建一个HTTP GET请求,指定要...

    java使用HttpClient发送http请求

    在提供的文件`HttpTest`中,可能包含了使用HttpClient进行测试的代码。这将帮助你更好地理解HttpClient的实际应用和可能遇到的问题。分析这些测试代码,可以帮助你学习如何编写健壮的HTTP请求逻辑,处理各种异常情况...

    使用httpclient访问servlet

    本篇文章将详细探讨如何使用HttpClient来访问Servlet,并阐述相关的核心知识点。 首先,我们需要理解Servlet的本质。Servlet是一种Java类,它扩展了服务器的功能,用于处理HTTP请求并生成响应。通常,Servlet部署在...

    httpClient自动接受https的证书代码

    最后,我们定制了一个HttpClient实例,设置了这个自定义的SSL连接工厂。 请注意,这种方法仅适用于测试环境,因为信任所有证书可能会导致安全隐患,例如中间人攻击。在生产环境中,你应该始终使用受信任的CA签发的...

Global site tag (gtag.js) - Google Analytics