数据库读写分离,分库分表

海量数据的存储与访问瓶颈解决方案-数据切分

垂直切分

垂直切分就是按照不同的表或者Schema切分到不同的数据库中,如:订单表和商品表在同一个数据库中,而我们现在要对其切分,使得订单表和商品表分别落在不同的数据库中,使其完全隔离,从而达到降低数据库负载的效果。
在这里插入图片描述
优点:

  • 拆分之后业务清晰,拆分规则明确;
  • 系统之间容易扩展和整合
  • 数据维护简单

缺点

  • 部分业务表无法join,只能通过接口调用,提升了系统的复杂度
  • 跨事务难以处理
  • 垂直切分后,某些业务数据过于庞大,仍然存在单体性能瓶颈

水平切分

它需要将一个表中的数据,根据某种规则拆分到不同的数据库中,如:订单尾号为奇数的订单放在了订单数据库1中,而订单尾号为偶数的订单放在了订单数据库2中。这样,原本存在一个数据库中的订单数据,被水平切分成了两个数据库,在查询订单数据时,我们还要根据订单的尾号,判断这个订单在数据库1中,还是在数据库2中,然后将sql语句发送到正确的数据库中,查询订单
在这里插入图片描述
水平分片规则:

  • 用户id求模,
  • 按照日期去拆分数据
  • 按照合理的字段去拆分数据

优点:

  • 解决了单库大数据,高并发的性能瓶颈
  • 提高了系统的稳定性和负载能力

缺点:

  • 分片事务一致性难以解决
  • 二次扩展时,数据迁移,维护难度大,如:开始我们按照用户id对2求模,但是随着业务的增长,2台数据库难以支撑,还要继续拆分成4个数据库,这时就需要做数据迁移了

数据库读写分离
互联网系统应用是一个读多写少的应用,把读操作和写操作分开,让所有读的请求落到专门负责读的数据库上,所有写的操作落在专门负责写的数据库上。
在这里插入图片描述
读写分离的弊端:

  • 当同步挂掉,活着同步延迟比较大时,写库和读库的数据不一致。

mycat
mycat是一个强大的数据库中间件,介于应用和数据库之间,是进行数据处理和交互的中间服务

基本概念:

  • 逻辑库(Schema)

在实际的开发中,开发人员不需要知道数据库中间件的存在,开发人员只需要有数据库的概念就可以了。所以数据库中间件可以被看做是一个或者多个数据库集群构成的逻辑库。例如:上图中的例子,我们可以理解为系统先做了垂直切分,被分为了3个库,用户库,订单库,商品库,而这3个库就被称为逻辑库。

  • 逻辑表(table)

既然有逻辑库,那么就有逻辑表,对于应用系统来说,读写数据的表,就是逻辑表。而逻辑表中的数据,则是被水平切分后,分布在不同的分片库中。如上图所示:假设用户库中有一张用户表,这个用户表就被称为逻辑表,而用户表又被水平切分为3个表,每一个表中都存储一部分用户数据。业务系统在进行用户数据的读写时,只需要操作逻辑表就可以了,后面的分片细节则由MyCat进行操作,这些对于业务开发人员来说时完全透明的。当然,有些表的数据量没有那么大,完全不需要进行分片,只在一个物理的数据库表中即可。

凡是我们做的数据水平切分的表,我们把它叫做分片表。而数据量比较小,没有进行分片的表,我们叫它非分片表。

在真实的业务系统中,往往存在着大量的字典表,这些表的数据基本上很少变动,比如:订单状态。我们查询的时候,往往需要关联字典表去查询,比如:查询订单时,需要把订单状态关联查出,如果订单表做了分片,分布在不同的数据库中,而订单状态表由于数据量小,没有做分片,那么我们查询的时候就要跨库关联查询订单状态,增加了不必要的麻烦,不如我们干脆把订单状态表冗余到所有的订单分片库中,这样关联查询就不需要跨库了。我们把这种通过数据冗余方式复制到所有的分片库中的表,叫做全局表。

  • 分片节点(dataNode)

数据被切分后,一张大表被分到不同的分片数据库上面,每个分片表所在的数据库就叫做分片节点。

  • 节点主机(dataHost)

数据切分后,每一个分片节点不一定都会占用一个真正的物理主机,会存在多个分片节点在同一个物理主机上的情况,这些分片节点所在的主机就叫做节点主机。为了避免单节点并发数的限制,尽量将读写压力高的分片节点放在不同的节点主机上。

  • 分片规则(rule)

一个大表被拆分成多个分片表,就需要一定的规则,按照某种业务逻辑,将数据分到一个确定的分片当中,这个规则就叫做分片规则。数据切分选择合适的分片规则非常重要,这将影响到后的数据处理难度,结合业务,选择合适的分片规则,是对架构师的一个重大考验。对于架构师来说,选择分片规则是一个艰难的,难以抉择的过程。

  • 全局序列号(sequence)

