0

0

PHP Nginx MySQL 高并发调优 小试

php中文网

php中文网

发布时间:2016-06-23 13:41:56

|

1166人浏览过

|

来源于php中文网

原创

项目要求实现一个免费抢券的功能,涉及到高并发的问题,研究了几天,记录下来,欢迎工友们扔砖头~~

整个项目是PHP+Nginx+Mysql的架构,由于PHP是阻塞的单线程模型,不支持多线程,因此也没有Java那么好用的同步机制,我想到的办法就是在数据库级别做相应的同步互斥的控制,Mysql的锁机制我放在了Mysql数据库锁机制这篇博文当中。通过查看Mysql官方文档,我想到了两种解决方案:一、使用LOCK TABLE 或START TRANSACTION 写SQL 语句; 二、使用CREATE PROCEDURE 直接在数据库中创建存储过程,接下来我就分别试了这两种方法。

一、 使用锁机制

SET autocommit=0;LOCK TABLE test;select count(*) from test where value=1;COMMIT;

这是 查询当天中奖的用户(为了示意简化了业务逻辑),然后我用PHP做一个判断:是否中奖用户超过了当天的限额,没超过则该用户中奖,那么此时要UPDATE 一下数据库,若两个用户同时读取中奖用户总数,其中一个update了数据库,另一个用户读到的自然是脏数据,这也就是为什么我没有释放刚才那张表的锁,按照业务逻辑,是要跳出mysql用程序判断一下,然后update数据库再释放锁。

立即学习PHP免费学习笔记(深入)”;

立即学习PHP免费学习笔记(深入)”;

update test(name,value) values('Tomcat',1);COMMIT;UNLOCK TABLE;

这种方法的缺点在于使用了两次数据库连接,中间插入了PHP判断,必定会造成性能上的损失,好处是数据库不必插入业务逻辑,松耦合。

立即学习PHP免费学习笔记(深入)”;


二、 使用存储过程

立即学习PHP免费学习笔记(深入)”;

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 ;

稍微解释一下代码(熟悉的工友请pass):1. 将mysql默认的分隔符分号重定义为// 避免mysql 只执行其中一句话;2. 创建存储过程传入参数cnt (中奖用户限额), user (此次抢票的用户); 3. 定义两个临时变量num (目前中奖用户数), success(是否中奖);4.查询当前中奖用户数目,未超额则插入用户状态1,反之0 ; 5. 返回中奖与否标志,恢复mysql的sql分隔符.

立即学习PHP免费学习笔记(深入)”;

在php中调用此存储过程: $db->query("call proc(100,'hehe')");

此方法的缺点是在数据库引入了业务逻辑,程序修改不易,优点是只使用一次数据库连接,表的锁定时间大大减少,并发效率很高。


三、 奇葩windows环境下的PHP

在我满怀欣喜的开始模拟高并发用户访问的时候,问题来了。。。

先贴 java 写的多线程并发访问程序(php不支持多线程。。)

立即学习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();		}	}}

立即学习PHP免费学习笔记(深入)”;

满怀信心地跑程序,结果发现控制台1秒1秒地给我蹦出结果,也就是1个用户服务器需要大约1秒的处理时间,这简直是一坨翔!! 没办法赶紧做测试查原因,测试方案及结果:

1. 单独测试所有线程的产生和发出请求是否符合要求。

结果:通过打印线程名和时间,发现线程随机地被fork出来,在几乎同一时间点开始run, run的顺序跟fork的顺序不一样,更显出其随机性,因此不是多线程的问题。

2. 分别使用锁机制和存储过程的方式访问数据库,比较二者差异。 

结果:锁机制第一个用户耗时1078 ms, 第二个2146ms, 第三个3199ms ;存储过程 1023ms, 2084ms; 3115ms; 不相伯仲,这说明一个问题:PHP似乎是串行地处理这些请求的,就算这些线程几乎是同时到达服务器的。

3. 直接使用PHP向mysql 中插入一条数据,是否插入就需要1s;

结果:插入一条数据时间真TM是1s左右!!!这PHP跟mysql连接也忒慢了!

4. 使用linux 服务器测试,是否是系统影响

结果:插入数据30ms左右,100并发在300ms左右搞定!!!


从第三个方案想到第四个花了老长时间了,根本没想到居然是系统的原因,google上说这TM是 PHP 的bug

“The problem is that the PHP_FCGI_CHILDREN environment variable is ignored under windows, therefore php-cgidoes not spawn children, and when PHP_FCGI_MAX_REQUESTS is reached the process terminates.So, php with fast-cgi will **never** work on Windows.”From  https://bugs.php.net/bug.php?id=49859

我只想说WTF, windows看来真不适合做服务器,或许php的缔造者压根不想使用windows。在windows下,php-cgi是默认在监听9000端口,只有唯一一个进程在服务于用户,纵使nginx多么的高并发,转发给php-cgi的时候只能串行执行了。有一个非常机智的哥们直接fork了好几个php-cgi进程来处理请求,膜拜一下:

立即学习PHP免费学习笔记(深入)”;

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 的配置文件中使用upstream 建立4个进程来处理请求,然后将php请求转发到这个类似与负载均衡器的东西上,就可以一下提高并发的处理能力了。

