Heim  >  Artikel  >  Java  >  Spring Boot implementiert die MySQL-Technologie zur Lese-/Schreibtrennung

Spring Boot implementiert die MySQL-Technologie zur Lese-/Schreibtrennung

Java后端技术全栈
Java后端技术全栈nach vorne
2023-08-15 16:52:561093Durchsuche

So implementieren Sie die Lese- und Schreibtrennung, Spring Boot-Projekt, die Datenbank ist MySQL und die Persistenzschicht verwendet MyBatis.

Tatsächlich ist es sehr einfach, dies zu implementieren. Überlegen Sie sich zunächst eine Frage:

Welche Optimierungsmethoden gibt es für die Datenbank in Szenarien mit hoher Parallelität?

Die folgenden Implementierungsmethoden werden häufig verwendet: Lese-/Schreibtrennung, Caching, Master-Slave-Architekturcluster, Unterdatenbank und Untertabelle usw.

Bei Internetanwendungen lesen die meisten mehr und schreiben weniger. Es werden zwei Bibliotheken eingerichtet, die Hauptbibliothek und die Lesebibliothek.

Die Hauptbibliothek ist für das Schreiben verantwortlich, und die Slave-Bibliothek ist hauptsächlich für das Lesen verantwortlich. Durch die Isolierung der Lese- und Schreibfunktionen auf der Datenquelle können Lese- und Schreibkonflikte reduziert werden Dadurch kann eine Entlastung der Datenbank und ein Schutz der Datenbank erreicht werden. Bei der tatsächlichen Verwendung wird jeder Teil, der das Schreiben betrifft, direkt auf die Hauptbibliothek umgeschaltet, und der Leseteil wird direkt auf die Lesebibliothek umgeschaltet. Dies ist eine typische Lese-/Schreib-Trennungstechnologie.

Dieser Artikel konzentriert sich auf die Trennung von Lesen und Schreiben und untersucht, wie man sie umsetzt.

Spring Boot implementiert die MySQL-Technologie zur Lese-/Schreibtrennung

Einschränkungen der Master-Slave-Synchronisation: Diese ist in Master-Datenbank und Slave-Datenbank unterteilt. Die Master-Datenbank ist für das Schreiben von Daten verantwortlich Die Daten werden automatisch mit der Slave-Datenbank synchronisiert. Die Slave-Datenbank ist für das Lesen verantwortlich. Wenn eine Leseanforderung eingeht, werden die Daten direkt aus der Lesedatenbank gelesen und die Master-Datenbank kopiert die Daten automatisch in die Slave-Datenbank. In diesem Blog wird dieser Teil des Konfigurationswissens jedoch nicht vorgestellt, da er sich eher auf Betriebs- und Wartungsarbeiten konzentriert.

Hier liegt ein Problem vor:

Das Verzögerungsproblem der Master-Slave-Replikation kommt plötzlich zu einer Leseanforderung und die Daten sind zu diesem Zeitpunkt nicht vollständig synchronisiert, es erscheint eine Leseanforderung . Die Daten können nicht gelesen werden oder die gelesenen Daten sind kleiner als der ursprüngliche Wert. Die einfachste spezifische Lösung besteht darin, die Leseanforderung vorübergehend an die Hauptbibliothek zu richten, gleichzeitig verliert sie jedoch auch einen Teil der Bedeutung der Master-Slave-Trennung. Das heißt, im strengen Sinne von Datenkonsistenzszenarien ist die Lese-Schreib-Trennung nicht vollständig geeignet. Achten Sie auf die Aktualität von Aktualisierungen als Nachteil der Verwendung der Lese-Schreib-Trennung.

Okay, dieser Teil dient nur dem Verständnis. Schauen wir uns als Nächstes an, wie man durch Java-Code eine Lese- und Schreibtrennung erreicht:

Hinweis: Dieses Projekt muss die folgenden Abhängigkeiten einführen: Spring Boot, Spring-Aop, Spring-JDBC, Aspectjweaver usw.

Programmierer: Es gibt nur 30 Tage, wie bereitet man sich vor?

1: Konfiguration der Master-Slave-Datenquelle

Wir müssen die Master-Slave-Datenbank konfigurieren. Die Konfiguration der Master-Slave-Datenbank wird im Allgemeinen in die Konfigurationsdatei geschrieben. Über die Annotation @ConfigurationProperties wird die Konfigurationsdatei (allgemein benannt: application.Properties)里的属性映射到具体的类属性上,从而读取到写入的值注入到具体的代码配置中,按照习惯大于约定的原则,主库我们都是注为 master,从库注为 slave。

本项目采用了阿里的 druid 数据库连接池,使用 build 建造者模式创建 DataSource 对象,DataSource 就是代码层面抽象出来的数据源,接着需要配置 sessionFactory、sqlTemplate、事务管理器等。

