SMProxy分析(SwooleベースのMySQLデータベース接続プール)


前言:
  • SMProxyを深く理解する前に、接続プールはmysql接続オブジェクトを統一的に管理する処理であると考えられていたが、それに伴う問題は、既存のphpフレームワークがmysql接続プールを持っていないことであり、フレームワークのデータベースモジュールを最小の代価で置き換える方法が難題であることである.
  • SMProxyを深く理解した後、SMProxyの奇妙な点は、フレームワークのデータベースモジュールを変更する必要がなく、mysqlクライアントとmysqlサービス側のミドルウェアに基づいてmysqlメッセージとのインタラクションをswoole/server自身でシミュレートし、接続プールオブジェクトを内部管理することで効率を向上させることです.

  • メリット:
  • 客観的なパフォーマンス向上により、接続の作成にかかるリソース消費量を削減
  • フレームを変更することなく、
  • を使用できます.
    欠点:
  • はmysqlプロトコルについて一定の理解が必要であり、変更したい場合はプロトコルについてより深い理解が必要である.
  • エラーが発生した場合、エラーをチェックするコストが増加します.

  • 知識点の補足:
  • プロトコルでよく使用されるデータ、10進数、16進数、2進数.
  • なぜ16進数でバイトの内容を表すのか、バイナリでは4ビットごとに16進数の1ビットを表し、バイナリと16進数の相互変換は10進数よりも速い.
  • プロトコルで運用されている|,&演算をよりよく理解するために、簡単な呼び方をしました(もちろん正確ではないかもしれませんが)
  • |演算子=2|8=8
  • のように大きな値をとる.
  • &演算子=2&8=2
  • などの小さな値をとる

    swoole:使用した知識点swoole/serverおよびswoole/clientは、tcp粘着パッケージの問題をより多く紹介しません.https://www.cnblogs.com/JsonM...client->tcp buffer(cpuコマンドを待機し、bufferキャッシュが上限に達するとserverに直接送信され、複数のデータが一度に受信される可能性があるすべてのデータ)->server
    mysqlプロトコル解析https://www.cnblogs.com/davyg...
    SMProxy:
    実行フローチャート
    次にmysqlとSMProxyのswooleサービスのインタラクションについて説明します.(以下のメッセージに疑問がある場合は、mysqlプロトコルをよく参照してください.https://www.cnblogs.com/davyg...
    さらにtcpインタラクションを行うには,サービス側がmysqlクライアントに握手初期化メッセージを送信する必要があり,mysqlプロトコルに適合する握手メッセージを送信すれば,mysqlクライアントは次の認証要求を送信する操作を行う.
    //   SMProxy/src/Handler/Frontend/FrontendAuthticator
    public function getHandshakePacket(int $server_id)
    {
        $rand1 = RandomUtil::randomBytes(8);
        $rand2 = RandomUtil::randomBytes(12);
        $this->seed = array_merge($rand1, $rand2);
        $hs = new HandshakePacket();
        $hs->packetId = 0;
        //         
        //      
        $hs->protocolVersion = Versions::PROTOCOL_VERSION;
        //         
        $hs->serverVersion   = Versions::SERVER_VERSION;
        //      
        $hs->threadId = $server_id;
        //    
        $hs->seed = $rand1;
        //    ,       ,
        $hs->serverCapabilities = $this->getServerCapabilities();
        //     
        $hs->serverCharsetIndex = (CharsetUtil::getIndex(CONFIG['server']['charset'] ?? 'utf8mb4') & 0xff);
        //      
        $hs->serverStatus = 2;
        //        +   
        $hs->restOfScrambleBuff = $rand2;
        return getString($hs->write());
    }
    
    //   SMProxy/src/HandshakePacket
    public function write()
    {
        // default init 256,so it can avoid buff extract
        $buffer = [];
        //        
        BufferUtil::writeUB3($buffer, $this->calcPacketSize());
        //      --     
        $buffer[] = $this->packetId;
        //        
        $buffer[] = $this->protocolVersion;
        //          
        BufferUtil::writeWithNull($buffer, getBytes($this->serverVersion));
        //        ID
        BufferUtil::writeUB4($buffer, $this->threadId);
        //       9           
        BufferUtil::writeWithNull($buffer, $this->seed);
        //        
        BufferUtil::writeUB2($buffer, $this->serverCapabilities);
        // 1       
        $buffer[] = $this->serverCharsetIndex;
        //      
        BufferUtil::writeUB2($buffer, $this->serverStatus);
        if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) {
            //         16 
            BufferUtil::writeUB2($buffer, $this->serverCapabilities);
            //     +   +     
            $buffer[] = max(13, count($this->seed) + count($this->restOfScrambleBuff) + 1);
            $buffer = array_merge($buffer, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
        } else {
            // 10     
            $buffer = array_merge($buffer, self::$FILLER_13);
        }
        // +12       
        if ($this ->serverCapabilities & Capabilities::CLIENT_SECURE_CONNECTION) {
            BufferUtil::writeWithNull($buffer, $this->restOfScrambleBuff);
        }
        if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) {
            BufferUtil::writeWithNull($buffer, getBytes($this->pluginName));
        }
        return $buffer;
    }

    mysqlクライアントがログイン認証メッセージを送信と、ログインアカウントのパスワード検証を行うのはmysqlサービス側ではなくswoole/serverであるため、プロファイルserver.jsonのrootとpasswordはmysqlクライアントが要求するアカウントとパスワードであり、swoole/serverとmysqlサービス側のインタラクティブロックに必要なアカウントパスワードはdatabaseの構成にあります.
    //   SMProxy/src/SMProxyServer
    private function auth(BinaryPacket $bin, \swoole_server $server, int $fd)
    {
        //        20, --       ,  4-20   ,   4      
        if ($bin->data[0] == 20) {
            //      16 ,       
            $checkAccount = $this->checkAccount($server, $fd, $this->source[$fd]->user, array_copy($bin->data, 4, 20));
            if (!$checkAccount) {
                //   ERROR  
                $this ->accessDenied($server, $fd, 4);
            } else {
                if ($server->exist($fd)) {
                    //   OK  
                    $server->send($fd, getString(OkPacket::$SWITCH_AUTH_OK));
                }
                //        true
                $this->source[$fd]->auth = true;
            }
        } elseif ($bin->data[4] == 14) {
            //     14
            if ($server->exist($fd)) {
                //        
                $server->send($fd, getString(OkPacket::$OK));
            }
        } else {
            $authPacket = new AuthPacket();
            //              
            $authPacket->read($bin);
            //       
            $checkAccount = $this->checkAccount($server, $fd, $authPacket->user ?? '', $authPacket->password ?? []);
            if (!$checkAccount) {
                //       
                if ($authPacket->pluginName == 'mysql_native_password') {
                    //   ERROR  
                    $this ->accessDenied($server, $fd, 2);
                } else {
                    //       
                    $this->source[$fd]->user = $authPacket ->user;
                    $this->source[$fd]->database = $authPacket->database;
                    //    
                    $this->source[$fd]->seed = RandomUtil::randomBytes(20);
                    //   EOF  
                    $authSwitchRequest = array_merge(
                        [254],
                        getBytes('mysql_native_password'),
                        [0],
                        $this->source[$fd]->seed,
                        [0]
                    );
                    if ($server->exist($fd)) {
                        $server->send($fd, getString(array_merge(getMysqlPackSize(count($authSwitchRequest)), [2], $authSwitchRequest)));
                    }
                }
            } else {
                //        OK  ,      
                if ($server->exist($fd)) {
                    $server->send($fd, getString(OkPacket::$AUTH_OK));
                }
                $this->source[$fd]->auth = true;
                $this->source[$fd]->database = $authPacket->database;
            }
        }
    }

    tcp握手やログイン認証に成功するとmysql側は実行文などの操作を転送することができ,ここでSMProxyは文を一定の分析を行い,読み,書き,物事などを判断する.リード・ステートメントであり、リード・データベースが構成されている場合、リード・ステートメントはリード・プールからのみリンクを取得し、リード・データベースが構成されていない場合はライト・データベースを取得するため、ここで使用するときは、会社のリード・データベースの構成がライト・データベースより低い場合、このアーキテクチャを使用するとリード・データベースに一定の圧力がかかる可能性があることに注意してください.
    このステップになると、SMProxyの仕事も大体終わり、swoole/serverはmysqlクライアントから転送された実行コマンドテキストをmysqlサービス側に送信し、mysqlサービス側から返されたデータSMProxyもあまり処理しなくなり、クライアントはmysqlクライアントであるため、mysqlサービス側から返されたメッセージを直接解析することができる.
    SMProxyアーキテクチャの基本的な流れの説明が終わりましたが、接続プールやmysqlメッセージなどのより詳細な処理は、私もコードに注釈を付けてgithubにアップロードしました.もっと深く知りたい方はダウンロードして見ることができますが、注釈はあまり完璧ではありませんが、ほとんどの文には自分の見解を追加しました(解読ミスもあります)
    https://github.com/linjinmin/...
    要するに、SMProxyは非常に優れたフレームワークです.
    SMProxy githubアドレス:
    https://github.com/louislivi/...
    参考資料の出所:
    https://www.cnblogs.com/JsonM...//tcp接着問題https://www.cnblogs.com/davyg...//mysqlプロトコル