JDBC接続プールのテストクエリ「1を選択し、」AWS RDSライター/リーダーのフェイルオーバーをキャッチしていません

バーニーレンツ:

私たちは、作家や作家が読者に複製されたリーダーのインスタンスを持つクラスタ内のAWS RDSオーロラ/ MySQLデータベースを実行しています。

データベースにアクセスするアプリケーションは、HikariCP接続プールを使用して、標準のJavaアプリケーションです。プールが使用するように設定され"SELECT 1"、チェックアウトのテストクエリを。

私たちが気づいたことは一回RDSは、読者へのライターの上に失敗しながらということです。フェイルオーバーはまた、AWSコンソールで「インスタンスアクション/フェイルオーバー」をクリックして手動で複製することができます。

接続プールは、フェイルオーバーとしてそれが今、読者のデータベースに接続されていることを検出することができません"SELECT 1"テストクエリはまだ成功しています。しかし、それ以降のデータベースの更新が失敗すると"java.sql.SQLException: The MySQL server is running with the --read-only option so it cannot execute this statement"、エラー。

代わりのように見える"SELECT 1"のテストクエリ、接続プールを使用することによって、それが今リーダーに接続されていることを検出することができ"SELECT count(1) FROM test_table WHERE 1 = 2 FOR UPDATE"、テストクエリを。

  1. 誰もが同じ問題を経験していますか?
  2. 使用上の任意の欠点はあります"FOR UPDATE"テストクエリでは?
  3. AWS RDSクラスタライター/リーダーのフェイルオーバーを処理するアプローチの任意の代替またはそれはありますか?

あなたのヘルプははるかに高く評価されます

バーニー

kdgregory:

私がしてきたこれに思考の多くを与える私のオリジナル返事以来、2ヶ月で...


どのようにオーロラが作業をエンドポイント

あなたはオーロラクラスタを起動すると、あなたが得る複数のホスト名のクラスタにアクセスするために。この回答の目的のために、私たちは気にすることを2つだけのために読み書きされる「クラスタエンドポイント」である、と(あなたがそれを推測)である「読み取り専用エンドポイントは、」読み取り専用。また、クラスタ内の各ノードのエンドポイントを持っていますが、私はそれらを再度言及しないようにノードにアクセスする直接、オーロラを使用しての目的に反し。

私は、「例」という名前のクラスタを作成した場合、私は次のエンドポイントを取得します:

  • クラスタのエンドポイント: example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com
  • 読み取り専用のエンドポイント: example.cluster-ro-x91qlr44xxxz.us-east-1.rds.amazonaws.com

あなたはこれらのエンドポイントは、フェイルオーバーにリダイレクトトラフィックにスマート十分だろう弾性ロードバランサ、のようなものを参照するだろうと思うかもしれないが、あなたは間違っていると思います。実際には、彼らは単に本当に短い生存時間を持つDNS CNAMEエントリーしています:

dig example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com


; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 40120
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com. IN A

;; ANSWER SECTION:
example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com. 5 IN CNAME example.x91qlr44xxxz.us-east-1.rds.amazonaws.com.
example.x91qlr44xxxz.us-east-1.rds.amazonaws.com. 4 IN CNAME ec2-18-209-198-76.compute-1.amazonaws.com.
ec2-18-209-198-76.compute-1.amazonaws.com. 7199 IN A 18.209.198.76

;; Query time: 54 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Fri Dec 14 18:12:08 EST 2018
;; MSG SIZE  rcvd: 178

フェイルオーバーが発生した場合、のCNAMEは(から更新されているexampleexample-us-east-1a):

; <<>> DiG 9.11.3-1ubuntu1.3-Ubuntu <<>> example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27191
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com. IN A

;; ANSWER SECTION:
example.cluster-x91qlr44xxxz.us-east-1.rds.amazonaws.com. 5 IN CNAME example-us-east-1a.x91qlr44xxxz.us-east-1.rds.amazonaws.com.
example-us-east-1a.x91qlr44xxxz.us-east-1.rds.amazonaws.com. 4 IN CNAME ec2-3-81-195-23.compute-1.amazonaws.com.
ec2-3-81-195-23.compute-1.amazonaws.com. 7199 IN A 3.81.195.23