/**
 * 主从配置 
 */
@Configuration
@MapperScan(basePackages = "com.wyq.mysqlreadwriteseparate.mapper", sqlSessionTemplateRef = "sqlTemplate")
public class DataSourceConfig {

    /**
     * 主库
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource master() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 从库
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaver() {
        return DruidDataSourceBuilder.create().build();
    }


    /**
     * 实例化数据源路由
     */
    @Bean
    public DataSourceRouter dynamicDB(@Qualifier("master") DataSource masterDataSource,
                                      @Autowired(required = false) @Qualifier("slaver") DataSource slaveDataSource) {
        DataSourceRouter dynamicDataSource = new DataSourceRouter();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceEnum.MASTER.getDataSourceName(), masterDataSource);
        if (slaveDataSource != null) {
            targetDataSources.put(DataSourceEnum.SLAVE.getDataSourceName(), slaveDataSource);
        }
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        return dynamicDataSource;
    }


    /**
     * 配置sessionFactory
     * @param dynamicDataSource
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sessionFactory(@Qualifier("dynamicDB") DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*Mapper.xml"));
        bean.setDataSource(dynamicDataSource);
        return bean.getObject();
    }


    /**
     * 创建sqlTemplate
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionTemplate sqlTemplate(@Qualifier("sessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }


    /**
     * 事务配置
     *
     * @param dynamicDataSource
     * @return
     */
    @Bean(name = "dataSourceTx")
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDB") DataSource dynamicDataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dynamicDataSource);
        return dataSourceTransactionManager;
    }
}

二: 数据源路由的配置

路由在主从分离是非常重要的,基本是读写切换的核心。Spring 提供了 AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,作用就是在执行查询之前,设置使用的数据源,实现动态路由的数据源,在每次数据库查询操作前执行它的抽象方法 determineCurrentLookupKey()

Dieses Projekt verwendet den Druiden-Datenbankverbindungspool und den Build-Builder-Modus, um DataSource-Objekte zu erstellen. DataSource ist die auf Codeebene abstrahierte Datenquelle. Anschließend müssen Sie sessionFactory, sqlTemplate, Transaktionsmanager usw. konfigurieren.

public class DataSourceRouter extends AbstractRoutingDataSource {

    /**
     * 最终的determineCurrentLookupKey返回的是从DataSourceContextHolder中拿到的,因此在动态切换数据源的时候注解
     * 应该给DataSourceContextHolder设值
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();

    }
}

🎜🎜🎜Zweitens: Konfiguration des Datenquellen-Routings🎜🎜 🎜🎜🎜Routing ist bei der Master-Slave-Trennung sehr wichtig und im Grunde der Kern der Lese-/Schreibumschaltung. Spring bietet AbstractRoutingDataSource gemäß benutzerdefinierten Regeln Die Funktion der Auswahl der aktuellen Datenquelle besteht darin, die verwendete Datenquelle vor der Ausführung der Abfrage festzulegen, die Datenquelle des dynamischen Routings zu implementieren und ihre abstrakte Methode vor jedem Datenbankabfragevorgang auszuführendetermineCurrentLookupKey() Bestimmt, welche Datenquelle verwendet werden soll. 🎜🎜Um einen globalen Datenquellenmanager zu haben, müssen wir den DataSourceContextHolder einführen, einen Datenbankkontextmanager, der als globale Variable verstanden werden kann und auf den jederzeit zugegriffen werden kann (siehe detaillierte Einführung unten). um die aktuelle Datenquelle zu speichern. 🎜
public class DataSourceRouter extends AbstractRoutingDataSource {

    /**
     * 最终的determineCurrentLookupKey返回的是从DataSourceContextHolder中拿到的,因此在动态切换数据源的时候注解
     * 应该给DataSourceContextHolder设值
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.get();

    }
}

三:数据源上下文环境

数据源上下文保存器,便于程序中可以随时取到当前的数据源,它主要利用 ThreadLocal 封装,因为 ThreadLocal 是线程隔离的,天然具有线程安全的优势。这里暴露了 set 和 get、clear 方法,set 方法用于赋值当前的数据源名,get 方法用于获取当前的数据源名称,clear 方法用于清除 ThreadLocal 中的内容,因为 ThreadLocal 的 key 是 weakReference 是有内存泄漏风险的,通过 remove 方法防止内存泄漏。

/**
 * 利用ThreadLocal封装的保存数据源上线的上下文context
 */
public class DataSourceContextHolder {

    private static final ThreadLocal<String> context = new ThreadLocal<>();

    /**
     * 赋值
     *
     * @param datasourceType
     */
    public static void set(String datasourceType) {
        context.set(datasourceType);
    }

