Sharding-JDBC获取链接失败问题排查

问题背景

项目中需要存储公司的部分 UGC 视频,数据量级在10亿级别,存储数据库使用的是MySQL, 因此需要分库分表。分库分表中间件使用的是Sharding-jdbc,在使用该分库分表中间件过程中遇到一些问题,特记录在此。

问题一:项目启动慢

我们线上是分了4个库,每个库是分了1024张表。每次启动时,Sharding-jdbc默认要加载缺省的数据源的表元信息,这个元信息是当Sharding-jdbc无法找到某个表的分片配置时,会使用这个数据源的配置。而这个过程就要耗时1分钟左右。网上给的建议是调大max.connections.size.per.query 的值,该值默认是1, 也就是在启动阶段默认同时只会有1个连接去加载元数据信息,并行度为1。我们在线上调大到16之后,相当于并行度为17,因此启动速度快了很多。

问题二:启动阶段无法获取到连接

当我们为了加速启动阶段加载默认数据源的表元数据信息而提高加载的并行度时,启动时会报无法获取到更多连接的错误。我们的MySQL数据库连接池使用的是Springboot 2.1.6.RELEASE 默认的Hikar连接池,默认的连接池最大连接数 maximum-pool-size 是10,而我们配置的

max.connections.size.per.query 是16, 也就是说当数据源中的表数量大于16时,则并行度也大于16,也就是同时会启动至少17个线程同时去加载表的元信息,由于数据库连接池的最大连接数为10,因此会报获取连接失败的问题。当我们把连接数调大到32时,问题便解决了。

问题三:批量查询参数过多时报获取连接失败

前面提到我们为了解决项目启动慢的问题,把 max.connections.size.per.query 和缺省数据源的 maximum-pool-size 参数都调大到了16. 我们在运行时基于JPA的 findAllById() 批量查询数据时,当传参的集合大小大于10时,会报获取连接失败的错误。这是因为我们的项目中使用了多个数据库,前面为了加速项目的启动过程,只是把缺省的数据源的最大连接数调大到了10,但本次失败查询所查询的数据源是其它数据库,而这些数据库对应的数据源依然是用的Hikari默认的最大连接数10. 而且 max.connections.size.per.query=16 是对所有的数据源都生效的。因此,当查询参数大于10时,框架会默认启动大于10个线程并行请求数据,同时也需要大于10个连接。因此会报获取连接失败。基于此,我们把所有的数据源的maximum-pool-size 参数都调整到了 16. 同时我们也把该问题反馈给了官方:https://github.com/apache/shardingsphere/issues/6145

问题四:保存数据时报获取连接失败

我们在日志中发现执行JPA默认的save方法也会报获取连接失败的错误。经过排查是由于底层的Hibernate在实际执行save之前,会先根据当前的主键id执行select查询操作,以降低写入插入重复的主键id的概率。而主键在分表中并不是分表键,因此查询操作会在每个分表上都执行,此时会开启配置的最大连接数数量的连接,我们项目中配置的max.connections.size.per.query=16,maximum-pool-size=32,因此当同时执行save的并发数大于2时很容易将连接池耗尽从而造成连接不可用。

为了解决这个问题,我们通过将Entity对象实现Persistable接口,并重写其中的isNew方法默认返回true,从而避免了每次save前都去执行select操作.代码示例如下:

import com.fasterxml.jackson.annotation.JsonFormat;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import org.springframework.data.domain.Persistable;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.Id;

import javax.persistence.Table;

import java.io.Serializable;

@Entity

@Table(name = "transaction")

@JsonIgnoreProperties(ignoreUnknown = true, value = {"hibernateLazyInitializer", "handler"})

public class WalletTransactionEntity implements Persistable<Long>, Serializable {

    @Id

    private Long id;

    @Column(name = "uid")

    private Long uid;

    @Override

    public boolean isNew() {

        return true;

    }

}

所以这里应该注意的是,应尽量避免基于非分表键的查询操作,否则会在所有的分表上都发起查询操作并建立连接,此时很容易将连接池中的连接耗尽

问题五:服务器连接池超限

实际项目中,我们批量读取数据库是放在公司视频库数据变更的消息者中,为了提高消费速度降低消息挤压,我们把消费的消息批次大小调整到了256,这样的话每次批量查询数据库的参数的集合大小最大可能到256(大部分情况下到不了256,这是因为消息变更的QPS一般情况下都不高,分担到每个消费者上面只有几十),基于此,我们把每个数据源的 maximum-pool-size 参数调整到了 256. 因为我们线上是多实例部署,因此启动阶段同时向MySQL服务端请求连接数 256 × 32 = 8192 个,我们公司分配的MySQL实例,服务端配置的默认最大连接数是 1122 个(最大连接数越多,服务端消耗的资源也多), 因此在启动阶段报了连接无法获取到的问题,导致实例无法正常启动。

发现这个问题后,一方面,我们申请将服务端的最大连接数调整大了2048个,同时我们也将项目中配置的消费消息批次大小和数据源最大连接池数调整到了32,项目终于能够正常启动。

以上,便是我们解决消费速度慢,消息积压问题的过程中遇到的一系列 Sharding-jdbc 无法获取到连接的问题过程。其中也发现了Sharding-jdbc在设计中一些不合理的地方,后面我们的优化建议会以MR的形式来提给官方。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • ShardingSphere之Sharding-JDBC分库分表、读写分离 一、数据切分概念 关系型数据库...
    孙运盛阅读 7,395评论 0 7
  • Sharding-JDBC是当当开源的分库分表中间件,通过重写jdbc api的方式为Java应用提供分库分表的能...
    Yuan2021阅读 5,751评论 1 8
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,606评论 28 53
  • 信任包括信任自己和信任他人 很多时候,很多事情,失败、遗憾、错过,源于不自信,不信任他人 觉得自己做不成,别人做不...
    吴氵晃阅读 6,231评论 4 8
  • 怎么对待生活,它也会怎么对你 人都是哭着来到这个美丽的人间。每个人从来到尘寰到升入天堂,整个生命的历程都是一本书,...
    静静在等你阅读 5,025评论 1 6