数据库设定了主从同步后,单纯的数据多点存放已经不能满足我了(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选项是否关闭,否则会造成回滚失败
- 而且要注意,这个命令只针对当前用户,如果需要全局生效,则需要使用global关键字
-
还要注意的是,这个选项对于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中的配置监控,方便查看统计
- 先晒两张监控图
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等统计数据。