PHP Nginx MySQL の高同時実行チューニングのトライアル

WBOY
リリース: 2016-06-23 13:41:56
オリジナル
1089 人が閲覧しました

このプロジェクトには無料クーポン取得機能の実装が必要ですが、これには高い同時実行性の問題が伴います。作業者はレンガを投げることを歓迎します~~

プロジェクト全体は、次のアーキテクチャに基づいています。 PHP+Nginx+Mysql。PHP はブロッキングシングルスレッドモデルであるため、マルチスレッドをサポートしていないため、Java ほど使いやすい同期メカニズムがありません。私が考えた方法は、対応する同期を実行することでした。データベース レベルでの相互排他制御については、このブログ投稿の Mysql データベース ロック メカニズムに Mysql ロック メカニズムを記載しました。 Mysql の公式ドキュメントを参照して、次の 2 つの解決策を考えました。 1. LOCK TABLE または START TRANSACTION を使用して SQL ステートメントを作成します。 2. CREATE PROCEDURE を使用してデータベースにストアド プロシージャを直接作成します。 次に、これら 2 つの方法をそれぞれ試しました。 。

1. ロック機構を使用する

SET autocommit=0;LOCK TABLE test;select count(*) from test where value=1;COMMIT;
ログイン後にコピー
これは、当日賞品を獲得したユーザーをクエリし (ビジネス ロジックを簡素化するため)、その後、PHP を使用して、当選ユーザーが制限を超えているかどうかを判断します。その日、そうでない場合は、ユーザーが勝ちます。この時点で、2 人のユーザーが同時に勝利ユーザーの総数を読み取り、そのうちの 1 人がデータベースを更新する場合、もう 1 人のユーザーがデータベースを更新する必要があります。ビジネスロジックは、MySQL から飛び出してプログラムを使用して判断し、データベースを更新してからロックを解放することです。

update test(name,value) values('Tomcat',1);COMMIT;UNLOCK TABLE;
ログイン後にコピー

この方法の欠点は、2 つのデータベース接続を使用し、途中で PHP 判定を挿入するため、間違いなくパフォーマンスの低下が発生することです。利点は、データベースがビジネス ロジックを挿入する必要がなく、疎結合であることです。 。


2. ストアド プロシージャを使用します

DELIMITER //DROP PROCEDURE IF EXISTS proc;CREATE PROCEDURE proc(IN cnt INT,IN user VARCHAR(32))BEGIN	DECLARE num INT;	DECLARE success INT;	select count(*) INTO num from test where value=1;	IF num<cnt THEN		insert into test(user,value) values(user,1);		SET success=1;			ELSE		insert into test(user,value) values(user,0);		SET success=0;	END IF;	SELECT success;END//DELIMITER ;
ログイン後にコピー

コードを少し説明します (詳しい人はパスしてください): 1. mysql が 1 つの文だけを実行しないように、mysql のデフォルトの区切り文字セミコロンを // に再定義します。ストアド プロシージャを作成し、パラメーター cnt (当選ユーザーの制限)、user (今回チケットを取得したユーザー) を渡します。 3. 2 つの一時変数 num (現在の当選ユーザー数)、success (宝くじかどうか) を定義します。勝利した); 4. 現在の勝利ユーザー数をクエリします。割り当てを超えていない場合はユーザー ステータス 1 が挿入され、そうでない場合は 0 が挿入されます。 PHP でこのストアド プロシージャを呼び出します: $db->query(" call proc(100,'hehe')");

このメソッドの欠点は、データベースにビジネス ロジックが導入され、プログラムを実行するのが簡単ではないことです。利点は、使用されるデータベース接続が 1 つだけであること、テーブルのロック時間が大幅に短縮されること、および同時実行効率が非常に高いことです。

3. 素晴らしい Windows 環境での PHP

喜んで高い同時ユーザー アクセスのシミュレーションを開始したとき、問題が発生しました。 。 。

まず、Java で書かれたマルチスレッド同時アクセス プログラムを投稿します (php はマルチスレッドをサポートしていません...)

