背景
我们使用的数据库是mysql+shardingproxy(v5.3.1)实现分库分表,突然有一天收到大量的告警显示数据库连接失败,经过排查日志发现是shardingproxy内存溢出导致了重启,从而导致大量的服务告警。
引发原因
经过排查是一条sql语句导致的,我大概简写一下就是如下所示的一条简单sql
select * from member_info limit 3000000,10;
因为这个member_info是逻辑表,实际上是分了8张表,即member_info_0~7。 经过sharingproxy改写后成如下
select * from member_info_0 limit 0,3000010;
select * from member_info_1 limit 0,3000010;
select * from member_info_2 limit 0,3000010;
select * from member_info_3 limit 0,3000010;
select * from member_info_4 limit 0,3000010;
select * from member_info_5 limit 0,3000010;
select * from member_info_6 limit 0,3000010;
select * from member_info_7 limit 0,3000010;
从这个改写我们可以分析出,shardingproxy需要从这8张表中将前3000010数据取出来,然后再进行数据的合并,然后取出3000000~3000010条数据返回客户端,所以猜想应该是在数据合并时使用了大量的内存且超过了shardingproxy的jvm内存导致内存溢出。
探究shardingproxy内存溢出原因
在做技术选型的时候对shardingproxy进行过比较深入的了解,在执行引擎中有一个连接模式,分别为内存限制模式(MEMORY_STRICTLY)和连接限制模式(CONNECTION_STRICTLY)
内存限制模式:模式不限制连接数,也就是说会建立多个数据连接,采用流式归并,ResultSet移动游标读取数据到内存,减少内存开销
连接限制模式:对连接数进行限制,也即是说至少有一个数据库连接会要去读取多个数据分片的数据,采用内存归并,一次性读取ResultSet数据到内存,减少数据库连接开销。
这两个模式的选择涉及到一个参数max-connections-size-per-query(默认值为1)
shardingproxy会根据sql的路由和改写结果计算出所需在数据库执行的sql数量(如我们刚刚所需执行的语句数量为8),然后与配置的maxConnectionsSizePerQuery值进行比较,maxConnectionsSizePerQuery<sql数量,那么选择连接限制模式,否则选择内存限制模式。
执行流程如下:
所以有了这些了解我们可以再来分析一下造成内存溢出这条语句的过程
1.将原始sql进行路由,改写为8条需要在数据库执行的sql语句
2.根据所需执行的 sqlUnits.size()数量与maxConnectionsSizePerQuery(默认值为1)进行对比,发现 sqlUnits.size()>maxConnectionsSizePerQuery,选择连接限制模式
3.获取一个连接采用串行的方式一次执行8条sql语句,并将数据加载到内存(内存不足导致溢出)进行数据归并,获取第3000000~3000010条数据
关于两种连接模式适合场景(个人愚见)
连接限制:因为连接限制模式会对连接数进行限制,并将数据全部读入到内存,进行统一的数据内存归并。这种方式归并效率会比较高,例如一个MAX归并,直接就能拿到最大值,而流式归并(内存限制)就需要一条条的比较。比较适合OLTP场景
内存限制:内存限制模式不会限制连接数,它可以同时建立多个连接,并发的去读取数据分片的数据,这样可以最快速度的把所有需要的数据读出来。并且在后面的归并阶段,会选择以每一条数据为单位进行流式归并。这种归并方式归并完一批数据后,可以释放内存了,可以很好的提高数据归并的效率,并且防止出现内存溢出或垃圾回收频繁的情况。他的吞吐量比较大,比较适合OLAP场景。
解决
分析完问题后,我们认为有两个方式进行解决
1.从业务上进行妥协,客户列表这个页面去除跳页这个功能,避免用户直接进行跳页导致深度分页的情况,其次优化分页语句(就不展开了)。
2.调整maxConnectionsSizePerQuery的值,使shardingproxy在执行时选择内存限制模式
props:
max-connections-size-per-query: 8
sql-show: true
最后我们采用的方式一作为解决方案,因为我们系统OLTP场景居多,采用连接限制模式可以提高系统并发。