大家有没有想过,数据切分以后,数据库表的中的id怎么办?原来在一张表的时候,我们采用id自增,但是数据分布到多个库怎么办?比如:向用户表插入数据,第一条记录插入了用户库1,它的id为1;第二条记录插入了用户库2,如果是自增,它的id也为1。这样id就混乱了,我们也无法确定一条数据的唯一标识了。这时,我们需要借助外部的机制保证数据的唯一标识,这种保证数据唯一标识的机制,我们叫做全局序列号。

应用场景:

  • 单纯的读写分离,主从切换
  • 分库分表,对于超1000w的表进行分片

在这里插入图片描述

实现读写分离和数据切分的两种模式

  • 模式一:中间层代理(MyCat)

在这里插入图片描述

  • 模式二:客户端模式

在这里插入图片描述
mycat配置文件

  • server.xml:用于配置系统参数、用户信息、访问权限及SQL防火墙和SQL拦截功能等
  • schema.xml:用于配置逻辑库、逻辑表相关信息
  • rule.xml:如果使用了水平切分,就需要使用该文件配置切分规则

应用连接Mycat服务时,Mycat首先会通过server.xml中的配置信息进行用户认证。用户通过验证后,所看到的逻辑库、逻辑表都是schema.xml中所配置的。当使用了水平切分时,Mycat会通过rule.xml里配置的规则来定位具体的物理数据库位置,从而完成写入/读取数据。

system 标签

<system>
    <!-- mycat 服务连接端口 -->
    <property name="serverPort">8066</property>
    <!-- mycat 服务管理端口 -->
    <property name="managerPort">9066</property>
    <!-- mycat 服务监听的ip -->
    <property name="bindIp">0.0.0.0</property>
    <!-- 0为需要密码登陆、1为不需要密码登陆;默认为0,设置为1则需要指定默认账户-->
    <property name="nonePasswordLogin">0</property>
    <!-- 前端连接的写队列大小 -->
    <property name="frontWriteQueueSize">2048</property>
    <!-- 设置字符集编码 -->
    <property name="charset">utf8</property>
    <!-- mycat 的进程数量 -->
    <property name="processors">8</property>
    <!-- 闲置连接超时时间,单位:毫秒 -->
    <property name="idleTimeout">1800000</property>
    <!-- 默认最大返回的数据集大小 -->
    <property name="defaultMaxLimit">100</property>
    <!-- 允许的最大包大小 -->
    <property name="maxPacketSize">104857600</property>
    <!-- 0遇上没有实现的报文(Unknown command:),就会报错、1为忽略该报文,返回ok报文。
在某些mysql客户端存在客户端已经登录的时候还会继续发送登录报文,mycat会报错,该设置可以绕过这个错误-->
    <property name="ignoreUnknownCommand">0</property>
    <property name="useHandshakeV10">1</property>
    <property name="removeGraveAccent">1</property>
    <!-- 1为开启实时统计、0为关闭 -->
    <property name="useSqlStat">0</property>
    <!-- 1为开启全加班一致性检测、0为关闭 -->
    <property name="useGlobleTableCheck">0</property>
    <!-- SQL 执行超时 单位:秒-->
    <property name="sqlExecuteTimeout">300</property>
    <property name="sequnceHandlerType">1</property>
    <!--必须带有MYCATSEQ_或者 mycatseq_进入序列匹配流程 注意MYCATSEQ_有空格的情况-->
    <property name="sequnceHandlerPattern">(?:(\s*next\s+value\s+for\s*MYCATSEQ_(\w+))(,|\)|\s)*)+</property>
    <!-- 子查询中存在关联查询的情况下,检查关联字段中是否有分片字段 .默认 false -->
    <property name="subqueryRelationshipCheck">false</property>
    <property name="sequenceHanlderClass">io.mycat.route.sequence.handler.HttpIncrSequenceHandler</property>
    <!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena | type 2 NettyBufferPool -->
    <property name="processorBufferPoolType">0</property>
    <!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),2为不过滤分布式事务,但是记录分布式事务日志-->
    <property name="handleDistributedTransactions">0</property>
    <!-- off heap for merge/order/group/limit  1开启;0关闭 -->
    <property name="useOffHeapForMerge">0</property>
    <!--是否采用zookeeper协调切换  -->
    <property name="useZKSwitch">false</property>
    <!--如果为 true的话 严格遵守隔离级别,不会在仅仅只有select语句的时候在事务中切换连接-->
    <property name="strictTxIsolation">false</property>
    <!-- Mycat连接数据库时使用的隔离级别
         1 - 读未提交
         2 - 读已提交
         3 - 可重复读
         4 - 串行化
     -->
    <property name="txIsolation">2</property>
    <property name="useZKSwitch">true</property>
    <!--如果为0的话,涉及多个DataNode的catlet任务不会跨线程执行-->
    <property name="parallExecute">0</property>
