数据库性能瓶颈主要原因:
- 随着用户数的增多,带来的是数据库连接的大幅度增长
- 随着业务体量的增长,表数据量(空间存储的问题)的大幅增长,其中涉及到索引的优化,mysql默认的索引是硬盘级别的,BTREE(B树)
- 硬件资源限制(QPS\TPS)
数据性能优化方案:
- sql优化
- 缓存
- 建好索引
- 读写分离
- 分库分表
在分布式架构的数据库优化方案中,最有效的则是读写分离与分库分表了。
大数据量数据库性能解决方案:
1、读写分离区别读、写多数据源方式进行数据的存储和加载。数据的存储(增删改)一般指定写数据源,数据的读取查询指定读数据源(读写分离会基于主从复制)
2、分库分表对数据的库表进行拆分,用分片的方式对数据进行管理。
Mycat:
- 一个彻底开源的,面向企业应用开发的大数据库集群
- 支持事务、ACID、可以替代MySQL的加强版数据库
- 一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群
- 一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server
- 结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
- 一个新颖的数据库中间件产品
通俗点讲,应用层可以将它看作是一个数据库的代理(或者直接看成加强版数据库)。
逻辑库(schema):
通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道 数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。 在云计算时代,数据库中间件可以以多租户的形式给一个或多个应用提供服务,每个应用访问的可能是一个 独立或者是共享的物理库,常见的如阿里云数据库服务器 RDS。
逻辑表(table):
既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可 以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。
分片表 :
是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所 有分片构成了完整的数据。 例如在 mycat 配置中的 t_node 就属于分片表,数据按照规则被分到 dn1,dn2 两个分片节点(dataNode) 上。
非分片表 :
一个数据库中并不是所有的表都很大,某些表是可以不用进行切分的,非分片是相对分片表来说的,就是那 些不需要进行数据切分的表。只存在于分片节点(dataNode)dn1 上。
ER 表 :
关系型数据库是基于实体关系模型(Entity-Relationship Model)之上,通过其描述了真实世界中事物与关 系,Mycat 中的 ER 表即是来源于此。根据这一思路,提出了基于 E-R 关系的数据分片策略,子表的记录与所关 联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据 Join 不会跨 库操作。 表分组(Table Group)是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的重要一条规则。 如用户的地址表,用户表对应的用户可能有多个地址,在进行分片后所对应的地址表也应该放入相同分区。
全局表 :
一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几个特 性: • 变动不频繁; • 数据量总体变化不大; • 数据规模不大,很少有超过数十万条记录。 对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关 联,就成了比较棘手的问题,所以 Mycat 中通过数据冗余来解决这类表的 join,即所有的分片都有一份数据的拷 贝,所有将字典表或者符合字典表特性的一些表定义为全局表。 数据冗余是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则。
主从复制:
Mysql作为目前世界上使用最广泛的免费数据库,相信所有从事系统运维的工程师都一定接触过。但在实际的生产环境中,由单台Mysql作为独立的数据库是完全不能满足实际需求的,无论是在安全性,高可用性以及高并发等各个方面。
因此,一般来说都是通过 主从复制(Master-Slave)的方式来同步数据,再通过读写分离(MySQL-Proxy)来提升数据库的并发负载能力 这样的方案来进行部署与实施的。
主从复制的原理:
- master将操作记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary logevents)
- Slave通过I/O Thread异步将master的binary logevents拷贝到它的中继日志(relay log);
- Slave执行relay日志中的事件,匹配自己的配置将需要执行的数据,在slave服务上执行一遍从而达到复制数据的目的。
master 配置:
1.接入mysql并创建主从复制的用户create user "username" identified by 'password';
2.给新建的用户赋权GRANT REPLICATION SLAVE ON *.* TO 'username'@'%' IDENTIFIED BY 'password';
3.指定服务ID,开启binlog日志记录,在my.cn中加入
server-id=137 // 唯一
log-bin=dbstore_binlog //日志
binlog-do-db=db_store //需要同步的db 可以不写,同步所有
skip-name-resolve //禁止dns 反解析,也可以不配置,需要配置/etc/hosts 文件,域名和IP的对应关系。
4.重启mysql服务。通过SHOW MASTER STATUS;查看Master db状态
master 其他相关配置:
1 log-bin=mysql-bin
控制master的是否开启binlog记录功能;二进制文件最好放在单独的目录下,这不但方便优化、更方便维护。如下例子:要重新调整logbin的路径为“/home/mysql/binlog”
log_bin=/home/mysql/binlog/binlog.log需要注意:指定目录时候一定要以*.log结尾,即不能仅仅指定到文件夹的级别,否则在重启mysql时会报错。
2. server-id=1
每个server服务的标识,在master/slave环境中,此变量一定要不一样
3. expire_logs_days=15
通过此来实现master自动删除binlog
4. innodb_flush_log_at_trx_commit=1
此参数表示在事务提交时,处理重做日志的方式;此变量有三个可选值0,1,2:
0:当事务提交时,并不将事务的重做日志写入日志文件,而是等待每秒刷新一次
1:当事务提交时,将重做日志缓存的内容同步写到磁盘日志文件,为了保证数据一致性,在replication环境中使用此值。
2:当事务提交时,将重做日志缓存的内容异步写到磁盘日志文件(写到文件系统缓存中)
建议必须设置innodb_flush_log_at_trx_commit=1
5.sync_binlog=1
1、此参数表示每写缓冲多少次就同步到磁盘;
2、sync_binlog=1表示同步写缓冲和磁盘二进制日志文件,不使用文件系统缓存
在使用innodb事务引擎时,在复制环境中,为了保证最大的可用性,都设置为“1”,但会对影响io的性能。
3、即使设置为“1”,也会有问题发生:
假如当二进制日志写入磁盘,但事务还没有commit,这个时候宕机,当服务再次起来的恢复的时候,无法回滚以及记录到二进制日志的未提交的内容;这个时候就会造成master和slave数据不一致
解决方案:
需要参数innodb_support_xa=1来保证。建议必须设置
6 .innodb_support_xa=1
此参数与XA事务有关,它保证了二进制日志和innodb数据文件的同步,保证复制环境中数据一致性。建议必须设置
7.binlog-do-db=skate_db
只记录指定数据库的更新到二进制日志中
8. binlog-do-table=skate_tab
只记录指定表的更新到二进制日志中
9. binlog-ignore-db=skate_db
忽略指定数据库的更新到二进制日志中
10.log_slave_updates=1
此参数控制slave数据库是否把从master接受到的log并在本slave执行的内容记录到slave的二进制日志中
在级联复制环境中(包括双master环境),这个参数是必须的
11.binlog_format=statement|row|mixed
控制以什么格式记录二进制日志的内容,默认是mixed
12. max_binlog_size
master的每个二进制日志文件的大小,默认1G
13.binlog_cache_size
1、所有未提交的事务都会被记录到一个缓存或临时文件中,待提交时,统一同步到二进制日志中,
2、此变量是基于session的,每个会话开启一个binlog_cache_size大小的缓存。
3、通过变量“Binlog_cache_disk_use”和“Binlog_cache_use”来设置binlog_cache_size的大小。
说明:
Binlog_cache_disk_use: 使用临时文件写二进制日志的次数
Binlog_cache_use: 使用缓冲记写二进制的次数
14.auto_increment_increment=2 //增长的步长
auto_increment_offset=1 //起始位置
在双master环境下可以防止键值冲突
slave 配置:
1.指定服务器ID,指定同步的binlog存储位置,在 my.cnf中加入
server-id=101
relay-log=slave-relay-bin --中继日志
relay-log-index=slave-relay-bin.index
read_only=1 //从服务器只读
replicate_do_db=db_store //同步哪个db,可以不写同步所有
skip-name-resolve //禁止dns 反解析,也可以不配置,需要配置/etc/hosts 文件,域名和IP的对应关系。
2.重启mysql服务。接入slave的mysql服务,并配置
change master to master_host='192.168.254.138',master_port=3306,master_user='slave1',master_password='slave1',master_log_file='dbstore_binlog.000001',master_log_pos=120;
其中 master_user 跟master_password 是由master配置的第一步所写的 username跟password,而master_log_file跟master_log_pos 则是在master端用 show master status 查看状态的时候所显示的 ,也就是上文图片中提到的 dbstore_binlog.000001 跟928,由于我这边是之前就做了主从之后有操作,所以从120变成了 928。
3.start slave; 如果要结束主从 执行 stop slave
4. show slave status\G ;查看slave服务器状态 如下
如图,看到 Slave_IO_Running 跟 Slave_SQL_Running 都为YES 说明主从关系建立完毕,可以用客户端工具连上测试。
slave 其他相关配置:
1.server-id=2
和master的含义一样,服务标识
2.log-bin=mysql-bin
和master的含义一样,开启二进制
3.relay-log=relay-bin
中继日志文件的路径名称
4. relay-log-index=relay-bin.index
中继日志索引文件的路径名称
5. log_slave_updates=1
和master的含义一样,如上
6.read_only=1
1、使数据库只读,此参数在slave的复制环境和具有super权限的用户不起作用,
2、对于复制环境设置read_only=1非常有用,它可以保证slave只接受master的更新,而不接受client的更新。
3、客户端设置:
mysq> set global read_only=1
7. skip_slave_start
使slave在mysql启动时不启动复制进程,mysql起来之后使用 start slave启动,建议必须
8.replicate-do-db
只复制指定db
9.replicate-do-table
只复制指定表
10. replicate-ingore-table
忽略指定表
11. replicate_wild_do_table=skatedb.%
模糊匹配复制指定db
12. auto_increment_increment=2
auto_increment_offset=1
和master含义一样,参考如上
13. log_slow_slave_statements
在slave上开启慢查询日志,在query的时间大于long_query_time时,记录在慢查询日志里
14. max_relay_log_size
slave上的relay log的大小,默认是1G
15.relay_log_info_file
中继日志状态信息文件的路径名称
16. relay_log_purge
当relay log不被需要时就删除,默认是on
SET GLOBAL relay_log_purge=1
17.replicate-rewrite-db=from_name->to_name
数据库的重定向,可以把分库汇总到主库便于统计分析
Mycat 安装:
下载地址 : http://dl.mycat.io/1.6.6.1/
下载完上传至服务器 解压后进入 mycat 主目录:
简单说一下 bin与 conf 文件夹:
bin 程序目录:存放了可执行文件./mycat {start|restart|stop|status…}。
conf 目录下存放配置文件:
server.xml 是 Mycat 服务器参数调整和用户授权的配置文件。
schema.xml 是逻辑库定义和表。
rule.xml 是分片规则的配置文件,分片规则的具体一些参数信息单独存放为文件,也在这个目录下。
log4j2.xml配置logs目录日志输出规则.
wrapper.conf JVM相关参数调整。
启动 mycat ,进入 bin 目录 ./mycat start : 默认端口 8066
由于mycat 可以看作是一个数据库的代理(或者直接看成加强版数据库),mycat 可以支持所有的基于JDBC这种连接的相关的东西。命令如下:
mysql -uroot -p -h192.168.254.138 -P8066
或者 mysql -uroot -p123456 -h192.168.254.138 -P8066 注意这里的密码是mycat的,不是mysql的
输入密码以后查看当前数据库会发现:
这个 TESTDB 并非 mysql 服务的,这是 mycat 的配置文件 schema.xml 中配置的逻辑库:
<schema name="TESTDB" checkSQLschema="false" sqlMaxLimit="100">
我们自己来搭建一个mycat 的服务,由于配置关系到数据库的节点,我们根据下图的节点关系来配置:
Mycat 只是一个逻辑库,需要先在mysql里面建好库表。还有需要注意的是。修改完mysql的数据,mycat里也会变,因为他只是一个逻辑库。
Mycat不支持二维路由,仅支持单库多表或多库单表 由于自定义连接池,这样就会存在mycat自身维护一个连接池,MySQL也有一个连接池,任何一个连接池上限都会成为性能的瓶颈。如下配置单库分表:
<!-- name: 表名,分表主键,子表,数据节点,分表规则 --> <table name="city" primaryKey="id" autoIncrement="true" subTables="city_$1-3" dataNode="dn1" rule="mod-long" />
先来简单的过一下配置文件,既然提到了schema.xml,就从他开始吧:
Mycat不负责任何的数据同步,需要事先配置mysql的主从复制哦。
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<!-- 逻辑库配置-->
<!--一个schema标签就是一个逻辑库, 逻辑库名称 -->
<schema name="db_store" checkSQLschema="false" sqlMaxLimit="100">
<!--非分片表的配置 名称,从属节点 主键-->
<table name="store" dataNode="db_store_dataNode" primaryKey="storeID"/>
<table name="employee" dataNode="db_store_dataNode" primaryKey="employeeID"/>
</schema>
<schema name="db_user" checkSQLschema="false" sqlMaxLimit="100">
<!--全局表 指定 type ,指定所在分片 ,主键-->
<table name="data_dictionary" type="global" dataNode="db_user_dataNode1,db_user_dataNode2" primaryKey="dataDictionaryID"/>
<!--分片表的配置 ,配置分片节点 ,rule 配置分片规则,具体配置在rule.xml里面,在rule.xml再细说-->
<table name="users" dataNode="db_user_dataNode$1-2" rule="mod-userID-long" primaryKey="userID">
<!--ER表,通过parentKey去找 users表对应的分片,放到同一个分片下-->
<childTable name="user_address" joinKey="userID" parentKey="userID" primaryKey="addressID"/>
</table>
</schema>
<!-- 节点配置
为什么 db_store只有一个节点呢?因为这个节点理的两个主机是主从复制实现了读写分离
这里即具备读也具备了写。在节点主机配置中有体现。
-->
<!-- db_store -->
<dataNode name="db_store_dataNode" dataHost="db_storeHOST" database="db_store" />
<!-- db_user -->
<dataNode name="db_user_dataNode1" dataHost="db_userHOST1" database="db_user" />
<dataNode name="db_user_dataNode2" dataHost="db_userHOST2" database="db_user" />
<!-- 主从复制 节点主机配置 -->
<!-- 配置db_store的节点主机 最大连接数100 最小10 -->
<dataHost name="db_storeHOST" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<!-- 心跳机制 -->
<heartbeat>select user()</heartbeat>
<!-- 读写分离 can have multi write hosts 写节点(master)-->
<writeHost host="hostM1" url="192.168.254.138:3306" user="root" password="wuzhenzhao">
<!-- can have multi read hosts 读节点(slave)-->
<readHost host="hostS1" url="192.168.254.136:3306" user="root" password="wuzhenzhao" />
</writeHost>
</dataHost>
<!-- 配置db_user的节点主机 -->
<dataHost name="db_userHOST1" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="userHost1" url="192.168.254.138:3306" user="root" password="wuzhenzhao">
</writeHost>
</dataHost>
<dataHost name="db_userHOST2" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="userHost2" url="192.168.254.136:3306" user="root" password="wuzhenzhao">
</writeHost>
</dataHost>
</mycat:schema>
以上在配置分片的时候涉及到了规则的配置,来看看rule.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License. - You
may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0
- - Unless required by applicable law or agreed to in writing, software -
distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the
License for the specific language governing permissions and - limitations
under the License. -->
<!DOCTYPE mycat:rule SYSTEM "rule.dtd">
<mycat:rule xmlns:mycat="http://io.mycat/">
........
<tableRule name="mod-long">
<rule>
<columns>id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<!-- 分片规则定义-->
<tableRule name="mod-userID-long">
<rule>
<!--根据哪个列来分区-->
<columns>userID</columns>
<!--分区算法,指向下面的function 标签-->
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<tableRule name="sharding-by-murmur">
<rule>
<columns>id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<tableRule name="crc32slot">
<rule>
<columns>id</columns>
<algorithm>crc32slot</algorithm>
</rule>
</tableRule>
<!--指定分片类。指定规则与2取模-->
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">2</property>
</function>
<function name="murmur"
class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默认是0 -->
<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
<!-- <property name="weightMapFile">weightMapFile</property> 节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
<!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
</function>
<function name="crc32slot"
class="io.mycat.route.function.PartitionByCRC32PreSlot">
<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片 -->
</function>
........
</mycat:rule>
server.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!-- - - Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License. - You
may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0
- - Unless required by applicable law or agreed to in writing, software -
distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the
License for the specific language governing permissions and - limitations
under the License. -->
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="useSqlStat">0</property> <!-- 1为开启实时统计、0为关闭 -->
<property name="useGlobleTableCheck">0</property> <!-- 1为开启全加班一致性检测、0为关闭 -->
<property name="sequnceHandlerType">2</property>
<!-- <property name="useCompression">1</property>--> <!--1为开启mysql压缩协议-->
<!-- <property name="fakeMySQLVersion">5.6.20</property>--> <!--设置模拟的MySQL版本号-->
<!-- <property name="processorBufferChunk">40960</property> -->
<!-- <property name="processors">1</property>
<property name="processorExecutor">32</property>-->
<!--默认为type 0: DirectByteBufferPool | type 1 ByteBufferArena-->
<property name="processorBufferPoolType">0</property>
<!--默认是65535 64K 用于sql解析时最大文本长度 -->
<!--<property name="maxStringLiteralLength">65535</property>-->
<!--<property name="sequnceHandlerType">0</property>-->
<!--<property name="backSocketNoDelay">1</property>-->
<!--<property name="frontSocketNoDelay">1</property>-->
<!--<property name="processorExecutor">16</property>-->
<!--
<property name="serverPort">8066</property> <property name="managerPort">9066</property>
<property name="idleTimeout">300000</property> <property name="bindIp">0.0.0.0</property>
<property name="frontWriteQueueSize">4096</property> <property name="processors">32</property> -->
<!--分布式事务开关,0为不过滤分布式事务,1为过滤分布式事务(如果分布式事务内只涉及全局表,则不过滤),
2为不过滤分布式事务,但是记录分布式事务日志-->
<property name="handleDistributedTransactions">0</property>
<!--off heap for merge/order/group/limit 1开启 0关闭-->
<property name="useOffHeapForMerge">1</property>
<!--单位为m-->
<property name="memoryPageSize">1m</property>
<!--单位为k-->
<property name="spillsFileBufferSize">1k</property>
<property name="useStreamOutput">0</property>
<!--单位为m-->
<property name="systemReserveMemorySize">384m</property>
<!--是否采用zookeeper协调切换 -->
<property name="useZKSwitch">true</property>
</system>
<!-- 全局SQL防火墙设置 -->
<!--
<firewall>
<whitehost>
<host host="127.0.0.1" user="mycat"/>
<host host="127.0.0.2" user="mycat"/>
</whitehost>
<blacklist check="false">
</blacklist>
</firewall>
-->
<!--把mycat看成一个超级数据库,在这里配置数据库的用户密码及可以访问的数据库-->
<user name="root">
<property name="password">wuzhenzhao</property>
<property name="schemas">db_store,db_user</property>
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="db_user" dml="0110" >
<table name="users" dml="1111"></table> IUSD
<table name="useraddres" dml="1110"></table>
</schema>
</privileges>-->
</user>
</mycat:server>
-->
</user>
</mycat:server>
初略认识了这3个基本的配置文件后,我们默认的配置文件修改成自己修改过的来测试一下。重启mycat,如下重启成功
然后用 Navicat 工具直连:这样就完成了mycat 的搭建。
可以做个简单的测试,如上图连接到 mycat,在db_user里面的user表插入5条数据:先来看看mycat上的存储:
再来看看两个分片上面的数据:192.168.254.138
192.168.254.139:
可以发现我们的分片规则已经生效。其他的表都可以分别的去测试一把。
下面我会继续学习 mycat 服务的相关配置。来深入的学习一下。