立即学习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倍,因此快乐不少~~

启科网络PHP商城系统
启科网络PHP商城系统

启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。

下载

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

立即学习PHP免费学习笔记(深入)”;

  • Nginx 配置调优:
  • 立即学习PHP免费学习笔记(深入)”;

    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免费学习笔记(深入)”;

  • PHP-fpm 调优:开启process.max = 128
  • 立即学习PHP免费学习笔记(深入)”;

    立即学习PHP免费学习笔记(深入)”;

  • 关于Mysql调优可以参考这两篇文章:
  • 立即学习PHP免费学习笔记(深入)”;

    立即学习PHP免费学习笔记(深入)”;

    LAMP 系统性能调优,第 3 部分: MySQL 服务器调优
    论MySQL的监控和调优

    立即学习PHP免费学习笔记(深入)”;

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

    立即学习PHP免费学习笔记(深入)”;

    //读取共享内存中的变量,输入内存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);


    立即学习PHP免费学习笔记(深入)”;


    分享促进社会进步~~



    参考文献:

    nginx upstream的分配方式;

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

    PHP内核探索;

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



    立即学习PHP免费学习笔记(深入)”;

    相关文章

    PHP速学教程(入门到精通)
    PHP速学教程(入门到精通)

    PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

    下载

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    热门AI工具

    更多
    DeepSeek
    DeepSeek

    幻方量化公司旗下的开源大模型平台

    豆包大模型
    豆包大模型

    字节跳动自主研发的一系列大型语言模型

    通义千问
    通义千问

    阿里巴巴推出的全能AI助手

    腾讯元宝
    腾讯元宝

    腾讯混元平台推出的AI助手

    文心一言
    文心一言

    文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

    讯飞写作
    讯飞写作

    基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

    即梦AI
    即梦AI

    一站式AI创作平台,免费AI图片和视频生成。

    ChatGPT
    ChatGPT

    最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

    相关专题

    更多
    pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
    pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

    本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

    463

    2026.02.13

    微博网页版主页入口与登录指南_官方网页端快速访问方法
    微博网页版主页入口与登录指南_官方网页端快速访问方法

    本专题系统整理微博网页版官方入口及网页端登录方式,涵盖首页直达地址、账号登录流程与常见访问问题说明,帮助用户快速找到微博官网主页,实现便捷、安全的网页端登录与内容浏览体验。

    135

    2026.02.13

    Flutter跨平台开发与状态管理实战
    Flutter跨平台开发与状态管理实战

    本专题围绕Flutter框架展开,系统讲解跨平台UI构建原理与状态管理方案。内容涵盖Widget生命周期、路由管理、Provider与Bloc状态管理模式、网络请求封装及性能优化技巧。通过实战项目演示,帮助开发者构建流畅、可维护的跨平台移动应用。

    64

    2026.02.13

    TypeScript工程化开发与Vite构建优化实践
    TypeScript工程化开发与Vite构建优化实践

    本专题面向前端开发者,深入讲解 TypeScript 类型系统与大型项目结构设计方法,并结合 Vite 构建工具优化前端工程化流程。内容包括模块化设计、类型声明管理、代码分割、热更新原理以及构建性能调优。通过完整项目示例,帮助开发者提升代码可维护性与开发效率。

    20

    2026.02.13

    Redis高可用架构与分布式缓存实战
    Redis高可用架构与分布式缓存实战

    本专题围绕 Redis 在高并发系统中的应用展开,系统讲解主从复制、哨兵机制、Cluster 集群模式及数据分片原理。内容涵盖缓存穿透与雪崩解决方案、分布式锁实现、热点数据优化及持久化策略。通过真实业务场景演示,帮助开发者构建高可用、可扩展的分布式缓存系统。

    26

    2026.02.13

    c语言 数据类型
    c语言 数据类型

    本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

    29

    2026.02.12

    雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法
    雨课堂网页版登录入口与使用指南_官方在线教学平台访问方法

    本专题系统整理雨课堂网页版官方入口及在线登录方式,涵盖账号登录流程、官方直连入口及平台访问方法说明,帮助师生用户快速进入雨课堂在线教学平台,实现便捷、高效的课程学习与教学管理体验。

    14

    2026.02.12

    豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法
    豆包AI网页版入口与智能创作指南_官方在线写作与图片生成使用方法

    本专题汇总豆包AI官方网页版入口及在线使用方式,涵盖智能写作工具、图片生成体验入口和官网登录方法,帮助用户快速直达豆包AI平台,高效完成文本创作与AI生图任务,实现便捷智能创作体验。

    524

    2026.02.12

    PostgreSQL性能优化与索引调优实战
    PostgreSQL性能优化与索引调优实战

    本专题面向后端开发与数据库工程师,深入讲解 PostgreSQL 查询优化原理与索引机制。内容包括执行计划分析、常见索引类型对比、慢查询优化策略、事务隔离级别以及高并发场景下的性能调优技巧。通过实战案例解析,帮助开发者提升数据库响应速度与系统稳定性。

    53

    2026.02.12

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号