</system>

user 标签

<!-- 用户名 -->
<user name="mall">
    <!-- 密码 -->
    <property name="password">123456</property>
    <!-- 允许该用户访问的逻辑库 -->
    <property name="schemas">mall_db,db1,db2</property>
    <!-- 是否只读 -->
    <property name="readOnly">false</property>
    <!-- 表级 DML 权限配置,check属性表示是否开启该配置 -->
    <privileges check="true">
        <!-- 特别权限应用的逻辑库 -->
        <schema name="mall_db" dml="0110">
            <!-- 
                配置用户对该表的访问权限,dml属性用于指定权限位,
                如果table标签没有配置该属性的话,默认取schema标签的dml属性值,
                剩余没有配置的其他表默认也是取schema标签的dml属性值
            -->
            <table name="user_table" dml="0000"></table>
            <table name="order_table" dml="1111"></table>
        </schema>
    </privileges>
</user>

dml属性配置的数字是权限位,分别对应着insert,update,select,delete四种权限。例如,当dml的值为0110时,表示拥有update和select权限,不具有insert和delete权限。所以权限位为1时代表拥有对应的操作权限,为0时代表没有该操作权限。

在该示例中,mall用户对:

user_table表不具有任何操作权限
order_table表拥有所有操作权限
其他表只拥有update和select权限

schema.xml文件
用途:

配置逻辑库及逻辑表
配置逻辑表所存储的数据节点
配置数据节点所对应的物理数据库服务器信息
schema 标签
schema 标签用于定义逻辑库,示例:

<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
....
</schema>
  • checkSQLschema属性判断是否检查发给Mycat的SQL是否含有库名,为true时会将SQL中的库名删除掉
  • name属性定义逻辑库的名字,必须唯一不能重复
  • sqlMaxLimit属性用于限制返回结果集的行数,值为-1时表示关闭该限制。如果没有开启限制则默认取server.xml里配置的限制
  • randomDataNode属性定义将一些随机语句发送到该数据节点中 table 标签

使用了schema 标签定义逻辑库之后,还需要使用table 标签定义逻辑表。示例:

<schema name="TESTDB" checkSQLschema="true" sqlMaxLimit="100" randomDataNode="dn1">
    <!-- 多表定义 -->
    <table name="travelrecord,address" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" splitTableNames ="true"/>
    <!-- 单表定义 -->
    <table name="oc_call" primaryKey="id" dataNode="dn1$0-743" rule="latest-month-calldate"/>
</schema>
  • name 属性定义逻辑表的名字,必须唯一不能重复且需要与数据库中的物理表名一致。使用逗号分割配置多个表,即多个表使用这个配置
  • primaryKey 属性指定逻辑表中的主键,也是需要与物理表的主键一致
  • dataNode
    属性指定物理表所在数据节点的名称,配置多个数据节点时需按索引顺序并使用逗号分隔,或指定一个索引范围:dn1$0-743。注意数据
    节点定义之后,顺序不能再发生改变,否则会导致数据混乱
  • rule 属性用于指定分片规则名称,对应rule.xml中的标签的
    name属性,如无需分片可以不指定
  • splitTableNames 属性定义是否允许多个表的定义

dataNode 标签用于定义数据节点,数据节点指向的是存储逻辑表的物理数据库。示例:

<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataNode name="dn2" dataHost="localhost1" database="db2" />
<dataNode name="dn3" dataHost="localhost1" database="db3" />
<!-- 可以配置一个范围 -->
<dataNode name="dn1$0-743" dataHost="localhost1" database="db$0-743"/>
  • name 属性定义数据节点的名称,必须唯一
  • dataHost 属性指定分片所在的物理主机
  • database 属性指定物理数据库的名称

dataHost 标签
dataHost 标签用于定义后端物理数据库主机信息,该标签内有两个子标签,可以定义一组数据库主机信息。例如,定义一组主从集群结构的数据库主机信息:
mycat配置文件的详细介绍

writeHost标签配置写实例,即主从中的master节点
readHost 标签配置读实例,即主从中的salve节点
readHost是writeHost的子标签,与writeHost有绑定关系
在一个dataHost内可以定义多个writeHost和readHost。但是,如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另一方面,由于这个writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去。

配置示例:

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0"
          writeType="0" dbType="Mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <!-- 可以配置多个写实例 -->
    <writeHost host="localhost" url="localhost:3306" user="root"
               password="123456">
        <readHost host="localhost" url="localhost:3306" 
                  user="root" password="123456"></readHost>
    </writeHost>
