MySQL与分布式
主从复制
其思路和redis的主从复制思路相似,也是采用增量复制的方式。
MySQL在运行过程中,会记录二进制日志,所有的DML和DDL操作都会被记录到日志中,主库将操作记录复制给从库,从库也运行一次,就实现了主从复制。注意:它不会再一开始进行全量复制,所以最好在开始主从之前将数据库的内容保持一致。
主从复制模式下,主库挂掉,从库也能正常提供服务,并且还可以实现读写分离操作。
这里使用两台主机搭建一主一:
create user test identified with mysql_native_password by '123456'
sudo vim /etc/mysql/mysql.conf.d/mysqld.cof
注释掉 bind-address = 127.0.0.1
注释掉 mysqlx-bind-address = 127.0.0.1
然后重启MySQL服务。
- 配置主库,给刚才创建的用户分配一个主从复制的权限即可:
- grant replication slave on *.* to test;flush privileges;
复制代码 这时主库就搭建完成了,然后开始进行从库配置
- # 这里需要将server-id配置为其他的值(默认1),所有MySQL主从实例的id必须唯一!,不然会开启失败server-id = 2
复制代码- change replication source to SOURCE_HOST='192.168.0.8',SOURCE_USER='test',SOURCE_PASSWORD='123456',SOURCE_LOG_FILE='BINLOG.000004',SOURCE_LOG_POS=591;
复制代码 SOURCE_HOST -- 主库地址
SOURCE_LOG_FILE-- 二进制日志,就是主库的日志文件(在主库输入 show master status 可以查到)
SOURCE_LOG_POS -- 日志的偏移地址(在主库输入 show master status 可以查到)
这时从库就启动了!
输入show replica status\G;
最关键的就是Replica_IO_Running和Replica_SQL_Running必须同时为yes才可以!
实际上从库会创建2个线程,一个线程复制与主库进行通信,获取二进制日志,暂时存放到一个中间表(Relay_Log)中,另一个线程将中间表保存的二进制日志信息进程执行,然后插入到从库中。
此时就配置完成了。在主库创建一个数据库,从库就会跟着创建一个数据库.
那要是主库挂了会怎么样?
这时IO线程处于重连状态,等待主库恢复运行.
主库恢复之后,它的日志表名变了,此时需要重新配置从库,不然从库使用的还是之前的错误日志表.
分库分表
大型互联网系统中,单台MySQL的存储容量无法满足需求,这时就需要扩容.
我们通过多台实例进行扩容,可以将数据分散,让多台主机共同保存数据.
那么如何进行拆分?
- 垂直拆分:将数据库中的所有表,按照业务功能拆分到各个数据库中,而对于一张表,也可以通过外键之类的机制,将其拆分成多个表.
- 水平拆分:水平拆分针对的是数据,不是表.让多个具有相同表的数据库协同存放一部分数据,相当于把数据分散到各个节点上.
实现这样的拆分操作,目前已经有一些解决方案了,比如mycat(数据库中间件,相当于挂了一层代理,再通过mycat进行分库分表操作数据库,只需要连接就能使用)或者Sharding JDBC(应用程序中字节对sql语句进行解析,然后转换成分库分表操作,需要自己编写一些逻辑代码),这里以Sharding JDBC为例!
Sharding JDBC
Sharding-JDBC 它定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。
分库实现
具体实现水平拆分步骤如下:
- org.apache.shardingsphere shardingsphere-jdbc-core-spring-boot-starter 5.1.0 org.mybatis.spring.boot mybatis-spring-boot-starter 4.0.1 org.projectlombok lombok true
复制代码- spring: shardingsphere: datasource: # 有几个数据源,就写几个,名称+数字的形式 names: ds0,ds1 # 每个数据源的具体配置 ds0: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/yyds?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC username: root password: 123456 ds1: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/yyds?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC username: root password: 123456
复制代码- @AllArgsConstructor@Datapublic class User { int id; String name; String password;}
复制代码- @Mapperpublic interface UserMapper { @Select("select * from user where id = #{id}") User getUserById(int id); @Insert("insert into user (id, name, password) values (#{id}, #{name}, #{password})") int addUser(User user);}
复制代码 这些都是常规操作,编写代码的关注点依然放在业务上.
现在需要编写配置文件,告诉Sharding JDBC要如何进行分片.(现在是两个数据库都有user表,我们的目标是将用户信息分别存放在这两个数据库中)- spring: shardingsphere: rules: sharding: tables: # 这里填写表名,程序中对这张表的所有操作 都会采用下面的路由方案 user: # 实际的路由数据节点,我们要分两个库,每个库一个表,所以是两个数据节点,格式是 数据源名称.表名 # 也可以使用表达式的方式来写,比如:ds$->{0..1}.user actual-data-nodes: ds0.user,ds1.user # 分库策略配置 database-strategy: # 这里选择标准的分库策略,也可以配置复杂的分库策略,具体可以参考官方文档 standard: # 参与分片运算的字段,通常是主键,下面的算法会根据这个字段的值来决定路由到哪个库 sharding-column: id # 选择分片算法,下面我们会自定义一个分片算法,命名为my-alg sharding-algorithm-name: my-alg sharding-algorithms: # 自定义的分片算法,命名为my-alg my-alg: # 算法类型(更多类型可以查询官方文档),这里选择MOD算法,MOD算法会根据分片字段的值对分片数量根据2取模来决定路由到哪个库 type: MOD props: shard-count: 2 props: # 开启SQL日志输出,方便我们观察SQL路由的情况 sql-show: true
复制代码 这时就完成分库了,然后接可以进行业务开发了.
分表实现和查询
假如数据库中有test_01 和test_02两张表,表结构一样.我们希望能根据id取模运算的结果分别放到这两个不同的表中.首先需要明白两个概念:什么是逻辑表,什么是真实表
- 逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是sql中表的逻辑标识,例如:订单数据根据主键拆分为10张表,分别是t_order_0到t_order_9,他们的逻辑表名就是t_order
- 真实表:真实存在的表,就是上面的t_order_0到t_order_9
实现的时候,不用修改任何业务逻辑代码,只需要做相应的配置即可:- spring: shardingsphere: rules: sharding: tables: test: actual-data-nodes: ds0.test$->{0..1} # 配置分表策略 table-strategy: standard: sharding-column: id sharding-algorithm-name: my-alg sharding-algorithms: my-alg: # INLINE方式,自定义表达式来决定路由到哪个库,表达式中可以使用分片字段的值来进行计算 type: INLINE props: algorithm-expression: test$->{id % 2} # 查询也可以根据分片策略来进行,如果使用的是范围查询,依然会进行全量查询,这里先关闭 allow-range-query-with-inline-sharding: false
复制代码 查询的时候,如果是根据id获取一行数据,实际上是去两张表里面查.
如果是范围查询,也就是返回结果是一个list集合的查询,需要配置allow-range-query-with-inline-sharding: true实际执行的sql是把两张表的查询结果union all起来,比如:- select * from test where id >10 -- 就是执行下面的sqlselect * from test_0 where id >10 union all select * from test_1 where id >10
复制代码 分布式序列算法
在复杂分布式系统中,特别是微服务架构中,需要对大量数据和消息进行唯一标识.随着数据增多,分库分表成为常见方案,对数据分库分表后需要有一个唯一id来标识一条数据或消息(如订单号,事件编号等),此时需要一个能够生成全局唯一id的算法。这个id有以下要求:
- 必须全局唯一
- 尽可能保持有效,这样会提升索引的查询效率
满足以上需求的方案有:
- uuid:是一组32位数的16禁止数字随机构成,全局唯一 但无需。
- 雪花算法(Snowflake):生成一个64bit大小的整型id,需要使用Long类型。int类型装不下
雪花算法虽然具备了唯一性和有序性,但是依然有缺点,第一就是时间问题,如果机器时间出现倒退,那么就会导致生成重复的id,并且节点容量只有1024个,如果是超大规模集群,也存在隐患。
这里让Sharding JDBC用雪花算法为我们生成主键:
- 先把表中的主键字段类型改为bigint
- 修改配置文件,添加对应配置:
- spring: shardingsphere: rules: sharding: tables: user: actual-data-nodes: ds0.user,ds1.user # 分库策略配置 database-strategy: # 这里选择标准的分库策略,也可以配置复杂的分库策略,具体可以参考官方文档 standard: # 参与分片运算的字段,通常是主键,下面的算法会根据这个字段的值来决定路由到哪个库 sharding-column: id # 选择分片算法,下面我们会自定义一个分片算法,命名为my-alg sharding-algorithm-name: my-alg key-generate-strategy: column: id key-generator-name: my-gen key-generators: # 自定义的主键生成算法,命名为my-gen my-gen: # 算法类型,这里选择SNOWFLAKE算法,SNOWFLAKE算法会生成全局唯一的ID,适合分布式环境 type: SNOWFLAKE props: # SNOWFLAKE算法需要配置一个worker-id,通常是一个唯一的数字,可以根据实际情况来设置,确保在分布式环境中不同的实例有不同的worker-id worker-id: 666 sharding-algorithms: # 自定义的分片算法,命名为my-alg my-alg: # 算法类型,这里选择MOD算法,MOD算法会根据分片字段的值对分片数量取模来决定路由到哪个库 type: MOD props: shard-count: 2 props: # 开启SQL日志输出,方便我们观察SQL路由的情况 sql-show: true
复制代码 此时就完成了主键生成策略设置
读写分离
之前实现的MySQL主从复制模式,我们可以将主库作为写操作,从库作为只读操作
在实现了主从复制模式的基础上进行配置:
- 在从库的配置文件中,设置 read-only =1,这样从库就只能读数据了。
然后重启从库
- spring: shardingsphere: rules: # 配置读写分离 readwrite-splitting: data-sources: #名称随便写 user-db: type: static props: # 配置写库,只能写一个 write-data-source-name: ds0 # 配置读库,可以配置多个,逗号分隔 read-data-source-names: ds1 load-balancer-name: my-load load-balancers: # 自定义的负载均衡算法,命名为my-load my-load: type: round_robin
复制代码 此时读写分离配置完成!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |