记录一下自己的踩坑血泪史,昨天,金融组的项目经理问我是不是自己搭了一套自动化平台,有代码质量检测功能,给他做一下代码质量检测。我跟他说,没问题,我给你配置一下持续集成都行。
然后,我找他要了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: ------------------------------------------------------------------------