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

数据库设定了主从同步后,单纯的数据多点存放已经不能满足我了(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>
       <!-- ReplicationDriver跟Driver在同一路径下 -->
      <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="jdbc:mysql:replication://192.168.1.234:3306,192.168.1.164:3306/yxdb?useUnicode=true&characterEncoding=UTF-8" />-->    
         <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配置

       <!-- druid相关监控数据 -->
       <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配置

        <!-- 设置druid数据源相关监控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等统计数据。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容