最近有很多反馈说项目卡顿,查了oracle的阻塞语句,发现很奇怪的事情,里面居然有查询语句而且是带索引的,感觉很奇怪。
oracle查询阻塞方法,这个网上都找得到,我顺便复制到这里
-- 查询连接数量
SELECT username, machine, program, status, COUNT (machine) AS
连接数量
FROM v$session
GROUP BY username, machine, program, status
ORDER BY machine;
-- 查询阻塞的sql
select l.session_id sid,
s.serial#,
l.locked_mode,
l.oracle_username,
s.user#,
l.os_user_name,
s.machine,
s.terminal,
a.sql_text,
a.action
from v$sqlarea a, v$session s, v$locked_object l
where l.session_id = s.sid
and s.prev_sql_addr = a.address
order by sid, s.serial#;
-- (2)查看哪个表被锁
select b.owner,b.object_name,a.session_id,a.locked_mode from
v$locked_object a,dba_objects b where b.object_id = a.object_id;
--(3)查看是哪个session引起的
select a.OS_USER_NAME, c.owner, c.object_name, b.sid, b.serial#,
logon_time from v$locked_object a, v$session b, dba_objects c where
a.session_id = b.sid and a.object_id = c.object_id order by
b.logon_time;
-- 杀掉阻塞会话
alter system kill session '512,37473';
百思不得其解之时,去看了一眼报错的日志,发现大量的这种错误
连接超时,对方接口没问题,那就只能是连接资源被耗光了
netstat -ant|awk '/^tcp/ {++S[$NF]} END {for(a in S) print (a,S[a])}'
发现close_wait 数量上千,这个状态会消耗资源,可以参考一下这篇文章
https://blog.csdn.net/smallbirdnq/article/details/106426315
解决方式
先看一下之前的请求工具类的方法
public ResponseBase doGet(String url) {
try {
//创建一个httpGet请求
HttpGet request = new HttpGet(url);
//创建一个htt客户端
HttpClient httpClient = HttpClientBuilder.create().build();
//添加cookie到头文件
//接受客户端发回的响应
HttpResponse httpResponse = httpClient.execute(request);
//获取返回状态
int statusCode = httpResponse.getStatusLine().getStatusCode();
//如果是响应成功
if (statusCode != HttpStatus.SC_OK) {
throw new DIYException(setResultError("发送GET请求失败").toString());
}
//得到客户段响应的实体内容
HttpEntity responseHttpEntity = httpResponse.getEntity();
String string = EntityUtils.toString(responseHttpEntity, "utf-8");
return setResultSuccessData(string, "发送GET请求成功");
} catch (Exception e) {
log.error("发送GET请求异常:" + e.getMessage());
throw new DIYException(setResultError("发送GET请求异常:" + e.getMessage()).toString());
}
}
每次请求都会创建一个httpClient,非常浪费,也没做资源的回收。
那么我们可以考虑只创建一个httpClient,连接池的方式去管理,并且创建一个后台线程去做资源的释放与回收。
// 使用管理器,管理HTTP连接池 无效链接定期清理功能
public class HttpClientConnectionMonitorThread extends Thread{
private final HttpClientConnectionManager connManager;
private volatile boolean shutdown;
public HttpClientConnectionMonitorThread(HttpClientConnectionManager connManager) {
super();
this.setName("http-connection-monitor");
this.setDaemon(true);
this.connManager = connManager;
this.start();
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
// 等待5秒
wait(5000);
// 关闭过期的链接
connManager.closeExpiredConnections();
// 选择关闭 空闲30秒的链接
connManager.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
}
}
}
public class HttpClientFactory {
/** 连接池最大连接数 */
private static final Integer MAX_TOTAL = 1200;
/** 单个路由默认最大连接数 */
private static final Integer MAX_PER_ROUTE = 200;
/** 请求超时时间ms */
private static final Integer REQ_TIMEOUT = 5 * 1000;
/** 连接超时时间ms */
private static final Integer CONN_TIMEOUT = 5 * 1000;
/** 读取超时时间ms */
private static final Integer SOCK_TIMEOUT = 25 * 1000;
/** HTTP链接管理器线程 */
private static HttpClientConnectionMonitorThread thread;
public static HttpClientConnectionMonitorThread getThread() {
return thread;
}
public static void setThread(HttpClientConnectionMonitorThread thread) {
HttpClientFactory.thread = thread;
}
public static HttpClient createSimpleHttpClient(){
SSLConnectionSocketFactory sf = SSLConnectionSocketFactory.getSocketFactory();
return HttpClientBuilder.create()
.setSSLSocketFactory(sf)
.build();
}
public static HttpClient createHttpClient() {
// 定义keepAlive
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
if (response == null) {
throw new IllegalArgumentException("HTTP response may not be null");
}
HeaderElementIterator it= new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while(it.hasNext()) {
HeaderElement he=it.nextElement();
String param=he.getName();
String value=he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
//如果没有约定,则默认定义时长为60s
return 60 * 1000;
}
};
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
poolingHttpClientConnectionManager.setMaxTotal(MAX_TOTAL);
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(MAX_PER_ROUTE);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(REQ_TIMEOUT)
.setConnectTimeout(CONN_TIMEOUT)
.setSocketTimeout(SOCK_TIMEOUT)
.build();
//管理 http连接池
HttpClientFactory.thread=new HttpClientConnectionMonitorThread(poolingHttpClientConnectionManager);
return HttpClients.custom()
.setConnectionManager(poolingHttpClientConnectionManager)
.setKeepAliveStrategy(myStrategy)
.setDefaultRequestConfig(requestConfig)
.build();
}
}
前置连接池就算创建好了,接下来是使用方式
@Slf4j
@Component
public class HttpUtil {
/**
* 自定义请求工具类 连接池
*/
private static final HttpClient myHttpClient = HttpClientFactory.createHttpClient();
/**
* GET请求
*/
public ResponseBase doGet(String url) {
try {
// 通过HttpPost来发送post请求
HttpGet httpGet = new HttpGet(url);
CustomResponseHandler responseHandler = new CustomResponseHandler();
String result = myHttpClient.execute(httpGet, responseHandler);
return setResultSuccessData(result, "发送GET请求成功");
} catch (Exception e) {
log.error("发送GET请求异常:" + e.getMessage());
throw new DIYException(setResultError("发送GET请求异常:" + e.getMessage()).toString());
}
}
/**
* POST请求
*/
public ResponseBase doPost(String url, String param, String contentType){
try {
// 通过HttpPost来发送post请求
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(param, "UTF-8");
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType(contentType);
httpPost.setEntity(stringEntity);
CustomResponseHandler responseHandler = new CustomResponseHandler();
String result = myHttpClient.execute(httpPost, responseHandler);
return setResultSuccessData(result, "发送POST请求成功");
} catch (Exception e) {
log.error("发送POST请求异常:" + e.getMessage());
throw new DIYException(setResultError("发送POST请求异常:" + e.getMessage()).toString());
}
}
}
更推荐使用HttpClient.execute(httpPost, responseHandler) 响应处理器的方式,这样资源会少消耗,并且我们不用去管资源的回收
public class CustomResponseHandler extends BasicResponseHandler {
/**
* Returns the entity as a body as a String.
*
* @param entity
*/
@Override
public String handleEntity(HttpEntity entity) throws IOException {
return EntityUtils.toString(entity, "UTF-8");
}
}
有兴趣的去看下源码 CloseableHttpClient
public <T> T execute(final HttpHost target, final HttpRequest request,
final ResponseHandler<? extends T> responseHandler, final HttpContext context)
throws IOException, ClientProtocolException {
Args.notNull(responseHandler, "Response handler");
final CloseableHttpResponse response = execute(target, request, context);
try {
// 注意这里
final T result = responseHandler.handleResponse(response);
final HttpEntity entity = response.getEntity();
EntityUtils.consume(entity);
return result;
} catch (final ClientProtocolException t) {
// Try to salvage the underlying connection in case of a protocol exception
final HttpEntity entity = response.getEntity();
try {
EntityUtils.consume(entity);
} catch (final Exception t2) {
// Log this exception. The original exception is more
// important and will be thrown to the caller.
this.log.warn("Error consuming content after an exception.", t2);
}
throw t;
} finally {
response.close();
}
}
@Override
public T handleResponse(final HttpResponse response)
throws HttpResponseException, IOException {
final StatusLine statusLine = response.getStatusLine();
final HttpEntity entity = response.getEntity();
if (statusLine.getStatusCode() >= 300) {
EntityUtils.consume(entity);
throw new HttpResponseException(statusLine.getStatusCode(),
statusLine.getReasonPhrase());
}
return entity == null ? null : handleEntity(entity);
}
无论怎样,返回不是成功或者异常最后都会消费掉EntityUtils.consume(entity);