最近発売されたプロジェクトのデータベースデータは、飽和に近づいています。最大のテーブルデータは3000Wに近く、何百万ものデータを備えたテーブルがいくつかあります。このプロジェクトでは、データの読み取り時間は0.05秒を超えることはできませんが、実際の状況は要件を満たしていません。インデックス作成の説明とRedisおよびEhcacheキャッシュテクノロジーの使用は、要件を満たすことができなくなりました。したがって、読み取りおよび書き込み分離技術を使用し始めました。たぶん、データボリュームが将来1億以上を超える場合、分散データベースの展開を考慮する必要があるかもしれません。ただし、現在、読み取りと書き込み分離 +キャッシュ +インデックス +テーブルパーティション + SQL最適化 +ロードバランシングは、1億ボリュームのクエリ作業を満たすことができます。 Springを使用して読み取りと書き込みの分離を実現するための手順を見てみましょう。
1。背景
私たちの一般的なアプリケーションは、データベースの「より多くの読み取りと書き込みを少なくする」ことです。つまり、データベースのデータを読み取る圧力は比較的高いことを意味します。 1つのアイデアは、データベースクラスターソリューションを使用することです。
そのうちの1つはメインライブラリであり、データの作成を担当しています。これを次のように呼びます。
その他はすべて図書館から来ており、データを読む責任があります。これは次のように呼ばれます。
だから、私たちの要件は次のとおりです。
1.読み取りライブラリと書き込みライブラリのデータは一貫しています。 (これは非常に重要な問題です。ビジネスロジックの処理は、DAOまたはマッパーレベルではなく、サービスレイヤーで処理する必要があります)
2。データを書くときは、ライブラリに書く必要があります。
3.データを読むには、読書ライブラリにアクセスする必要があります。
2。計画
読み取りと書き込みの分離を解決するための2つのソリューションがあります:アプリケーションレイヤーソリューションとミドルウェアソリューション。
2.1。アプリケーションレイヤーソリューション:
アドバンテージ:
1.複数のデータソースは切り替えが簡単で、プログラムによって自動的に完了します。
2.ミドルウェアは必要ありません。
3。理論的には、任意のデータベースをサポートします。
欠点:
1。プログラマーによって完了し、運用とメンテナンスは関与していません。
2.データソースを動的に増やすことはできません。
2.2。ミドルウェアソリューション
長所と短所:
アドバンテージ:
1.ソースプログラムは、変更なしで読み取りおよび書き込みの分離を実現できます。
2。データソースを動的に追加するには、プログラムの再起動は必要ありません。
欠点:
1。プログラムはミドルウェアに依存しているため、データベースを切り替えることが困難になります。
2。ミドルウェアはトランジットエージェントとして使用され、パフォーマンスは低下しました。
3.アプリケーションレイヤーに基づいてスプリングを使用して実装します
3.1。原理
サービスを入力する前に、AOPを使用して、文書ライブラリまたは読み取りライブラリを使用するかどうかを判断します。クエリ、検索、取得などから始まるメソッド名など、判断基準はメソッド名に基づいて審査できます。
3.2。 DynamicDataSource
Import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource;/***動的データソースを定義し、Integration Springが提供するAbstractroutingDataSourceを実装します。 DynamicDataSourceはSingletonであり、Thread-Insecureであるため、DynamicDataSourceHolderによって完了するスレッドセーフティを確保するために使用されるため、DynamicDataSourceはシングルトンであり、スレッドインセキュアであるため、seciencurrentlookupkeyメソッドを実装する必要があります。 * * @author zhijun * */public class dynamicdatasourceは、abstractroutingdatasourceを拡張します{@overrideプロテクションオブジェクトsequernecurrentlookupkey(){// dynamicdatasourceholderを使用してスレッドの安全性を確保し、現在のスレッドリターンダイナミックダタターセルのデータソースキーを取得します。 }} 3.3。 DynamicDataSourceHolder
/** * * ThreadLocalテクノロジーを使用して、現在のスレッドのデータソースのキーを記録します * * @author zhijun * */public class dynamicdatasourceholder {//ライブラリに対応するデータソースを書き込みます。 //ライブラリに対応するデータソースを読み取りますprivate private static final string slave = "Slave"; // threadlocalを使用して、現在のスレッドのデータソースを記録しますprivate static final threadlocal <string> holder = new threadlocal <string>(); / ** *データソースキーを設定 * @param key */ public static void putdatasourcekey(string key){holder.set(key); } / ** *データソースキーを取得 * @return * / public static string getDataSourcekey(){return holder.get(); } / ***マークアップライブラリ* / public static void markmaster(){putdatasourcekey(master); } / ***マークアップライブラリを読み取り* / public static void markslave(){putdatasourcekey(slave); }} 3.4。 DataSourceaspect
Import org.apache.commons.lang3.stringutils; Import org.aspectj.lang.joinpoint;/** *データソースのAOPセクションを定義し、サービスのメソッド名を介してライブラリを読むか、ライブラリを書く時であるかどうかを判断します。 before(joinpoint point){//現在実行されているメソッド名を取得しますstring methodname = point.getSignature()。getName(); if(isslave(methodname)){// read as read library dynamicdatasourceholder.markslave(); } else {//書き込みライブラリdynamicdatasourceholder.markmaster()としてマーク}} / ** *それが読み取りライブラリであるかどうかを判断します * * @param methodname * @return * / private boolean isslave(string methodname){//メソッド名はクエリで始まります。 }}3.5。 2つのデータソースを構成します
3.5.1。 JDBC.Properties
jdbc.master.driver = com.mysql.jdbc.driverjdbc.master.url = jdbc:mysql://127.0.0.1:3306/mybatis_1128?useunicode = true&ch aracterencoding = utf8&autoreconnect = true&approwmultiqueries = truejdbc.master.username = rootjdbc.master.password = 123456jd bc.slave01.driver = com.mysql.jdbc.driverjdbc.slave01.url = jdbc:mysql://127.0.0.1:3307/mybatis_1128?useunicode = true&ch aracterencoding = utf8&autoreconnect = true&approwmultiqueries = truejdbc.slave01.username = rootjdbc.slave01.password = 123456
3.5.2。接続プールを定義します
< /> <! - データベースのユーザー名 - > <プロパティname = "username" value = "$ {jdbc.master.username}" /> <! - データベースのパスワード - > <プロパティ名= "value =" $ {jdbc.master.password} " /> <!ユニットは分数です。デフォルト値は240です。キャンセルする場合は、0に設定します。-> <プロパティ名= "idleconnectionTestPeriod" value = "60" /> <! - 接続プールで使用されない接続の最大数。ユニットは分数です。デフォルト値は60です。永久に生き残りたい場合、0に設定します。-> <プロパティ名= "idlemaxage" value = "30" /> <! - パーティションあたりの接続の最大数 - > <プロパティ名= "値="値= "150" /> <!パーティションあたりの接続の最小数 - > <プロパティ名= "maxconnectionsperpartition" value = "150" /> <! - パーティションあたりの接続の最小数 - > <プロパティ名= "minconnectionsperpartition" value = "5" /> < /bean> < <プロパティ名= "driverclass" value = "$ {jdbc.slave01.driver}" /> <!-jdbcurl対応するドライバー - > <プロパティ名= "jdbcurl" value = "$ {jdbc.slave01.url}" />> <! value = "$ {jdbc.slave01.username}" /> <! - データベースのパスワード - > <プロパティname = "password" value = "$ {jdbc.slave01.password}" /> <! - データベース接続プールのアイドル接続の間隔時間を確認します。ユニットは分数です。デフォルト値は240です。キャンセルする場合は、0に設定します。-> <プロパティ名= "idleconnectionTestPeriod" value = "60" /> <! - 接続プールの未使用リンクの最大生存時間。ユニットは分数です。デフォルト値は60です。永遠に生き残りたい場合、0に設定します。-> <プロパティ名= "idlemaxage" value = "30" /> <! - パーティションあたりの接続の最大数 - > <プロパティ名= "値=" 150 " /> < 3.5.3。 DataSourceを定義します
<! - データソースを定義し、実装したデータソースを使用 - > <bean id = "dataSource"> <! - 複数のデータソースを設定します - > <プロパティ名= "ターゲットダタソース"> <マップkey-タイプ= "java.lang.string"> <! - このキーは、プログラムのキーと一貫性がある必要があります。 key = "Slave" value-ref = "Slave01DataSource"/> </map> </property> <! - デフォルトのデータソースを設定します。
3.6。トランザクション管理を構成し、データソースの表面を動的に切り替えます
3.6.1。トランザクションマネージャーの定義
<! - 定義トランザクションマネージャー - > <bean id = "transactionmanager"> <プロパティ名= "dataSource" ref = "dataSource" /> < /bean>
3.6.2。トランザクションポリシーを定義します
<! - トランザクションポリシーの定義 - > <TX:アドバイスID = "TXADVICE" Transaction-Manager = "TransactionManager"> <TX:属性> <! read-only = "true" /> <! - メインライブラリは操作を実行し、トランザクション伝播動作はデフォルトの動作として定義されます - > <tx:method name = "save*" propagation = "expent" /> <tx: "upprocation =" execly " /> <tx:method name =" dellete*"open other other transaction policy" name = "*"/> </tx:属性> </tx:アドバイス>
3.6.3。ファセットを定義します
<! - AOPセクションプロセッサを定義します - > <Bean ID = "DataSourceaspect" /> <aop:config> <! - すべてのサービスのすべてのメソッド、> <aop:aop: "txpointcut" expression = "実行(*xx.xxx.xxxxx.service。 Advice-ref = "txadvice" pointcut-ref = "txpointcut" /> <! - セクションをカスタムセクションプロセッサに適用します。-9999は、セクションが最も優先度のある実行 - > <aop = "datasourceaspect" order = "-9999" -9999 "> </aop:aspect> </aop:config>
4。セクションの実装を改善し、トランザクションポリシールールマッチングを使用します
以前の実装では、トランザクションポリシーで定義を使用する代わりにメソッド名と一致し、トランザクション管理ポリシーでルールマッチングを使用します。
4.1。構成の改善
<! - AOPセクションプロセッサを定義します - > <bean id = "datasourceaspect"> <! - トランザクションポリシーを指定 - > <プロパティ名= "txadvice"/> <! - スレーブメソッドのプレフィックス(必須)を指定 - > <プロパティ名= <slavemethodstart "value ="
4.2。実装の改善
Java.lang.Reflect.field; Import java.util.arraylist; Import java.util.list; Import Java.util.map; Import org.apache.commons.lang3.Stringutils; Import org.aspectJ.lang.joinpoint; Import org.springfrancework.transaction.transaction.transaction.transaction org.springframework.transaction.interceptor.transactionAttribute; Import org.springframework.transaction.interceptor.transactionattributesource; Import org.springframework.transaction.interceptor.transactionInterceptor; Import org.springframework.util.util.patternmatchutils; org.springframework.util.reflectionutils;/***マスターとスレーブの使用を制御するデータソースのAOPセクションを定義します。 * *トランザクションポリシーがトランザクション管理で構成されている場合、構成されたトランザクションポリシーでReadonlyをマークする方法はスレーブを使用することであり、もう1つはマスターを使用します。 * *トランザクション管理を構成するポリシーがない場合、メソッド名マッチングの原則が採用され、スレーブはクエリ、検索、取得から始まるものとして使用され、その他のメソッドはマスターとして使用されます。 * * @author zhijun * */public class datasourceaspect {private list <string> slavemethodpattern = new ArrayList <String>(); private static final string [] defaultslavemethodstart = new String [] {"query"、 "find"、 "get"};プライベート文字列[] Slavemethodstart; / ** *トランザクション管理のポリシーを読む * * @param txadvice * @throws例外 */ @suppresswarnings( "un -checked")public void setxadvice(transactionInterceptor txadvice)スロー{(txadvice == null){//トランザクション管理ポリシーは構成されていません。 } // txadviceからポリシー構成情報を取得するトランザクションAttributesource transactionAttributesource = txadvice.getTransactionAttributesource(); if(!(transactionAttributesource instance of namematchtransactionAttributesource)){return; } //反射テクノロジーを使用して、NamematransactionAttributesource ObjectでNamemap属性値を取得します。 FIELD NAMEMAPFIELD = REFRECTIONUTILS.FINDFIELD(NAMEMATCHTRANSACTIONATTRIBUTESOURCE.CLASS、 "NAMEMAP"); namemapfield.setAccessible(true); //このフィールドをアクセスに設定します// namemap <string、transactionAttribute> map =(map <string、transactionAttribute>)namemapfield.get(matchtransactionattributesource);の値を取得します。 // transactionAttribute> entry:map.entryset()){if(!entry.getValue()。isreadonly()){//判断後、slavemethodpatternに追加する前に読み取りポリシーが定義されます。 } slavemethodpattern.add(entry.getKey()); }} / ** service service method* @param point face object* / public void before(joinpoint point){//現在実行されているメソッド名String methodname = point.getSignature()。getName(); boolean isslave = false; if(slavemethodpattern.isempty()){//現在のスプリングコンテナに構成されたトランザクションポリシーはなく、メソッド名マソジーメソッドinsslave = ysslave(methodname); } else {//ポリシールールを使用して(string mappedname:slavemethodpattern){if(ismatch(methodname、mappedname)){sisslave = true;壊す; }}}} if(sisslave){// read as read read dynamicdatasourceholder.markslave(); } else {//書き込みライブラリdynamicdatasourceholder.markmaster()としてマーク}} / ** *それが読み取りライブラリであるかどうかを判断します * * @param methodname * @return * / private boolean isslave(string methodname){//メソッド名はクエリで始まり、検索、return strintils.startswithany(methodname、getslavemethodstart(); } /** *ワイルドカードマッチング * *指定されたメソッド名がマッピングされた名前と一致している場合は返されます。 *<p>*デフォルトの実装は、「xxx*」、「*xxx」、および「*xxx*」の一致、および直接*等式のチェックをチェックします。サブクラスでオーバーライドできます。 * * @param methodname classのメソッド名 * @paramマッピング名記述子の名前は * @returnの名前が一致している場合 * @see org.sphatringframework.util.patternmatchutils#simplematch(string、string) */保護されたブーリアンismatch(string methanmame) methodName); } / ***ユーザーの指定されたスレーブのメソッド名の接頭辞* @param slavemethodstart* / public void setslavemethodstart(string [] slavemethodstart){this.slavemethodstart = slavemethodstart; } public string [] getSlavemethodstart(){if(this.slavemethodstart == null){//指定されていない、デフォルトの返されたdefaultslavemethodstartを使用します。 } slavemethodstartを返します。 }}5。1人のマスターと複数の奴隷の実装
多くの実用的な使用シナリオでは、「1人のマスター、マルチスレーブ」アーキテクチャを使用するため、このアーキテクチャをサポートしているため、現在はDynamicDataSourceを変更するだけです。
5.1。実装
java.lang.reflect.field; Import java.util.arraylist; Import java.util.list; Import java.util.map; Import java.util.concurrent.atomic.atomicinteger; Import javax.sql.datasource; import org.slf4j.logger; ORG.SLF4J.LOGGERFACTORY; Import org.springframework.jdbc.datasource.lookup.abstractroutingdatasource; Import org.springframework.util.reflectionutils;方法 * * DynamicDataSourceはシングルトンであり、スレッドがインスケイであるため、ThreadlocalはDynamicDataSourceHolderによって完了するスレッドセーフティを確保するために使用されます。 * * @author zhijun * */public class dynamicdatasourceは、abstractroutingdatasourceを拡張します{private static final logger logger = loggerfactory.getlogger(dynamicdatasource.class);プライベートインテガースラヴェカウント; //投票数、最初は-1、AtomicIntegerはスレッドセーフプライベートAtomicInteger counter = new AtomicInteger(-1)です。 //キープライベートリストを記録<オブジェクト> slavedataSources = new ArrayList <Object>(0); @Override Protected Object detinecurrentlookupkey(){// dynamicdatasourceholderを使用してスレッドの安全性を確保し、現在のスレッドでデータソースキーを取得する場合は(dynamicdatasourceholder.ismaster()){object key = dynamicdatasourceholder.getdatasourcekey(); if(logger.isdebugenabled()){logger.debug( "現在のDataSourceのキーは:" + key); }キーを返します。 } object key = getSlaveKey(); if(logger.isdebugenabled()){logger.debug( "現在のDataSourceのキーは:" + key); }キーを返します。 } @suppresswarnings( "unchecked")@Override public void avherpropertiesset(){super.afterpropertiesset(); //親クラスのResolvedDataSourcesプロパティは取得できないプライベートサブクラスであるため、反射を使用してフィールドフィールドを取得する必要があります。 field.setAccessible(true); //アクセシビリティを設定してください{Map <Object、DataSource> ResolvedDataSources =(Map <Object、DataSource>)field.get(this); //読み取りライブラリ内のデータの量は、書き込みライブラリの数を差し引いたデータソースの総数に等しくなります。 for(map.entry <object、dataSource> entry:ResolvedDataSources.EntrySet()){if(dynamicDataSourceHolder.master.equals(entry.getKey())){contion; } slavedatasources.add(entry.getKey()); }} catch(Exception e){logger.error( "afthpropertiesset error!"、e); }} / ** *ポーリングアルゴリズムの実装 * * @return * / public object getSlaveKey(){//結果のサブスクリプトは次のとおりです。 if(counter.get()> 9999){//整数範囲を超えることを避けますcounter.set(-1); // restore} slavedatasources.get(index);を返します。 }}6。MySQLマスタースレーブレプリケーション
6.1。原理
MySQLマスター(マスターと呼ばれる)スレーブ(スレーブと呼ばれる)をコピーする原則:
1.マスターは、データの変更をバイナリログに記録します。つまり、構成ファイルログビンによって指定されたファイル(これらのレコードはバイナリログイベント、バイナリログイベントと呼ばれます)
2。スレーブコピーマスターのバイナリログベントはリレーログ(リレーログ)に
3.スレーブログのスレーブリワークイベントは、それ自体を反映するデータを変更します(データリプレイ)
6.2。マッハ構成の場合に何を注意すべきか
1.プライマリDBサーバーとスレーブDBサーバーデータベースのバージョンは同じです
2。マスターDBサーバーとスレーブDBサーバーのデータベースデータは同じです[ここでは、マスターのバックアップをスレーブに復元できます。または、マスターのデータディレクトリを対応するスレーブのデータディレクトリに直接コピーできます)
3.メインDBサーバーはバイナリログを有効にし、メインDBサーバーとスレーブDBサーバーは一意でなければなりません。
6.3。メインライブラリの構成(Windows、Linuxと同様)
一部の友人は、マスターおよびスレーブデータベースのIPアドレス、ユーザー名、およびアカウント構成をあまり明確にしていない場合があります。以下は、私がテストしたマスターとスレーブの構成です。 IPSはすべて127.0.0.1です。例を終えた後、それを書きます。
マスタースレーブIPは、さまざまな構成の例です。この例を使用して、構成方法をより直感的に理解できます。
my.ini [mysqld]の下で変更する(ライブラリから):
#enable Master-Slave Replication、メインライブラリLog-Bin = MySQL3306-Bin#の構成#メインライブラリServerIdServer-ID = 101を指定します#同期データベースを指定します。指定されていない場合、すべてのデータベースは同期されたbinlog-do-db = mybatis_1128です
(my.iniに入力されたコマンドには、以下のスペースが必要です。そうでなければ、mysqlはそれを認識しません)
SQLステートメントクエリステータスを実行:マスターステータスを表示します
位置値を記録する必要があり、同期開始値をライブラリに設定する必要があります。
もう一つ言ってみましょう。 MySQLでMaster Statusをshowを実行し、my.iniで構成されているコンテンツが機能していないことがわかりました。 my.iniファイルを選択しなかったか、サービスを再起動しなかった可能性があります。それは後者によって引き起こされる可能性が非常に高いです。
構成を有効にするには、MySQLサービスをオフにして再起動する必要があります。
サービスを閉じる方法:
Winキーを開き、Services.mscを入力してサービスを呼び出します。
SQLYOGをもう一度開始し、構成が有効になっていることがわかります。
6.4。メインライブラリに同期ユーザーを作成します
#AuthorizedユーザーSlave01は、123456パスワードを使用してMySQLGrantレプリケーションスレーブにログイン *。
6.5。ライブラリからの構成
my.iniで変更する:
#specify serverIDが繰り返されない限り、ライブラリからの構成は1つだけで、他はSQLステートメントServer-ID = 102で動作します
以下はSQLを実行します(スレーブのルートアカウントを使用して実行):
changematertomater_hot = '127.0.0.1'、//ホストのIPアドレス材料材料= 'lave01'、//ホストのユーザー(ホストに作成されたアカウントはQL)MATER_PAWORD = '123456'、MATER_PORT = 3306、MATER_LOG_FILE = 'myql3306-bin.000006'、// filemate_log_po = 1120; // poition
#Startスレーブ同期スレーブを開始; #View同期ステータスはスレーブステータスを表示します。
2つの異なるIPコンピューターのマスターおよびスレーブ構成方法は次のとおりです。
メインデータベースが存在するオペレーティングシステム:win7
プライマリデータベースのバージョン:5.0
メインデータベースのIPアドレス:192.168.1.111
データベースが存在するオペレーティングシステムから:Linux
データのバージョンから:5.0
データベースからのIPアドレス:192.168.1.112
環境を導入した後、構成手順について説明しましょう。
1.マスターデータベースがスレーブデータベースとまったく同じであることを確認してください。
たとえば、メインデータベース内のAのデータベースには表B、C、およびDがあるため、Aと表B、C、およびDのデータベースには金型が刻まれている必要があります。
2。メインデータベースに同期アカウントを作成します。
コードコピーは次のとおりです。
レプリケーションスレーブを付与し、 *。
192.168.1.112:ユーザーを使用して実行されるのはIPアドレスです
MSTEST:新しく作成されたユーザー名です
123456:新しく作成されたユーザー名のパスワードです
上記のコマンドの詳細な説明は、Baiduで最もよく行われます。書きすぎると、それがより不明確になります。
3.メインデータベースのmy.iniを構成します(ウィンドウの下にあるため、my.iniではなくmy.iniです)。
[mysqld] server-id = 1log-bin = logbinlog-do-db = mstest // mstestデータベースを同期するには、複数のデータベースを同期する場合は、さらにいくつかのbinlog-do-db = database name name binlog-ignoredb = mysql //
4.データベースからmy.cnfを構成します。
[mysqld] server-id = 2master-host = 192.168.1.111master-user = mstest //ステップ1。アカウントのユーザー名を作成するマスターパスワード= 123456 //ステップ1。複数のデータベースを同期し、さらにいくつかのReplicate-Do-DB =データベース名Replicate-Ignore-DB = MySQL //無視するデータベースを追加します
5.成功しているかどうかを確認します
mysqlを入力し、コマンドを入力します。スレーブステータス/gを表示します。次の画像が表示されます。 Slave_io_runningとSlave_sql_runningの両方がイエスである場合、それは同期を正常にすることができることを意味します
6.同期データをテストします。
メインデータベースを入力し、コマンドを入力します。
次に、データベースから入力コマンドを入力します。
現時点でデータベースからデータが取得された場合、同期が成功し、マスターとスレーブが実装されることを意味します。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。