</dataHost>

dataHost 标签属性
name 属性用于定义主机名称,必须唯一

maxCon 属性指定每个读/写实例连接池的最大连接数。也就是说,标签内嵌套的writeHost、readHost 标签都会使用这个属性的值来实例化出连接池的最大连接数

minCon 属性指定每个读写实例连接池的最小连接数,即初始化连接池的大小

dbType 属性指定后端连接的数据库类型,目前支持二进制的MYSQL协议,还有其他使用JDBC连接的数据库

dbDriver 属性指定连接后端数据库使用的驱动,目前可选的值有native和JDBC

slaveThreshold 属性用于定义主从复制延时阈值,当Seconds_Behind_Master > slaveThreshold时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据

balance
属性指定读写分离的

负载均衡

类型,目前的取值有4 种:

0:不开启读写分离机制,所有读操作都发送到当前可用的 writeHost 上
1:全部的readHost与stand by writeHost参与select语句的负载均衡
2:所有读操作都随机在writeHost、readhost 上分发
3:所有读请求随机分发到 wiriterHost 对应的readhost 执行。即 writerHost 不负担读压力,全部读请求由 readhost 执行。注意该取值只在1.4及其以后版本有,1.3没有
writeType
属性指定写实例的负载均衡类型,目前的取值有4 种:

-1:表示不自动切换
0:所有写操作发送到配置的第一个writeHost,第一个挂了切到还生存的第二个writeHost。重新启动后以切换后的为准,切换记录在配置文件中:dnindex.properties
1:所有写操作都随机的发送到配置的writeHost,1.5 以后废弃不推荐使用
2:基于MySQL主从同步的状态决定是否切换(1.4 新增)
switchType
属性用于指定主从切换的方式:

-1:表示不自动切换
1:默认值,自动切换
2:基于MySQL主从同步的状态决定是否切换,心跳检测语句为:show slave status
3:基于MySQL galary cluster的切换机制(适合集群,1.4.1新增),心跳检测语句为show status like ‘wsrep%’
另外,slaveThreshold 属性是用于配合writeType属性实现根据主从延时来进行主从切换的,其官方文档描述如下:

1.4 开始支持MySQL主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下:MyCAT 心跳检查语句配置为show slave status ,dataHost 上定义两个新属性:switchType=“2” 与 slaveThreshold=“100”,此时意味着开启MySQL主从复制状态绑定的读写分离与切换机制,Mycat心跳机制通过检测show slave status 中的"Seconds_Behind_Master"、“Slave_IO_Running”、“Slave_SQL_Running” 三个字段来确定当前主从同步的状态以及Seconds_Behind_Master 主从复制时延,当Seconds_Behind_Master > slaveThreshold时,读写分离筛选器会过滤掉此Slave机器,防止读到很久之前的旧数据,而当主节点宕机后,切换逻辑会检查Slave上的Seconds_Behind_Master是否为0,为0时则表示主从同步,可以安全切换,否则不会切换。

heartbeat 标签
heartbeat 标签内指明用于和后端数据库进行心跳检查的语句。例如,MySQL可以使用select user(),Oracle可以使用select 1 from dual 等。

这个标签还有一个connectionInitSql属性,主要是当使用Oracla数据库时,需要执行的初始化SQL语句就这个放到这里面来。例如:alter session set nls_date_format=‘yyyy-mm-dd hh34:mi:ss’

注:如果是配置主从切换的语句在1.4之后必须是:show slave status

writehost 标签、readHost标签
这两个标签都用于配置一组主从数据库的相关信息,Mycat用这两个标签配置的连接信息实例化后端连接池。唯一不同的是,writeHost 配置写实例(master)、readHost 配置读实例(salve),并且readHost 为writeHost 的子标签。通过这两个标签可以组合读/写实例以满足系统的要求。

在一个dataHost内可以定义多个writeHost和readHost。但是,如果writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另一方面,当一个writeHost宕机时系统会自动检测到,并切换到备用的writeHost 上去。

writehost 标签及readHost标签的属性
这两个标签的属性相同,这里就一起介绍:

host 属性用于标识不同实例名称,一般writeHost名称使用M1作为后缀,readHost则使用S1作为后缀
url 属性用于配置数据库的连接地址,如果是使用native的dbDriver,则一般为address:port这种形式。用JDBC或其他的dbDriver,则需要特殊指定。例如,当使用JDBC 时则可以这么写:jdbc:mysql://localhost:3306/
user 属性配置数据库用户名
password 属性配置数据库密码
weight 属性配置某个数据库在 readhost 中作为读节点的权重
usingDecrypt 属性指定是否对密码加密,默认为0, 若需要开启则配置为1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值