作者:王大喜
在很多高并发场景中(如定时秒杀),流量之势并非如滔滔江水延绵不绝,而是黄河泛滥一发不可收拾。后者由于其突发性,往往不及处理或者处理完流量洪峰已经过去,其后果和影响具有较大破坏性。本文将对突发流量的对付方法之一:数据库连接的预热做一些说明。
在继续阅读之前,请诸位读者先人肉执行以下代码:
loop 1 to 10
readAloud("建立连接的代价是很昂贵的")
gasp()
end
既然建立连接费时费力,所以在流量到来前先建好放连接池就是很自然的想法了。以tomcat.jdbc数据源配置为例:
<bean id="dataSource" parent="parentDataSource"
p:initialSize="300"
p:maxActive="500"
p:maxIdle="500"
p:minIdle="300"/>
指定了initialSize和minIdle,使应用初始化一定数量的连接并保持,这样在流量进来的时候就有现成的连接可用。
如果事情都这么简答,码农们也不用为头发发愁了。先来泼两盆水:
<bean id="dataSource">
<property name="targetDataSources">
<list>
<ref bean="dataSource1"/>
<ref bean="dataSource2"/>
</list>
</property>
</bean>
其中,dataSource1和dataSource2的配置类似'如何初始化连接'一节。
第二个问题是我们实际碰到,先看这个。过程分析其实很简单,应用起来后看下到数据库的连接,发现只有一个数据源的连接初始化了。看来得分析下数据源怎么初始化了,这时候没有比看源代码更好的办法。顺着SqlSessionFactoryBean的afterPropertiesSet方法(因为实现了InitializingBean接口),很容易找到初始化的地方:
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
再到databaseIdProvider的实现一看就明白,在我们的MultipleHashDataSource事先中,没有提供key的情况下默认使用第一个数据源, 而databaseIdProvider也只会初始化一个数据源。原因找到解决办法就很简单了:
if (dataSource instanceof Iterable) {
Iterator<DataSource> it = ((Iterable<DataSource>) dataSource).iterator();
while (it.hasNext()) {
productName = getDatabaseProductName(it.next());
}
} else {
productName = getDatabaseProductName(dataSource);
}
private DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
WTF! 低头认命,老实去搞个BeanPostProcessor吧,问题也能解决。事实上,在后来的大促场景中,多数据源初始化后成功的抗住了第一波流量。
事后网上看到有类似的反馈,看了下新版本已经不默认设置了。真棒,更新了下版本,这下注入databaseIdProvider方便多了,也更优雅。 但是!如果不显示注入databaseIdProvider,数据源就不会被初始化了。 所以凡事都有两面性,就像白加黑治感冒:白天吃白片,不精神;晚上吃黑片,睡不着。
ps: 1.2.1及之后的版本中:
//issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
突发流量场景一定要预热资源,数据库连接只是其中之一,本文抛砖引玉,希望没浪费各位看官时间。
网易云大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者王大喜授权发布。