Java互联网架构-告白气球分布式高并发下mysql数据库读写分离

发布时间:2018-01-10 16:20:23编辑:丝画阁阅读(247)

概述

读写分离(Read/Write Splitting)。

1.原理:让主数据库(master)处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库(slave)处理SELECT查询操作。

2.诞生原因:

2.1 为了确保数据库产品的稳定性,很多数据库拥有双机热备功能。也就是,第一台数据库服务器,是对外提供增删改查业务的生产服务器;第二台数据库服务器,仅仅接收来自第一台服务器的备份数据(注意,不同数据库产品,第一台数据库服务器,向第二台数据库服务器发送备份数据的方式不同)。当第一台数据库崩溃后,第二台数据库服务器,可以立即上线来代替第一台数据库服务器,并且,在第一台数据库服务器崩溃后,宝贵的数据,依然会存在于第二台数据库服务器里(根据目前业界的备份数据发送方式来看,当第一台数据库崩溃后,第一台数据库里的仍然会有少量的新数据,没能来得及被发送到第二台数据库服务器,所以,这部分数据就丢失了)。

2.2 一般来说,为了配置方便,以及稳定性,这两台数据库服务器,都用的是相同的配置(思考一下,如果两台服务器的配置不同,会导致什么结果)。

2.3 从上文的描述中,大家能看到,在实际运行中,第一台数据库服务器的压力,远远大于第二台数据库服务器。因此,很多人希望合理利用第二台数据库服务器的空闲资源。那么,第二台数据库服务器能做些什么事情呢?

2.4 从数据库的基本业务来看,数据库的操作无非就是增删改查这4个操作。但对于“增删改”这三个操作,如果是双机热备的环境中做,一台机器做了这三个操作的某一个之后,需要立即将这个操作,同步到另一台服务器上。单向的同步,不复杂。但如果两台机器都需要向对方进行同步,那逻辑就非常复杂,而且还会大大降低性能。(从保证ACID特性的角度,思考一下为什么双向同步会非常复杂且低性能?而单向同步却不会?)出于这个原因,第二台备用的服务器,就只做了查询操作。进一步,为了降低第一台服务器的压力,干脆就把查询操作全部丢给第二台数据库服务器去做,第一台数据库服务器就只做增删改了。

2.4 到这一步,就实现了所谓的读写分离。这样做,缺点也非常明显了。本来第二台数据库服务器,是用来做热备的,它就应该在一个压力非常小的环境下,保证运行的稳定性。而读写分离,却增加了它的压力,也就增加了不稳定性。因此,读写分离,实质上是一个在资金比较缺乏,但又需要保证数据安全的需求下,在双机热备方案上,做出的一种折中的扩展方案。

读写分离

MySQL读写分离基本原理是让master数据库处理写操作,slave数据库处理读操作。master将写操作的变更同步到各个slave节点。

MySQL读写分离能提高系统性能的原因在于:

主从只负责各自的读和写,极大程度缓解X锁和S锁争用。

slave可以配置MyIASM引擎,提升读性能以及节约系统开销。

master直接写是并发的,slave通过主库发送来的binlog恢复数据是异步。

slave可以单独设置一些参数来提升其读的性能。

实现方法

1. MySQLProxy

MySQLProxy是在客户端请求与MySQLServer之间建立了一个连接池。所有客户端请求都是发向MySQLProxy,然后经由MySQLProxy进行相应的分析,判断出是读操作还是写操作,分发至对应的MySQLServer上。对于多节点Slave集群,也可以起做到负载均衡的效果。

1.1 存在的问题

当一个事务中先执行update,后执行select时,MySQLProxy 存在一个问题,由于它只是简单的将update打到master,select打到slave,由于mysql 主从复制是异步的,存在一定的延时,所以select 可能读取不到刚更新的数据。

2. Sharding JDBC

sharding jdbc官方文档

Sharding-JDBC是当当开源的一款分库分表&读写分离框架。经过评估后,决定使用该框架。

选择原因:

测试覆盖率达到95%

代码整体框架比较清晰,方便阅读及二次开发

社区活跃度较高,且持续维护

支持JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC

可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等

2.1 遇到的问题

上周在开发过程中遇到一个问题。当在一个spring Transactional中,先执行select操作,后执行update操作时,报以下异常:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: UPDATE command denied to user 'read'@'192.168.168.1' for table 'Book'

at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)

at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

at java.lang.reflect.Constructor.newInstance(Constructor.java:408)

at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)

at com.mysql.jdbc.Util.getInstance(Util.java:387)

at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:939)

at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3878)

at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3814)

at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2478)

at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2625)

at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2551)

at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861)

at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1192)

at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2931)

at com.alibaba.druid.wall.WallFilter.preparedStatement_execute(WallFilter.java:588)

at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2929)

at com.alibaba.druid.filter.FilterEventAdapter.preparedStatement_execute(FilterEventAdapter.java:440)

at com.alibaba.druid.filter.FilterChainImpl.preparedStatement_execute(FilterChainImpl.java:2929)

at com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl.execute(PreparedStatementProxyImpl.java:118)

at com.alibaba.druid.pool.DruidPooledPreparedStatement.execute(DruidPooledPreparedStatement.java:493)

at com.dangdang.ddframe.rdb.sharding.executor.PreparedStatementExecutor.executeInternal(PreparedStatementExecutor.java:183)

2.2 报错原因:

首先执行select语句,sharding JDBC判断该语句打到slave数据库上,获取slave的连接并放到Transaction中

其次执行update语句,因为Transaction中已经存在slave的连接,故直接使用该连接进行update

slave配置的用户只能对数据库进行读操作,故爆出异常

2.3 解决方案

为了避免update 使用slave导致报错,故强制select & update都适用master,方法如下:

HintManager hintManager = HintManager.getInstance();

hintManager.setMasterRouteOnly();

该方法会强制事务中的所有数据库操作使用master。

总结

到这里,告白气球分布式高并发下mysql数据库读写分离就结束了,,不足之处还望大家多多包涵!!觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持。(吹一波,233~~)

下面和大家交流几点编程的经验:

1、多写多敲代码,好的代码与扎实的基础知识一定是实践出来的

2丶 测试、测试再测试,如果你不彻底测试自己的代码,那恐怕你开发的就不只是代码,可能还会声名狼藉。

3丶 简化编程,加快速度,代码风骚,在你完成编码后,应回头并且优化它。从长远来看,这里或那里一些的改进,会让后来的支持人员更加轻松。

最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步。


关键字