;; Query time: 158 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Fri Dec 14 18:15:33 EST 2018
;; MSG SIZE  rcvd: 187

フェイルオーバー中に起こる他の事は、「クラスタ」のエンドポイントへの接続のすべては(あなたは、合理的なクエリのタイムアウトを設定したと仮定して)すべての処理中のトランザクションが失敗した、閉じてしてしまうことがあります。

「読み取り専用」エンドポイントに接続していないものは何でもノードが促進されることを意味は、読み書きのトラフィックを得るであろう、閉じ取得に加えて、読み取り専用アプリケーションは、単に送信しないことトラフィック(もちろん、と仮定し、クラスタのエンドポイントへのすべての要求)。読み取り専用の接続は通常、比較的高価なクエリ(例えば、レポート)のために使用されているので、これはあなたの読み書き操作のパフォーマンスの問題が発生することがあります。

問題:DNSキャッシュ

フェイルオーバーが発生すると、すべてのインプロセスのトランザクションが(再び、クエリのタイムアウトを設定したと仮定して)が失敗します。それが回復して行うの前に接続プールの試行が同じホストに接続すると、すべての新しい接続も失敗することを短い時間があります。私の経験では、フェイルオーバーは、アプリケーションが接続を取得するために期待するべきではありません、その間、約15秒かかります。

それは新しい読み書きノードのIPアドレスに解決する、あなたの接続プールの試みは、クラスタのエンドポイントに接続するには、すべてが順調です。(そうか)その15秒後、すべてが正常に戻ります。何の防止はのCNAMEの連鎖を解決する場合しかし、あなたはあなたの接続プールを使用すると、更新操作をしようとするとすぐに失敗する読み取り専用のエンドポイントへの接続を行うことがあります。

OPの場合、彼は長いタイムアウトで彼自身のCNAMEを持っていました。そうではなく、直接クラスタエンドポイントへの接続よりも、彼は次のように接続しますdatabase.example.comこれは、手動フェイルオーバしまうレプリカデータベースに世界で有用な技術です。私はそれがオーロラとあまり便利だと思います。データベース・エンドポイントを参照するために、独自のCNAMEを使用している場合に関係なく、あなたは短い生存時間の値(確かにこれ以上5秒以下)を持たないためにそれらを必要とします。

私のオリジナルの答えでは、私はまた、Javaが永遠にいくつかのケースでは、DNSルックアップをキャッシュすることを指摘しました。このキャッシュの動作は、(私は信じている)上でのJavaのバージョンに依存し、また、あなたがインストールされたセキュリティマネージャで実行しているかどうか。OpenJDKの8のアプリケーションとして実行されていると、JVMがすべてのネーミングの検索ではなくキャッシュ何も自分自身を委任することが表示されます。しかし、あなたが精通している必要がありnetworkaddress.cache.ttlで説明したように、システムプロパティこのOracleドキュメントこのSOの質問

しかし、あなたが予期しないキャッシュを排除してきた後も、まだクラスタのエンドポイントが読み取り専用のノードに解決された回があるかもしれません。その葉あなたはこの状況をどのように処理するかという問題。

それほどよくない解決策:チェックアウトの読み取り専用テストを使用

OPは、彼のアプリケーションが読み取り専用ノードで実行されていたことを確認するために、データベース接続のテストを使用することを期待していました。これを行うのは意外に難しいです:(OPを使用しているものであるHikariCP、を含む)ほとんどの接続プールは、単にテストクエリが正常に実行されることを確認してください。それは返す何を見てする能力はありません。任意のテストクエリが失敗する例外をスローする必要があることをこれが意味。

私は、MySQLだけで、スタンドアローンのクエリで例外をスローにする方法を考え出すことができませんでした。私が作ってみた最高のは、関数を作成することです。

DELIMITER EOF

CREATE FUNCTION throwIfReadOnly() RETURNS INTEGER
BEGIN
    IF @@innodb_read_only THEN
        SIGNAL SQLSTATE 'ERR0R' SET MESSAGE_TEXT = 'database is read_only';
    END IF;
    RETURN 0;
