Практическая реализация разделения чтения и записи MySQL и аварийного переключения

MySQL

предисловие

Давно это было.Из-за нового проекта в последнее время я был очень занят работой,поэтому давно не обновлял.Последнюю статью выложили.Ручная сборка кластера Redis и синхронизации master-slave MySQL (без Docker)После этого многих студентов заинтересовало разделение чтения и письма, упомянутое в статье в структуре master-slave Я планировал поделиться разделением чтения и письма со всеми в период Double Eleven, но моя работа никогда не прекращалась, поэтому Я нашел время, чтобы поделиться ими в эти выходные.

Что касается реализации разделения чтения и записи MySQL, есть два пути, первый способ заключается в том, что мы вручную реализуем логику на уровне кода, анализируем запросы на чтение или запросы на запись, распределяем по разным базам данных, реализуем разделение на чтение и запись; Второй способ основан на промежуточном программном обеспечении MYCAT для достижения эффекта чтения и записи; эти два способа я представлю, создам и проанализирую преимущества и недостатки в этом блоге.

Предварительное изучение принципа

Начиная с синхронизации master-slave в MySQL, наша архитектура базы данных с самого начала такая.

主从架构

Основная библиотека отвечает за все операции чтения и записи, в то время как подчиненная библиотека только резервирует основную библиотеку.Как я уже говорил в предыдущей статье, я думаю, что если реализовано только одно резервное копирование, разделение чтения-записи и отказоустойчивость не могут быть выполнены ., не может уменьшить нагрузку ввода-вывода главного узла, такая архитектура ведущий-ведомый не кажется очень рентабельной.

Архитектура ведущий-подчиненный, которую мы хотим, заключается в том, что когда мы записываем данные, все запросы отправляются на главный узел, а когда нам нужно прочитать данные, все запросы отправляются на подчиненный узел. И несколько подчиненных узлов предпочтительно могут иметь балансировку нагрузки, чтобы максимизировать эффективность кластера.

Тогда такой архитектуры нам недостаточно для использования, нам нужно найти какой-то способ добиться разделения чтения-записи. Ну на самом деле есть два пути.

  • Метод 1: уровень кода реализует разделение чтения и записи.

    Преимущество этого метода в том, что он более гибкий, мы можем определять правила чтения и записи в соответствии с его собственной логикой. Если этот метод используется, мы можем суммировать всю базу данных всей базы данных:

    代码层实现读写分离

  • Способ 2. Используйте средний уровень (виртуальный узел) для пересылки запросов.

    Основная особенность этого метода заключается в том, что мы строим новый виртуальный узел в дополнение к базе данных, и все наши запросы отправляются на этот виртуальный узел, а этот виртуальный узел перенаправляет запросы на чтение и запись в соответствующую базу данных.

    Особенность этого метода заключается в том, что он строит независимый узел для получения всех запросов, вместо того, чтобы настраивать несколько источников данных в нашей программе, нашему проекту нужно только указать URL-адрес этого виртуального узла, а затем использовать этот виртуальный узел для обработки чтения и пишите запросы. Нет ли такой фразы,Профессиональные вещи оставлены профессиональным людям, наверное, это и имеется в виду. А существующее сейчас промежуточное ПО типа MyCat и есть такой "профессионал".

    使用虚拟节点读写分离

Затем я начну реализовывать два вышеупомянутых решения разделения чтения и записи,Слой кода для разделения чтения и записииИспользуйте промежуточное ПО для разделения чтения и записи

Вручную реализовать разделение чтения и записи

Существует много способов добиться разделения чтения и записи. Здесь я упомяну два. Первый — использовать MyBatis и Spring, написанный вручную перехватчик MyBatis для определения того, читается или пишется SQL, чтобы выбрать источник данных, и, наконец, передать его в Spring для внедрения источника данных, чтобы добиться разделения чтения и записи, во-вторых, использовать промежуточное ПО MyCat для настройки разделения чтения и записи, каждый метод имеет свои достоинства, вы можете выбрать в зависимости от ситуации.

  • Описание окружающей среды

    Я использовал свой последний блог здесьРучная сборка кластера Redis и синхронизации master-slave MySQL (без Docker)Встроенная синхронизация master-slave MySQL, если у вас нет этой среды, вы можете сначала создать ее по сравнению с этим блогом. Но следует отметить, что версия MySQL 8.0 должна быть изменена на 5.7.

    192.168.43.201:3306 Master

    192.168.43.202:3306 Slave

    Среда разработки:

    IDE:Eclipse

    Spring boot 2.1.7

    MySQL 5.7

    CentOS 7.3

  • Создайте новый проект Maven

Для удобства демонстрации SpringBoot используется в качестве базовой среды для тестирования, что позволяет сэкономить много XML-конфигураций, необходимых для Spring. Неважно, использовали ли вы SpringBoot, я продемонстрирую работу шаг за шагом.

  • Импортная зависимость

    <parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.1.7.RELEASE</version>
    		<relativePath /> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
    		<!-- Web相关 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<!-- 数据库相关 -->
    		<dependency>
    			<groupId>com.oracle</groupId>
    			<artifactId>ojdbc7</artifactId>
    			<version>12.1.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>2.0.0</version>
    		</dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    		<!-- 测试相关依赖 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    		</dependency>
        	<!-- -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>
    				spring-boot-configuration-processor
    			</artifactId>
    			<optional>true</optional>
    		</dependency>
    </dependencies>
    
  • application.yml

    Чтобы сделать тестовый проект максимально простым, нам не нужно слишком много настраивать другие вещи. Только некоторая базовая конфигурация и конфигурация источника данных.

    server: 
      port: 10001
    spring: 
      datasource:
        url: jdbc:mysql://192.168.43.201:3306/springtestdemo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        username: Object
        password: Object971103.
        driver-class-name: com.mysql.cj.jdbc.Driver
       
    #MyBatis配置
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      configuration:
        map-underscore-to-camel-case: true
    
  • Напишите стартовый класс

    @SpringBootApplication
    public class ApplicationStarter {
    	public static void main(String[] args) {
    		SpringApplication.run(ApplicationStarter.class, args);
    	}
    }
    
  • запускать

    启动

    Если появляется указанная выше информация, запуск выполнен успешно. Хм... Это должен быть блог, связанный с базой данных, кажется, слишком много говорится о SpringBoot.

    Это означает, что с нашим проектом SpringBoot нет проблем, и он был успешно построен. Если вы не уверены, вы можете самостоятельно перейти по пути http://localhost:10001. Если есть 404 SpringBoot, это означает запуск проходит успешно.

  • Создайте новую сущность Student и создайте базу данных

    package cn.objectspace.springtestdemo.domain;
    public class Student {
    	private String studentId;
    	private String studentName;
    	public String getStudentId() {
    		return studentId;
    	}
    	public void setStudentId(String studentId) {
    		this.studentId = studentId;
    	}
    	public String getStudentName() {
    		return studentName;
    	}
    	public void setStudentName(String studentName) {
    		this.studentName = studentName;
    	}	
    }
    
    CREATE TABLE student(
    	student_id VARCHAR(32),
        student_name VARCHAR(32)
    );
    
  • Напишите интерфейс StudentDao и протестируйте его

    интерфейс:

    package cn.objectspace.springtestdemo.dao;
    
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;
    
    import cn.objectspace.springtestdemo.domain.Student;
    
    @Mapper
    public interface StudentDao {
    	@Insert("INSERT INTO student(student_id,student_name)VALUES(#{studentId},#{studentName})")
    	public Integer insertStudent(Student student);
    	@Select("SELECT * FROM student WHERE student_id = #{studentId}")
    	public Student queryStudentByStudentId(Student student);
    }
    

    Тестовый класс:

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {ApplicationStarter.class})// 指定启动类
    public class DaoTest {
    	@Autowired StudentDao studentDao;
    	@Test
    	public void test01() {
    		Student student = new Student();
    		student.setStudentId("20191130");
    		student.setStudentName("Object6");
    		studentDao.insertStudent(student);
    		studentDao.queryStudentByStudentId(student);
    	}
    }
    

    Если вы можете правильно вставить данные в базу данных, как показано на рисунке ниже, MyBatis успешно построен.

    MyBatis搭建成功

  • Официально построен

    Благодаря вышеуказанным приготовлениям мы смогли читать и записывать в базу данных, но мы не добились разделения чтения и записи.Теперь мы начинаем реализовывать разделение чтения и записи базы данных.

  • Изменить application.yml

    В нашем конфигурационном файле сейчас только один источник данных, и разделение чтения-записи определенно не будет одним источником данных, поэтому сначала нам нужно настроить несколько источников данных в application.yml.

    server: 
      port: 10001
    spring: 
      datasource:
        master: 
          url: jdbc:mysql://192.168.43.201:3306/springtestdemo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
          username: Object
          password: Object971103.
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave: 
          url: jdbc:mysql://192.168.43.202:3306/springtestdemo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
          username: Object
          password: Object971103.
          driver-class-name: com.mysql.cj.jdbc.Driver
        
        
    #MyBatis配置
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      configuration:
        cache-enabled: true #开启二级缓存
        map-underscore-to-camel-case: true
    
  • Конфигурация источника данных

    Прежде всего, нам нужно создать два класса ConfigurationProperties.Этот шаг не является обязательным.Также можно настроить DataSource напрямую, но я все еще привык писать эти свойства.

    • MasterProperpties

      package cn.objectspace.springtestdemo.config;
      
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.stereotype.Component;
      
      @ConfigurationProperties(prefix = "spring.datasource.master")
      @Component
      public class MasterProperties {
      	private String url;
      	private String username;
      	private String password;
      	private String driverClassName;
      	public String getUrl() {
      		return url;
      	}
      	public void setUrl(String url) {
      		this.url = url;
      	}
      	public String getUsername() {
      		return username;
      	}
      	public void setUsername(String username) {
      		this.username = username;
      	}
      	public String getPassword() {
      		return password;
      	}
      	public void setPassword(String password) {
      		this.password = password;
      	}
      	public String getDriverClassName() {
      		return driverClassName;
      	}
      	public void setDriverClassName(String driverClassName) {
      		this.driverClassName = driverClassName;
      	}
      	
      }
      
    • SlaveProperties

      package cn.objectspace.springtestdemo.config;
      
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.stereotype.Component;
      
      @ConfigurationProperties(prefix = "spring.datasource.slave")
      @Component
      public class SlaveProperties {
      	private String url;
      	private String username;
      	private String password;
      	private String driverClassName;
      	public String getUrl() {
      		return url;
      	}
      	public void setUrl(String url) {
      		this.url = url;
      	}
      	public String getUsername() {
      		return username;
      	}
      	public void setUsername(String username) {
      		this.username = username;
      	}
      	public String getPassword() {
      		return password;
      	}
      	public void setPassword(String password) {
      		this.password = password;
      	}
      	public String getDriverClassName() {
      		return driverClassName;
      	}
      	public void setDriverClassName(String driverClassName) {
      		this.driverClassName = driverClassName;
      	}
      	
      }
      
    • DataSourceConfig

      Эта конфигурация в основном предназначена для настройки источника данных ведущий-ведомый.

      @Configuration
      public class DataSourceConfig {
      	private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
          @Autowired
          private MasterProperties masterProperties;
      
          @Autowired
          private SlaveProperties slaveProperties;
      
          //默认是master数据源
          @Bean(name = "masterDataSource")
          @Primary
          public DataSource masterProperties(){
              logger.info("masterDataSource初始化");
              HikariDataSource dataSource = new HikariDataSource();
              dataSource.setJdbcUrl(masterProperties.getUrl());
              dataSource.setUsername(masterProperties.getUsername());
              dataSource.setPassword(masterProperties.getPassword());
              dataSource.setDriverClassName(masterProperties.getDriverClassName());
              return dataSource;
          }
      
          @Bean(name = "slaveDataSource")
          public DataSource dataBase2DataSource(){
              logger.info("slaveDataSource初始化");
              HikariDataSource dataSource = new HikariDataSource();
              dataSource.setJdbcUrl(slaveProperties.getUrl());
              dataSource.setUsername(slaveProperties.getUsername());
              dataSource.setPassword(slaveProperties.getPassword());
              dataSource.setDriverClassName(slaveProperties.getDriverClassName());
              return dataSource;
          }
      }
      
  • Переключение источников динамических данных

    В основном здесь используется AbstractRoutingDataSource, предоставляемый Spring, который обеспечивает функцию динамического источника данных и может помочь нам добиться разделения чтения и записи. Его defineCurrentLookupKey() может определить, какой источник данных в конечном итоге используется.Здесь мы сами создаем DynamicDataSourceHolder, чтобы передать ему тип источника данных (главный, подчиненный).

    package cn.objectspace.springtestdemo.dao.split;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    /**
     * 
    * @Description: spring提供了AbstractRoutingDataSource,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:
    * @Author: Object
    * @Date: 2019年11月30日
     */
    public class DynamicDataSource extends AbstractRoutingDataSource{
        //注入主从数据源
    	@Resource(name="masterDataSource")
    	private DataSource masterDataSource;
    	@Resource(name="slaveDataSource")
    	private DataSource slaveDataSource;
    	@Override
        public void afterPropertiesSet() {
            setDefaultTargetDataSource(masterDataSource);
            Map<Object, Object> dataSourceMap = new HashMap<>();
            //将两个数据源set入目标数据源
            dataSourceMap.put("master", masterDataSource);
            dataSourceMap.put("slave", slaveDataSource);
            setTargetDataSources(dataSourceMap);
    
            super.afterPropertiesSet();
        }
    	@Override
    	protected Object determineCurrentLookupKey() {
            //确定最终的目标数据源
    		return DynamicDataSourceHolder.getDbType();
    	}
    }
    
  • Реализация DynamicDataSourceHolder

    Этот класс реализован нами, в основном, чтобы предоставить Spring тип источника данных, который нам нужно использовать.

    package cn.objectspace.springtestdemo.dao.split;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
    * @Description: 获取DataSource
    * @Author: Object
    * @Date: 2019年11月30日
    */
    public class DynamicDataSourceHolder {
    	private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
    	private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
    	public static final String DB_MASTER = "master";
    	public static final String DB_SLAVE="slave";
    	/**
    	 * @Description: 获取线程的DbType
    	 * @Param: args
    	 * @return: String
    	 * @Author: Object
    	 * @Date: 2019年11月30日
    	 */
    	public static String getDbType() {
    		String db = contextHolder.get();
    		if(db==null) {
    			db = "master";
    		}
    		return db;
    	}
    	/**
    	 * @Description: 设置线程的DbType
    	 * @Param: args
    	 * @return: void
    	 * @Author: Object
    	 * @Date: 2019年11月30日
    	 */
    	public static void setDbType(String str) {
    		logger.info("所使用的数据源为:"+str);
    		contextHolder.set(str);
    	}
    	
    	/**
    	 * @Description: 清理连接类型
    	 * @Param: args
    	 * @return: void
    	 * @Author: Object
    	 * @Date: 2019年11月30日
    	 */
    	public static void clearDbType() {
    		contextHolder.remove();
    	}
    }
    
  • Реализация перехватчиков mybatis

    Наконец, мы реализуем разделение чтения и записиосновнойТеперь этот класс может решить, следует ли читать SQL или писать SQL, чтобы выбрать источник данных и, наконец, вызвать метод setDbType DynamicDataSourceHolder для передачи типа источника данных.

    package cn.objectspace.springtestdemo.dao.split;
    
    import java.util.Locale;
    import java.util.Properties;
    
    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.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    /**
     * @Description: MyBatis级别拦截器,根据SQL信息,选择不同的数据源
     * @Author: Object
     * @Date: 2019年11月30日
     */
    @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 })
    	})
    @Component
    public class DynamicDataSourceInterceptor implements Interceptor {
    	private Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
    	// 验证是否为写SQL的正则表达式
    	private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
    
    	/**
    	 * 主要的拦截方法
    	 */
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		// 判断当前是否被事务管理
    		boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
    		String lookupKey = DynamicDataSourceHolder.DB_MASTER;
    		if (!synchronizationActive) {
                //如果是非事务的,则再判断是读或者写。
    			// 获取SQL中的参数
    			Object[] objects = invocation.getArgs();
    			// object[0]会携带增删改查的信息,可以判断是读或者是写
    			MappedStatement ms = (MappedStatement) objects[0];
    			// 如果为读,且为自增id查询主键,则使用主库
    			// 这种判断主要用于插入时返回ID的操作,由于日志同步到从库有延时
    			// 所以如果插入时需要返回id,则不适用于到从库查询数据,有可能查询不到
    			if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)
    					&& 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).replaceAll("[\\t\\n\\r]", " ");
    				// 正则验证
    				if (sql.matches(REGEX)) {
    					// 如果是写语句
    					lookupKey = DynamicDataSourceHolder.DB_MASTER;
    				} else {
    					lookupKey = DynamicDataSourceHolder.DB_SLAVE;
    				}
    			}
    		} else {
    			// 如果是通过事务管理的,一般都是写语句,直接通过主库
    			lookupKey = DynamicDataSourceHolder.DB_MASTER;
    		}
    
    		logger.info("在" + lookupKey + "中进行操作");
    		DynamicDataSourceHolder.setDbType(lookupKey);
    		// 最后直接执行SQL
    		return invocation.proceed();
    	}
    
    	/**
    	 * 返回封装好的对象,或代理对象
    	 */
    	@Override
    	public Object plugin(Object target) {
    		// 如果存在增删改查,则直接拦截下来,否则直接返回
    		if (target instanceof Executor)
    			return Plugin.wrap(target, this);
    		else
    			return target;
    	}
    
    	/**
    	 * 类初始化的时候做一些相关的设置
    	 */
    	@Override
    	public void setProperties(Properties properties) {
    		// TODO Auto-generated method stub
    
    	}
    
    }
    
  • расчесывание кода

    С помощью вышеприведенной программы мы уже можем реализовать разделение чтения и записи, но на это все еще довольно грязно смотреть. Итак, повторно объедините приведенный выше код здесь.

    На самом деле логика не сложная:

    1. Для настройки нескольких источников данных через @Configuration.
    2. Используйте перехватчик MyBatis, DynamicDataSourceInterceptor, чтобы определить, читается или пишется оператор SQL.Если он прочитан, вызовите DynamicDataSourceHolder.setDbType("slave"), в противном случае вызовите DynamicDataSourceHolder.setDbType("master").
    3. С помощью метода defineCurrentLookupKey() объекта AbstractRoutingDataSource верните DynamicDataSourceHolder.getDbType(), то есть источник данных, который мы установили в перехватчике.
    4. Выполните SQL для внедренного источника данных.
  • контрольная работа

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = {ApplicationStarter.class})// 指定启动类
    public class DaoTest {
    	@Autowired StudentDao studentDao;
    	@Test
    	public void test01() {
    		Student student = new Student();
    		student.setStudentId("20191130");
    		student.setStudentName("Object6");
    		studentDao.insertStudent(student);
    		studentDao.queryStudentByStudentId(student);
    	}
    }
    

    Результаты теста:

    代码层测试成功

    На данный момент полностью реализовано разделение чтения и записи на уровне кода.

Реализуйте разделение чтения-записи и аварийное переключение на основе промежуточного программного обеспечения MyCat.

  • Введение

    В вышеприведенном мы реализовали разделение чтения и записи базы данных с помощью рукописного кода, но я не знаю, нашли ли вы его, я использую только один мастер и один слейв. ТакПочему я реализую только разделение чтения-записи одного ведущего и одного ведомого в среде с одним ведущим и двумя ведомыми?Потому что я не буду писать, чтобы реализовать разделение чтения-записи одного мастера и нескольких ведомых на уровне кода. Затем предположим, что кластер базы данных состоит из более чем одного главного и двух подчиненных устройств, ноМастер из трех, четыре из мастера, мультимадер из несколькихШерстяная ткань? еслиГлавный узел не работает, что делать??

    Каждый раз, когда нода динамически добавляется, нам приходится заново модифицировать наш код, что не только сильно нагружает разработчиков, но и не соответствует принципу открытости-закрытости.

    Так что следующий MyCat должен решить такую ​​проблему. И я буду непосредственно использовать демонстрацию среды одного мастера и двух подчиненных.

  • О MyCat

    Официальная документация находится непосредственно здесь.

    Большой кластер базы данных с полностью открытым исходным кодом для разработки корпоративных приложений.

    Расширенная база данных, которая поддерживает транзакции, ACID и может заменить MySQL.

    База данных корпоративного уровня, которую можно рассматривать как кластер MySQL для замены дорогого кластера Oracle.

    Новый SQL Server, объединяющий технологию кэширования памяти, технологию NoSQL и большие данные HDFS.

    Новое поколение продуктов баз данных корпоративного уровня, сочетающих в себе традиционные базы данных и новые распределенные хранилища данных.

    Новый промежуточный продукт для баз данных

  • Описание окружающей среды

    MyCat 192.168.43.90

    MySQL master 192.168.43.201

    MySQL slave1 192.168.43.202

    MySQL slave2 192.168.43.203

    База данных MySQL, подключенная к предыдущему блогу, состоит из одного главного и двух подчиненных устройств, ноВерсия MySQL должна быть изменена с 8.0 на 5.7, иначе будет проблема с паролем и вы не сможете подключиться.

    Кроме того, нам нужно создать учетную запись для MyCat в каждой базе данных и назначить разрешения:

    CREATE USER  'user_name'@'host'  IDENTIFIED BY  'password';
    GRANT privileges ON  databasename.tablename  TO  ‘username’@‘host’;
    --可以使用下面这句 赋予所有权限
    GRANT ALL PRIVILEGES ON *.* TO  ‘username’@‘host’;
    --最后刷新权限
    FLUSH PRIVILEGES;
    

    Перед запуском убедитесь, что построение библиотеки master-slave прошло успешно:

    一主二从成功

    Я не буду здесь говорить о том, как установить MyCat, на Baidu есть много постов, и нет никаких проблем в том, чтобы следовать приведенному выше руководству шаг за шагом. Давайте сосредоточимся на двух файлах конфигурации, связанных с нашей конфигурацией MyCat:схема.xml и сервер.xml, конечно, есть rules.xml, но подтаблица подбазы данных здесь не представлена, поэтому пока об этом не упоминается.

  • Описание файла конфигурации

    1. server.xml

      Открываем mycat install файлы /conf/server.xml в каталоге, этот конфигурационный файл длиннее, смотря больше затратит мозг, но на самом деле для начала нам нужно для настройки места не много, так что не сильно боимся этот длинный профиль. (Вообще-то в конце предыдущей статьи я сказал, что лицо новой технологии нельзя не знать, когда сначала заставляют, шаг за шагом анализировать и принимать его, отталкивая) после файла конфигурации, наверное, такой упрощенный структура .

      <?xml version="1.0" encoding="UTF-8"?>
      <!DOCTYPE mycat:server SYSTEM "server.dtd">
      <mycat:server xmlns:mycat="http://io.mycat/">
          <system>
          </system>
          <user name="MyCat" defaultAccount="true">
          </user>
      
      </mycat:server>
      
      

      Это кажется намного проще?На самом деле для Server.xml мы в основном настраиваем следующий пользовательский модуль.Расширяем его и фокусируемся на этой части конфигурации.

      <user name="这里写MyCat的用户名 可以自定义" defaultAccount="true">
         <property name="password">这里写MyCat的密码</property>
         <property name="schemas">这里配置MyCat的虚拟database</property>
                  <!-- 表级 DML 权限设置 -->
                  <!--
      				这里是我们配置的mycat用户对某张表的权限配置,我们这里暂不配置但是还是说一					下。下文中的0000 1111 每一位 代表CRUD  1111就是有增删改查的权限,0000就				是没有这些权限。以此类推
                  <privileges check="false">
                          <schema name="TESTDB" dml="0110" >
                                  <table name="tb01" dml="0000"></table>
                                  <table name="tb02" dml="1111"></table>
                          </schema>
                  </privileges>
                   -->
      </user>
      

      user представляет пользователя MyCat, у нас будет пользователь, когда мы будем использовать MySQL,MyCat как виртуальный узел, мы можем представить его как MySQL, поэтому, естественно, у него также должен быть пользователь. Но его пользователь не создается нами с помощью команды, а настраивается непосредственно в конфигурационном файле.После того, как мы авторизуемся в MyCat, мы авторизуемся с логином и паролем здесь. Что касается того, как настроить, я написал это в приведенной выше конфигурации. Следите за собой, и у вас все будет хорошо.

    2. schema.xml

      Откройте conf/schema.xml в каталоге установки MyCat, этот файл конфигурацииэто файл конфигурации, на который нам нужно обратить внимание, потому что наше разделение чтения-записи, подтаблица подбазы данных и отработка отказа настроены в этом файле конфигурации. Но этот конфигурационный файл не длинный, мы можем разобрать его по крупицам.

      во-первыхэто содержимое тега. Эта метка в основном предназначена для создания виртуальной базы данных для MyCat.Здесь настраивается база данных, которую мы видим при подключении к MyCat.Подбаза данных и подтаблица также в основном настраиваются на этой вкладке.. в этом тегеnameАтрибут предназначен для указания имени виртуальной базы данных, а также имени библиотеки базы данных, которую мы видим при подключении к MyCat.dataNodeОно соответствует имени в метке dataNode ниже, представляя, что эта виртуальная база данных привязана к dataNode ниже.

      <schema name="MyCatDatabase" checkSQLschema="false" sqlMaxLimit="100" dataNode="这里写节点名,需要和dataNode中的name相对应">
      	<!-- 分库分表 -->
      		<!--<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />-->
      </schema>
      

      вторая этикеткаэто метка, эта метка связана с базой данных в нашей реальной базе данных,nameАтрибут — это имя, которое мы настраиваем для этого узла данных. Следует отметить, что это имя должно соответствовать содержимому узла данных в теге схемы.databaseСвойство записывается с именем реальной базы данных в нашей реальной базе данных. иdataHostСодержимое должно соответствовать значению атрибута имени в следующем теге.

      <dataNode name="这里写节点名,需要和schema中的dataNode相对应" dataHost="这里也是一个自定义名字,需要和dataHost中的name相对应" database="这里填MySQL真实的数据库名" />
      

      третья вкладкаЧто я хочу сказать, так это метка.Эта метка связана с разделением master-slave и read-write нашей реальной базы данных.Что это значит? В этом теге есть два вложенных тега, которые представляют нашунаписать библиотекуичитать библиотеку, настроенная библиотека может использоваться для чтения или записи, а настроенная библиотека может использоваться только для чтения.

      Видно, что конфигурация schema.xml связана одна за другой, и каждый тег имеет атрибуты, связанные друг с другом. Наш окончательный сконфигурированный schema.xml должен выглядеть так:

      <?xml version="1.0"?>
      <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
      <mycat:schema xmlns:mycat="http://io.mycat/">
          <schema name="这里写虚拟database名,需要和server.xml中的schema相对应" checkSQLschema="false" sqlMaxLimit="100" dataNode="这里写节点名,需要和dataNode中的name相对应">
      	<!-- 分库分表 -->
      		<!--<table name="travelrecord" dataNode="dn1,dn2,dn3" rule="auto-sharding-long" />-->
          </schema>
      		<dataNode name="这里写节点名,需要和schema中的dataNode相对应" dataHost="这里也是一个自定义名字,需要和dataHost中的name相对应" database="这里填MySQL真实的数据库名" />
              <dataHost name="这里写和dataNode中的dataHost相同的名字" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                  <!-- 心跳语句,证明myCat和mySQL是相互连接的状态-->
                  <heartbeat>show slave status</heartbeat>
      			<!-- 读写分离 -->
                  <writeHost host="节点的名字,随便取" url="数据库的url(IP:PORT)" user="数据库中给MyCat创建的用户名" password="数据库中给MyCat创建的密码">
      				<readHost host="节点的名字,随便取" url="数据库的url(IP:PORT)" user="数据库中给MyCat创建的用户名" password="数据库中给MyCat创建的密码">
      				</readHost>
      				<readHost host="节点的名字,随便取" url="数据库的url(IP:PORT)" user="数据库中给MyCat创建的用户名" password="数据库中给MyCat创建的密码">
      				</readHost>
                  </writeHost>
      			<!-- 主从切换 -->
      			<writeHost host="节点的名字,随便取" url="数据库的url(IP:PORT)" user="数据库中给MyCat创建的用户名" password="数据库中给MyCat创建的密码"></writeHost>
      			<writeHost host="节点的名字,随便取" url="数据库的url(IP:PORT)" user="数据库中给MyCat创建的用户名" password="数据库中给MyCat创建的密码"></writeHost>
              </dataHost>
      </mycat:schema>
      
  • MyCat организовал раздельное чтение и запись

    Выше мы сделали базовую интерпретацию двух файлов конфигурации MyCat, поэтому теперь мы начнем строить разделение чтения и записи на основе MyCat. У меня здесь три базы данных, одна главная и две подчиненные, еще раз повторю среду:

    192.168.43.201 главная библиотека

    192.168.43.202 подчиненная библиотека

    192.168.43.203 подчиненная библиотека

    Тогда конфигурация для server.xml и schema.xml выглядит следующим образом:

    server.xml:

    <user name="MyCat" defaultAccount="true">
    	<property name="password">123456</property>
    	<property name="schemas">MyCat</property>
    </user>
    

    schema.xml:

    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
            <schema name="MyCat" checkSQLschema="false" sqlMaxLimit="100" dataNode="mycatdb"></schema>
        	<!-- testcluster是我真实数据库中的名字 -->
            <dataNode name="mycatdb" dataHost="mycluster" database="testcluster" />
        	<!-- 开启读写分离必须将balance修改为1-->
            <dataHost name="mycluster" maxCon="1000" minCon="10" balance="1"
                              writeType="0" dbType="mysql" dbDriver="native" switchType="1"  slaveThreshold="100">
                    <heartbeat>show slave status</heartbeat>
    				<!-- 读写分离 -->
                    <writeHost host="Master201" url="192.168.43.201:3306" user="MyCat" password="123456">
    					<readHost host="Slave202" url="192.168.43.202:3306" user="MyCat" password="123456">
    					</readHost>
    					<readHost host="Slave203" url="192.168.43.203:3306" user="MyCat" password="123456">
    					</readHost>
                    </writeHost>
            </dataHost>
    </mycat:schema>
    

    Запустите MyCat и протестируйте:

    Запустите MyCat:

    ./mycat start

    Подключить MyCat:

    mysql -u MyCat -h 192.168.43.90 -P 8066 -p

    连接MyCat

    Правильно видно, что в MyCat есть база данных с именем MyCat, и эта база данных виртуализирована нами и реально не существует.На самом деле играет роль именно наша конфигурация.

    Создайте таблицу в библиотеке MyCat

    CREATE TABLE student(student_id VARCHAR(32),student_name VARCHAR(32));

    MyCat建表

    从库同步

    Это может доказать, что наше подключение mycat к реальной базе данных прошло успешно. Затем мы начнем доказывать разделение чтения и письма Что такое разделение чтения и письма?То есть операция чтения данных читается из ведомой библиотеки, а основная библиотека отвечает только за операции записи, Давайте начнемпроверять.

    Перед проверкой нам нужно установить журнал MyCat в режим отладки, потому что в информационном режиме невозможно показать, в какую базу данных перенаправляется оператор SQL для запроса в журнале..

    Как настроить:

    Откройте файл conf/log4j2.xml.

    修改MyCat日志等级

    выполнить оператор вставки:

    INSERT INTO student(student_id,student_name) VALUES('20191130','Object');

    插入数据

    Посмотреть журнал:

    查看写日志

    Видно, что инструкция INSERT написана в 201, а 201 — это Master-библиотека, то есть библиотека записи.

    напиши мы приедемвыполнить оператор чтения:

    SELECT * FROM student;

    查看读日志1

    Видно, что оператор SELECT выполняется в 202, а 202 — это библиотека Slave, то есть библиотека чтения.

    Сделай это снова:

    查看读日志2

    В это время выполняется оператор чтения в 203 или читается библиотека.Эти две библиотеки чтения читаются на основе правил балансировки нагрузки..

    Это завершает настройку разделения чтения-записи. Когда нам нужно выполнить INSERT/UPDATE/DELETE, мы будем писать непосредственно в ведущую, а затем синхронизироваться с подчиненной библиотекой.Когда мы хотим выполнить операцию SELECT, мы будем читать в вместо этого Ведомый. , не влияет на написание Мастера,Это разделение чтения и записи расширяет функцию синхронизации master-slave MySQL, что может повысить производительность базы данных при аварийном восстановлении и резервном копировании..

  • Переключение конфигурации MyCat

    Мы уже сделали MyCat на вышеуказанномразделение чтения-записиконфигурации, то мы смело предполагаем, что еслиНаша база данных Master внезапно выходит из строя, поэтому весь кластер теряет функцию записи??

    Этот ответ до отказа.определенно, Когда основная библиотека не работает, подчиненная библиотека как библиотека чтения не будет иметь функции записи, а весь кластер лишится функции записи, чего мы не хотим видеть.

    Сценарии, которые мы хотим видеть, следующие:Когда главная библиотека выходит из строя, подчиненная библиотека автоматически становится главной библиотекой, которая берет на себя функцию записи и обеспечивает доступность всего кластера..

    Затем мы начнем настройку, на самом деле, идея очень проста, в теге mycat наблюдается свойство SwitchPype, которая определяет условие для переключения.

    switchType относится к режиму переключения, и в настоящее время существует 4 значения:

    1. switchType='-1' означает отсутствие автоматического переключения

    2. switchType='1' Значение по умолчанию, означающее автоматическое переключение

    3. switchType='2' Определяет, следует ли переключаться, основываясь на состоянии синхронизации master-slave MySQL.

    4. switchType='3' основан на механизме переключения галерейного кластера MySQL (подходит для кластера) (1.4.1), а оператор сердцебиения показывает состояние, подобное 'wsrep%'.

    Мы напрямую изменяем switchType на 2, а затем настраиваем две библиотеки чтения в качестве первой библиотеки записи.тот же уровеньписательская библиотека.

    Файл конфигурации выглядит следующим образом:

    <?xml version="1.0"?>
    <!DOCTYPE mycat:schema SYSTEM "schema.dtd">
    <mycat:schema xmlns:mycat="http://io.mycat/">
        <schema name="MyCat" checkSQLschema="false" sqlMaxLimit="100" dataNode="mycatdb">
        </schema>
        <dataNode name="mycatdb" dataHost="mycluster" database="testcluster" />
        <dataHost name="mycluster" maxCon="1000" minCon="10" balance="1"
                          writeType="0" dbType="mysql" dbDriver="native" switchType="2"  slaveThreshold="100">
                <heartbeat>show slave status</heartbeat>
    			<!-- 读写分离 -->
                <writeHost host="Master201" url="192.168.43.201:3306" user="MyCat" password="123456">
    				<readHost host="Slave202" url="192.168.43.202:3306" user="MyCat" password="123456">
    				</readHost>
    				<readHost host="Slave203" url="192.168.43.203:3306" user="MyCat" password="123456">
    				</readHost>
                </writeHost>
    			<!-- 主从切换 -->
    			<writeHost host="Slave202" url="192.168.43.202:3306" user="MyCat" password="123456"></writeHost>
    			<writeHost host="Slave203" url="192.168.43.203:3306" user="MyCat" password="123456"></writeHost>
            </dataHost>
    </mycat:schema>
    

    Перезапустите MyCat

    Теперь давайте остановим основную библиотеку, а затем выполним операцию записи, чтобы посмотреть, что получится в результате.

    service mysqld stop

    Журнал MyCat:

    模拟主库宕机

    故障转移日志

    воплощать в жизньINSERT INTO student(student_id,student_name)VALUES('test','testdown');

    Журналы MyCat

    主库宕机后写入
    Видно, что когда мы заканчиваем выполнение этого оператора, он автоматически переключается на базу данных 202 для записи, а 202 является слейвом вместо мастера, а это означает, что MyCat автоматически переключил базу данных для записи, и наш кластер MySQL все еще может обеспечить написать функцию.

    Конечно, в настоящее время наша архитектура MySQL master-slave разрушена, и если нам нужно восстановить структуру master-slave, нам нужно вручную восстановить нашу архитектуру master-slave. Нам нужно использовать 201 и 203 как Slave и 202 как Master, т.к. у Master самые полные данные.

Анализ преимуществ и недостатков

Что касается преимуществ и недостатков этих двух методов, я считаю, что студенты, которые внимательно прочитают эту статью, получат глубокий опыт.

Слой кода для разделения чтения и записиОсновным преимуществом являетсяГибкость, вы можете настроить разработку правил разделения чтения-записи в соответствии с различными потребностями., но его недостаток также весьма очевиден, то есть, когда мы динамически увеличиваем или уменьшаем количество master-slave библиотек, нам нужно вносить более или менее модификацию кода. И когда основная база данных выходит из строя, если мы не реализуем соответствующую логику аварийного восстановления, весь кластер базы данных потеряет функцию внешней записи.

Используйте промежуточное ПО MyCat для разделения операций чтения и записи., преимущества очень очевидны, нам нужно только настроить, чтобы наслаждаться повышением эффективности, вызванным разделением чтения и записи, без написания строки кода, и когда основная библиотека не работает, мы также можем автоматически переключать библиотеку ведущий-ведомый через конфигурации, Таким образом, даже если основная база данных выйдет из строя, весь наш кластер не потеряет функцию записи. Недостатком может быть то, что мы должны платить за сервер как за виртуальную ноду, ведь серверу тоже нужна стоимость.

Как выбрать между двумя способами: Если ваш текущий проект относительно небольшой, или это просто выпускной проект, курсовой дизайн и т.д., и нет необходимости динамически увеличивать или уменьшать базу данных, то вам будет более подходящим реализовать разделение чтения и написание базы данных самостоятельно.Когда придет время, вы можете объяснить (чжуан) объяснить (би) с вашим репетитором и одноклассниками строка за строкой кода. Если проект относительно большой, узлы базы данных могут быть увеличены или уменьшены, и требуются такие функции, как переключение ведущий-ведомый, тогда используйте второй метод. Реализация такой конфигурации может снизить вероятность засорения канализационной трубы при мытье головы на следующий день.

Эпилог

Я постараюсь не затягивать!

Добро пожаловать в мой личный блог:Object's Blog