美文网首页项目变迁Java学习笔记程序员
系统变迁之五--mysql读写分离

系统变迁之五--mysql读写分离

作者: AlanKim | 来源:发表于2016-09-20 17:18 被阅读600次

    数据库设定了主从同步后,单纯的数据多点存放已经不能满足我了(O(∩_∩)O)...
    读写分离一直有各种方案,MySQL-Proxy也好,amoeba也罢,或者其他的中间件,都是在DB和application之间引入一个proxy,使用proxy来处理application中对DB的各种操作。
    还有一种伪读写分离的方案,在项目中配置多个数据源,不同的操作连接不同的数据源。。。
    不过我更倾向于在驱动层去处理这个问题,不太想再去额外维护一个中间件。而mysql的驱动目前已经支持使用ReplicationDriver来替代Driver,实现读写分离。

    ReplicationDriver

    官方关于ReplicationDriver的说明可以参考这里:http://dev.mysql.com/doc/connector-j/5.1/en/connector-j-master-slave-replication-connection.html

    ReplicationDriver驱动分离的机制是靠判断connection.setReadOnly(true)来决定是否访问从库,而Spring的事务管理,可以使用@Tranactional(readonly=true)来设置连接是否为只读,基本上就这些,看起来挺简单吧...一路还是不断踩坑,且听俺慢慢道来。。。

    先提一下,数据库连接池bonecp是不支持使用ReplicationDriver做读写分离的。最初项目为了求快和求简,用了bonecp(配制简单,块头小),不过在引入ReplicationDriver的时候,却踩了坑,无论怎么配置都是访问的主库。后面切换为druid才解决,关于淘宝开源的数据库连接池druid介绍,可以参考这里:https://github.com/alibaba/druid/wiki

    在数据库连接池中配置ReplicationDriver
    • 首先引入druid及ReplicationDriver的依赖
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>${druid-version}</version>
      </dependency>

      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.39</version>
      </dependency>

    • 数据库连接池配置
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">

      <property name="url" value="${dataSourceUrl}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>

           <!-- druid可以根据url自动识别数据库类型并自己选择驱动,不过默认会识别成Driver,所以这里强制指定ReplicationDriver -->
          <property name="driverClassName" value="com.mysql.jdbc.ReplicationDriver" />    
          
          <!-- druid监控相关设定,mergeStat--合并统计sql -->
          <!--<property name="filters" value="stat" />-->    
          <property name="filters" value="mergeStat"/>    
      
          <property name="maxActive" value="20" />    
          <property name="initialSize" value="1" />    
          <property name="maxWait" value="60000" />    
          <property name="minIdle" value="1" />   
          <property name="timeBetweenEvictionRunsMillis" value="60000" />    
          <property name="minEvictableIdleTimeMillis" value="300000" />    
          <property name="testWhileIdle" value="false" />    
          <property name="testOnBorrow" value="false" />    
          <property name="testOnReturn" value="false" />    
          <property name="poolPreparedStatements" value="true" />    
          <property name="maxOpenPreparedStatements" value="20" />
       </bean>
      

    每个属性的说明可以参考这里:
    https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

    • 注意url中,mysql的数据库连接要改成 jdbc:mysql:replication:// 开头,而后面的IP地址,则依照masterIP:port,slave1IP:port,slave2IP:port的顺序往下写,主库在先,从库在后。
    spring事务中对于连接的处理
    • 做读写分离,首先需要梳理哪些方法是只读操作,哪些方法是读写一体或只写操作。而对于比较关键的只读操作,也不建议直接迁移到从库,毕竟主从复制是有一定的时间延迟的。
    • 既然提到事务了,如果在一个事务中涉及到了多张表的操作,一定要看下mysql的autocommit选项是否关闭,否则会造成回滚失败
    Paste_Image.png
    • 而且要注意,这个命令只针对当前用户,如果需要全局生效,则需要使用global关键字
    Paste_Image.png
    • 还要注意的是,这个选项对于mysql的root用户是不生效的。。。而且,如果是使用spring统一管理数据库连接,这块spring是在DataSourceTransactionManager.java中默认设置为false的,如下:
      // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
      // so we don't want to do it unnecessarily (for example if we've explicitly
      // configured the connection pool to set it already).
      if (con.getautocommit()) {
      txobject.setmustrestoreautocommit(true);
      if (logger.isdebugenabled()) {
      logger.debug("switching jdbc connection [" + con + "] to manual commit");
      }
      con.setautocommit(false);
      }

    • 代码级别的设置:
      在只需要访问读库的方法上,添加注解 ,搞定,收工。。。
      @Transactional(readOnly = true,...)

      • 此注解只能在public方法上使用才会生效
      • readOnly默认为false的,故对于需要访问主库的,这个属性可以不设置
    附上druid中的配置监控,方便查看统计
    • 先晒两张监控图
    Paste_Image.png Paste_Image.png

    druid跟其他数据库连接池除了在连接上面做了很多优化之外,亮点就是在监控这块了

    • 配置druid web监控
      • 在项目的web.xml中加入filter配置

        <filter>
        <filter-name>DruidWebStatFilter</filter-name>
        <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
        <init-param>
        <param-name>exclusions</param-name>
        <param-value>.js,.gif,.jpg,.png,.css,.ico,/druid/</param-value>
        </init-param>
        <init-param>
        <param-name>profileEnable</param-name>
        <param-value>true</param-value>
        </init-param>
        </filter>
        <filter-mapping>
        <filter-name>DruidWebStatFilter</filter-name>
        <url-pattern>/
        </url-pattern>
        </filter-mapping>

      • 在web.xml中加入servlet配置

        <servlet>
        <servlet-name>DruidStatView</servlet-name>
        <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
        <init-param>

        <param-name>resetEnable</param-name>
        <param-value>true</param-value>
        </init-param>
        <init-param>

        <param-name>loginUsername</param-name>
        <param-value>druid</param-value>
        </init-param>
        <init-param>

        <param-name>loginPassword</param-name>
        <param-value>****</param-value>
        </init-param>
        </servlet>

          <servlet-mapping>    
              <servlet-name>DruidStatView</servlet-name>    
              <url-pattern>/druid/*</url-pattern>
          </servlet-mapping>
        

    重启后,直接访问http://ip/domainname[:port]/appname/druid ,使用上述指定的用户名及密码就可以登录了,可以查看sql及bean相关的统计,以及慢sql等统计数据。

    相关文章

      网友评论

      • 6494df96c4e2:主库和从库用户名密码不一致怎么破
        6494df96c4e2:@AlanKim 嗯嗯 谢啦,最后还是用aop做的动态改变数据源,mycat还是太重了
        AlanKim:@TuringHero 主库和从库密码设置成不一致的情况确实是个弊端,使用这种取巧的方式来做读写分离确实无法解决你说的问题。建议去考虑mycat之类的开源框架,看能否实现。

      本文标题:系统变迁之五--mysql读写分离

      本文链接:https://www.haomeiwen.com/subject/rgfnettx.html