根據需求開發了生產者消費者功能,生產者從Oracle數據庫讀取數據後,通過kafka發送給消費者,消費者從kafka中讀取數據後,寫入到mysql數據庫中,功能需求就這樣,比較簡單,總量5000W數據,通過3個生產者讀取寫入到kafka中。
代碼寫完後經過簡單的功能測試沒問題,開始在測試環境上運行,大概運行了不到1小時發送了不到200萬的數據時出現OOM GC overhead limit exceeded,重複運行多次後確認存在問題,每次發送大概150W-200W數據時候出現OOM異常。
接下來在本地使用Jprofiler進行代碼測試,發現運行一段時間後有大量的char[] 和String對象沒有釋放,如圖:
而且size還在不斷增大,這是導致OOM的直接原因,那麼問題來了,是什麼導致了這麼多的char[] 和String對象呢?
首先排查代碼,
String topic="testCsrk1";
String clientId="CsrkProducer0";
int begin=630681;//初始位置 630681 28000000
int end = 28000000;//結束位置70987838 204138956
int count=1 ;//循環控制條件
int posi=100;//一次循環數量
int id=begin;
OracleProducer producer=new OracleProducer(topic,false,clientId);
SqlSession sqlSession=factory.openSession(false);
OCsrkMapper oCsrkMapper = sqlSession.getMapper(OCsrkMapper.class);
int num=0;
while(count>0) {
Map<String,Object> paramMap=new HashMap<String,Object>();
paramMap.put("id", id);
paramMap.put("posi", posi);
paramMap.put("endid", id+posi);
List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//從數據庫查詢數據
int size=list.size();
num+=size;
logger.info("*******此次條數 "+size);
System.out.println("*******總計條數 "+num);
if(id>=end) { //判斷id是否超過發送的範圍
System.out.println(" id is " +id);
count=0;
}
if(size==0) {
id=id+posi;
}
for(int i=0;i<size;i++) {
Csrk csrk=list.get(i);
if(csrk!=null) {
producer.send(csrk);//封裝的kafka生產者發送數據
}
id=list.get(i).getID();
}
}
producer.close();
System.out.println("producer closed");
沒有使用char[]這種類型,代碼中String對象也都是正常的,還得從其它方向上尋找問題。
這種類型的對象還有一種可能就是緩存,mybatis分爲一級緩存和二級緩存。
一級緩存的作用域是SqlSession範圍的,當在同一個sqlSession中執行兩次相同的sql語句時,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次查詢時會從緩存中獲取數據,不再去底層數據庫查詢,從而提高查詢效率。
需要注意的是,如果SqlSession執行了DML操作(增刪改),並且提交到數據庫,MyBatis則會清空SqlSession中的一級緩存,這樣做的目的是爲了保證緩存中存儲的是最新的信息,避免出現髒讀現象。
當一個SqlSession結束後該SqlSession中的一級緩存也就不存在了。
二級緩存的作用域是mapper的同一個namespace。不同的sqlSession兩次執行相同的namespace下的sql語句,且向sql中傳遞的參數也相同,即最終執行相同的sql語句,則第一次執行完畢會將數據庫中查詢的數據寫到緩存,第二次查詢會從緩存中獲取數據,不再去底層數據庫查詢,從而提高效率。也就是說二級緩存是跨SqlSession進行數據共享的
注意:一級緩存是不能被關閉的
好了,回到代碼中sqlSession只被創建了一次,因此一級緩存被創建了後並未被清楚,加入以下清除一級緩存的代碼進行修改
sqlSession.clearCache();
List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//從數據庫查詢數據
重新使用Jprofiler進行測試,觀察發現char[]和String對象的size仍然在增長,那代開mybatis的xml文件發現開啓了二級緩存
<cache/>
那關閉對應sql的二級緩存
useCache="false"
重新使用Jprofiler執行,觀察,發現char[]和String對象的size穩定在10M以下,連續運行超過2200萬數據用時不到90分鐘,內存使用量穩定在80M以下
後續的思考
1.如果xml中沒有使用</cache>標籤會不會不會出現這個問題
在xml中去掉</cache>標籤,同時去掉useCache="false"然後執行,觀察char[]和String對象的size,會隨着時間的增長而增長。
而在每次查詢前加入sqlSession.clearCache();代碼用來清除之前的一級緩存,觀察觀察char[]和String對象的size,很穩定,不會隨着時間的增長而變化,說明清除一級緩存成功。
部分內容參考https://www.jianshu.com/p/c553169c5921