Heim  >  Artikel  >  Java  >  Beispiel dafür, wie Spring die Lese-/Schreibtrennung einer Datenbank implementiert

Beispiel dafür, wie Spring die Lese-/Schreibtrennung einer Datenbank implementiert

高洛峰
高洛峰Original
2017-01-24 10:14:471221Durchsuche

Die meisten aktuellen großen E-Commerce-Systeme verwenden Lese-/Schreib-Trennungstechnologie auf Datenbankebene, bei der es sich um eine Master-Datenbank und mehrere Slave-Datenbanken handelt. Die Master-Bibliothek ist für die Datenaktualisierung und Echtzeit-Datenabfrage verantwortlich, und die Slave-Bibliothek ist natürlich für die Nicht-Echtzeit-Datenabfrage verantwortlich. Denn in tatsächlichen Anwendungen liest die Datenbank mehr und schreibt weniger (die Häufigkeit des Lesens von Daten ist hoch und die Häufigkeit der Aktualisierung von Daten relativ gering), und das Lesen von Daten dauert normalerweise lange und beansprucht viel CPU des Datenbankservers , also Auswirkungen auf die Benutzererfahrung. Unser üblicher Ansatz besteht darin, die Abfrage aus der Hauptdatenbank zu extrahieren, mehrere Slave-Datenbanken zu verwenden und den Lastausgleich zu verwenden, um den Abfragedruck auf jede Slave-Datenbank zu reduzieren.

Das Ziel der Verwendung der Lese-/Schreib-Trennungstechnologie besteht darin, den Druck auf die Master-Bibliothek effektiv zu reduzieren und Benutzeranfragen für Datenabfragen auf verschiedene Slave-Bibliotheken zu verteilen und so die Robustheit des Systems sicherzustellen. Schauen wir uns den Hintergrund der Einführung der Lese-/Schreibtrennung an.

Da das Geschäft der Website weiter wächst, die Datenmenge weiter zunimmt und es immer mehr Benutzer gibt, nimmt der Druck auf die Datenbank grundsätzlich zu Bei Bedarf kann die Strategie der Trennung von Lesen und Schreiben genutzt werden, um den Status quo zu ändern.

Wie können wir insbesondere in der Entwicklung leicht eine Trennung von Lesen und Schreiben erreichen? Derzeit werden zwei häufig verwendete Methoden verwendet:

1 Die erste Methode ist unsere am häufigsten verwendete Methode, nämlich das Definieren 2 Datenbankverbindung, eine ist MasterDataSource und die andere ist SlaveDataSource. Beim Aktualisieren von Daten lesen wir die MasterDataSource und beim Abfragen der Daten lesen wir die SlaveDataSource. Diese Methode ist sehr einfach, daher werde ich nicht auf Details eingehen.

2 Die zweite Methode des dynamischen Datenquellenwechsels besteht darin, die Datenquelle dynamisch in das Programm einzubinden, wenn das Programm ausgeführt wird, und dabei zu wählen, ob sie aus der Hauptbibliothek oder der Slave-Bibliothek liest. Die wichtigsten verwendeten Technologien sind: Annotation, Spring AOP, Reflection. Die Implementierungsmethode wird im Folgenden ausführlich vorgestellt.

Bevor wir die Implementierungsmethode vorstellen, bereiten wir zunächst einige notwendige Kenntnisse über die AbstractRoutingDataSource-Klasse vor.

Die AbstractRoutingDataSource-Klasse wurde nach Spring 2.0 hinzugefügt >

AbstractRoutingDataSource erbt AbstractDataSource, eine Unterklasse von DataSource. DataSource ist die Datenquellenschnittstelle von javax.sql, die wie folgt definiert ist:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {}

Die DataSource-Schnittstelle definiert zwei Methoden, die beide Datenbankverbindungen erhalten. Schauen wir uns an, wie AbstractRoutingDataSource die DataSource-Schnittstelle implementiert:
public interface DataSource extends CommonDataSource,Wrapper {
 
 /**
  * <p>Attempts to establish a connection with the data source that
  * this <code>DataSource</code> object represents.
  *
  * @return a connection to the data source
  * @exception SQLException if a database access error occurs
  */
 Connection getConnection() throws SQLException;
 
 /**
  * <p>Attempts to establish a connection with the data source that
  * this <code>DataSource</code> object represents.
  *
  * @param username the database user on whose behalf the connection is
  * being made
  * @param password the user&#39;s password
  * @return a connection to the data source
  * @exception SQLException if a database access error occurs
  * @since 1.4
  */
 Connection getConnection(String username, String password)
  throws SQLException;
 
}

public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
  }
 
  public Connection getConnection(String username, String password) throws SQLException {
    return determineTargetDataSource().getConnection(username, password);
  }

Offensichtlich ruft es seine eigene discoverTargetDataSource()-Methode auf, um die Verbindung zu erhalten. Die Methode „determinTargetDataSource“ ist wie folgt definiert:

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = determineCurrentLookupKey();
    DataSource dataSource = this.resolvedDataSources.get(lookupKey);
    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
      dataSource = this.resolvedDefaultDataSource;
    }
    if (dataSource == null) {
      throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    }
    return dataSource;
  }

Was uns am meisten beschäftigt, sind die folgenden 2 Sätze:

Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);

Die Methode „defineCurrentLookupKey“ gibt den LookupKey zurück und die Methode „resolvedDataSources“ ruft die Datenquelle aus der Karte basierend auf dem LookupKey ab. „resolvedDataSources“ und „determinedCurrentLookupKey“ sind wie folgt definiert:

