В последнее время рабочая задача относительно легкая, и у меня есть время для изучения методов обучения, поэтому я пришел изучить, как добиться разделения чтения и письма. Здесь мы используем блог для записи процесса, с одной стороны, его можно просмотреть в будущем, и в то же время им можно поделиться со всеми (большая часть информации в Интернете действительно копируется, и это не отформатирован, так что читать действительно неудобно).
Полный код:GitHub.com/FoYX/demo-…
1. Предпосылки
Самая основная и основная база данных в проекте — это автономная база данных, а чтение и запись выполняются в одной библиотеке. Когда количество пользователей постепенно увеличивается, а база данных на одном компьютере не может удовлетворить требования к производительности, будет выполняться преобразование разделения чтения-записи (подходит для большего чтения и меньшего количества записей), запись в одну базу данных и чтение в несколько баз данных. , Обычно создается кластер базы данных, и включается режим ведущий-ведомый.Резервное копирование, один главный и несколько подчиненных для повышения производительности чтения. Когда большее разделение пользователей на чтение и запись не может быть удовлетворено, необходима распределенная база данных (может быть, научиться делать это позже).
В нормальных условиях для реализации разделения чтения и записи требуется кластер базы данных с одним ведущим и несколькими подчиненными, а также требуется синхронизация данных. В этой статье описано, как использовать mysql для создания конфигурации из одного мастера и нескольких раз, а в следующей статье описано, как добиться разделения чтения и записи на уровне кода.
2. Создайте кластер базы данных с несколькими подчиненными с одним ведущим
Резервное копирование Master-Slave требует нескольких виртуальных машин.Я использовал wmware для полного клонирования нескольких экземпляров.Обратите внимание, что прямое клонирование виртуальной машины приведет к тому, что uuid каждой базы данных будет одинаковым, и его необходимо будет изменить на другой uuid. Обратитесь к этому для метода модификации:Нажмите, чтобы прыгнуть.
-
Конфигурация основной библиотеки
Создайте нового пользователя в базе данных master (master), чтобы прочитать двоичный журнал базы данных master из базы данных (slave), оператор sql выглядит следующим образом:
mysql> CREATE USER 'repl'@'%' IDENTIFIED BY '123456';#创建用户 mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';#分配权限 mysql>flush privileges; #刷新权限В то же время измените файл конфигурации mysql, чтобы открыть двоичный журнал.Новые части заключаются в следующем:
[mysqld] server-id=1 log-bin=master-bin log-bin-index=master-bin.indexЗатем перезапустите базу данных, используйте
show master status;оператор для просмотра состояния основной библиотеки следующим образом:
-
Настроить из библиотеки
Также добавьте несколько строк конфигурации:
[mysqld] server-id=2 relay-log-index=slave-relay-bin.index relay-log=slave-relay-binЗатем перезапустите базу данных и используйте следующую инструкцию для подключения к основной базе данных:
CHANGE MASTER TO MASTER_HOST='192.168.226.5', MASTER_USER='root', MASTER_PASSWORD='123456', MASTER_LOG_FILE='master-bin.000003', MASTER_LOG_POS=154;затем беги
start slave;Включите резервное копирование, нормальная ситуация показана на следующем рисунке: Slave_IO_Running и Slave_SQL_Running — да.
Вы можете использовать этот шаг для запуска нескольких подчиненных библиотек.
По умолчанию все операции основной библиотеки будут резервироваться в подчиненной библиотеке.На самом деле, некоторые библиотеки, возможно, придется игнорировать.Вы можете добавить следующую конфигурацию в основную библиотеку:
# 不同步哪些数据库
binlog-ignore-db = mysql
binlog-ignore-db = test
binlog-ignore-db = information_schema
# 只同步哪些数据库,除此之外,其他不同步
binlog-do-db = game
3. Разделение чтения и записи на уровне кода
Среда кода — пул соединений springboot+mybatis+druib. Если вы хотите разделить чтение и запись, вам необходимо настроить несколько источников данных.При выполнении операции записи выберите источник данных для записи, а при выполнении операции чтения выберите источник данных для чтения. Есть два ключевых момента:
- Как переключать источники данных
- Как выбрать правильный источник данных на основе различных методов
1), как переключить источник данных
Обычно springboot использует свою конфигурацию по умолчанию, просто задайте свойства соединения в файле конфигурации, но теперь нам нужно настроить его самостоятельно, spring поддерживает несколько источников данных, а несколько источников данных помещаются в HashMapTargetDataSourceв, черезdertermineCurrentLookupKeyПолучите ключ, чтобы определить, какой источник данных использовать. Итак, наша цель очень ясна: создать несколько источников данных и поместить их в TargetDataSource, а также переписать метод dertermineCurrentLookupKey, чтобы решить, какой ключ использовать.
2) Как выбрать источник данных
Транзакции обычно аннотируются на уровне службы, поэтому вам необходимо определить источник данных при запуске вызова метода службы. Есть ли какой-либо общий способ сделать это до того, как вы начнете выполнять метод? Я полагаю, вы уже подумали, что это **резка**. Вырезать можно двумя способами:
- Тип аннотации, определяет аннотацию только для чтения, метод, аннотированный данными, использует библиотеку чтения
- Имя метода, напишите pointcut в соответствии с именем метода, например, getXXX использует библиотеку чтения, setXXX использует библиотеку записи
3), написание кода
А. Напишите файл конфигурации и настройте информацию о двух источниках данных.
Только необходимая информация, остальные имеют настройки по умолчанию
mysql:
datasource:
#读库数目
num: 1
type-aliases-package: com.example.dxfl.dao
mapper-locations: classpath:/mapper/*.xml
config-location: classpath:/mybatis-config.xml
write:
url: jdbc:mysql://192.168.226.5:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
read:
url: jdbc:mysql://192.168.226.6:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
б. Напишите класс DbContextHolder
Этот класс используется для установки категории базы данных, которая имеет ThreadLocal для сохранения того, использует ли каждый поток библиотеку чтения или библиотеку записи. код показывает, как показано ниже:
/**
* Description 这里切换读/写模式
* 原理是利用ThreadLocal保存当前线程是否处于读模式(通过开始READ_ONLY注解在开始操作前设置模式为读模式,
* 操作结束后清除该数据,避免内存泄漏,同时也为了后续在该线程进行写操作时任然为读模式
* @author fxb
* @date 2018-08-31
*/
public class DbContextHolder {
private static Logger log = LoggerFactory.getLogger(DbContextHolder.class);
public static final String WRITE = "write";
public static final String READ = "read";
private static ThreadLocal<String> contextHolder= new ThreadLocal<>();
public static void setDbType(String dbType) {
if (dbType == null) {
log.error("dbType为空");
throw new NullPointerException();
}
log.info("设置dbType为:{}",dbType);
contextHolder.set(dbType);
}
public static String getDbType() {
return contextHolder.get() == null ? WRITE : contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
в) Перепишите метод определенияCurrentLookupKey.
spring будет использовать этот метод, чтобы решить, какую базу данных использовать при запуске операций с базой данных, поэтому здесь мы вызываем приведенный выше класс DbContextHolder.getDbType()Метод получает текущую категорию операции и может одновременно выполнять балансировку нагрузки библиотеки чтения.Код выглядит следующим образом:
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
@Value("${mysql.datasource.num}")
private int num;
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Override
protected Object determineCurrentLookupKey() {
String typeKey = DbContextHolder.getDbType();
if (typeKey == DbContextHolder.WRITE) {
log.info("使用了写库");
return typeKey;
}
//使用随机数决定使用哪个读库
int sum = NumberUtil.getRandom(1, num);
log.info("使用了读库{}", sum);
return DbContextHolder.READ + sum;
}
}
г. Напишите класс конфигурации
Из-за разделения чтения и записи стандартную конфигурацию springboot больше нельзя использовать, и нам необходимо настроить ее вручную. Сначала создайте источник данных, используйте @ConfigurProperties для автоматического создания источника данных:
/**
* 写数据源
*
* @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。
* 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean
*/
@Primary
@Bean
@ConfigurationProperties(prefix = "mysql.datasource.write")
public DataSource writeDataSource() {
return new DruidDataSource();
}
Источник данных для чтения аналогичен, обратите внимание на то, сколько существует источников данных для чтения, а имя компонента — это чтение + серийный номер.
Затем установите источник данных, используя класс MyAbstractRoutingDataSource, который мы написали ранее.
/**
* 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源
*/
@Bean
public AbstractRoutingDataSource routingDataSource() {
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DbContextHolder.WRITE, writeDataSource());
targetDataSources.put(DbContextHolder.READ+"1", read1());
proxy.setDefaultTargetDataSource(writeDataSource());
proxy.setTargetDataSources(targetDataSources);
return proxy;
}
Затем вам нужно установить sqlSessionFactory
/**
* 多数据源需要自己设置sqlSessionFactory
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(routingDataSource());
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
// 实体类对应的位置
bean.setTypeAliasesPackage(typeAliasesPackage);
// mybatis的XML的配置
bean.setMapperLocations(resolver.getResources(mapperLocation));
bean.setConfigLocation(resolver.getResource(configLocation));
return bean.getObject();
}
Наконец, вы должны настроить транзакцию, иначе транзакция не вступит в силу
/**
* 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理
*/
@Bean
public DataSourceTransactionManager dataSourceTransactionManager() {
return new DataSourceTransactionManager(routingDataSource());
}
4), выберите источник данных
Настроено несколько источников данных, но как выбрать источник данных на уровне кода? Вот два способа:
а. Аннотация
Сначала определите аннотацию только для чтения, которая используется этим методом аннотации для чтения библиотеки, а другие — для использования библиотеки записи.Если проект преобразуется в разделение чтения-записи на полпути, этот метод можно использовать без изменения бизнес-кода , просто добавьте аннотацию к сервисному методу, доступному только для чтения. Вот и все.
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}
Затем напишите аспект, чтобы переключить источник данных, используемый для данных, перепишите getOrder, чтобы приоритет этого аспекта был выше, чем приоритет аспекта транзакции, и добавьте его в класс запуска.@EnableTransactionManagement(order = 10), чтобы закодировать следующим образом:
@Aspect
@Component
public class ReadOnlyInterceptor implements Ordered {
private static final Logger log= LoggerFactory.getLogger(ReadOnlyInterceptor.class);
@Around("@annotation(readOnly)")
public Object setRead(ProceedingJoinPoint joinPoint,ReadOnly readOnly) throws Throwable{
try{
DbContextHolder.setDbType(DbContextHolder.READ);
return joinPoint.proceed();
}finally {
//清楚DbType一方面为了避免内存泄漏,更重要的是避免对后续在本线程上执行的操作产生影响
DbContextHolder.clearDbType();
log.info("清除threadLocal");
}
}
@Override
public int getOrder() {
return 0;
}
}
б. Название метода
Данный метод нельзя аннотировать, но для него необходимо прописать имя метода в сервисе по определенным правилам, а затем установить категорию базы данных через аспект, напримерsetXXXнастроиться на запись,getXXXЕсли он настроен на чтение, я не буду писать код, я должен знать, как его писать.
4. Тест
Напишите хороший код, чтобы опробовать результаты, ниже приведен скриншот операции:
Разделение чтения-записи это только временное решение для расширения БД, и не может быть сделано раз и навсегда.По мере дальнейшего увеличения нагрузки одной библиотеки для записи точно не хватит, а для однотабличной БД есть верхний предел , а количество уровней в MySQL может достигать десятков миллионов. Данные могут поддерживать хорошую производительность запросов. В конечном итоге это станет...Подбиблиотека и подтаблицаархитектурный. Для подтаблиц подбазы данных вы можете увидеть эту статью:Уууу, она этого заслуживает? top/blog/detail…
Эта статья была первоначально опубликована на:Уууу, она этого заслуживает? top/blog/detail…