記錄一下自己的踩坑血淚史,昨天,金融組的項目經理問我是不是自己搭了一套自動化平臺,有代碼質量檢測功能,給他做一下代碼質量檢測。我跟他說,沒問題,我給你配置一下持續集成都行。
然後,我找他要了SVN地址,配置到jenkins,修改他的項目配置文件,配置了gradle的sonar插件,然後5分鐘不到,全部配好,開掃。
瞟了眼日誌,5000個java文件,有點愣,他說這項目做了好多年了,頭一次做代碼質量檢測。
等了10分鐘,終於掃描完畢,然後報了個500錯誤,失敗了。
我的項目一直在自動構建,從來沒失敗過啊,心裏有點慌,是不是自動構建環境哪裏出問題了,趕緊構建了一下我自己的項目,2分鐘後,我自己的項目成功構建完成。
那估計就是他的項目引發的問題了,shell中的日誌被頂不見了,無奈又跑了一次他的項目,10分鐘又過去了。
看到錯誤信息
INFO: Analysis report generated in 5163ms, dir size=86 MB
INFO: Analysis reports compressed in 8786ms, zip size=28 MB
ERROR: Error during SonarQube Scanner execution
ERROR: Failed to upload report - 500: An error has occurred. Please contact your administrator
沒有詳細信息,不知道什麼問題,於是 -x又跑了一遍……10分鐘又過去了
沒啥特別有用的信息,還是說上傳報告出錯,500
想了想,500那就是sonarqube報錯了,服務端應該有日誌,於是去到sonarqube/logs/web.log找到具體的錯誤信息
2019.03.19 08:42:37 ERROR web[AWlGy9kW/lhOnmA/AAUn][o.s.s.w.WebServiceEngine] Fail to process request http://localhost:9000/api/ce/submit?projectKey=dfcwpc&projectName=dfcwpc
java.lang.IllegalStateException: Fail to insert data of CE task AWmTZm4R6CYg244CjrN5
at org.sonar.db.ce.CeTaskInputDao.insert(CeTaskInputDao.java:56)
Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (34143478 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.
原來是mysql的數據包大小限制問題,趕緊查一下現在是多少
mysql> show variables like '%max_allowed_packet%';
+--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| max_allowed_packet | 4194304 |
| slave_max_allowed_packet | 1073741824 |
+--------------------------+------------+
---------------------
才4M……話說剛纔我們的包是多大來着,28M吧,怪不得報錯,趕緊修改mysql設置
vim /etc/my.cnf,根據實際情況進行參數調整:
[mysqld]
max_allowed_packet = 100M
重啓mysql,這下改好了吧(一個小時已經過去了,同事已經向我投來了懷疑的眼光……而且,馬上就要下班了)
重新運行掃描,10分鐘又過去了,又報錯了,客戶端報錯信息跟之前一樣,還是500
趕緊看一眼服務端日誌,內容如下:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline.
還是那一行代碼,換了個錯誤信息。
行數據大於8126?查閱mysql相關文檔,瞭解到
- 一個16KB的InnoDB數據Page必須至少包含兩行數據。此外,每個Page都有一個頁眉和一個包含頁面校驗和和日誌序列號的頁腳,依此類推。這就是你的每行限制小於8KB的地方。
- 固定大小的數據類型(如INTEGER,DATE,FLOAT,CHAR)存儲在此主數據Page上,並計入行大小限制。
- 可變大小的數據類型(如VARCHAR,TEXT,BLOB)存儲在溢出頁面上,因此它們不會完全計入行大小限制。在Antelope中,除了存儲在溢出頁面上之外,在主數據頁面上還存儲多達768個字節的此類列。Barracuda支持 動態行格式,因此它可能只在主數據頁面上存儲一個20字節的指針。
- 可變大小的數據類型也以1個或多個字節爲前綴來編碼長度。而InnoDB行格式也有一個字段偏移數組。因此,他們的wiki中或多或少都記錄了內部結構。
因此,我們要把問題所在的表的 ROW_FORMAT改成COMPRESSED或者DYNAMIC,應該就能解決問題。
於是,再次修改mysql設置
[mysqld]
#服務器發送和接受的最大包長度,當單行數據較大時,需要調整該參數。
max_allowed_packet = 100M
#開啓page獨立空間
innodb_file_per_table = 1
#innodb的文件格式更改爲Barracuda
innodb_file_format = Barracuda
Antelope是innodb-base的文件格式,Barracude是innodb-plugin後引入的文件格式,同時Barracude也支持Antelope文件格式。兩者區別在於:
文件格式 | 支持行格式 | 特性 |
Antelope
(Innodb-base) |
ROW_FORMAT=COMPACT
ROW_FORMAT=REDUNDANT |
Compact和redumdant的區別在就是在於首部的存存內容區別。
compact的存儲格式爲首部爲一個非NULL的變長字段長度列表 redundant的存儲格式爲首部是一個字段長度偏移列表(每個字段佔用的字節長度及其相應的位移)。 在Antelope中對於變長字段,低於768字節的,不會進行overflow page存儲,某些情況下會減少結果集IO. |
Barracuda
(innodb-plugin) |
ROW_FORMAT=DYNAMIC
ROW_FORMAT=COMPRESSED
|
這兩者主要是功能上的區別功能上的。 另外在行裏的變長字段和Antelope的區別是隻存20個字節,其它的overflow page存儲。
另外這兩都需要開啓innodb_file_per_table=1 (這個特性對一些優化還是很有用的) |
重啓數據庫
然後需要修改問題所在表的行格式,然而,到底是哪張表出問題了呢?
沒辦法,開始翻看sonarqube的源碼https://github.com/SonarSource/sonarqube
終於找到了
public void insert(DbSession dbSession, String taskUuid, InputStream data) {
long now = system.now();
Connection connection = dbSession.getConnection();
try (PreparedStatement stmt = connection.prepareStatement(
"INSERT INTO ce_task_input (task_uuid, created_at, updated_at, input_data) VALUES (?, ?, ?, ?)")) {
stmt.setString(1, taskUuid);
stmt.setLong(2, now);
stmt.setLong(3, now);
stmt.setBinaryStream(4, data);
stmt.executeUpdate();
connection.commit();
} catch (SQLException e) {
throw new IllegalStateException("Fail to insert data of CE task " + taskUuid, e);
}
}
問題所在的表就是 ce_task_input,於是執行SQL
DROP TABLE ce_task_input;
CREATE TABLE `ce_task_input` (
`task_uuid` VARCHAR(40) COLLATE utf8_bin NOT NULL,
`input_data` LONGBLOB,
`created_at` BIGINT(20) NOT NULL,
`updated_at` BIGINT(20) NOT NULL,
PRIMARY KEY (`task_uuid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
這個時候已經晚上7點多了,同事已經要放棄了,叫我別折騰了,我說弄好了,這次肯定能成,然後讓他跟我一起見證奇蹟。
接着在大家的注視下,我執行了掃描命令,然而這次讓我更加尷尬了,還是500,錯誤日誌變了
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help. In current row format, BLOB prefix of 0 bytes is stored inline.
字面上的意思是讓我把某些字段改成TEXT或者BLOB……WTF?特麼就這4個字段,還有啥好改的
同事已經回家了,我已經忘記了晚飯的事,開始繼續折騰……
谷歌,百度各種搜索,各種查,基本都是說上面的改innodb文件類型的方案,完全沒有用……不知不覺就快9點了,家裏的電腦壞了,兒子學不了英語,老婆火氣特別大,趕緊關電腦回家。
今天早上剛到公司,繼續磕mysql論壇,突然看到有人說設置innodb_log_file_size,日誌文件大小,突然想到,sql日誌裏面是包含完整的sql內容的,日誌文件大小也可能影響插入。
趕緊查一下,默認的sql日誌文件是500M,果斷改成1G
[mysqld]
#服務器發送和接受的最大包長度,當單行數據較大時,需要調整該參數。
max_allowed_packet = 100M
#開啓page獨立空間
innodb_file_per_table = 1
#innodb的文件格式更改爲Barracuda
innodb_file_format = Barracuda#日誌文件大小
innodb_log_file_size=1024M
重啓mysql,開始掃描,10分鐘過去,終於成功了!
INFO: Analysis report generated in 5163ms, dir size=86 MB
INFO: Analysis reports compressed in 8786ms, zip size=28 MB
INFO: Analysis report uploaded in 2366ms INFO: ANALYSIS SUCCESSFUL, you can browse http://192.168.95.45:9000/dashboard/index/ly.mp:dfcwpc
INFO: Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
INFO: More about the report processing at http://192.168.95.45:9000/api/ce/task?id=AWmVAuVJVEKL64HKj-4e INFO: Task total time: 6:23.107 s
INFO: ------------------------------------------------------------------------
INFO: EXECUTION SUCCESS
INFO: ------------------------------------------------------------------------
INFO: Total time: 6:24.156s INFO: Final Memory: 24M/1434M
INFO: ------------------------------------------------------------------------