精华内容
下载资源
问答
  • 神通数据库帮助手册(SQL语言手册,数据库备份恢复,读写分离集群,数据库审计,数据库进程守护,数据库接口
  • 神通数据库帮助手册(SQL语言手册,数据库备份恢复,读写分离集群,数据库审计,数据库进程守护,数据库接口
  • 主要是thinkphp获取php页面执行时间,数据库读写次数,函数调用次数等,需要的朋友可以参考下
  • 读取SQL / MysqL数据库接口

    千次阅读 2018-07-17 09:49:33
    GetLine1Data : System.Web.UI.Page { //SELECT TOP 264 [TagName],[TagValue] FROM [JiaLi].[dbo].[Line1] order by ID desc protected void Page_Load(object sender, EventArgs e) { //数据库+Json MyJson.Json...
    ​
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    
    using System.Data.SqlClient;
    using System.Security.Cryptography;
    public partial class Local_GetLine1Data : System.Web.UI.Page
    {
        //SELECT TOP 264 [TagName],[TagValue] FROM [JiaLi].[dbo].[Line1] order by ID desc
        protected void Page_Load(object sender, EventArgs e)
        {
            //数据库+Json
            MyJson.JsonNode_Object json = new MyJson.JsonNode_Object();
            using (SqlConnection myconn = dbhelper.Instance().getMySqlCon())
            {
                SqlCommand mySqlCommand = dbhelper.Instance().getSqlCommand("SELECT TOP 264 [TagName],[TagValue] FROM [JiaLi].[dbo].[Line1] order by ID desc", myconn);
                myconn.Open();
                SqlDataReader reader = mySqlCommand.ExecuteReader();
    
                json.asDict()["nameList"] = new MyJson.JsonNode_Array();
    
                while (reader.Read())
                {
                    if (reader.HasRows)
                    {
                        MyJson.JsonNode_Object rowsJson = new MyJson.JsonNode_Object();
                        //rowsJson.asDict()["name"] = new MyJson.JsonNode_ValueString(reader["name"].ToString());
                        rowsJson.asDict()["TagName"] = new MyJson.JsonNode_ValueString(reader["TagName"].ToString());
                        rowsJson.asDict()["TagValue"] = new MyJson.JsonNode_ValueString(reader["TagValue"].ToString());
                        //rowsJson.asDict()["order_no"] = new MyJson.JsonNode_ValueString(reader["order_no"].ToString());
                        //rowsJson.asDict()["title"] = new MyJson.JsonNode_ValueString(reader["title"].ToString());
                        //rowsJson.asDict()["mark"] = new MyJson.JsonNode_ValueString(reader["mark"].ToString());
                        //rowsJson.asDict()["temp"] = new MyJson.JsonNode_ValueString(reader["temp"].ToString());
                        json.asDict()["nameList"].AsList().Add(rowsJson);
                    }
                }
                reader.Close();
                myconn.Close();
            }
            json.asDict()["state"] = new MyJson.JsonNode_ValueNumber(1);
    
            Response.Write(json.ToString());
        }
    }
    
    ​

     

    展开全文
  • 数据库读写分离的优点

    千次阅读 2020-06-26 13:23:17
    今天,根据博主我作为程序猿的经验,来和大家分享一下数据库读写分离带来的优点。 一,读写分离带来的扩展性更强 在我们编码的过程中,随着项目的业务增多,必然会致使业务接口越来越多,接口越多,带来的维护成本就...

    读写分离的优点

    在传统的编码的过程中,往往是在数据库由于抗不住服务器的压力,或者是IO达到瓶颈之后,必须用到分库的时候,才采用读写分离的方案,个人认为读写分离的作用远不止此。今天,根据博主我作为程序猿的经验,来和大家分享一下数据库读写分离带来的优点。

    一,读写分离带来的扩展性更强

    在我们编码的过程中,随着项目的业务增多,必然会致使业务接口越来越多,接口越多,带来的维护成本就相对较高,如果没有对应文档的记录,即使作为研发人员的我们,都很大可能忘记那些接口有那些功能,那些接口被调用过多少次。

    以上就很可能带来一个很严重的问题,举例说明:在学校考试成绩管理系统中,我写了100个select接口,10个insert接口,10个update接口,10个delete接口,分别对应不同业务需求,这些接口被调用的次数无限,随着服务器的压力增加,需要对部分查询接口(查询最新的成绩等)进行优化,最开始的常见的查询方式可能是按照直接在数据库中查询时间最新的成绩记录,进行返回,优化的方案为给最新的成绩记录打一个标记。可是,后续的插入,修改,删除接口,都需要更新标记,如此多的接口,在没有文档的情况下,维护起来基本不可能,此时要怎么办呢?

    此时都希望,要是所有的插入,修改,删除(即写接口)都可以调用一下我的维护标记接口就好了,对!要是按照读写分离的架构进行设计,我们就可以把我们的维护接口写到写接口里面,这样可以极大简化我们的维护量。

    二,读写分离方便管理

    按照数据库的常用接口,由于功能的特定性,增,删,改可以归为一类,查可以单独归为一类,采用读写分离的数据库设计,在业务调用起来更加规范,相对于增删查改一起,粒度较小,更容易管理。

    而且写接口容易对数据造成影响,写文档的时候可能需要重点记录,读取接口由于不会影响数据,相对好管理一点,博主一向的原则是重点记录写接口,能复用的不增加接口。

    展开全文
  • 目录 1、读写分离解决了什么问题 2、读写分离与业务的架构 3、实际案例 4、解决方案 1、读写分离解决了什么问题 ...其实,用一句话来概括,读写分离基本原理就是将数据库读写操作路由到不...

    目录

    1、读写分离解决了什么问题

    2、读写分离与业务的架构

    3、实际案例

    4、解决方案


    模拟binlog协议   延迟消费

     

    1、读写分离解决了什么问题

           读写分离其实将数据库分离一个主库,多个从库,主从库之前通过某种机制(如binlog)进行数据同步,这是一种常见的数据库架构。在大多数互联网业种中,都是读多写少的业务,为了能够线性提升数据库的读性能,消除读写冲突并提升写的性能,一般可以采用读写分离的思想(当然还有其他解决方案:如复本集,缓存策略等等)。其实,用一句话来概括,读写分离基本原理就是将数据库的读写操作路由到不同的节点上,用来解决数据库的读性能瓶颈的。

    2、读写分离与业务的架构

          在实际的项目中,读写分离与业务架构如下图所示。应用程序A通过执行事件(事务执行)成功后,发送消息队列通知应用程序C。应用程序C通过上游应用程序B获取数据。其中,应用程序A主要是写操作,连接master库,应用程序B主要是读操作连接slave库。master与slave通过某种机制进行数据同步。

           

        在这样一个简单的架构中,其实存在一个主要的问题,也是本文将着重探讨及解决的——应用程序B获取的数据可能是还未同步到从库的数据。

    3、实际案例

        下面首先我结合案例介绍一下遇到的问题(数据库为innodb)。

        在**年初,有商家反馈**写操作后数据没有生效,实际的业务视图与上面的保持一致。其实原因现在来看已经很清楚了,但定位问题还是花费了几天,首先排查代码(有事务,并且通过TSM的afterCommit后再发送的消息队列,当然缓存的设置也是没有问题【如先del还是直接set】,这里就不表了),单中代码层面上是没有问题的,接着就从系统级别排查问题,并且对比数据库的写时间,调用从库获取的数据等日志综合定位——由数据同步延迟导致的。

          于主库来说,事务成功提交后,该数据一定对另一个事务可见(本文所讨论的数据库在没有特殊说明下,都指Innodb, 隔离级别为RR)。接口则是从从库获取数据。这里简单梳理下一条数据写入数据库的整个过程:对于update/insert/delete等操作,依次会产生undo log, redo log,在2PC下,当redolog prepare(是否落盘则根据innodb_flush_log_at_trx_commit控制)ok后,接着进行binlog(是否落盘则根据sync_binlog控制)的commit及redo log的commit; 当然在整个过程中,会读取相关的page到内存中进行更改,也即所谓的dirty page。当事务commit后,其实仅仅代表日志已经落盘,最终内存中的dirty page什么时候刷新到磁盘,这则是checkpoint的事儿了,其间会经历checksum,double write等等,这不是本文的重点,点到为止。 先保证redolog/binlog/undolog成功,这就是有名的WAL(为啥要WAL呢?redolog与binlog共存的意义何在?留个思考题吧)。整个过程,主库就完成了数据的写入;

           接着mysql就利用binlog进行数据同步,从库上一般会有IO线程,从主库中获取binlog日志存放在从库的内存中,即relay log,假设消耗时间为T1;然后利用sql线程进行回放数据,假设消耗时间为T2。目前线上,我们使用的mysql版本号为5.5(貌似在5.6的哪个版本是支持binlog的多线程复制,不过在5.5一定是单线程),binlog的复制方式是RBR(另外两种是SBR,MBR),从库使用的数据库存储引擎也是Innodb。假设业务上的时间(如发送mq,下游接收mq并调用接口)的时间为T3。很显示,当T3<T1+T2时,是不是意味着获取到的数据是上一次发布的数据呢?没错,下游获取数据没有更新就是这种情况导致的。这是当时线上获取的binlog同步到从库相对主库的延迟监控图,从图中可以看到,在某些时刻,主从同步最大有甚至有1s,此时主库的CPU消耗也是挺高的。

      

    4、解决方案

          解决数制库读写分离下的数据同步思路其实相当简单——确保从库与主库的数据已经同步即可。如轮询从库数据(当然这个不是很优雅的解决方案),订阅从库的binlog日志(当生成binlog日志时)。接下来我们对后面一种方案进行简单的描述。

          目前,已经有很多成熟的工具来模拟从库获取数据库实例的binlog日志并对其解析,如阿里的canal,京东的binlake等。他们可以按需过滤部分的binlog日志(如某个table的某些操作),然后发送到消息队列中,应用程序程序订阅该消息后,然后进行业务逻辑处理(如通过已经有工具解析binlog日志,然后进行业务逻辑处理,并按需发送消息给下游)。这里虽然解决了读写分离因同步延迟导致的业务不正确的问题,但并不完美,因此并没有彻底解决该问题。具体原因如下:

         在2PC中,分为两个阶段:第一阶段是redo/undo log的sync落盘, 第二阶段包含两步:前一步骤为binlog的sync操作,当这个阶段完成时,binlog已经写入,此时就可以发送给订阅者然后进行消费,后一步骤是commit操作[如释放mutex锁],只有commit全部完成之后,其他事务才能查询binlog日志所对应的事务产生的数据变更(这里有可能网友有疑问,如果binlog落盘了数据库崩溃了,能否恢复数据?答案当然是能够的)。因此在实际的项目中,我是通过延迟消费500ms的策略解决的,这里就完美的解决了数据同步的问题。

         binog日志落盘到整个2PC提交结束这个时间差可能导致数据不同步,要特别感谢该博文:https://www.jiqizhixin.com/articles/2018-12-05-14, 在这里我才意识到可能存在这个问题,并在实际的项目中与小伙伴一起验证,通过连接7天的数据验证,终于发现了一条这样的数据。

     

     

     

     

    展开全文
  • 数据库读写分离,主从同步实现方法

    万次阅读 多人点赞 2017-12-06 21:37:06
    通过实际的例子编码实现数据库读写分离,实现数据库主从同步

    前言

    众所周知,随着用户量的增多,数据库操作往往会成为一个系统的瓶颈所在,而且一般的系统“读”的压力远远大于“写”,因此我们可以通过实现数据库的读写分离来提高系统的性能。

    实现思路

    通过设置主从数据库实现读写分离,主数据库负责“写操作”,从数据库负责“读操作”,根据压力情况,从数据库可以部署多个提高“读”的速度,借此来提高系统总体的性能。

    基础知识

    要实现读写分离,就要解决主从数据库数据同步的问题,在主数据库写入数据后要保证从数据库的数据也要更新。
    

    主从数据库同步的实现思路如图:
    主从同步

    主服务器master记录数据库操作日志到Binary log,从服务器开启i/o线程将二进制日志记录的操作同步到relay log(存在从服务器的缓存中),另外sql线程将relay log日志记录的操作在从服务器执行。
    记住这张图,接下来基于这个图实际设置主从数据库。

    主从数据库设置的具体步骤

    首先要有两个数据库服务器master、slave(也可以用一个服务器安装两套数据库环境运行在不同端口,slave也可以举一反三设置多个),我们穷人就买虚拟云服务器玩玩就行 0.0。以下操作假设你的两台服务器上都已经安装好了mysql服务。

    1.打开mysql数据库配置文件

    vim /etc/my.cnf

    2.在主服务器master上配置开启Binary log,主要是在[mysqld]下面添加:

    server-id=1
    log-bin=master-bin
    log-bin-index=master-bin.index

    如图:
    master my.cnf

    3.重启mysql服务

    service mysql restart

    ps:重启方式随意

    4.检查配置效果,进入主数据库并执行

    mysql> SHOW MASTER STATUS;

    可以看到下图表示配置没问题,这里面的File名:master-bin.000001 我们接下来在从数据库的配置会使用:

    5.配置从服务器的 my.cnf

    在[mysqld]节点下面添加:
    master status

    server-id=2
    relay-log-index=slave-relay-bin.index
    relay-log=slave-relay-bin

    这里面的server-id 一定要和主库的不同,如图:
    slave my.cnf
    配置完成后同样重启从数据库一下

    service mysql restart

    6.接下来配置两个数据库的关联

    首先我们先建立一个操作主从同步的数据库用户,切换到主数据库执行:

    mysql> create user repl;
    mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'从xxx.xxx.xxx.xx' IDENTIFIED BY 'mysql';
    mysql> flush privileges;

    这个配置的含义就是创建了一个数据库用户repl,密码是mysql, 在从服务器使用repl这个账号和主服务器连接的时候,就赋予其REPLICATION SLAVE的权限, *.* 表面这个权限是针对主库的所有表的,其中xxx就是从服务器的ip地址。
    进入从数据库后执行:

    mysql> change master to master_host='主xxx.xxx.xxx.xx',master_port=3306,master_user='repl',master_password='mysql',master_log_file='master-bin.000001',master_log_pos=0;

    这里面的xxx是主服务器ip,同时配置端口,repl代表访问主数据库的用户,上述步骤执行完毕后执行start slave启动配置:

    mysql> start slave;

    start slave
    停止主从同步的命令为:

    mysql> stop slave;

    查看状态命令,\G表示换行查看

    mysql> show slave status \G; 

    可以看到状态如下:
    slave status
    这里看到从数据库已经在等待主库的消息了,接下来在主库的操作,在从库都会执行了。我们可以主库负责写,从库负责读(不要在从库进行写操作),达到读写分离的效果。

    我们可以简单测试:

    在主数据库中创建一个新的数据库:

    mysql> create database testsplit;

    在从数据库查看数据库:

    mysql> show databases;

    可以看到从数据库也有testsplit这张表了,这里就不上图了,亲测可用。在主数据库插入数据,从数据库也可以查到。
    至此已经实现了数据库主从同步

    代码层面实现读写分离

    上面我们已经有了两个数据库而且已经实现了主从数据库同步,接下来的问题就是在我们的业务代码里面实现读写分离,假设我们使用的是主流的ssm的框架开发的web项目,这里面我们需要多个数据源。

    在此之前,我们在项目中一般会使用一个数据库用户远程操作数据库(避免直接使用root用户),因此我们需要在主从数据库里面都创建一个用户mysqluser,赋予其增删改查的权限:
    
    mysql> GRANT select,insert,update,delete ON *.* TO 'mysqluser'@'%' IDENTIFIED BY 'mysqlpassword' WITH GRANT OPTION;

    然后我们的程序里就用mysqluser这个用户操作数据库:

    1.编写jdbc.propreties

    #mysql驱动
    jdbc.driver=com.mysql.jdbc.Driver
    #主数据库地址
    jdbc.master.url=jdbc:mysql://xxx.xxx.xxx.xx:3306/testsplit?useUnicode=true&characterEncoding=utf8
    #从数据库地址
    jdbc.slave.url=jdbc:mysql://xxx.xxx.xxx.xx:3306/testsplit?useUnicode=true&characterEncoding=utf8
    #数据库账号
    jdbc.username=mysqluser
    jdbc.password=mysqlpassword

    这里我们指定了两个数据库地址,其中的xxx分别是我们的主从数据库的ip地址,端口都是使用默认的3306

    2.配置数据源

    在spring-dao.xml中配置数据源(这里就不累赘介绍spring的配置了,假设大家都已经配置好运行环境),配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!-- 配置整合mybatis过程 -->
        <!-- 1.配置数据库相关参数properties的属性:${url} -->
        <context:property-placeholder location="classpath:jdbc.properties" />
        <!-- 扫描dao包下所有使用注解的类型 -->
        <context:component-scan base-package="c n.xzchain.testsplit.dao" />
        <!-- 2.数据库连接池 -->
        <bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
            <!-- c3p0连接池的私有属性 -->
            <property name="maxPoolSize" value="30" />
            <property name="minPoolSize" value="10" />
            <!-- 关闭连接后不自动commit -->
            <property name="autoCommitOnClose" value="false" />
            <!-- 获取连接超时时间 -->
            <property name="checkoutTimeout" value="10000" />
            <!-- 当获取连接失败重试次数 -->
            <property name="acquireRetryAttempts" value="2" />
        </bean>
        <!--主库配置-->
        <bean id="master" parent="abstractDataSource">
            <!-- 配置连接池属性 -->
            <property name="driverClass" value="${jdbc.driver}" />
            <property name="jdbcUrl" value="${jdbc.master.url}" />
            <property name="user" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
        </bean>
        <!--从库配置-->
        <bean id="slave" parent="abstractDataSource">
            <!-- 配置连接池属性 -->
            <property name="driverClass" value="${jdbc.driver}" />
            <property name="jdbcUrl" value="${jdbc.slave.url}" />
            <property name="user" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
        </bean>
        <!--配置动态数据源,这里的targetDataSource就是路由数据源所对应的名称-->
        <bean id="dataSourceSelector" class="cn.xzchain.testsplit.dao.split.DataSourceSelector">
            <property name="targetDataSources">
                <map>
                    <entry value-ref="master" key="master"></entry>
                    <entry value-ref="slave" key="slave"></entry>
                </map>
            </property>
        </bean>
        <!--配置数据源懒加载-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
            <property name="targetDataSource">
                <ref bean="dataSourceSelector"></ref>
            </property>
        </bean>
    
        <!-- 3.配置SqlSessionFactory对象 -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!-- 注入数据库连接池 -->
            <property name="dataSource" ref="dataSource" />
            <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
            <property name="configLocation" value="classpath:mybatis-config.xml" />
            <!-- 扫描entity包 使用别名 -->
            <property name="typeAliasesPackage" value="cn.xzchain.testsplit.entity" />
            <!-- 扫描sql配置文件:mapper需要的xml文件 -->
            <property name="mapperLocations" value="classpath:mapper/*.xml" />
        </bean>
    
        <!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 注入sqlSessionFactory -->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
            <!-- 给出需要扫描Dao接口包 -->
            <property name="basePackage" value="cn.xzchain.testsplit.dao" />
        </bean>
    </beans>

    说明:
    首先读取配置文件jdbc.properties,然后在我们定义了一个基于c3p0连接池的父类“抽象”数据源,然后配置了两个具体的数据源master、slave,继承了abstractDataSource,这里面就配置了数据库连接的具体属性,然后我们配置了动态数据源,他将决定使用哪个具体的数据源,这里面的关键就是DataSourceSelector,接下来我们会实现这个bean。下一步设置了数据源的懒加载,保证在数据源加载的时候其他依赖的bean已经加载好了。接着就是常规的配置了,我们的mybatis全局配置文件如下

    3.mybatis全局配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!-- 配置全局属性 -->
        <settings>
            <!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
            <setting name="useGeneratedKeys" value="true" />
    
            <!-- 使用列别名替换列名 默认:true -->
            <setting name="useColumnLabel" value="true" />
    
            <!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
            <setting name="mapUnderscoreToCamelCase" value="true" />
            <!-- 打印查询语句 -->
            <setting name="logImpl" value="STDOUT_LOGGING" />
        </settings>
        <plugins>
            <plugin interceptor="cn.xzchain.testsplit.dao.split.DateSourceSelectInterceptor"></plugin>
        </plugins>
    </configuration>

    这里面的关键就是DateSourceSelectInterceptor这个拦截器,它会拦截所有的数据库操作,然后分析sql语句判断是“读”操作还是“写”操作,我们接下来就来实现上述的DataSourceSelector和DateSourceSelectInterceptor

    4.编写DataSourceSelector

    DataSourceSelector就是我们在spring-dao.xml配置的,用于动态配置数据源。代码如下:

    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * @author lihang
     * @date 2017/12/6.
     * @description 继承了AbstractRoutingDataSource,动态选择数据源
     */
    public class DataSourceSelector extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceHolder.getDataSourceType();
        }
    }

    我们只要继承AbstractRoutingDataSource并且重写determineCurrentLookupKey()方法就可以动态配置我们的数据源。
    编写DynamicDataSourceHolder,代码如下:

    /**
     * @author lihang
     * @date 2017/12/6.
     * @description
     */
    public class DynamicDataSourceHolder {
    
        /**用来存取key,ThreadLocal保证了线程安全*/
        private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        /**主库*/
        public static final String DB_MASTER = "master";
        /**从库*/
        public static final String DB_SLAVE = "slave";
    
        /**
         * 获取线程的数据源
         * @return
         */
        public static String getDataSourceType() {
            String db = contextHolder.get();
            if (db == null){
                //如果db为空则默认使用主库(因为主库支持读和写)
                db = DB_MASTER;
            }
            return db;
        }
    
        /**
         * 设置线程的数据源
         * @param s
         */
        public static void setDataSourceType(String s) {
            contextHolder.set(s);
        }
    
        /**
         * 清理连接类型
         */
        public static void clearDataSource(){
            contextHolder.remove();
        }
    }
    

    这个类决定返回的数据源是master还是slave,这个类的初始化我们就需要借助DateSourceSelectInterceptor了,我们拦截所有的数据库操作请求,通过分析sql语句来判断是读还是写操作,读操作就给DynamicDataSourceHolder设置slave源,写操作就给其设置master源,代码如下:

    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    import java.util.Locale;
    import java.util.Properties;
    
    /**
     * @author lihang
     * @date 2017/12/6.
     * @description 拦截数据库操作,根据sql判断是读还是写,选择不同的数据源
     */
    @Intercepts({@Signature(type = Executor.class,method = "update",args = {MappedStatement.class,Object.class}),
    @Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
    public class DateSourceSelectInterceptor implements Interceptor{
    
        /**正则匹配 insert、delete、update操作*/
        private static final String REGEX = ".*insert\\\\u0020.*|.*delete\\\\u0020.*|.*update\\\\u0020.*";
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //判断当前操作是否有事务
            boolean synchonizationActive = TransactionSynchronizationManager.isSynchronizationActive();
            //获取执行参数
            Object[] objects = invocation.getArgs();
            MappedStatement ms = (MappedStatement) objects[0];
            //默认设置使用主库
            String lookupKey = DynamicDataSourceHolder.DB_MASTER;;
            if (!synchonizationActive){
                //读方法
                if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)){
                    //selectKey为自增主键(SELECT LAST_INSERT_ID())方法,使用主库
                    if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    }else {
                        BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]"," ");
                        //如果是insert、delete、update操作 使用主库
                        if (sql.matches(REGEX)){
                            lookupKey = DynamicDataSourceHolder.DB_MASTER;
                        }else {
                            //使用从库
                            lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                        }
                    }
                }
            }else {
                //一般使用事务的都是写操作,直接使用主库
                lookupKey = DynamicDataSourceHolder.DB_MASTER;
            }
            //设置数据源
            DynamicDataSourceHolder.setDataSourceType(lookupKey);
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            if (target instanceof Executor){
                //如果是Executor(执行增删改查操作),则拦截下来
                return Plugin.wrap(target,this);
            }else {
                return target;
            }
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }

    通过这个拦截器,所有的insert、delete、update操作设置使用master源,select会使用slave源。

    接下来就是测试了,我这是生产环境的代码,直接打印日志,小伙伴可以加上日志后测试使用的是哪个数据源,结果和预期一样,这样我们就实现了读写分离~

    ps:我们可以配置多个slave用于负载均衡,只需要在spring-dao.xml中添加slave1、slave2、slave3……然后修改dataSourceSelector这个bean,

    <bean id="dataSourceSelector" class="cn.xzchain.o2o.dao.split.DataSourceSelector">
            <property name="targetDataSources">
                <map>
                    <entry value-ref="master" key="master"></entry>
                    <entry value-ref="slave1" key="slave1"></entry>
                    <entry value-ref="slave2" key="slave2"></entry>
                    <entry value-ref="slave3" key="slave3"></entry>
                </map>
            </property>

    在map标签中添加slave1、slave2、slave3……即可,具体的负载均衡策略我们在DynamicDataSourceHolder、DateSourceSelectInterceptor中实现即可。

    最后整理一下整个流程:
    1.项目启动后,在依赖的bean加载完成后,我们的数据源通过LazyConnectionDataSourceProxy开始加载,他会引用dataSourceSelector加载数据源。
    2.DataSourceSelector会选择一个数据源,我们在代码里设置了默认数据源为master,在初始化的时候我们就默认使用master源。
    3.在数据库操作执行时,DateSourceSelectInterceptor拦截器拦截了请求,通过分析sql决定使用哪个数据源,“读操作”使用slave源,“写操作”使用master源。

    写在后面

    现在很多读写分离中间件已经大大简化了我们的工作,但是自己实现一个小体量的读写分离有助于我们进一步理解数据库读写分离在业务上的实现,呼~

    展开全文
  • java连接三大数据库一般都会用到的jar包,希望对大家有用。
  • 2: Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读写分离、高可用性等...
  • MySQL数据库读写分离中间件Atlas

    千次阅读 2017-02-17 16:12:44
    作者:朱超,奇虎360公司资深研发工程师,2011年加入公司,先后负责数据库中间件、分布式消息队列、配置管理系统等软件基础设施的设计与开发,对高性能分布式系统架构有浓厚兴趣。 本文为《程序员》原创文章,未经...
  • 数据库读写分离和负载均衡策略

    万次阅读 2017-02-26 11:26:37
    最近在学习数据库读写分离和主从复制,采用的是一主多从策略,采用轮询的方式,读取从数据库的内容。但是,假如某一台从数据库宕机了,而客户端不知道,每次轮选到此从数据库,不都要报错?到网上查阅了资料,找到...
  • 数据库读写分离一致性问题

    万次阅读 2018-12-21 19:28:10
    ♦ MariaDB Galera Cluster 是一套在MySQL InnoDB存储引擎上面实现multi-master及数据实时同步的系统架构,业务层面无需做读写分离工作,数据库读写压力都能按照既定的规则分发到各个节点上去; ♦ 同步复制...
  • 虽然近十年来各种存储技术飞速发展,但关系数据库由于其 ACID 的特性和功能强大的 SQL 查询,目前还是各种业务系统中关键和核心的存储系统,很多场景下高性能的设计最核心的部分就是关系数据库的设计。 不管是为了...
  • 这种嵌在高级语言程序中的SQL语句称为嵌入式SQL(或者称为ESQL),和以前使用的交互式SQL(或者称为ISQL)不一样,它们随着程序执行被调用,辅助程序完成数据库数据读写的功能,而高级语言程序则负责对数据库中的...
  • 在前一章中,我们介绍完了数据库读写分离所需要的基础环境的的搭建。详见:【基于SpringBoot无XML文件的数据库读写分离(一)——基础环境搭建】在本章中,将介绍如何使用Springboot去实现动态切换数据源。 一、...
  • java实现数据库读写分离

    千次阅读 2017-03-31 17:07:56
    **数据库配置为一个主库 多个从库 主库用于写操作 从库只读操作 读写分离实现即为配置两个数据源,一个用于读写 连接主库 假设为ds_wr,一个用于只读 连接从库 假设为ds_r。对数据库读操作时,操作ds_r数据源。 对...
  • VC 中利用OO4O接口从Oracle数据库读写图像.pdf
  • C++读写sqlite数据库接口封装项目工程代码,可以根据需要自行扩展
  • 采用的开源的Sharding-JDBC作为数据库读写分离的框架。Matrix-Web后台数据库这一块采用的技术栈如下: 使用Mybatis-Plus作为ORM框架 使用Druid或者HikariCP作为数据库连接池 使用Sharding-JDBC 作为数据库读写分离...
  • 采用VC6.0开发工具,设计了SL500 RFID与Access数据库接口程序,实现了SL500 RFID与数据库接口功能。  0 引言  射频识别(RFID)技术是近年来随着无线电技术和大规模集成电路的普及应用而出现的一项先进的...
  • 采用VC6.0开发工具,设计了SL500 RFID与Access数据库接口程序,实现了SL500 RFID与数据库接口功能。  0 引言  射频识别(RFID)技术是近年来随着无线电技术和大规模集成电路的普及应用而出现的一项先进的...
  • 众所周知,随着用户量的增多,数据库操作往往会成为一个系统的瓶颈所在,而且一般的系统“读”的压力远远大于“写”,因此我们可以通过实现数据库读写分离来提高系统的性能。 实现思路 通过设置主从数据库实现读写...
  • 2,新建数据库配置类DataSourceConfiguration,如下 package com.aop.writeAndRead.config; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; ...
  • 假定在程序效率和关键过程相当且不计入缓存...动态网站一般都是用数据库来存储信息,如果信息的及时性要求不高 可以加入缓存来减少频繁读写数据库。 两种方式一般都支持,但是绕过操作系统直接操作磁盘的性能较高,而
  • 数据集成是开放数据库还是接口

    千次阅读 2018-03-21 19:05:11
    系统集成,集成方少不了要读取对方...这种数据共享方式,到底是直接读对方的数据库,还是对方提供一个接口,供集成方调用来获得数据? 接口,而不是直接开放数据库。为啥呢? 1、权限控制 2、后续维护 3、逻辑思路
  • q=SELECT * FROM cpu_load") #其中db为数据库名,q为查询语句 print(a.text) 写入(post请求) import requests headers = {'Content-Type': 'text/plain'} #设置请求头为文本 datas = "cpu_load_short3,h.
  • 本文讨论在数据库读写分离时使用事务可能会出现哪些问题? 1、在读写分离时会不会造成事务主从切换错误 一个线程在Service层Select时选择的是从库,DynamicDataSourceHolder中ThreadLocal对应线程存储的是Slave,...
  • 微服务化的数据库设计与读写分离

    千次阅读 2018-05-15 14:02:06
    本文由 网易云 发布作者:刘超,网易云解决方案架构师数据库永远是应用最关键的一环,同时越到高并发阶段,数据库往往成为瓶颈,如果数据库表和索引不在一开始就进行良好的设计,则后期数据库横向扩展,分库分表...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 160,764
精华内容 64,305
热门标签
关键字:

数据库读写接口