private Map<Object, DataSource> resolvedDataSources;
 
protected abstract Object determineCurrentLookupKey()

Haben wir angesichts der obigen Definition einige Ideen? „ResolvedDataSources“ ist ein Kartentyp und SlaveDataSource Speichern Sie es wie folgt in der Karte:

Beispiel dafür, wie Spring die Lese-/Schreibtrennung einer Datenbank implementiertWir schreiben eine Klasse DynamicDataSource, die AbstractRoutingDataSource erbt und deren Methode „defineCurrentLookupKey()“ implementiert, die den Kartenschlüssel zurückgibt, Master oder Sklave.

Okay, ich bin etwas müde, nachdem ich so viel geredet habe. Mal sehen, wie ich es umsetzen kann.

Die Technologie, die wir verwenden möchten, wurde oben erwähnt. Schauen wir uns zunächst die Definition von Annotation an:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
  String value();
}

Wir brauchen auch Um Spring zu implementieren, implementiert die abstrakte Klasse AbstractRoutingDataSource die Methode discoverCurrentLookupKey:

public class DynamicDataSource extends AbstractRoutingDataSource {
 
  @Override
  protected Object determineCurrentLookupKey() {
    // TODO Auto-generated method stub
    return DynamicDataSourceHolder.getDataSouce();
  }
 
}
 
 
public class DynamicDataSourceHolder {
  public static final ThreadLocal<String> holder = new ThreadLocal<String>();
 
  public static void putDataSource(String name) {
    holder.set(name);
  }
 
  public static String getDataSouce() {
    return holder.get();
  }
}

Aus der Definition von DynamicDataSource wird der DynamicDataSourceHolder.getDataSouce()-Wert zurückgegeben Die Methode DynamicDataSourceHolder.putDataSource() muss aufgerufen werden, wenn das Programm ausgeführt wird, um ihr einen Wert zuzuweisen. Das Folgende ist der Kernteil unserer Implementierung, der AOP-Teil, der wie folgt definiert ist:

public class DataSourceAspect {
 
  public void before(JoinPoint point)
  {
    Object target = point.getTarget();
    String method = point.getSignature().getName();
 
    Class<?>[] classz = target.getClass().getInterfaces();
 
    Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
        .getMethod().getParameterTypes();
    try {
      Method m = classz[0].getMethod(method, parameterTypes);
      if (m != null && m.isAnnotationPresent(DataSource.class)) {
        DataSource data = m
            .getAnnotation(DataSource.class);
        DynamicDataSourceHolder.putDataSource(data.value());
        System.out.println(data.value());
      }
       
    } catch (Exception e) {
      // TODO: handle exception
    }
  }
}

Um das Testen zu erleichtern, habe ich Definiert 2 Datenbanken, Shop simuliert die Master-Bibliothek und Test simuliert die Slave-Bibliothek. Die Tabellenstruktur von Shop und Test ist gleich, aber die Daten sind unterschiedlich. Die Datenbankkonfiguration ist wie folgt:

<bean id="masterdataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/shop" />
    <property name="username" value="root" />
    <property name="password" value="yangyanping0615" />
  </bean>
 
  <bean id="slavedataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="yangyanping0615" />
  </bean>
   
    <beans:bean id="dataSource" class="com.air.shop.common.db.DynamicDataSource">
    <property name="targetDataSources">
       <map key-type="java.lang.String">
         <!-- write -->
         <entry key="master" value-ref="masterdataSource"/>
         <!-- read -->
         <entry key="slave" value-ref="slavedataSource"/>
       </map>
        
    </property>
    <property name="defaultTargetDataSource" ref="masterdataSource"/>
  </beans:bean>
 
  <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>
 
 
  <!-- 配置SqlSessionFactoryBean -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:config/mybatis-config.xml" />
  </bean>

Konfiguration im Frühjahr AOP-Konfiguration hinzufügen

<!-- 配置数据库注解aop -->
  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  <beans:bean id="manyDataSourceAspect" class="com.air.shop.proxy.DataSourceAspect" />
  <aop:config>
    <aop:aspect id="c" ref="manyDataSourceAspect">
      <aop:pointcut id="tx" expression="execution(* com.air.shop.mapper.*.*(..))"/>
      <aop:before pointcut-ref="tx" method="before"/>
    </aop:aspect>
  </aop:config>
  <!-- 配置数据库注解aop -->

Das Folgende ist die Definition von MyBatis's UserMapper Beim Testen liest die Anmeldung die Master-Bibliothek und die Benutzerliste liest die Slave-Bibliothek:

public interface UserMapper {
  @DataSource("master")
  public void add(User user);
 
  @DataSource("master")
  public void update(User user);
 
  @DataSource("master")
  public void delete(int id);
 
  @DataSource("slave")
  public User loadbyid(int id);
 
  @DataSource("master")
  public User loadbyname(String name);
   
  @DataSource("slave")
  public List<User> list();
} 

Okay, führen Sie unsere Eclipse aus, um den Effekt zu sehen, geben Sie ein Geben Sie den Benutzernamen admin ein und melden Sie sich an, um den Effekt zu sehen

Spring 实现数据库读写分离的示例

Das Obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass er zum Lernen aller beiträgt. Ich hoffe auch, dass jeder die PHP-Chinesisch-Website unterstützt.

Weitere verwandte Artikel zu Beispielen für die Implementierung der Datenbank-Lese-Schreib-Trennung durch Spring finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn