从零开发分布式数据库中间件 一、读写分离的数据库中间件(转)

从零开发分布式数据库中间件 一、读写分离的数据库中间件

.embody { padding: 10px 10px 10px; margin: 0 -20px; border-bottom: solid 1px #ededed }
.embody_b { margin: 0; padding: 10px 0 }
.embody .embody_t,.embody .embody_c { display: inline-block; margin-right: 10px }
.embody_t { font-size: 12px; color: #999 }
.embody_c { font-size: 12px }
.embody_c img,.embody_c em { display: inline-block; vertical-align: middle }
.embody_c img { width: 30px; height: 30px }
.embody_c em { margin: 0 20px 0 10px; color: #333; font-style: normal }

在传统的单机体系中,我们在操作数据库时,只需要直接得到数据库的连接,然后操作数据库即可,可是在现在的数据爆炸时代,只靠单机是无法承载如此大的用户量的,即我们不能纵向扩展,那么我们就只能水平进行扩展,即使用读写分离的主从数据库来缓解数据库的压力,而在读写分离之后,如何使程序能正确的得到主数据库的连接或者是从数据库的连接,就是我们今天读写分离的数据库中间件需要实现的。

一、主从数据库介绍:

主从数据库即为一个主数据库会有对应n个从数据库,而从数据库只能有一个对应的从数据库。主从数据库中写的操作需要使用主数据库,而读操作使用从数据库。主数据库与从数据库始终保持数据一致性。其中保持数据库一致的原理即为当主数据库数据发生变化时,会将操作写入到主数据库日志中,而从数据库会不停的读取主数据库的日志保存到自己的日志系统中,然后进行执行,从而保持了主从数据库一致。

二、开发前准备及ConnectionFactory类的开发:

在了解了主从数据库后,我们可以进行分布式数据库中间件的开发,由于mysql本身支持主从数据库,但限于篇幅,就不讲mysql的主从配置了,我们先使用本机的mysql作为一主两从的数据库源即可,下面是我本机的数据库连接配置文件,其中有一主两从:

[java] view plain copy

print?

  1. master.driver=com.mysql.jdbc.Driver
  2. master.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
  3. master.user=root
  4. master.password=mytestcon
  5. slave1.driver=com.mysql.jdbc.Driver
  6. slave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
  7. slave1.user=root
  8. slave1.password=mytestcon
  9. slave2.driver=com.mysql.jdbc.Driver
  10. slave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_db
  11. slave2.user=root
  12. slave2.password=mytestcon
master.driver=com.mysql.jdbc.Drivermaster.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbmaster.user=rootmaster.password=mytestconslave1.driver=com.mysql.jdbc.Driverslave1.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbslave1.user=rootslave1.password=mytestconslave2.driver=com.mysql.jdbc.Driverslave2.dburl=jdbc\:mysql\://127.0.0.1\:3306/master_slave_dbslave2.user=rootslave2.password=mytestcon

有了主从数据库的连接配置后,就可以将配置进行封装。

封装的DataSource类:

[java] view plain copy

print?

  1. public class DataSource {
  2. private String driver;
  3. private String url;
  4. private String user;
  5. private String password;
  6. public String getDriver() {
  7. return driver;
  8. }
  9. public void setDriver(String driver) {
  10. this.driver = driver;
  11. }
  12. public String getUrl() {
  13. return url;
  14. }
  15. public void setUrl(String url) {
  16. this.url = url;
  17. }
  18. public String getUser() {
  19. return user;
  20. }
  21. public void setUser(String user) {
  22. this.user = user;
  23. }
  24. public String getPassword() {
  25. return password;
  26. }
  27. public void setPassword(String password) {
  28. this.password = password;
  29. }
  30. }
public class DataSource {    private String driver;    private String url;    private String user;    private String password;    public String getDriver() {        return driver;    }    public void setDriver(String driver) {        this.driver = driver;    }    public String getUrl() {        return url;    }    public void setUrl(String url) {        this.url = url;    }    public String getUser() {        return user;    }    public void setUser(String user) {        this.user = user;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }}

在Spring配置文件中,从配置文件中读取配置并将配置转换为封装的DataSource类:

[java] view plain copy

print?

  1. <bean id="masterDataSource" class="com.happyheng.connection.DataSource">
  2. <property name="driver" value="${master.driver}"/>
  3. <property name="url" value="${master.dburl}"/>
  4. <property name="user" value="${master.user}"/>
  5. <property name="password" value="${master.password}"/>
  6. </bean>
  7. <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">
  8. <property name="driver" value="${slave1.driver}"/>
  9. <property name="url" value="${slave1.dburl}"/>
  10. <property name="user" value="${slave1.user}"/>
  11. <property name="password" value="${slave1.password}"/>
  12. </bean>
  13. <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">
  14. <property name="driver" value="${slave2.driver}"/>
  15. <property name="url" value="${slave2.dburl}"/>
  16. <property name="user" value="${slave2.user}"/>
  17. <property name="password" value="${slave2.password}"/>
  18. </bean>
  19. <bean id="propertyConfigurer"
  20. class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  21. <property name="locations">
  22. <list>
  23. <value>classpath:dataSource.properties</value>
  24. </list>
  25. </property>
  26. </bean>
    <bean id="masterDataSource" class="com.happyheng.connection.DataSource">        <property name="driver" value="${master.driver}"/>        <property name="url" value="${master.dburl}"/>        <property name="user" value="${master.user}"/>        <property name="password" value="${master.password}"/>    </bean>    <bean id="slaveDataSource1" class="com.happyheng.connection.DataSource">        <property name="driver" value="${slave1.driver}"/>        <property name="url" value="${slave1.dburl}"/>        <property name="user" value="${slave1.user}"/>        <property name="password" value="${slave1.password}"/>    </bean>    <bean id="slaveDataSource2" class="com.happyheng.connection.DataSource">        <property name="driver" value="${slave2.driver}"/>        <property name="url" value="${slave2.dburl}"/>        <property name="user" value="${slave2.user}"/>        <property name="password" value="${slave2.password}"/>    </bean>    <bean id="propertyConfigurer"          class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">        <property name="locations">            <list>                <value>classpath:dataSource.properties</value>            </list>        </property>    </bean>

有了主从的连接之后,我们就可以写一个ConnectionFactory类,此类可以为外部类直接提供主数据库、从数据库的连接,相当于数据库连接的封装:

[java] view plain copy

print?

  1. @Service
  2. public class ConnectionFactory {
  3. @Autowired
  4. private DataSource masterDataSource;
  5. @Autowired
  6. private DataSource slaveDataSource1;
  7. @Autowired
  8. private DataSource slaveDataSource2;
  9. private List<DataSource> slaveDataSourceList;
  10. private int slaveDataSourceSize;
  11. @PostConstruct
  12. private void init() {
  13. slaveDataSourceList = new ArrayList<>();
  14. slaveDataSourceList.add(slaveDataSource1);
  15. slaveDataSourceList.add(slaveDataSource2);
  16. slaveDataSourceSize = slaveDataSourceList.size();
  17. }
  18. /**
  19. * 得到主数据的连接
  20. */
  21. public Connection getMasterConnection() {
  22. return getConnection(masterDataSource);
  23. }
  24. /**
  25. * 得到从数据库的连接数量
  26. */
  27. public int getSlaveDataSourceSize() {
  28. return slaveDataSourceSize;
  29. }
  30. /**
  31. * 得到从数据n的连接
  32. */
  33. public Connection getSlaveConnection(int index){
  34. return getConnection(slaveDataSourceList.get(index));
  35. }
  36. private Connection getConnection(DataSource dataSource){
  37. Connection connection = null;
  38. try {
  39. Class.forName(dataSource.getDriver());
  40. connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. return connection;
  45. }
  46. }
@Servicepublic class ConnectionFactory {    @Autowired    private DataSource masterDataSource;    @Autowired    private DataSource slaveDataSource1;    @Autowired    private DataSource slaveDataSource2;    private List<DataSource> slaveDataSourceList;    private int slaveDataSourceSize;    @PostConstruct    private void init() {        slaveDataSourceList = new ArrayList<>();        slaveDataSourceList.add(slaveDataSource1);        slaveDataSourceList.add(slaveDataSource2);        slaveDataSourceSize = slaveDataSourceList.size();    }    /**     * 得到主数据的连接     */    public Connection getMasterConnection() {        return getConnection(masterDataSource);    }    /**     * 得到从数据库的连接数量     */    public int getSlaveDataSourceSize() {        return slaveDataSourceSize;    }    /**     * 得到从数据n的连接     */    public Connection getSlaveConnection(int index){        return getConnection(slaveDataSourceList.get(index));    }    private Connection getConnection(DataSource dataSource){        Connection connection = null;        try {            Class.forName(dataSource.getDriver());            connection = DriverManager.getConnection(dataSource.getUrl(), dataSource.getUser(), dataSource.getPassword());        } catch (Exception e) {            e.printStackTrace();        }        return connection;    }}

封装完成后,我们就可以使用getMasterConnection()直接得到主数据库的连接,使用getSlaveConnection(int)可以得到从数据库1或者是从数据2的连接。

三、Proxy代理类的实现:

代理类即是可以让程序中数据库访问得到正确的数据库连接,所以称为代理。

1、使用ThreadLocal为当前线程指定数据库访问模式:

由于Proxy不知道程序使用的是主数据库还是从数据库,所以程序在访问数据库之前要调用Proxy代理类来为当前线程打一个Tag,即指定是使用主数据库还是从数据库。由于而web服务器中每个请求是多线程环境,所以使用ThreadLocal类:

2、使用随机法来访问从数据库:

由于从数据库有多个,所以我们可以使用随机法来随机访问每个从数据库,随机法在高并发的情况下有很平均的分布,性能也非常好。

3、具体实现:

[java] view plain copy

print?

  1. @Service
  2. public class DataSourceProxy {
  3. ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();
  4. public static final String MASTER = "master";
  5. public static final String SLAVE = "slave";
  6. @Resource
  7. private ConnectionFactory connectionFactory;
  8. /**
  9. * 设置当前线程的数据库Mode
  10. */
  11. public void setMode(String dataMode) {
  12. dataSourceThreadLocal.set(dataMode);
  13. }
  14. /**
  15. * 得到当前数据库Mode
  16. */
  17. public String getMode() {
  18. return dataSourceThreadLocal.get();
  19. }
  20. /**
  21. * 根据当前Mode得到Connection连接对象
  22. */
  23. public Connection getThreadConnection() {
  24. // 1.判断当前是从数据还是主数据库,默认是主数据库
  25. String mode = getMode();
  26. if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {
  27. // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接
  28. double random = Math.random();
  29. int index = (int) (random * connectionFactory.getSlaveDataSourceSize());
  30. System.out.println("----使用的为第" + (index + 1) + "从数据库----");
  31. return connectionFactory.getSlaveConnection(index);
  32. } else {
  33. System.out.println("----使用的为主数据库----");
  34. // f1.如果是主数据库,因为只有一个,所以直接获取即可
  35. return connectionFactory.getMasterConnection();
  36. }
  37. }
  38. }
@Servicepublic class DataSourceProxy {    ThreadLocal<String> dataSourceThreadLocal = new ThreadLocal<>();    public static final String MASTER = "master";    public static final String SLAVE = "slave";    @Resource    private ConnectionFactory connectionFactory;    /**     * 设置当前线程的数据库Mode     */    public void setMode(String dataMode) {        dataSourceThreadLocal.set(dataMode);    }    /**     * 得到当前数据库Mode     */    public String getMode() {        return dataSourceThreadLocal.get();    }    /**     * 根据当前Mode得到Connection连接对象     */    public Connection getThreadConnection() {        // 1.判断当前是从数据还是主数据库,默认是主数据库        String mode = getMode();        if (!StringUtils.isEmpty(mode) && SLAVE.equals(mode)) {            // y1.如果是从数据库,那么使用随机数的形式来得到从数据库连接            double random = Math.random();            int index = (int) (random * connectionFactory.getSlaveDataSourceSize());            System.out.println("----使用的为第" + (index + 1) + "从数据库----");            return connectionFactory.getSlaveConnection(index);        } else {            System.out.println("----使用的为主数据库----");            // f1.如果是主数据库,因为只有一个,所以直接获取即可            return connectionFactory.getMasterConnection();        }    }}

4、此工程已在github上开源,可以完整实现数据库的读写分离,地址为:github 。如果觉得不错,那么就star一下来鼓励我吧。

原文地址:https://www.cnblogs.com/jpfss/p/8136349.html

时间: 2024-10-28 23:01:39

从零开发分布式数据库中间件 一、读写分离的数据库中间件(转)的相关文章

net Core 使用MyCat分布式数据库,实现读写分离

net Core 使用MyCat分布式数据库,实现读写分离 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 MyCat2.0版本很快就发布了,关于MyCat的动态和一些问题,大家可以加一下MyCat的官方QQ群:106088787.我们今天主要介绍一下,在我们的Asp.net Core中如何使用Mycat,这源于一个大神(Amamiya Yuuko)的分享,但是,这中间还是有少许的 坑 : 首先,因为大神是比较忙的,而且主要分享关键技术,所以有些地方很简略,而往往这些简

浅谈高性能数据库集群——读写分离

本文主要介绍高性能数据库集群读写分离相关理论,基本架构,涉及的复杂度问题以及常见解决方案. 1 读写分离概述 基本架构图: 2 适用场景 读写分离不是银弹,并不是一有性能问题就上读写分离,而是应该先优化,例如优化慢查询,调整不合理的业务逻辑,引入缓存查询等只有确定系统没有优化空间后才考虑读写分离集群 3 引入的系统复杂度问题 问题一 主从复制延迟 问题二 分配机制 如何将读写操作区分开来,然后访问不同的数据库服务器? 解决方案1 客户端程序代码封装实现 基本架构图 业界开源实现 Sharding

构建读写分离的数据库集群

4.1实战案例——构建读写分离的数据库集群 使用Mycat作为数据库中间件服务构建读写分离的数据库集群 使用一台虚拟机部署Mycat数据库中间件服务,将用户提交的读写操作识别分发给相应的数据库 节点.这样将用户的访问操作.数据库的读与写操作分给3台主机,只有数据库集群的主节点接 收增.删.改SQL语句,从节点接收查询语句,分担了主节点的查询压力 基础准备 使用CentOS 7.2系统,创建3台虚拟机进行实验. 其中2台虚拟机db1和db2部署MariaDB数据库服务,搭建主从数据库集群:一台作为

数据库主从复制与读写分离(了解)

在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的.无论是在安全性.高可用性还是高并发等各个方面都是完全不能满足实际需求的.因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力.有点类似于前面我们学习过的rsync,但是不同的是rsync是对磁盘文件做备份,而mysql主从复制是对数据库中的数据.语句做备份. 如图所示: 一. 案例前置知识点 1. mysq支持的复制类型 1) 基于语句的复制.在服务器上执行sql语句,在从服务器上执行同样

数据库主从复制和读写分离

参考文档: http://www.cnblogs.com/crazylqy/p/5542558.html http://www.cnblogs.com/gl-developer/p/6170423.html https://mp.weixin.qq.com/s?__biz=MzAxMTEyOTQ5OQ==&mid=400643724&idx=1&sn=82e5b5ec9e0567abea4cc1d960265d9e#rd https://www.jianshu.com/p/89ad

微服务化的数据库设计与读写分离

数据库永远是应用最关键的一环,同时越到高并发阶段,数据库往往成为瓶颈,如果数据库表和索引不在一开始就进行良好的设计,则后期数据库横向扩展,分库分表都会遇到困难. 对于互联网公司来讲,一般都会使用Mysql数据库. 一.数据库的总体架构 我们首先来看Mysql数据的总体架构如下: 这是一张非常经典的Mysql的系统架构图,通过这个图可以看出Mysql各个部分的功能. 当客户端连接数据库的时候,首先面对的是连接池,用于管理用户的连接,并会做一定的认证和鉴权. 连接了数据库之后,客户端会发送SQL语句

十四、linux-MySQL的数据库集群读写分离及高可用性、备份等

一.数据库集群及高可用性 二. 三.mysql实现读写分离 mysql实现读写分离有多种方式: 1)代码语言(php\python\java等)层面实现读写分离,找开发进行实现. 2)通过软件工具实现读写分离,例如amoeba软件 amoeba软件既可以实现负载均衡,也可以实现读写分离,就是进行服务器端压力的分离. 原文地址:https://www.cnblogs.com/dangjingwei/p/11567368.html

数据库---mysql主从复制读写分离

http://m.open-open.com/m/lib/view/1413274853450.html 原理及架构分析 部署前准备 下载好源码包存放位置要与脚本中对应 mysql-5.5.22.tar.gz,cmake-2.8.6.tar.gz,amoeba-mysql-binary-2.2.0.tar.gz,jdk-6u14-linux-x64.bin selinux和iptables不做设置,关闭 系统光盘镜像为本地yum源,配置好yum文件 环境介绍: 主服务器(master):192.

windows NLB实现MSSQL读写分离--从数据库集群读负载均衡

主从模式,几乎大部分出名的数据库都支持的一种集群模式. 当Web站点的访问量上去之后,很多站点,选择读写分离,减轻主数据库的的压力.当然,一主多从也可以作用多个功能,比如备份.这里主要演示如何实现从数据库集群的读负载均衡 搭建一主三从的MSSQL集群 192.168.99.250 //主服务器 192.168.99.8 //从服务器(WIN-6S3JNU8C4TB) 192.168.99.10 //从服务器(WIN-HF1GQ5U288H) 192.168.99.11 //从服务器(WIN-EA