    /**
     * 获取值
     * @return
     */
    public static String get() {
        return context.get();
    }

    public static void clear() {
        context.remove();
    }
}

四:切换注解和 Aop 配置

首先我们来定义一个@DataSourceSwitcher 注解,拥有两个属性

① 当前的数据源② 是否清除当前的数据源,并且只能放在方法上,(不可以放在类上,也没必要放在类上,因为我们在进行数据源切换的时候肯定是方法操作),该注解的主要作用就是进行数据源的切换,在 dao 层进行操作数据库的时候,可以在方法上注明表示的是当前使用哪个数据源。

@DataSourceSwitcher 注解的定义:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DataSourceSwitcher {
    /**
     * 默认数据源
     * @return
     */
    DataSourceEnum value() default DataSourceEnum.MASTER;
    /**
     * 清除
     * @return
     */
    boolean clear() default true;

}

DataSourceAop配置:

为了赋予@DataSourceSwitcher 注解能够切换数据源的能力,我们需要使用 AOP,然后使用@Aroud 注解找到方法上有@DataSourceSwitcher.class 的方法,然后取注解上配置的数据源的值,设置到 DataSourceContextHolder 中,就实现了将当前方法上配置的数据源注入到全局作用域当中。

@Slf4j
@Aspect
@Order(value = 1)
@Component
public class DataSourceContextAop {

    @Around("@annotation(com.wyq.mysqlreadwriteseparate.annotation.DataSourceSwitcher)")
    public Object setDynamicDataSource(ProceedingJoinPoint pjp) throws Throwable {
        boolean clear = false;
        try {
            Method method = this.getMethod(pjp);
            DataSourceSwitcher dataSourceSwitcher = method.getAnnotation(DataSourceSwitcher.class);
            clear = dataSourceSwitcher.clear();
            DataSourceContextHolder.set(dataSourceSwitcher.value().getDataSourceName());
            log.info("数据源切换至:{}", dataSourceSwitcher.value().getDataSourceName());
            return pjp.proceed();
        } finally {
            if (clear) {
                DataSourceContextHolder.clear();
            }

        }
    }

    private Method getMethod(JoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        return signature.getMethod();
    }

}

五:用法以及测试

在配置好了读写分离之后,就可以在代码中使用了,一般而言我们使用在 service 层或者 dao 层,在需要查询的方法上添加@DataSourceSwitcher(DataSourceEnum.SLAVE),它表示该方法下所有的操作都走的是读库。在需要 update 或者 insert 的时候使用@DataSourceSwitcher(DataSourceEnum.MASTER)表示接下来将会走写库。

其实还有一种更为自动的写法,可以根据方法的前缀来配置 AOP 自动切换数据源,比如 update、insert、fresh 等前缀的方法名一律自动设置为写库。select、get、query 等前缀的方法名一律配置为读库,这是一种更为自动的配置写法。缺点就是方法名需要按照 aop 配置的严格来定义,否则就会失效。

@Service
public class OrderService {

    @Resource
    private OrderMapper orderMapper;


    /**
     * 读操作
     *
     * @param orderId
     * @return
     */
    @DataSourceSwitcher(DataSourceEnum.SLAVE)
    public List<Order> getOrder(String orderId) {
        return orderMapper.listOrders(orderId);

    }

    /**
     * 写操作
     *
     * @param orderId
     * @return
     */
    @DataSourceSwitcher(DataSourceEnum.MASTER)
    public List<Order> insertOrder(Long orderId) {
        Order order = new Order();
        order.setOrderId(orderId);
        return orderMapper.saveOrder(order);
    }
}

六:总结

还是画张图来简单总结一下:

Spring Boot implementiert die MySQL-Technologie zur Lese-/Schreibtrennung

In diesem Artikel wird erläutert, wie die Lese-Schreib-Trennung in der Datenbank implementiert wird. Beachten Sie, dass der Kernpunkt der Lese-Schreib-Trennung die Datenweiterleitung ist, die vererbt werden muss. AbstractRoutingDataSource,复写它的 determineCurrentLookupKey ()方法。同时需要注意全局的上下文管理器 DataSourceContextHolder ist die Hauptklasse, die den Datenquellenkontext speichert Der in der Routing-Methode gefundene Datenquellenwert. Es entspricht einer Übertragungsstation für Datenquellen und kombiniert mit der untersten Ebene der JDBC-Vorlage zum Erstellen und Verwalten von Datenquellen, Transaktionen usw. wird die Lese-/Schreibtrennung unserer Datenbank perfekt realisiert.

Das obige ist der detaillierte Inhalt vonSpring Boot implementiert die MySQL-Technologie zur Lese-/Schreibtrennung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:Java后端技术全栈. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen