MySQL 读写分离

优质博文:IT-BLOG-CN

一、背景

随着机票业务不断增长,订单库的读性能遇到了挑战,因此对订单库进行读写分离操作。主要目的是提高数据库的并发性能和可扩展性。当系统的所有写操作效率尚可,读数据请求效率较低时,比如之前订单表存放了几千万条数据,且查询订单信息需要关联十几个字表,每个字表的数据超亿条。查询超过设置的慢SQL查询时间3s。以下是一些具体原因和好处:读写分离的功能基于主从复制
1、性能提升:

【1】读写负载分担:通过将读操作和写操作分离,可以将写操作集中在主库Master上,而将读操作分散到多个从库Slave上。这可以显著减少主库的负载,提高整体系统的响应速度。

【2】并发处理能力:读写分离可以利用多个从库来处理并发读请求,从而提高系统的并发处理能力。

2、可扩展性:

【1】横向扩展:通过增加从库的数量,可以轻松地扩展系统的读处理能力,而不需要对主库进行复杂的扩展。

【2】负载均衡:可以使用负载均衡技术将读请求分发到不同的从库上,从而实现更好的资源利用和性能优化。

3、数据安全性和容错性:

【1】数据备份:从库可以用作数据备份的一部分,提供数据冗余和容错能力。如果主库发生故障,从库可以迅速接管读操作,甚至在必要时提升为新的主库。

【2】灾难恢复:在灾难恢复场景中,从库可以作为主库的备份,确保数据的安全性和可恢复性。

4、缓存和索引优化: 从库可以针对特定的读操作进行优化,如创建特定的索引或缓存策略,而不影响主库的写操作性能。

扩展:慢查询超时时间配置:在MySQL的配置文件(通常是my.cnfmy.ini)中设置long_query_time。在[mysqld]部分添加或修改以下行:

text 复制代码
[mysqld]
slow_query_log = 1
slow_query_log_file = /path/to/your/slow_query.log
long_query_time = 2

二、主从读写分离

主从复制完成后,我们还需要实现读写分离,master负责写入数据,两台slave负责读取数据。我们项目因为数据太大,所以使用Sharding-JDBC进行了分表分库。这里就说下通过Sharding-JDBC怎么实现数据读写分离的。

【1】引入依赖: 项目中引入Sharding-JDBC的依赖。

xml 复制代码
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>5.x.x</version> <!-- 请使用最新版本 -->
</dependency>

