背景
今年負責一個特別緊急的項目,從需求溝通到應用上線只有一個月的時間,四月中旬開始具體溝通項目需求并準備資源,五月中旬要完成第一次上線。這次不聊整個項目的開展過程,而是說一下通過一個月艱苦卓絕的奮斗,順利完成了需求溝通、分析、設計、開發、測試、驗收,上線前也準備了詳盡的上線序列并一一清點了,但上線時應用啟動時出現的一個異常的排查過程。
現象
測試環境驗證通過后,將鏡像推送給運維部署到生產環境,應用啟動時報如下錯誤
[ERROR] [DbSessionContextPlugin.java:] 測試數據庫連接失敗(Test DB Connection Fail)[driver:oracle.jdbc.driver.OracleDriver,url:store:xxxx.store:xxxx,user:uuuuuu]
java.sql.SQLException: No suitable driver found for store:xxxx.store:xxxx
at java.sql.DriverManager.getConnection(DriverManager.java:689)
at java.sql.DriverManager.getConnection(DriverManager.java:247)
at com.sinolife.sf.framework.dbcontext.DbSessionContextPlugin.testConnectionValidate(DbSessionContextPlugin.java:427)
at com.sinolife.sf.framework.dbcontext.DbSessionContextPlugin.postProcessAfterInitialization(DbSessionContextPlugin.java:61)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:437)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1710)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$146/1684106402.getObject(Unknown Source)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)
......
差異
測試環境與生產環境之間的差異主要有兩個方面
- 數據庫連接串的配置方式
測試環境的配置方式
jdbc.user=system
jdbc.password=oracle
jdbc.jdbcUrl=jdbc:oracle:thin:@ip:port:uid
生產環境的配置方式
jdbc.user=tmsopr
jdbc.jdbcUrl=store:xxxx.store:xxxx
生產的配置方式是將連接串及秘鑰信息加密保存到指定路徑下的xxxx.store文件,需要這些信息時再去store文件中讀取,目的主要是指定路徑權限和加密方式可以保證敏感信息的安全,防范攻擊。
- 數據庫版本差異
測試環境數據庫是11G,而生產環境數據庫是19c。公司其他應用使用的11G,11G官方支持周期到2018年就結束了,于是公司開始嘗試使用oracle 19c的版本,而我們的應用有幸成為了第一個嘗試者。
分析 - 數據庫版本差異
根據異常日志信息java.sql.SQLException: No suitable driver found for
第一反應是oracle升級了,但是ojdbc驅動沒有升級,有可能是驅動版本老舊導致的問題。解決方法有兩個:升級OJDBC驅動版本;回退oracle數據庫版本。
-
升級OJDBC驅動版本
由于項目中間使用了兩個個數據源,應用的數據庫是19c的版本,而另一個數據源數據庫的版本是11c,如果升級OJDBC驅動的版本,有可能會影響另一個數據源。另外,MAVEN中對OJDBC的依賴較多,調整影響面大,而且需要架構組支持,有可能會出現反復調整并測試驗證的情況。為了盡快解決問題讓應用部署上線,放棄升級OJDBC驅動的方案。
ojdbc的依賴關系 - 回退oracle數據庫版本
選定了方案之后,第一時間與運維溝通好后,運維開始協助回退oracle數據庫的版本。運維一陣犀利的操作過后(導出、刪除19c、創建11g、導入),數據庫回退成11c的版本。
滿懷希望的通知部署人員重新部署應用,然而令人絕望的是,依然是原來的錯誤,一模一樣。
說明: 此處優先選擇這個方案另一個重要的原因是測試環境oracle是11g,應用可以正常啟動,至于為什么測試和生產不一致,后面會提及。
分析 - 數據庫連接串的配置方式
數據庫回退沒解決掉問題,只能將矛頭指向數據庫連接串的配置方式上了。將生成環境的連接串配置方式調整為和測試環境一致,jdbc.jdbcUrl
和jdbc.password
直接在配置文件中寫死。調整后重新打包鏡像并推送運維部署。
應用居然成功啟動了!
開始懷疑是不是store文件的格式和解析是否有問題?但是這個是通用的模塊,已經使用很多年了。為了消除疑惑,改回從store文件讀取jdbc.jdbcUrl
和jdbc.password
配置,增加日志,重新部署啟動還是和原來一樣報錯。檢查輸出日志,參數都正常解析并獲取到了。那究竟為什么在properties文件中配置就可以,而在store文件中配置就報錯了呢?
數據源的配置很常規,很簡單,如下:
@Bean(name = "dataSource")
@Primary
@ConfigurationProperties(prefix = "jdbc")
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = DataSourceBuilder.create().type(ComboPooledDataSource.class).build();
String jdbcUrl = context.getEnvironment().getProperty("jdbc.jdbcUrl");
String user = context.getEnvironment().getProperty("jdbc.user");
String driverClass = context.getEnvironment().getProperty("jdbc.driverClass");
......
// 從store文件中解析并讀取jdbcUrl和password
String[] jdbcUrlAndPassword = DbParamterResolverUtil.resolveJdbcUrlAndPassword(jdbcUrl, user, null);
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl(jdbcUrlAndPassword[0]);
dataSource.setUser(user);
dataSource.setPassword(jdbcUrlAndPassword[1]);
......
return dataSource;
}
通過再次仔細分析異常日志,將焦點放在了@ConfigurationProperties(prefix = "jdbc")
這個注解上,
......// 原理待完善
將@ConfigurationProperties(prefix = "jdbc")
注解去掉,重新部署,一切正常。
思考
- 知其然,知其所以然
作為一個程序員不求甚解是要不得的,應該在熟練使用工具的同時,多了解其背景和原理,這樣才不會濫用,遇到問題才能快速準確的定位。
As a programmer, I hate to use things I don’t understand.
- 溝通
應該積極主動的溝通,不應該回避溝通,避免出現信息孤島。
其實運維的同事在開發環境部署了一套19c,但是并沒有通知我們。由于項目周期短,時間太緊張了,開發人員直接在測試環境開發,已期望提高部署和用戶驗證的效率。從而導致了很多不必要的操作。 - 規范
我們有開發、測試、生產三個環境,考慮到項目時間緊張,期望最大化提高效率,只維護了測試環境,直接忽略了開發環境。這樣不規范的操作導致了一系列的問題,環境問題,配置問題,開發頻繁部署,導致用戶測試體驗極差等等。
開發、測試、生產環境應該保持一致,都應該部署19c的數據庫,但是不知道什么原因運維再開發部署了19c、測試部署了11g、生產部署了19c。環境不一致增加了問題分析的很多不必要的復雜度。
引入更新或一項新技術的時候,應該先做好充足的知識儲備和測試環境驗證(最好由架構組先嘗試、總結、然后培訓推廣),而不是盲目的嘗試,評估的時候應該更謹慎。