突发流量应对之数据源预热

阿凡达2018-08-31 18:34

作者:王大喜


在很多高并发场景中(如定时秒杀),流量之势并非如滔滔江水延绵不绝,而是黄河泛滥一发不可收拾。后者由于其突发性,往往不及处理或者处理完流量洪峰已经过去,其后果和影响具有较大破坏性。本文将对突发流量的对付方法之一:数据库连接的预热做一些说明。

在继续阅读之前,请诸位读者先人肉执行以下代码:

  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,使应用初始化一定数量的连接并保持,这样在流量进来的时候就有现成的连接可用。

事实并没有那么简单

如果事情都这么简答,码农们也不用为头发发愁了。先来泼两盆水:

  1. 常见使用mybatis-spring中配置sqlSessionFactory,如果没有配置databaseIdProvider并且使用1.2.1以上版本,那么initialSize的配置是无效,也就说是突发流量来了必然扛不住。
  2. 在一次大促场景中,我们用了分库以避免db瓶颈,所以配置了多数据源,mybatis-spring的版本是1.2.0,并且每个数据源都设置了较大的初始连接数。然而在测试的时候最初一波流量来仍有很多请求响应时间超长。 数据源配置大致如下:

  <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也只会初始化一个数据源。原因找到解决办法就很简单了:

  1. 让MultipleHashDataSource实现Iterable接口
  2. 继承DatabaseIdProvider并实现,然后在getDatabaseName中兼容Iterable的数据源:
    if (dataSource instanceof Iterable) {
       Iterator<DataSource> it = ((Iterable<DataSource>) dataSource).iterator();
       while (it.hasNext()) {
         productName = getDatabaseProductName(it.next());
       }
     } else {
       productName = getDatabaseProductName(dataSource);
     }
    
  3. 将自定义的DatabaseIdProvider注入到SqlSessionFactoryBean中,完美! 但是。。。看下SqlSessionFactoryBean的代码(1.2.0),DatabaseIdProvider是这样定义的:

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

本文来自网易实践者社区,经作者王大喜授权发布。