END;
EOF

DELIMITER ;

次に、あなたのテストクエリで、その関数を呼び出します。

select throwIfReadOnly() 

これは主に、動作します。私の実行している場合は、テストプログラムを、私は「検証の接続に失敗しました」一連のメッセージを見ることができましたが、その後、どういうわけか、更新クエリは、読み取り専用接続で実行されます。ひかりは、それが出て渡した接続を示すために、デバッグメッセージを持っていないので、私はそれが伝えられるところでは、検証を通過したかどうかを識別できませんでした。

しかし、脇にその可能性の問題から、この実装とのより深い問題があります:それは問題があるという事実を隠します。ユーザーが要求を行い、そして多分応答を得るために、30秒間待ちます。この遅延の理由を与えるために(あなたはひかりのデバッグログを有効にしない限り)ログには何もありません。

データベースがアクセス不能である間、また、ひかりは猛烈に接続をしようとしている:私のシングルスレッドのテストでは、100ミリ秒ごとに新しい接続を試みます。そして、これらは、彼らは単に間違ったホストに行き、実際の接続です。数十または百のスレッドを持つアプリケーション・サーバーに投げ、それがデータベースに大きな波及効果を引き起こす可能性があります。

より良いソリューション:ラッパーを経由して、チェックアウト時に読み取り専用のテストを使用 Datasource

ひかり黙って再試行接続を聞かせするのではなく、あなたがラップする可能性がありHikariDataSource、独自にDataSource実装し、テスト/自分自身を再試行してください。これは、あなたが実際にあなたが別途インストール機能を呼び出すのではなく、自己完結型のクエリを使用することができることを意味し、テストクエリの結果、で見ることができるという利点を有します。また、あなたが試みる間、一時停止することができます、そしてあなたにプールの設定を変更する機会を与え、あなたがお好みのログレベルを使用して問題を記録することができます。

private static class WrappedDataSource
implements DataSource
{
    private HikariDataSource delegate;

    public WrappedDataSource(HikariDataSource delegate) {
        this.delegate = delegate;
    }

    @Override
    public Connection getConnection() throws SQLException {
        while (true) {
            Connection cxt = delegate.getConnection();
            try (Statement stmt = cxt.createStatement()) {
                try (ResultSet rslt = stmt.executeQuery("select @@innodb_read_only")) {
                    if (rslt.next() && ! rslt.getBoolean(1)) {
                        return cxt;
                    }
                }
            }
            // evict connection so that we won't get it again
            // should also log here
            delegate.evictConnection(cxt);
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException ignored) {
                // if we're interrupted we just retry
            }
        }
    }

    // all other methods can just delegate to HikariDataSource

このソリューションは、まだそれがユーザーの要求に遅延をもたらすという問題があります。確かに、あなたは(あなたがチェックアウトテストをしなかった)それが起こっていることを知って、あなたは(ループを回数を制限)のタイムアウトをもたらす可能性があります。しかし、それはまだ悪いユーザーエクスペリエンスを表します。

最高の(IMO)ソリューション:「メンテナンスモード」への切り替え

ユーザーは非常にせっかちです:それは、応答を取り戻すために数秒以上かかる場合、彼らはおそらくページをリロードしてみてください、またはもう一度フォームを送信、またはやる何か助けにはならないと傷つける可能性があります。

私は思うので、最善の解決策はすぐに失敗し、彼らはそのsomethngの間違っを知らせることです。コールスタックの上部近くのどこかで、あなたはすでに例外に応答することをいくつかのコードを持っている必要があります。たぶん、あなたは今、一般的な500ページを返しますが、あなたは少し良く行うことができます見て例外で、それは読み取り専用のデータベース例外だ場合ページ「申し訳ありませんが、一時的に利用できなく、数分後にもう一度試してください」を返します。

同時に、あなたはあなたに通知を送信する必要があり、スタッフをOPS:これは、通常のmaintanceウィンドウのフェールオーバーであってもよいし、より深刻な何か(かもしれないが、あなたはそれがより深刻だということを知っていくつかの方法を持っていない限り、それらをウェイクアップしていません)。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=140498&siteId=1
おすすめ