您好,登錄后才能下訂單哦!
這篇“SpringCloud問題實例分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“SpringCloud問題實例分析”文章吧。
某應用發布以后開始報數據庫連接池不夠用異常,日志如下:
com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 500, maxActive 500, creating 0
很明顯這是數據庫連接池滿了,當時處于業務低峰期,所以很顯然并不是由于流量突發造成的,另一種可能性是長事務導致,一般是事務中摻雜了外部網絡調用,最終跟業務負責人一起排除了長事務的可能性。
還有什么可能呢?我隨即想到了是不是沒有釋放連接導致,我跟業務負責人說了這個想法,他說這種可能性不大,連接的獲取和釋放都是由框架完成的,如果這塊有問題早反映出來了,我想也是。
框架的確給我們帶來了很大的便利性,將業務中一些重復性的工作下沉到框架中,提高了研發效率,不夸張的說有些人脫離了Spring,MyBatis,SpringMvc這些框架,都不會寫代碼了。
那會是什么原因呢?我又冒出來一個想法,有沒有可能是某些功能框架支持不了,所以開發繞過了框架自己實現,進而導致連接沒有釋放,我跟業務負責人說了這個想法以后,他說:“這個有可能,這次有個功能需要獲取到數據庫名,所以自己通過Connection對象獲取的”,說到這兒答案大概已經出來了,一起看下這段代碼:
public String getSchema(String tablename, boolean cached) throws Exception { return this.getJdbcTemplate(tablename).getDataSource().getConnection().getCatalog(); }
代碼很簡單通過JdbcTemplate獲取DataSource,再通過DataSource獲取Connection,最終通過Connection獲取數據庫名,就是這一行簡單的代碼將數據庫連接耗盡,因為這里并沒有釋放連接的動作,之前的為什么都沒有問題呢,因為普通的查詢都是委派給JdbcTemplate來實現的,它內部會釋放連接,找一個簡單的query方法看下:
public <T> T query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); this.logger.debug("Executing prepared SQL query"); return this.execute(psc, new PreparedStatementCallback<T>() { @Nullable public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; Object var3; try { if (pss != null) { pss.setValues(ps); } rs = ps.executeQuery(); var3 = rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer)pss).cleanupParameters(); } } return var3; } }); }
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException { Assert.notNull(psc, "PreparedStatementCreator must not be null"); Assert.notNull(action, "Callback object must not be null"); if (this.logger.isDebugEnabled()) { String sql = getSql(psc); this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } Connection con = DataSourceUtils.getConnection(this.obtainDataSource()); PreparedStatement ps = null; Object var13; try { ps = psc.createPreparedStatement(con); this.applyStatementSettings(ps); T result = action.doInPreparedStatement(ps); this.handleWarnings((Statement)ps); var13 = result; } catch (SQLException var10) { if (psc instanceof ParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } String sql = getSql(psc); psc = null; JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, this.getDataSource()); con = null; throw this.translateException("PreparedStatementCallback", sql, var10); } finally { if (psc instanceof ParameterDisposer) { ((ParameterDisposer)psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, this.getDataSource()); } return var13; }
query方法基于execute這個模板方法實現,在execute內部會通過finally來確保連接的釋放
DataSourceUtils.releaseConnection,所以不會有連接耗盡的問題,問題已經很清晰了,改造也很簡單,大概有幾下幾種方法:
1.顯示的關閉連接,這里可以借助jdk的try resource語句,簡單明了。
public String getSchema(String tablename, boolean cached) throws Exception { try(Connection connection = this.getJdbcTemplate(tablename).getDataSource().getConnection()){ return connection.getCatalog(); } }
2.借助于JdbcTemplate的模板方法設計思想來解決,它提供了一個execute方法,用戶只要實現ConnectionCallback這個接口就可以獲取到Connection對象,在內部執行獲取數據庫名的邏輯,最終關閉連接由finally完成。
/** * Execute a JDBC data access operation, implemented as callback action * working on a JDBC Connection. This allows for implementing arbitrary * data access operations, within Spring's managed JDBC environment: * that is, participating in Spring-managed transactions and converting * JDBC SQLExceptions into Spring's DataAccessException hierarchy. * <p>The callback action can return a result object, for example a domain * object or a collection of domain objects. * @param action a callback object that specifies the action * @return a result object returned by the action, or {@code null} if none * @throws DataAccessException if there is any problem */ @Nullable public <T> T execute(ConnectionCallback<T> action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(this.obtainDataSource()); Object var10; try { Connection conToUse = this.createConnectionProxy(con); var10 = action.doInConnection(conToUse); } catch (SQLException var8) { String sql = getSql(action); DataSourceUtils.releaseConnection(con, this.getDataSource()); con = null; throw this.translateException("ConnectionCallback", sql, var8); } finally { DataSourceUtils.releaseConnection(con, this.getDataSource()); } return var10; }
jdbcTemplate.execute(new ConnectionCallback<Object>() { @Override public Object doInConnection(Connection connection) throws SQLException, DataAccessException { return connection.getCatalog(); } });
雖然兩種都能解決問題,但我還是更推崇第二種方式,因為這種更貼合框架的設計思想,將一些重復性的邏輯繼續交給框架去實現,這里也體現出框架很重要的一個特點,就是對使用者提供擴展。
前幾天寫了一個Spring AOP的攔截功能,發現怎么也進不到攔截邏輯中,表達式確定沒問題,讓我百思不得其解,最終冷靜下來逐步排錯。
第一個很明顯的錯誤是被攔截的對象并沒有納入Spring管理,所以當即把對象交由Spring管理,問題依然沒有解決,我開始回想代理的原理。
Spring代理提供了兩種實現方式,一種是jdk的動態代理,另一種是cglib代理,這兩種方式分別適用于代理類實現了接口和代理類未實現接口的情況,其內部思想都是基于某種規約(接口或者父類)來生成一個Proxy對象,在Proxy對象方法調用時先調用InvocationHandler的invoke方法,在invoke方法內部先執行代理邏輯,再執行被代理對象的真實邏輯,這里貼一段jdk動態代理生成的Proxy對象的源文件供大家閱讀:
public class ProxyTest { /** 定義目標接口,內部包含一個hello方法(這其實就是一個規約) */ public interface ProxyT{ void hello(); } /** 實現類,實現了ProxyT接口 */ public static class ProxyTImpl implements ProxyT{ @Override public void hello() { System.out.println("aaaa"); } } public static void main(String[] args) { //設置生成Proxy對象的源文件 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); ProxyT proxyT1 = (ProxyT)Proxy.newProxyInstance(ProxyT.class.getClassLoader(),new Class[]{ProxyT.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke"); return method.invoke(proxyT,args); } }); proxyT1.hello(); } }
最終生成的Proxy源文件如下:
package com.sun.proxy; import coding4fun.ProxyTest.ProxyT; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; /** 生成的proxy源碼,繼承jdk的Proxy類,實現了ProxyT接口 (這里其實也解釋了為什么jdk的動態代理只能基于接口實現,不能基于父類,因為Proxy 必須繼承jdk的Proxy,而java又是單繼承,所以Proxy只能基于接口這個規約來生成) */ public final class $Proxy0 extends Proxy implements ProxyT { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } //hello方法將調用權交給了InvocationHandler public final void hello() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("coding4fun.ProxyTest$ProxyT").getMethod("hello"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
到這里其實我已經有了答案,是我給Spring的規約(接口或者父類)出現了問題,首先我要代理的類并沒有實現接口,所以這里的規約不是接口,而是我這個類本身,從cglib的原理來講,它是將要代理的類作為父類來生成一個Proxy類,重寫要代理的方法,進而添加代理邏輯,問題就在于我那個類的方法是static的,而static方法是沒法重寫的,所以導致一直沒有進攔截邏輯,將static方法改為實例方法就解決了問題,這里貼一段cglib動態代理生成的Proxy對象的源文件供大家閱讀:
public class cglibtest { //定義被代理的類ProxyT,內部有一個hello方法 public static class ProxyT{ public void hello() { System.out.println("aaaa"); } } //定義一個方法攔截器,和jdk的InvocationHandler類似 public static class Interceptor implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //簡單的打印 System.out.println("before invoke hello"); //執行被代理類的方法(hello) return methodProxy.invokeSuper(o,objects); } } public static void main(String[] args) { // 設置CGLib代理類的生成位置 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cg"); // 設置JDK代理類的輸出 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); MethodInterceptor methodInterceptor = new Interceptor(); Enhancer enhancer = new Enhancer(); //設置父類 enhancer.setSuperclass(ProxyT.class); //設置方法回調 enhancer.setCallback(methodInterceptor); ProxyT proxy = (ProxyT)enhancer.create(); proxy.hello(); } }
最終生成的Proxy源文件如下(刪除了部分代碼,只保留了重寫hello方法邏輯):
//繼承ProxyT public class cglibtest$ProxyT$$EnhancerByCGLIB$$8b3109a3 extends ProxyT implements Factory { final void CGLIB$hello$0() { super.hello(); } //重寫hello方法 public final void hello() { //方法攔截器 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { //執行方法攔截器 var10000.intercept(this, CGLIB$hello$0$Method, CGLIB$emptyArgs, CGLIB$hello$0$Proxy); } else { super.hello(); } } }
以上就是關于“SpringCloud問題實例分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。