【2】配置数据源: 需要配置多个数据源,通常包括一个主库Master``和一个或多个从库Slave。以下是一个简单的Spring Boot`配置示例:

java 复制代码
import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import javax.sql.DataSource;
import java.io.File;

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() throws Exception {
        File yamlFile = new File("path/to/sharding-jdbc-config.yaml");
        return YamlShardingSphereDataSourceFactory.createDataSource(yamlFile);
    }
}

【3】配置YAML文件:sharding-jdbc-config.yaml文件中,配置读写分离的相关信息。以下是一个示例配置:

yml 复制代码
dataSources:
  master:
    type: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/master_db
    username: root
    password: root
  slave0:
    type: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/slave_db0
    username: root
    password: root
  slave1:
    type: com.zaxxer.hikari.HikariDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    jdbcUrl: jdbc:mysql://localhost:3306/slave_db1
    username: root
    password: root

rules: # 用于定义数据分片、读写分离等规则。在这个例子中,我们定义了一个读写分离的规则。
  - !READWRITE_SPLITTING # 是一个 YAML 类型标签,用于指示接下来的配置是一个读写分离规则。
    dataSources: # 是一个键,表示接下来要定义的是数据源的配置。在这个例子中,我们定义了一个名为 pr_ds 的逻辑数据源。
      pr_ds: # pr_ds 是逻辑数据源的名称。你可以在应用程序中使用这个名称来引用这个读写分离的数据源。
        writeDataSourceName: master # 指定了用于写操作的主数据源。在这个例子中,主数据源的名称是 master。所有的写操作(例如 INSERT、UPDATE、DELETE)都会路由到这个数据源。
        readDataSourceNames: # 是一个列表,指定了用于读操作的从数据源。在这个例子中,有两个从数据源,分别是 slave0 和 slave1。所有的读操作(例如 SELECT)会根据负载均衡策略路由到这些从数据源。
          - slave0
          - slave1
        loadBalancerName: round_robin # loadBalancerName 指定了用于读操作的负载均衡策略。在这个例子中,负载均衡策略的名称是 round_robin,表示轮询策略。轮询策略会将读操作均匀地分布到多个从数据源上

loadBalancers:
  round_robin:
    type: ROUND_ROBIN

【4】使用Sharding-JDBC数据源: 使用配置好的Sharding-JDBC数据源。以下是一个简单的示例:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void performDatabaseOperations() {
        // 写操作会路由到主库
        jdbcTemplate.update("INSERT INTO my_table (name) VALUES (?)", "John Doe");

        // 读操作会路由到从库
        String name = jdbcTemplate.queryForObject("SELECT name FROM my_table WHERE id = ?", new Object[]{1}, String.class);
        System.out.println("Name: " + name);
    }
}

思考一:MySQL中的数据延迟同步的问题怎么解决?

我们主从使用的硬件配置都是一样的,MySQL使用的也是5.7版本,支持writeSet并行复制,但很多时间可能是网络延迟,就需要确保足够的宽带,以便数据传输,选择地理位置较近的服务器等等。重点是我们对数据实时性要求高的系统,会将主库写入的数据存入Redis缓存中,并给一个过期时间,过期时间的设计与主从复制延迟的时间成正比。

思考二:同一线程且同一数据库连接内,如果读写操作在一起,为了保证数据一致性,均从主库读取。

三、读非关系型数据库-技术选型

同步模式

适合查询数据的一致性和实时性高的系统。但业务代码侵入比较强,增大写操作的耗时,影响系统的写操作的QPS

异步模式

写入数据后,通过kafka异步建立查询数据,不影响业务流程,但需要考虑数据一致性问题。

binlog模式

这种方案也是我们使用的一套方案,还是借助自主研发的DRC服务,监听写数据库日志的方式建立查询数据,不影响主流程,代码无侵入。但需要注意数据一致性问题。

四、项目中的难点

消息幂等怎么保证?

消息幂等性在业务层保证一致,使用updateOrInsert方法,存在即更新,不存在则插入。那么在MongDB中也是一样的,使用条件更新Upsert,通过使用upsert选项,可以确保插入或更新操作是幂等的。如果文档不存在则插入,如果存在则更新。

sql 复制代码
db.collection.updateOne(
    { _id: documentId },
    { $set: { fieldName: newValue } },
    { upsert: true }
);

消费时序性怎么保证?

因为使用的是Kafka进行消息订阅消费,根据订单号进行分区消费,同一个订单分配至同一个分区,同一个分区是顺序消费的,从而保证消息的时序性。

消息一致性怎么保证?

搭建的数据同步系统DRC可以定时校验数据的一致性

查询数据存储为什么选 MongDB?

为了解决表数据量大查询缓慢的问题,不推荐选用关系型数据库了,内存数据库虽然性能非常高,比如Redis,但是不适合海量数据,太费钱了。所以重点考虑如下三种大数据存储模型:MongoDB/HBase/Elasticsearch

MongoDB:文档型数据库NoSQL,基于文档的存储,使用JSON(BSON)格式,丰富的查询语言,支持复杂的查询和聚合操作,水平扩展Sharding,最终一致性,支持多种一致性级别的配置。

相关推荐
lang2015092829 分钟前
MySQL I/O线程优化:提升性能的关键配置
数据库·mysql
Tony Bai1 小时前
【Go开发者的数据库设计之道】05 落地篇:Go 语言四种数据访问方案深度对比
开发语言·数据库·后端·golang
金仓数据库1 小时前
平替MongoDB | 金仓多模数据库助力电子证照国产化实践
数据库·mongodb
麻雀20252 小时前
一键面试prompt
面试·职场和发展·prompt
海琴烟Sunshine2 小时前
Leetcode 26. 删除有序数组中的重复项
java·算法·leetcode
RoboWizard2 小时前
移动固态硬盘连接手机无法读取是什么原因?
java·spring·智能手机·电脑·金士顿
PAK向日葵2 小时前
【算法导论】NMWQ 0913笔试题
算法·面试
PAK向日葵2 小时前
【算法导论】DJ 0830笔试题题解
算法·面试
PAK向日葵2 小时前
【算法导论】LXHY 0830 笔试题题解
算法·面试
笨蛋不要掉眼泪2 小时前
SpringBoot项目Excel成绩录入功能详解:从文件上传到数据入库的全流程解析
java·vue.js·spring boot·后端·spring·excel