构建更健壮的系统:数据库连接池的问题Too many connections、timeout等等

Excalibur0 2017-10-06

首先,连接池是为了节省打开关闭数据库连接的一个手段,如果不用连接池,最大能获取连接的数量限制则是mysql等数据库端配置的max_connections字段的数量。

使用连接池后,最大连接数就由程序这边来控制了,例如一般的最大连接数为20。

一个连接池的配置最常见,最关键的配置是最大连接数和超时时间

<!-- 配置初始化大小、最小、最大 -->
                <property name="maxActive" value="20" />
		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="60000" />

 然后大部分程序配置就是这些了。

首先说一下Too many connections这个问题,其实如果是使用的连接池,除非连接池存在bug或者多处初始化连接池一般是不会出现这个异常。原因是连接池就已经限制了最大连接数,基本上是不会超过数据库的最大连接数的。

然后就是超时问题:

现在你的程序可能会出现1个问题:获取连接超时了

一般情况下你可能想到的解决办法是调大最大连接池数量,增加超时时间。

但是这个做法只是掩盖了问题,而没有解决问题。而且sql抛出异常是个比较危险的行为。

因为,你不知道你的业务逻辑进行到哪一步的情况下才会抛出异常,而且大部分程序不会对dao业务进行try catch处理。举例说明:

某个service方法:

public void addGold(int gold){

User user=dao.getUser(uid);

user.setGold(user.getGold);

dao.update(user);

}

有可能在update这一步报错,导致了什么问题呢。现在的系统大部分都用上了缓存,user 的值被修改过了很可能已经在缓存的值修改过了。然后数据库操作这边抛出了异常导致了两遍数据不一致的问题。

而且,前面说了。调大部分数值只是掩盖、拖延问题。

如何解决问题?首先,出现超时错误的原因就是1个:获取不到连接。

为什么获取不到连接?因为某些逻辑一直占用了连接导致了没有连接分配给你了,造成你超时。

而一般正常的情况下,不会有这种逻辑出现。所以我们要找出这些问题所在。

而且,获取不到连接问题抛出异常可能会影响你的现有业务。因为这是一个比较随机的事情,你的业务可能进行一半的时候抛出异常。A执行了,B没有执行。你可能会说有事物在可以回滚,但是实际上A的数据已经被同步到了缓存里面了,B的数据不一致。你能回滚缓存里面的数据吗。

所以我的建议是:maxWait这个不能随便就超时了。因为这个会影响业务,而且连接不一定是一直都被阻断的,总有能恢复正常获取到的时候。

就例如前面配置的超时时间:1分钟,已经超时1分钟的业务不在乎多等一会直到业务完成。

反而我们要找出导致问题的元凶,长时间占用连接的方法。

<!-- 防止某些连接被长期占用 单位秒 -->
		<property name="removeAbandoned" value="true" />
		<property name="removeAbandonedTimeout" value="10" />
		<!-- 关闭abanded连接时输出错误日志 -->
		<property name="logAbandoned" value="true" />

使用removeAbandoned来找出哪些长期占用的连接。根据自己的业务来决定超时时间。

例如你的业务里面有很多统计查询,有的确实是耗时长。你可与另外配置一个dataSource,另外一套配置来执行这些操作。但是我想基本上没有什么业务是需要客户等待1分钟的吧。

maxWait如果变成永不超时或者超时时间过长当然也有会有其他问题,会导致上层业务一直阻塞住。进而更进一步的消耗整个系统的性能。

例如:要查询一个结果,调用查询方法。dao层获取连接这一步一直阻塞住直到超时期间,业务层也会阻塞住。如果一直重复操作会生成一堆被阻塞的业务。阻塞这个问题主要是阻塞线程。线程被阻塞了要么是新建线程要么总的线程数量有上限。这样就导致了所有的业务都被停顿下来。

停顿的时间由maxWait决定,但是这本身是个很复杂的问题。如果你的系统到了这一步,会出现非常混乱的事情,任何一个操作都会报错,而且这些报错对你没有任何用处。主要原因是因为就算当前操作抛出了异常,实际上其他业务还是会获取连接,被阻塞住。获取连接这个不是出现问题的原因。

设置maxWait超时时间当然也是有好处的。当系统恢复正常的时候(那些长期占用连接的业务被干掉或者运行完毕)你的系统不会有一堆阻塞的业务。

我的推荐设置:2套数据源配置。主要是用来区分长时间会占用连接的业务和其他普通业务。如果系统没有那种会导致长时间执行的那么就只需要一套配置。

maxWait这个配置不会影响你的系统,也不会帮助你找出问题所在。

removeAbandonedTimeout这个配置用来检查哪些逻辑长期占用连接。这个才是导致问题的原因。

并且通过这个配置,可以干掉部分异常耗时的业务。来防止其他正常的业务被干扰。开发过程中可以把这个值尽量的设置小点,这样哪些不合理的业务能更快的暴露出来。

生产环境值可以设置大一些,主要的区别是如果业务量巨大,同样会导致一些连接问题出现。

我想到这些事情是在测试线程池数量对程序性能提升关系的时候出现的。

当我创建1万个线程并发执行某个sql的时候,连接池的最大数量是20。1万个线程并发执行完需要70秒

maxWait超时时间设置的是60秒。

我在想虽然确实是超时了60秒,但是毕竟系统的并发量这么高有延迟排队的现象也是非常正常的。现在就有一个考虑:在这么高的并发下连接池处理不过来是很正常的事情,不应该因为超时了10秒就向上抛异常。而且,如果程序没有问题的情况下,高并发条件下超时是情有可原的一件事情。如果直接抛弃操作会导致不可预知的事情发生。

所以maxWait不要轻易设置过短,而设置过长只会在系统本身出现问题或者高并发情况下干扰一些其他业务。

而对系统业务保持问题这个事情,应该由更上层系统来决定。例如上一篇说的构建更加稳定的系统多线程这个问题上。

而不是拿最底层数据层做文章。

相关推荐