import java.util.concurrent.CyclicBarrier;import com.test.run.ThreadTest;public class Test {	public static void main(String[] args) {		CyclicBarrier cb=new CyclicBarrier(100);<span style="white-space:pre">	</span>//fork 100个线程		ThreadTest[] ttarray=new ThreadTest[100];<span style="white-space:pre">	</span>//待这些线程fork完毕,同时发起http请求		for (int i = 0; i < ttarray.length; i++) {			ttarray[i]=new ThreadTest(cb);			ttarray[i].start();		}	}}import java.io.BufferedReader;import java.io.DataInputStream;import java.io.IOException;import java.io.InputStreamReader;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;public class ThreadTest extends Thread {		private  CyclicBarrier cb;		public ThreadTest(CyclicBarrier cb) {		super();		this.cb = cb;	}	@Override	public void run() {		String path="http://127.16.0.57/concurrent/index.php?user="+Thread.currentThread().getName();		try {			URL url=new URL(path);			HttpURLConnection huc=(HttpURLConnection) url.openConnection();			huc.setRequestMethod("GET");			huc.setDoInput(true);			huc.setDoOutput(true);			huc.setUseCaches(false);			huc.connect();			cb.await();<span style="white-space:pre">	</span>//必须写上await 方法等待其他线程创建完毕,再统一发送			System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"\tbegin ");		//	long l1= ;			InputStreamReader isr =new InputStreamReader(huc.getInputStream(),"UTF-8");			BufferedReader bf=new BufferedReader(isr);			String str=bf.readLine();			while(str!=null)			{				System.out.println(str);				str=bf.readLine();			}			long l2= System.currentTimeMillis();			//System.out.println(l2-l1+"   "+Thread.currentThread().getName());			//System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"   end");		} catch (MalformedURLException e) {			e.printStackTrace();		} catch (IOException e) {			e.printStackTrace();		} catch (InterruptedException e) {			// TODO Auto-generated catch block			e.printStackTrace();		} catch (BrokenBarrierException e) {			// TODO Auto-generated catch block			e.printStackTrace();		}	}}
ログイン後にコピー


自信を持ってプログラムを実行すると、コンソールに結果がポップアップ表示されることがわかりました。つまり、1 ユーザー サーバーには約 1 秒の処理時間がかかり、これは膨大な量です。 ! 理由、テスト計画、結果を調べるためにテストをすぐに実行する方法はありません。

1. すべてのスレッドの生成とリクエストが要件を満たしているかどうかを個別にテストします。

結果: スレッド名と時間を出力することにより、スレッドがランダムにフォークされ、ほぼ同じ時点で実行を開始したことがわかりました。実行順序は、そのランダム性を示すフォーク順序とは異なるため、不規則ではありません。マルチスレッドの問題。

2. ロック機構とストアドプロシージャをそれぞれ使用してデータベースにアクセスし、2 つの違いを比較します。

結果: ロック メカニズムの最初のユーザーは 1078 ミリ秒、2 番目のユーザーは 2146 ミリ秒、3 番目のユーザーは 3199 ミリ秒かかりました。これらは均等ではなく、PHP がこれらを処理しているようです。これらのスレッドがほぼ同時にサーバーに到着した場合でも、連続してリクエストされます。

3. PHP を使用してデータを mysql に挿入するかどうかは 1 秒かかります。

結果: データの挿入にかかる時間は実際には約 1 秒です。 ! ! PHP と mysql 間の接続が遅すぎます。

4. Linux サーバーを使用して、システムへの影響があるかどうかをテストします

結果: データの挿入には約 30 ミリ秒かかり、100 個の同時実行は約 300 ミリ秒で完了します。 ! !

3 番目の解決策を考えるのに長い時間がかかり、4 番目の解決策を考えるまでには時間がかかりましたが、それがシステムだとは思いませんでした。Google は、これは PHP のバグだと言いました

」問題は、PHP_FCGI_CHILDREN 環境変数が無視されることです。したがって、php-cgidoes は子を生成せず、PHP_FCGI_MAX_REQUESTS に達するとプロセスが終了します。そのため、fast-cgi を使用した php は Windows では**決して**動作しません。」 https://bugs.php.net /bug より。 php?id=49859

なんと言いたいのですが、Windows はサーバーとしてはまったく適していないように思えます。おそらく php の作成者は Windows をまったく使いたくないのでしょう。 Windows では、php-cgi はデフォルトでポート 9000 で待機します。ユーザーにサービスを提供するプロセスは 1 つだけです。nginx は同時実行性が高く、php-cgi に転送される場合のみシリアルに実行されます。リクエストを処理するためにいくつかの php-cgi プロセスを直接フォークした非常に賢い仲間がいます。私は彼を崇拝します:

http {#window 不能派生子进程,只能人工配 PHP_FCGI_CHILDREN 在window不起作用的upstream fastcgi_backend {server 127.0.0.1:9000;server 127.0.0.1:9001;server 127.0.0.1:9002;server 127.0.0.1:9003;}server {listen       80;server_name  q.qq;access_log ./../log/q.qq.access.txt;root d:/web/www;location ~ \.php$ {fastcgi_pass   fastcgi_backend;}}
ログイン後にコピー
彼は nginx 設定ファイルのアップストリームを使用してリクエストを処理する 4 つのプロセスを作成し、php を転送しました。ロードバランサーのようなものを使用すると、同時処理能力を即座に向上させることができます。

回想了一下我在Linux下启动Php 的方式:命令行输入 spawn-fcgi -a 127.0.0.1 -p 9000 -C10-u www-data -f /usr/bin/php-cgi ,spawn出10个子进程来处理9000端口的并发的请求,因此100个请求的时间几乎是单线程的10倍,因此快乐不少~~

在查资料优化的过程中,也学到了一些调优的小技巧:

  • Nginx 配置调优:
  • worker_processes  4;//开启4个工作进程,数目不多于CPU的核数。Nginx是非阻塞IO & IO复用模型,适合高并发

    events {
          worker_connections  1024;//提高每个工作进程最多可接受请求的连接数
        multi_accept on;//开启接受多请求
    }

    关于上文提到的nginx upstream 可以通过ip_hash, 将不同的IP请求转发到相应的服务器做负载均衡,

    #定义负载均衡设备的Ip及设备状态

    upstream resinserver{
    ip_hash;
    server 127.0.0.1:8000 down;
    server 127.0.0.1:8080 weight=2;
    server 127.0.0.1:6801;
    server 127.0.0.1:6802 backup;
    }

    在需要使用负载均衡的server中增加均衡器地址 proxy_pass http://resinserver/;


    每个设备的状态设置为:
    1.down 表示单前的server暂时不参与负载
    2.weight 代表负载权重,默认为1。weight越大,负载的权重就越大。
    3.max_fails :允许请求失败的次数默认为1.当超过最大次数时,返回proxy_next_upstream 模块定义的错误
    4.fail_timeout:max_fails次失败后,暂停的时间。
    5.backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。

    nginx支持同时设置多组的负载均衡,用来给不用的server来使用。

    client_body_in_file_only 设置为On 可以讲client post过来的数据记录到文件中用来做debug
    client_body_temp_path 设置记录文件的目录 可以设置最多3层目录
    location 对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡


  • PHP-fpm 调优:开启process.max = 128
  • 关于Mysql调优可以参考这两篇文章:
  • LAMP 系统性能调优,第 3 部分: MySQL 服务器调优
    论MySQL的监控和调优

    plus: 对于PHP中无法存储全局变量在服务器中,类似于Java的application变量,我采用了一种共享内存的方法暂时解决这个问题,总感觉哪里不好,欢迎工友们多多指教~~

    //读取共享内存中的变量,输入内存ID,访问模式READ/WRITE,权限,块大小
    ログイン後にコピー
    function readMemory($systemid,$mode,$permissions,$size){		$shmid = shmop_open($systemid, $mode, $permissions, $size);		$size = shmop_size($shmid);	$res = shmop_read($shmid,0,$size);	shmop_close($shmid);	//close shared memory is a must in case of dead lock		return $res;}//写入变量,function writeMemory($systemid, $mode, $permissions, $size,$content){	$shmid = shmop_open($systemid, $mode, $permissions, $size);	shmop_write($shmid, $content, 0);	shmop_close($shmid);}
    ログイン後にコピー
    writeMemory(1024, 'c', 0755, 1024,$content);
    ログイン後にコピー
    readMemory(1024, 'a', 0755, 1024);
    ログイン後にコピー



    分享促进社会进步~~



    参考文献:

    nginx upstream的分配方式;

    window+nginx+php-cgi的php-cgi线程/子进程问题;

    PHP内核探索;

    探讨nginx与php-fpm是不是以多进程多线程方式运行的



    ソース:php.cn
    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート